feat: add organisations (#1820)

This commit is contained in:
David Nguyen
2025-06-10 11:49:52 +10:00
committed by GitHub
parent 0b37f19641
commit e6dc237ad2
631 changed files with 37616 additions and 25695 deletions

View File

@ -2,7 +2,7 @@ import { useMemo } from 'react';
import { useLingui } from '@lingui/react';
import { Trans } from '@lingui/react/macro';
import { RecipientRole } from '@prisma/client';
import { OrganisationType, RecipientRole } from '@prisma/client';
import { P, match } from 'ts-pattern';
import { RECIPIENT_ROLES_DESCRIPTION } from '@documenso/lib/constants/recipient-roles';
@ -18,9 +18,9 @@ export interface TemplateDocumentInviteProps {
assetBaseUrl: string;
role: RecipientRole;
selfSigner: boolean;
isTeamInvite: boolean;
teamName?: string;
includeSenderDetails?: boolean;
organisationType?: OrganisationType;
}
export const TemplateDocumentInvite = ({
@ -30,9 +30,9 @@ export const TemplateDocumentInvite = ({
assetBaseUrl,
role,
selfSigner,
isTeamInvite,
teamName,
includeSenderDetails,
organisationType,
}: TemplateDocumentInviteProps) => {
const { _ } = useLingui();
@ -50,21 +50,28 @@ export const TemplateDocumentInvite = ({
<Section>
<Text className="text-primary mx-auto mb-0 max-w-[80%] text-center text-lg font-semibold">
{match({ selfSigner, isTeamInvite, includeSenderDetails, teamName })
{match({ selfSigner, organisationType, includeSenderDetails, teamName })
.with({ selfSigner: true }, () => (
<Trans>
Please {_(actionVerb).toLowerCase()} your document
<br />"{documentName}"
</Trans>
))
.with({ isTeamInvite: true, includeSenderDetails: true, teamName: P.string }, () => (
<Trans>
{inviterName} on behalf of "{teamName}" has invited you to{' '}
{_(actionVerb).toLowerCase()}
<br />"{documentName}"
</Trans>
))
.with({ isTeamInvite: true, teamName: P.string }, () => (
.with(
{
organisationType: OrganisationType.ORGANISATION,
includeSenderDetails: true,
teamName: P.string,
},
() => (
<Trans>
{inviterName} on behalf of "{teamName}" has invited you to{' '}
{_(actionVerb).toLowerCase()}
<br />"{documentName}"
</Trans>
),
)
.with({ organisationType: OrganisationType.ORGANISATION, teamName: P.string }, () => (
<Trans>
{teamName} has invited you to {_(actionVerb).toLowerCase()}
<br />"{documentName}"

View File

@ -2,6 +2,7 @@ import { msg } from '@lingui/core/macro';
import { useLingui } from '@lingui/react';
import { Trans } from '@lingui/react/macro';
import type { RecipientRole } from '@prisma/client';
import { OrganisationType } from '@prisma/client';
import { RECIPIENT_ROLES_DESCRIPTION } from '@documenso/lib/constants/recipient-roles';
@ -15,10 +16,10 @@ export type DocumentInviteEmailTemplateProps = Partial<TemplateDocumentInvitePro
customBody?: string;
role: RecipientRole;
selfSigner?: boolean;
isTeamInvite?: boolean;
teamName?: string;
teamEmail?: string;
includeSenderDetails?: boolean;
organisationType?: OrganisationType;
};
export const DocumentInviteEmailTemplate = ({
@ -30,9 +31,9 @@ export const DocumentInviteEmailTemplate = ({
customBody,
role,
selfSigner = false,
isTeamInvite = false,
teamName = '',
includeSenderDetails,
organisationType,
}: DocumentInviteEmailTemplateProps) => {
const { _ } = useLingui();
const branding = useBranding();
@ -41,7 +42,7 @@ export const DocumentInviteEmailTemplate = ({
let previewText = msg`${inviterName} has invited you to ${action} ${documentName}`;
if (isTeamInvite) {
if (organisationType === OrganisationType.ORGANISATION) {
previewText = includeSenderDetails
? msg`${inviterName} on behalf of "${teamName}" has invited you to ${action} ${documentName}`
: msg`${teamName} has invited you to ${action} ${documentName}`;
@ -82,7 +83,7 @@ export const DocumentInviteEmailTemplate = ({
assetBaseUrl={assetBaseUrl}
role={role}
selfSigner={selfSigner}
isTeamInvite={isTeamInvite}
organisationType={organisationType}
teamName={teamName}
includeSenderDetails={includeSenderDetails}
/>
@ -91,7 +92,7 @@ export const DocumentInviteEmailTemplate = ({
<Container className="mx-auto mt-12 max-w-xl">
<Section>
{!isTeamInvite && (
{organisationType === OrganisationType.PERSONAL && (
<Text className="my-4 text-base font-semibold">
<Trans>
{inviterName}{' '}

View File

@ -2,8 +2,6 @@ import { msg } from '@lingui/core/macro';
import { useLingui } from '@lingui/react';
import { Trans } from '@lingui/react/macro';
import { formatTeamUrl } from '@documenso/lib/utils/teams';
import {
Body,
Button,
@ -20,27 +18,25 @@ import { useBranding } from '../providers/branding';
import { TemplateFooter } from '../template-components/template-footer';
import TemplateImage from '../template-components/template-image';
export type TeamInviteEmailProps = {
export type OrganisationInviteEmailProps = {
assetBaseUrl: string;
baseUrl: string;
senderName: string;
teamName: string;
teamUrl: string;
organisationName: string;
token: string;
};
export const TeamInviteEmailTemplate = ({
export const OrganisationInviteEmailTemplate = ({
assetBaseUrl = 'http://localhost:3002',
baseUrl = 'https://documenso.com',
senderName = 'John Doe',
teamName = 'Team Name',
teamUrl = 'demo',
organisationName = 'Organisation Name',
token = '',
}: TeamInviteEmailProps) => {
}: OrganisationInviteEmailProps) => {
const { _ } = useLingui();
const branding = useBranding();
const previewText = msg`Accept invitation to join a team on Documenso`;
const previewText = msg`Accept invitation to join an organisation on Documenso`;
return (
<Html>
@ -70,15 +66,15 @@ export const TeamInviteEmailTemplate = ({
<Section className="p-2 text-slate-500">
<Text className="text-center text-lg font-medium text-black">
<Trans>Join {teamName} on Documenso</Trans>
<Trans>Join {organisationName} on Documenso</Trans>
</Text>
<Text className="my-1 text-center text-base">
<Trans>You have been invited to join the following team</Trans>
<Trans>You have been invited to join the following organisation</Trans>
</Text>
<div className="mx-auto my-2 w-fit rounded-lg bg-gray-50 px-4 py-2 text-base font-medium text-slate-600">
{formatTeamUrl(teamUrl, baseUrl)}
{organisationName}
</div>
<Text className="my-1 text-center text-base">
@ -90,13 +86,13 @@ export const TeamInviteEmailTemplate = ({
<Section className="mb-6 mt-6 text-center">
<Button
className="bg-documenso-500 inline-flex items-center justify-center rounded-lg px-6 py-3 text-center text-sm font-medium text-black no-underline"
href={`${baseUrl}/team/invite/${token}`}
href={`${baseUrl}/organisation/invite/${token}`}
>
<Trans>Accept</Trans>
</Button>
<Button
className="ml-4 inline-flex items-center justify-center rounded-lg bg-gray-50 px-6 py-3 text-center text-sm font-medium text-slate-600 no-underline"
href={`${baseUrl}/team/decline/${token}`}
href={`${baseUrl}/organisation/decline/${token}`}
>
<Trans>Decline</Trans>
</Button>
@ -115,4 +111,4 @@ export const TeamInviteEmailTemplate = ({
);
};
export default TeamInviteEmailTemplate;
export default OrganisationInviteEmailTemplate;

View File

@ -2,34 +2,32 @@ import { msg } from '@lingui/core/macro';
import { useLingui } from '@lingui/react';
import { Trans } from '@lingui/react/macro';
import { formatTeamUrl } from '@documenso/lib/utils/teams';
import { Body, Container, Head, Hr, Html, Img, Preview, Section, Text } from '../components';
import { useBranding } from '../providers/branding';
import { TemplateFooter } from '../template-components/template-footer';
import TemplateImage from '../template-components/template-image';
export type TeamJoinEmailProps = {
export type OrganisationJoinEmailProps = {
assetBaseUrl: string;
baseUrl: string;
memberName: string;
memberEmail: string;
teamName: string;
teamUrl: string;
organisationName: string;
organisationUrl: string;
};
export const TeamJoinEmailTemplate = ({
export const OrganisationJoinEmailTemplate = ({
assetBaseUrl = 'http://localhost:3002',
baseUrl = 'https://documenso.com',
memberName = 'John Doe',
memberEmail = 'johndoe@documenso.com',
teamName = 'Team Name',
teamUrl = 'demo',
}: TeamJoinEmailProps) => {
organisationName = 'Organisation Name',
organisationUrl = 'demo',
}: OrganisationJoinEmailProps) => {
const { _ } = useLingui();
const branding = useBranding();
const previewText = msg`A team member has joined a team on Documenso`;
const previewText = msg`A member has joined your organisation on Documenso`;
return (
<Html>
@ -59,17 +57,11 @@ export const TeamJoinEmailTemplate = ({
<Section className="p-2 text-slate-500">
<Text className="text-center text-lg font-medium text-black">
<Trans>
{memberName || memberEmail} joined the team {teamName} on Documenso
</Trans>
</Text>
<Text className="my-1 text-center text-base">
<Trans>{memberEmail} joined the following team</Trans>
<Trans>A new member has joined your organisation {organisationName}</Trans>
</Text>
<div className="mx-auto my-2 w-fit rounded-lg bg-gray-50 px-4 py-2 text-base font-medium text-slate-600">
{formatTeamUrl(teamUrl, baseUrl)}
{memberName || memberEmail}
</div>
</Section>
</Container>
@ -85,4 +77,4 @@ export const TeamJoinEmailTemplate = ({
);
};
export default TeamJoinEmailTemplate;
export default OrganisationJoinEmailTemplate;

View File

@ -2,34 +2,32 @@ import { msg } from '@lingui/core/macro';
import { useLingui } from '@lingui/react';
import { Trans } from '@lingui/react/macro';
import { formatTeamUrl } from '@documenso/lib/utils/teams';
import { Body, Container, Head, Hr, Html, Img, Preview, Section, Text } from '../components';
import { useBranding } from '../providers/branding';
import { TemplateFooter } from '../template-components/template-footer';
import TemplateImage from '../template-components/template-image';
export type TeamLeaveEmailProps = {
export type OrganisationLeaveEmailProps = {
assetBaseUrl: string;
baseUrl: string;
memberName: string;
memberEmail: string;
teamName: string;
teamUrl: string;
organisationName: string;
organisationUrl: string;
};
export const TeamLeaveEmailTemplate = ({
export const OrganisationLeaveEmailTemplate = ({
assetBaseUrl = 'http://localhost:3002',
baseUrl = 'https://documenso.com',
memberName = 'John Doe',
memberEmail = 'johndoe@documenso.com',
teamName = 'Team Name',
teamUrl = 'demo',
}: TeamLeaveEmailProps) => {
organisationName = 'Organisation Name',
organisationUrl = 'demo',
}: OrganisationLeaveEmailProps) => {
const { _ } = useLingui();
const branding = useBranding();
const previewText = msg`A team member has left a team on Documenso`;
const previewText = msg`A member has left your organisation on Documenso`;
return (
<Html>
@ -59,17 +57,11 @@ export const TeamLeaveEmailTemplate = ({
<Section className="p-2 text-slate-500">
<Text className="text-center text-lg font-medium text-black">
<Trans>
{memberName || memberEmail} left the team {teamName} on Documenso
</Trans>
</Text>
<Text className="my-1 text-center text-base">
<Trans>{memberEmail} left the following team</Trans>
<Trans>A member has left your organisation {organisationName}</Trans>
</Text>
<div className="mx-auto my-2 w-fit rounded-lg bg-gray-50 px-4 py-2 text-base font-medium text-slate-600">
{formatTeamUrl(teamUrl, baseUrl)}
{memberName || memberEmail}
</div>
</Section>
</Container>
@ -85,4 +77,4 @@ export const TeamLeaveEmailTemplate = ({
);
};
export default TeamLeaveEmailTemplate;
export default OrganisationLeaveEmailTemplate;

View File

@ -12,29 +12,21 @@ export type TeamDeleteEmailProps = {
assetBaseUrl: string;
baseUrl: string;
teamUrl: string;
isOwner: boolean;
};
export const TeamDeleteEmailTemplate = ({
assetBaseUrl = 'http://localhost:3002',
baseUrl = 'https://documenso.com',
teamUrl = 'demo',
isOwner = false,
}: TeamDeleteEmailProps) => {
const { _ } = useLingui();
const branding = useBranding();
const previewText = isOwner
? msg`Your team has been deleted`
: msg`A team you were a part of has been deleted`;
const previewText = msg`A team you were a part of has been deleted`;
const title = isOwner
? msg`Your team has been deleted`
: msg`A team you were a part of has been deleted`;
const title = msg`A team you were a part of has been deleted`;
const description = isOwner
? msg`The following team has been deleted by you`
: msg`The following team has been deleted by its owner. You will no longer be able to access this team and its documents`;
const description = msg`The following team has been deleted. You will no longer be able to access this team and its documents`;
return (
<Html>

View File

@ -1,103 +0,0 @@
import { msg } from '@lingui/core/macro';
import { useLingui } from '@lingui/react';
import { Trans } from '@lingui/react/macro';
import { formatTeamUrl } from '@documenso/lib/utils/teams';
import { Body, Button, Container, Head, Hr, Html, Preview, Section, Text } from '../components';
import { TemplateFooter } from '../template-components/template-footer';
import TemplateImage from '../template-components/template-image';
export type TeamTransferRequestTemplateProps = {
assetBaseUrl: string;
baseUrl: string;
senderName: string;
teamName: string;
teamUrl: string;
token: string;
};
export const TeamTransferRequestTemplate = ({
assetBaseUrl = 'http://localhost:3002',
baseUrl = 'https://documenso.com',
senderName = 'John Doe',
teamName = 'Team Name',
teamUrl = 'demo',
token = '',
}: TeamTransferRequestTemplateProps) => {
const { _ } = useLingui();
const previewText = msg`Accept team transfer request on Documenso`;
return (
<Html>
<Head />
<Preview>{_(previewText)}</Preview>
<Body className="mx-auto my-auto font-sans">
<Section className="bg-white text-slate-500">
<Container className="mx-auto mb-2 mt-8 max-w-xl rounded-lg border border-solid border-slate-200 px-2 pt-2 backdrop-blur-sm">
<TemplateImage
assetBaseUrl={assetBaseUrl}
className="mb-4 h-6 p-2"
staticAsset="logo.png"
/>
<Section>
<TemplateImage
className="mx-auto"
assetBaseUrl={assetBaseUrl}
staticAsset="add-user.png"
/>
</Section>
<Section className="p-2 text-slate-500">
<Text className="text-center text-lg font-medium text-black">
<Trans>{teamName} ownership transfer request</Trans>
</Text>
<Text className="my-1 text-center text-base">
<Trans>
<span className="font-bold">{senderName}</span> has requested that you take
ownership of the following team
</Trans>
</Text>
<div className="mx-auto my-2 w-fit rounded-lg bg-gray-50 px-4 py-2 text-base font-medium text-slate-600">
{formatTeamUrl(teamUrl, baseUrl)}
</div>
<Text className="text-center text-sm">
<Trans>
By accepting this request, you will take responsibility for any billing items
associated with this team.
</Trans>
</Text>
<Section className="mb-6 mt-6 text-center">
<Button
className="bg-documenso-500 ml-2 inline-flex items-center justify-center rounded-lg px-6 py-3 text-center text-sm font-medium text-black no-underline"
href={`${baseUrl}/team/verify/transfer/${token}`}
>
<Trans>Accept</Trans>
</Button>
</Section>
</Section>
<Text className="text-center text-xs">
<Trans>Link expires in 1 hour.</Trans>
</Text>
</Container>
<Hr className="mx-auto mt-12 max-w-xl" />
<Container className="mx-auto max-w-xl">
<TemplateFooter isDocument={false} />
</Container>
</Section>
</Body>
</Html>
);
};
export default TeamTransferRequestTemplate;