Merge branch "main"

This commit is contained in:
Mythie
2024-11-08 22:56:22 +11:00
parent 3ca5e47ed4
commit 80c0468df2
121 changed files with 5668 additions and 1393 deletions

View File

@ -36,7 +36,7 @@ test('[DOCUMENT_AUTH]: should allow signing when no auth setup', async ({ page }
await expect(page.getByRole('heading', { name: 'Sign Document' })).toBeVisible();
// Add signature.
const canvas = page.locator('canvas');
const canvas = page.locator('canvas').first();
const box = await canvas.boundingBox();
if (box) {
await page.mouse.move(box.x + box.width / 2, box.y + box.height / 2);
@ -93,7 +93,7 @@ test('[DOCUMENT_AUTH]: should allow signing with valid global auth', async ({ pa
await expect(page.getByRole('heading', { name: 'Sign Document' })).toBeVisible();
// Add signature.
const canvas = page.locator('canvas');
const canvas = page.locator('canvas').first();
const box = await canvas.boundingBox();
if (box) {
await page.mouse.move(box.x + box.width / 2, box.y + box.height / 2);
@ -262,7 +262,7 @@ test('[DOCUMENT_AUTH]: should allow field signing when required for recipient au
}
// Add signature.
const canvas = page.locator('canvas');
const canvas = page.locator('canvas').first();
const box = await canvas.boundingBox();
if (box) {
await page.mouse.move(box.x + box.width / 2, box.y + box.height / 2);
@ -373,7 +373,7 @@ test('[DOCUMENT_AUTH]: should allow field signing when required for recipient an
}
// Add signature.
const canvas = page.locator('canvas');
const canvas = page.locator('canvas').first();
const box = await canvas.boundingBox();
if (box) {
await page.mouse.move(box.x + box.width / 2, box.y + box.height / 2);

View File

@ -106,7 +106,7 @@ test('[DOCUMENT_FLOW]: should be able to create a document', async ({ page }) =>
await page.getByRole('button', { name: 'Continue' }).click();
// Add subject and send
await expect(page.getByRole('heading', { name: 'Add Subject' })).toBeVisible();
await expect(page.getByRole('heading', { name: 'Distribute Document' })).toBeVisible();
await page.getByRole('button', { name: 'Send' }).click();
await page.waitForURL('/documents');
@ -190,7 +190,7 @@ test('[DOCUMENT_FLOW]: should be able to create a document with multiple recipie
await page.getByRole('button', { name: 'Continue' }).click();
// Add subject and send
await expect(page.getByRole('heading', { name: 'Add Subject' })).toBeVisible();
await expect(page.getByRole('heading', { name: 'Distribute Document' })).toBeVisible();
await page.getByRole('button', { name: 'Send' }).click();
await page.waitForURL('/documents');
@ -287,7 +287,7 @@ test('[DOCUMENT_FLOW]: should be able to create a document with multiple recipie
await page.getByRole('button', { name: 'Continue' }).click();
// Add subject and send
await expect(page.getByRole('heading', { name: 'Add Subject' })).toBeVisible();
await expect(page.getByRole('heading', { name: 'Distribute Document' })).toBeVisible();
await page.getByRole('button', { name: 'Send' }).click();
await page.waitForURL('/documents');
@ -566,7 +566,7 @@ test('[DOCUMENT_FLOW]: should be able to create and sign a document with 3 recip
await page.getByRole('button', { name: 'Continue' }).click();
await expect(page.getByRole('heading', { name: 'Add Subject' })).toBeVisible();
await expect(page.getByRole('heading', { name: 'Distribute Document' })).toBeVisible();
await page.getByRole('button', { name: 'Send' }).click();
await page.waitForURL('/documents');

View File

@ -462,6 +462,45 @@ test('[TEAMS]: check document visibility based on team member role', async ({ pa
}
});
test('[TEAMS]: ensure document owner can see document regardless of visibility', async ({
page,
}) => {
const team = await seedTeam();
// Seed a member user
const memberUser = await seedTeamMember({
teamId: team.id,
role: TeamMemberRole.MEMBER,
});
// Seed a document with ADMIN visibility but make the member user a recipient
await seedDocuments([
{
sender: memberUser,
recipients: [],
type: DocumentStatus.COMPLETED,
documentOptions: {
teamId: team.id,
visibility: 'ADMIN',
title: 'Admin Document with Member Document Owner',
},
},
]);
await apiSignin({
page,
email: memberUser.email,
redirectPath: `/t/${team.url}/documents?status=COMPLETED`,
});
// Check that the member user can see the document
await expect(
page.getByRole('link', { name: 'Admin Document with Member Document Owner', exact: true }),
).toBeVisible();
await apiSignout({ page });
});
test('[TEAMS]: ensure recipient can see document regardless of visibility', async ({ page }) => {
const team = await seedTeam();

View File

@ -0,0 +1,62 @@
import { expect, test } from '@playwright/test';
import { seedTeam } from '@documenso/prisma/seed/teams';
import { apiSignin } from '../fixtures/authentication';
test.describe.configure({ mode: 'parallel' });
test('[TEAMS]: update the default document visibility in the team global settings', async ({
page,
}) => {
const team = await seedTeam({
createTeamMembers: 1,
});
await apiSignin({
page,
email: team.owner.email,
password: 'password',
redirectPath: `/t/${team.url}/settings`,
});
await page.getByRole('combobox').click();
await page.getByRole('option', { name: 'Admin' }).click();
await page.getByRole('button', { name: 'Update team' }).click();
const toast = page.locator('li[role="status"][data-state="open"]').first();
await expect(toast).toBeVisible();
await expect(toast.getByText('Success', { exact: true })).toBeVisible();
await expect(
toast.getByText('Your team has been successfully updated.', { exact: true }),
).toBeVisible();
});
test('[TEAMS]: update the sender details in the team global settings', async ({ page }) => {
const team = await seedTeam({
createTeamMembers: 1,
});
await apiSignin({
page,
email: team.owner.email,
password: 'password',
redirectPath: `/t/${team.url}/settings`,
});
const checkbox = page.getByLabel('Send on Behalf of Team');
await checkbox.check();
await expect(checkbox).toBeChecked();
await page.getByRole('button', { name: 'Update team' }).click();
const toast = page.locator('li[role="status"][data-state="open"]').first();
await expect(toast).toBeVisible();
await expect(toast.getByText('Success', { exact: true })).toBeVisible();
await expect(
toast.getByText('Your team has been successfully updated.', { exact: true }),
).toBeVisible();
await expect(checkbox).toBeChecked();
});

View File

@ -29,7 +29,7 @@ test('[TEAMS]: initiate and cancel team transfer', async ({ page }) => {
await page.getByLabel('Confirm by typing transfer').fill('transfer');
await page.getByRole('button', { name: 'Transfer' }).click();
await expect(page.locator('[id="\\:r2\\:-form-item-message"]')).toContainText(
await expect(page.locator('[id*="form-item-message"]').first()).toContainText(
`You must enter 'transfer ${team.name}' to proceed`,
);

View File

@ -18,7 +18,7 @@ test('[USER] can sign up with email and password', async ({ page }: { page: Page
await page.getByLabel('Email').fill(email);
await page.getByLabel('Password', { exact: true }).fill(password);
const canvas = page.locator('canvas');
const canvas = page.locator('canvas').first();
const box = await canvas.boundingBox();
if (box) {

View File

@ -12,7 +12,7 @@ test('[USER] update full name', async ({ page }) => {
await page.getByLabel('Full Name').fill('John Doe');
const canvas = page.locator('canvas');
const canvas = page.locator('canvas').first();
const box = await canvas.boundingBox();
if (box) {

View File

@ -0,0 +1,44 @@
'use client';
import { createContext, useContext } from 'react';
type BrandingContextValue = {
brandingEnabled: boolean;
brandingUrl: string;
brandingLogo: string;
brandingCompanyDetails: string;
brandingHidePoweredBy: boolean;
};
const BrandingContext = createContext<BrandingContextValue | undefined>(undefined);
const defaultBrandingContextValue: BrandingContextValue = {
brandingEnabled: false,
brandingUrl: '',
brandingLogo: '',
brandingCompanyDetails: '',
brandingHidePoweredBy: false,
};
export const BrandingProvider = (props: {
branding?: BrandingContextValue;
children: React.ReactNode;
}) => {
return (
<BrandingContext.Provider value={props.branding ?? defaultBrandingContextValue}>
{props.children}
</BrandingContext.Provider>
);
};
export const useBranding = () => {
const ctx = useContext(BrandingContext);
if (!ctx) {
throw new Error('Branding context not found');
}
return ctx;
};
export type BrandingSettings = BrandingContextValue;

View File

@ -1,11 +1,18 @@
import * as reactEmail from '@react-email/render';
import * as ReactEmail from '@react-email/render';
import config from '@documenso/tailwind-config';
import { Tailwind } from './components';
import { BrandingProvider, type BrandingSettings } from './providers/branding';
export const render: typeof reactEmail.render = (element, options) => {
return reactEmail.render(
export type RenderOptions = ReactEmail.Options & {
branding?: BrandingSettings;
};
export const render = (element: React.ReactNode, options?: RenderOptions) => {
const { branding, ...otherOptions } = options ?? {};
return ReactEmail.render(
<Tailwind
config={{
theme: {
@ -15,14 +22,16 @@ export const render: typeof reactEmail.render = (element, options) => {
},
}}
>
{element}
<BrandingProvider branding={branding}>{element}</BrandingProvider>
</Tailwind>,
options,
otherOptions,
);
};
export const renderAsync: typeof reactEmail.renderAsync = async (element, options) => {
return reactEmail.renderAsync(
export const renderAsync = async (element: React.ReactNode, options?: RenderOptions) => {
const { branding, ...otherOptions } = options ?? {};
return await ReactEmail.renderAsync(
<Tailwind
config={{
theme: {
@ -32,8 +41,8 @@ export const renderAsync: typeof reactEmail.renderAsync = async (element, option
},
}}
>
{element}
<BrandingProvider branding={branding}>{element}</BrandingProvider>
</Tailwind>,
options,
otherOptions,
);
};

View File

@ -1,5 +1,4 @@
import { Trans } from '@lingui/macro';
import { useLingui } from '@lingui/react';
import { Button, Section, Text } from '../components';
import { TemplateDocumentImage } from './template-document-image';
@ -13,8 +12,6 @@ export const TemplateConfirmationEmail = ({
confirmationLink,
assetBaseUrl,
}: TemplateConfirmationEmailProps) => {
const { _ } = useLingui();
return (
<>
<TemplateDocumentImage className="mt-6" assetBaseUrl={assetBaseUrl} />

View File

@ -38,7 +38,7 @@ export const TemplateDocumentCompleted = ({
</Section>
<Text className="text-primary mb-0 text-center text-lg font-semibold">
<Trans>{customBody ?? `${documentName}” was signed by all signers`}</Trans>
{customBody || <Trans>{documentName} was signed by all signers</Trans>}
</Text>
<Text className="my-1 text-center text-base text-slate-400">
@ -46,13 +46,6 @@ export const TemplateDocumentCompleted = ({
</Text>
<Section className="mb-6 mt-8 text-center">
{/* <Button
className="mr-4 inline-flex items-center justify-center rounded-lg border border-solid border-slate-200 px-4 py-2 text-center text-sm font-medium text-black no-underline"
href={reviewLink}
>
<Img src={getAssetUrl('/static/review.png')} className="-mb-1 mr-2 inline h-5 w-5" />
Review
</Button> */}
<Button
className="rounded-lg border border-solid border-slate-200 px-4 py-2 text-center text-sm font-medium text-black no-underline"
href={downloadLink}

View File

@ -1,8 +1,9 @@
import { Trans } from '@lingui/macro';
import { useLingui } from '@lingui/react';
import { match } from 'ts-pattern';
import { RECIPIENT_ROLES_DESCRIPTION_ENG } from '@documenso/lib/constants/recipient-roles';
import type { RecipientRole } from '@documenso/prisma/client';
import { RECIPIENT_ROLES_DESCRIPTION } from '@documenso/lib/constants/recipient-roles';
import { RecipientRole } from '@documenso/prisma/client';
import { Button, Section, Text } from '../components';
import { TemplateDocumentImage } from './template-document-image';
@ -17,6 +18,7 @@ export interface TemplateDocumentInviteProps {
selfSigner: boolean;
isTeamInvite: boolean;
teamName?: string;
includeSenderDetails?: boolean;
}
export const TemplateDocumentInvite = ({
@ -28,10 +30,11 @@ export const TemplateDocumentInvite = ({
selfSigner,
isTeamInvite,
teamName,
includeSenderDetails,
}: TemplateDocumentInviteProps) => {
const { _ } = useLingui();
const { actionVerb, progressiveVerb } = RECIPIENT_ROLES_DESCRIPTION_ENG[role];
const { actionVerb } = RECIPIENT_ROLES_DESCRIPTION[role];
return (
<>
@ -41,29 +44,38 @@ export const TemplateDocumentInvite = ({
<Text className="text-primary mx-auto mb-0 max-w-[80%] text-center text-lg font-semibold">
{selfSigner ? (
<Trans>
{`Please ${_(actionVerb).toLowerCase()} your document`}
<br />
{`"${documentName}"`}
Please {_(actionVerb).toLowerCase()} your document
<br />"{documentName}"
</Trans>
) : isTeamInvite ? (
<Trans>
{`${inviterName} on behalf of ${teamName} has invited you to ${_(
actionVerb,
).toLowerCase()}`}
<br />
{`"${documentName}"`}
</Trans>
<>
{includeSenderDetails ? (
<Trans>
{inviterName} on behalf of {teamName} has invited you to{' '}
{_(actionVerb).toLowerCase()}
</Trans>
) : (
<Trans>
{teamName} has invited you to {_(actionVerb).toLowerCase()}
</Trans>
)}
<br />"{documentName}"
</>
) : (
<Trans>
{`${inviterName} has invited you to ${_(actionVerb).toLowerCase()}`}
<br />
{`"${documentName}"`}
{inviterName} has invited you to {_(actionVerb).toLowerCase()}
<br />"{documentName}"
</Trans>
)}
</Text>
<Text className="my-1 text-center text-base text-slate-400">
<Trans>Continue by {_(progressiveVerb).toLowerCase()} the document.</Trans>
{match(role)
.with(RecipientRole.SIGNER, () => <Trans>Continue by signing the document.</Trans>)
.with(RecipientRole.VIEWER, () => <Trans>Continue by viewing the document.</Trans>)
.with(RecipientRole.APPROVER, () => <Trans>Continue by approving the document.</Trans>)
.with(RecipientRole.CC, () => '')
.exhaustive()}
</Text>
<Section className="mb-6 mt-8 text-center">
@ -71,7 +83,12 @@ export const TemplateDocumentInvite = ({
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={signDocumentLink}
>
<Trans>{_(actionVerb)} Document</Trans>
{match(role)
.with(RecipientRole.SIGNER, () => <Trans>Sign Document</Trans>)
.with(RecipientRole.VIEWER, () => <Trans>View Document</Trans>)
.with(RecipientRole.APPROVER, () => <Trans>Approve Document</Trans>)
.with(RecipientRole.CC, () => '')
.exhaustive()}
</Button>
</Section>
</Section>

View File

@ -1,15 +1,18 @@
import { Trans } from '@lingui/macro';
import { Link, Section, Text } from '../components';
import { useBranding } from '../providers/branding';
export type TemplateFooterProps = {
isDocument?: boolean;
};
export const TemplateFooter = ({ isDocument = true }: TemplateFooterProps) => {
const branding = useBranding();
return (
<Section>
{isDocument && (
{isDocument && !branding.brandingHidePoweredBy && (
<Text className="my-4 text-base text-slate-400">
<Trans>
This document was sent using{' '}
@ -20,11 +23,24 @@ export const TemplateFooter = ({ isDocument = true }: TemplateFooterProps) => {
</Text>
)}
<Text className="my-8 text-sm text-slate-400">
Documenso, Inc.
<br />
2261 Market Street, #5211, San Francisco, CA 94114, USA
</Text>
{branding.brandingCompanyDetails ? (
<Text className="my-8 text-sm text-slate-400">
{branding.brandingCompanyDetails.split('\n').map((line, idx) => {
return (
<>
{idx > 0 && <br />}
{line}
</>
);
})}
</Text>
) : (
<Text className="my-8 text-sm text-slate-400">
Documenso, Inc.
<br />
2261 Market Street, #5211, San Francisco, CA 94114, USA
</Text>
)}
</Section>
);
};

View File

@ -2,6 +2,7 @@ import { msg } from '@lingui/macro';
import { useLingui } from '@lingui/react';
import { Body, Container, Head, Html, Img, Preview, Section } from '../components';
import { useBranding } from '../providers/branding';
import type { TemplateConfirmationEmailProps } from '../template-components/template-confirmation-email';
import { TemplateConfirmationEmail } from '../template-components/template-confirmation-email';
import { TemplateFooter } from '../template-components/template-footer';
@ -11,6 +12,7 @@ export const ConfirmEmailTemplate = ({
assetBaseUrl = 'http://localhost:3002',
}: TemplateConfirmationEmailProps) => {
const { _ } = useLingui();
const branding = useBranding();
const previewText = msg`Please confirm your email address`;
@ -26,11 +28,15 @@ export const ConfirmEmailTemplate = ({
<Section>
<Container className="mx-auto mb-2 mt-8 max-w-xl rounded-lg border border-solid border-slate-200 p-4 backdrop-blur-sm">
<Section>
<Img
src={getAssetUrl('/static/logo.png')}
alt="Documenso Logo"
className="mb-4 h-6"
/>
{branding.brandingEnabled && branding.brandingLogo ? (
<Img src={branding.brandingLogo} alt="Branding Logo" className="mb-4 h-6" />
) : (
<Img
src={getAssetUrl('/static/logo.png')}
alt="Documenso Logo"
className="mb-4 h-6"
/>
)}
<TemplateConfirmationEmail
confirmationLink={confirmationLink}

View File

@ -1,4 +1,4 @@
import { msg } from '@lingui/macro';
import { Trans, msg } from '@lingui/macro';
import { useLingui } from '@lingui/react';
import { formatTeamUrl } from '@documenso/lib/utils/teams';
@ -10,11 +10,13 @@ import {
Head,
Hr,
Html,
Img,
Link,
Preview,
Section,
Text,
} from '../components';
import { useBranding } from '../providers/branding';
import { TemplateFooter } from '../template-components/template-footer';
import TemplateImage from '../template-components/template-image';
@ -34,6 +36,7 @@ export const ConfirmTeamEmailTemplate = ({
token = '',
}: ConfirmTeamEmailProps) => {
const { _ } = useLingui();
const branding = useBranding();
const previewText = msg`Accept team email request for ${teamName} on Documenso`;
@ -45,11 +48,15 @@ export const ConfirmTeamEmailTemplate = ({
<Body className="mx-auto my-auto font-sans">
<Section className="bg-white">
<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"
/>
{branding.brandingEnabled && branding.brandingLogo ? (
<Img src={branding.brandingLogo} alt="Branding Logo" className="mb-4 h-6 p-2" />
) : (
<TemplateImage
assetBaseUrl={assetBaseUrl}
className="mb-4 h-6 p-2"
staticAsset="logo.png"
/>
)}
<Section>
<TemplateImage
@ -61,12 +68,14 @@ export const ConfirmTeamEmailTemplate = ({
<Section className="p-2 text-slate-500">
<Text className="text-center text-lg font-medium text-black">
Verify your team email address
<Trans>Verify your team email address</Trans>
</Text>
<Text className="text-center text-base">
<span className="font-bold">{teamName}</span> has requested to use your email
address for their team on Documenso.
<Trans>
<span className="font-bold">{teamName}</span> has requested to use your email
address for their team on Documenso.
</Trans>
</Text>
<div className="mx-auto mt-6 w-fit rounded-lg bg-gray-50 px-4 py-2 text-base font-medium text-slate-600">
@ -75,25 +84,29 @@ export const ConfirmTeamEmailTemplate = ({
<Section className="mt-6">
<Text className="my-0 text-sm">
By accepting this request, you will be granting <strong>{teamName}</strong> access
to:
<Trans>
By accepting this request, you will be granting <strong>{teamName}</strong>{' '}
access to:
</Trans>
</Text>
<ul className="mb-0 mt-2">
<li className="text-sm">
View all documents sent to and from this email address
<Trans>View all documents sent to and from this email address</Trans>
</li>
<li className="mt-1 text-sm">
Allow document recipients to reply directly to this email address
<Trans>Allow document recipients to reply directly to this email address</Trans>
</li>
<li className="mt-1 text-sm">
Send documents on behalf of the team using the email address
<Trans>Send documents on behalf of the team using the email address</Trans>
</li>
</ul>
<Text className="mt-2 text-sm">
You can revoke access at any time in your team settings on Documenso{' '}
<Link href={`${baseUrl}/settings/teams`}>here.</Link>
<Trans>
You can revoke access at any time in your team settings on Documenso{' '}
<Link href={`${baseUrl}/settings/teams`}>here.</Link>
</Trans>
</Text>
</Section>
@ -102,12 +115,14 @@ export const ConfirmTeamEmailTemplate = ({
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/verify/email/${token}`}
>
Accept
<Trans>Accept</Trans>
</Button>
</Section>
</Section>
<Text className="text-center text-xs text-slate-500">Link expires in 1 hour.</Text>
<Text className="text-center text-xs text-slate-500">
<Trans>Link expires in 1 hour.</Trans>
</Text>
</Container>
<Hr className="mx-auto mt-12 max-w-xl" />

View File

@ -2,6 +2,7 @@ import { msg } from '@lingui/macro';
import { useLingui } from '@lingui/react';
import { Body, Container, Head, Hr, Html, Img, Preview, Section } from '../components';
import { useBranding } from '../providers/branding';
import type { TemplateDocumentCancelProps } from '../template-components/template-document-cancel';
import { TemplateDocumentCancel } from '../template-components/template-document-cancel';
import { TemplateFooter } from '../template-components/template-footer';
@ -15,6 +16,7 @@ export const DocumentCancelTemplate = ({
assetBaseUrl = 'http://localhost:3002',
}: DocumentCancelEmailTemplateProps) => {
const { _ } = useLingui();
const branding = useBranding();
const previewText = msg`${inviterName} has cancelled the document ${documentName}, you don't need to sign it anymore.`;
@ -31,11 +33,15 @@ export const DocumentCancelTemplate = ({
<Section>
<Container className="mx-auto mb-2 mt-8 max-w-xl rounded-lg border border-solid border-slate-200 p-4 backdrop-blur-sm">
<Section>
<Img
src={getAssetUrl('/static/logo.png')}
alt="Documenso Logo"
className="mb-4 h-6"
/>
{branding.brandingEnabled && branding.brandingLogo ? (
<Img src={branding.brandingLogo} alt="Branding Logo" className="mb-4 h-6" />
) : (
<Img
src={getAssetUrl('/static/logo.png')}
alt="Documenso Logo"
className="mb-4 h-6"
/>
)}
<TemplateDocumentCancel
inviterName={inviterName}

View File

@ -2,6 +2,7 @@ import { msg } from '@lingui/macro';
import { useLingui } from '@lingui/react';
import { Body, Container, Head, Html, Img, Preview, Section } from '../components';
import { useBranding } from '../providers/branding';
import type { TemplateDocumentCompletedProps } from '../template-components/template-document-completed';
import { TemplateDocumentCompleted } from '../template-components/template-document-completed';
import { TemplateFooter } from '../template-components/template-footer';
@ -17,6 +18,7 @@ export const DocumentCompletedEmailTemplate = ({
customBody,
}: DocumentCompletedEmailTemplateProps) => {
const { _ } = useLingui();
const branding = useBranding();
const previewText = msg`Completed Document`;
@ -33,11 +35,15 @@ export const DocumentCompletedEmailTemplate = ({
<Section className="bg-white">
<Container className="mx-auto mb-2 mt-8 max-w-xl rounded-lg border border-solid border-slate-200 p-2 backdrop-blur-sm">
<Section className="p-2">
<Img
src={getAssetUrl('/static/logo.png')}
alt="Documenso Logo"
className="mb-4 h-6"
/>
{branding.brandingEnabled && branding.brandingLogo ? (
<Img src={branding.brandingLogo} alt="Branding Logo" className="mb-4 h-6" />
) : (
<Img
src={getAssetUrl('/static/logo.png')}
alt="Documenso Logo"
className="mb-4 h-6"
/>
)}
<TemplateDocumentCompleted
downloadLink={downloadLink}

View File

@ -1,9 +1,10 @@
import { Trans, msg } from '@lingui/macro';
import { useLingui } from '@lingui/react';
import { RECIPIENT_ROLES_DESCRIPTION_ENG } from '@documenso/lib/constants/recipient-roles';
import { RECIPIENT_ROLES_DESCRIPTION } from '@documenso/lib/constants/recipient-roles';
import { Body, Button, Container, Head, Html, Img, Preview, Section, Text } from '../components';
import { useBranding } from '../providers/branding';
import TemplateDocumentImage from '../template-components/template-document-image';
import { TemplateFooter } from '../template-components/template-footer';
import { RecipientRole } from '.prisma/client';
@ -24,8 +25,9 @@ export const DocumentCreatedFromDirectTemplateEmailTemplate = ({
assetBaseUrl = 'http://localhost:3002',
}: DocumentCompletedEmailTemplateProps) => {
const { _ } = useLingui();
const branding = useBranding();
const action = _(RECIPIENT_ROLES_DESCRIPTION_ENG[recipientRole].actioned).toLowerCase();
const action = _(RECIPIENT_ROLES_DESCRIPTION[recipientRole].actioned).toLowerCase();
const previewText = msg`Document created from direct template`;
@ -42,11 +44,15 @@ export const DocumentCreatedFromDirectTemplateEmailTemplate = ({
<Section className="bg-white">
<Container className="mx-auto mb-2 mt-8 max-w-xl rounded-lg border border-solid border-slate-200 p-2 backdrop-blur-sm">
<Section className="p-2">
<Img
src={getAssetUrl('/static/logo.png')}
alt="Documenso Logo"
className="mb-4 h-6"
/>
{branding.brandingEnabled && branding.brandingLogo ? (
<Img src={branding.brandingLogo} alt="Branding Logo" className="mb-4 h-6" />
) : (
<Img
src={getAssetUrl('/static/logo.png')}
alt="Documenso Logo"
className="mb-4 h-6"
/>
)}
<TemplateDocumentImage className="mt-6" assetBaseUrl={assetBaseUrl} />

View File

@ -1,10 +1,11 @@
import { Trans, msg } from '@lingui/macro';
import { useLingui } from '@lingui/react';
import { RECIPIENT_ROLES_DESCRIPTION_ENG } from '@documenso/lib/constants/recipient-roles';
import { RECIPIENT_ROLES_DESCRIPTION } from '@documenso/lib/constants/recipient-roles';
import type { RecipientRole } from '@documenso/prisma/client';
import { Body, Container, Head, Hr, Html, Img, Link, Preview, Section, Text } from '../components';
import { useBranding } from '../providers/branding';
import type { TemplateDocumentInviteProps } from '../template-components/template-document-invite';
import { TemplateDocumentInvite } from '../template-components/template-document-invite';
import { TemplateFooter } from '../template-components/template-footer';
@ -16,6 +17,7 @@ export type DocumentInviteEmailTemplateProps = Partial<TemplateDocumentInvitePro
isTeamInvite?: boolean;
teamName?: string;
teamEmail?: string;
includeSenderDetails?: boolean;
};
export const DocumentInviteEmailTemplate = ({
@ -29,16 +31,24 @@ export const DocumentInviteEmailTemplate = ({
selfSigner = false,
isTeamInvite = false,
teamName,
includeSenderDetails,
}: DocumentInviteEmailTemplateProps) => {
const { _ } = useLingui();
const branding = useBranding();
const action = _(RECIPIENT_ROLES_DESCRIPTION_ENG[role].actionVerb).toLowerCase();
const action = _(RECIPIENT_ROLES_DESCRIPTION[role].actionVerb).toLowerCase();
const previewText = selfSigner
? msg`Please ${action} your document ${documentName}`
: isTeamInvite
? msg`${inviterName} on behalf of ${teamName} has invited you to ${action} ${documentName}`
: msg`${inviterName} has invited you to ${action} ${documentName}`;
let previewText = msg`${inviterName} has invited you to ${action} ${documentName}`;
if (isTeamInvite) {
previewText = includeSenderDetails
? msg`${inviterName} on behalf of ${teamName} has invited you to ${action} ${documentName}`
: msg`${teamName} has invited you to ${action} ${documentName}`;
}
if (selfSigner) {
previewText = msg`Please ${action} your document ${documentName}`;
}
const getAssetUrl = (path: string) => {
return new URL(path, assetBaseUrl).toString();
@ -53,11 +63,15 @@ export const DocumentInviteEmailTemplate = ({
<Section>
<Container className="mx-auto mb-2 mt-8 max-w-xl rounded-lg border border-solid border-slate-200 p-4 backdrop-blur-sm">
<Section>
<Img
src={getAssetUrl('/static/logo.png')}
alt="Documenso Logo"
className="mb-4 h-6"
/>
{branding.brandingEnabled && branding.brandingLogo ? (
<Img src={branding.brandingLogo} alt="Branding Logo" className="mb-4 h-6" />
) : (
<Img
src={getAssetUrl('/static/logo.png')}
alt="Documenso Logo"
className="mb-4 h-6"
/>
)}
<TemplateDocumentInvite
inviterName={inviterName}
@ -69,6 +83,7 @@ export const DocumentInviteEmailTemplate = ({
selfSigner={selfSigner}
isTeamInvite={isTeamInvite}
teamName={teamName}
includeSenderDetails={includeSenderDetails}
/>
</Section>
</Container>
@ -89,7 +104,7 @@ export const DocumentInviteEmailTemplate = ({
<pre className="font-sans text-base text-slate-400">{customBody}</pre>
) : (
<Trans>
`${inviterName} has invited you to ${action} the document "${documentName}".`
{inviterName} has invited you to {action} the document "{documentName}".
</Trans>
)}
</Text>

View File

@ -2,6 +2,7 @@ import { msg } from '@lingui/macro';
import { useLingui } from '@lingui/react';
import { Body, Container, Head, Html, Img, Preview, Section } from '../components';
import { useBranding } from '../providers/branding';
import type { TemplateDocumentPendingProps } from '../template-components/template-document-pending';
import { TemplateDocumentPending } from '../template-components/template-document-pending';
import { TemplateFooter } from '../template-components/template-footer';
@ -13,6 +14,7 @@ export const DocumentPendingEmailTemplate = ({
assetBaseUrl = 'http://localhost:3002',
}: DocumentPendingEmailTemplateProps) => {
const { _ } = useLingui();
const branding = useBranding();
const previewText = msg`Pending Document`;
@ -29,11 +31,15 @@ export const DocumentPendingEmailTemplate = ({
<Section className="bg-white">
<Container className="mx-auto mb-2 mt-8 max-w-xl rounded-lg border border-solid border-slate-200 p-4 backdrop-blur-sm">
<Section>
<Img
src={getAssetUrl('/static/logo.png')}
alt="Documenso Logo"
className="mb-4 h-6"
/>
{branding.brandingEnabled && branding.brandingLogo ? (
<Img src={branding.brandingLogo} alt="Branding Logo" className="mb-4 h-6" />
) : (
<Img
src={getAssetUrl('/static/logo.png')}
alt="Documenso Logo"
className="mb-4 h-6"
/>
)}
<TemplateDocumentPending documentName={documentName} assetBaseUrl={assetBaseUrl} />
</Section>

View File

@ -2,6 +2,7 @@ import { msg } from '@lingui/macro';
import { useLingui } from '@lingui/react';
import { Body, Container, Head, Html, Img, Preview, Section } from '../components';
import { useBranding } from '../providers/branding';
import type { TemplateDocumentSelfSignedProps } from '../template-components/template-document-self-signed';
import { TemplateDocumentSelfSigned } from '../template-components/template-document-self-signed';
import { TemplateFooter } from '../template-components/template-footer';
@ -13,6 +14,7 @@ export const DocumentSelfSignedEmailTemplate = ({
assetBaseUrl = 'http://localhost:3002',
}: DocumentSelfSignedTemplateProps) => {
const { _ } = useLingui();
const branding = useBranding();
const previewText = msg`Completed Document`;
@ -29,11 +31,15 @@ export const DocumentSelfSignedEmailTemplate = ({
<Section className="bg-white">
<Container className="mx-auto mb-2 mt-8 max-w-xl rounded-lg border border-solid border-slate-200 p-2 backdrop-blur-sm">
<Section className="p-2">
<Img
src={getAssetUrl('/static/logo.png')}
alt="Documenso Logo"
className="mb-4 h-6"
/>
{branding.brandingEnabled && branding.brandingLogo ? (
<Img src={branding.brandingLogo} alt="Branding Logo" className="mb-4 h-6" />
) : (
<Img
src={getAssetUrl('/static/logo.png')}
alt="Documenso Logo"
className="mb-4 h-6"
/>
)}
<TemplateDocumentSelfSigned documentName={documentName} assetBaseUrl={assetBaseUrl} />
</Section>

View File

@ -2,6 +2,7 @@ import { msg } from '@lingui/macro';
import { useLingui } from '@lingui/react';
import { Body, Container, Head, Hr, Html, Img, Preview, Section } from '../components';
import { useBranding } from '../providers/branding';
import {
TemplateDocumentDelete,
type TemplateDocumentDeleteProps,
@ -16,6 +17,7 @@ export const DocumentSuperDeleteEmailTemplate = ({
reason = 'Unknown',
}: DocumentDeleteEmailTemplateProps) => {
const { _ } = useLingui();
const branding = useBranding();
const previewText = msg`An admin has deleted your document "${documentName}".`;
@ -32,11 +34,15 @@ export const DocumentSuperDeleteEmailTemplate = ({
<Section>
<Container className="mx-auto mb-2 mt-8 max-w-xl rounded-lg border border-solid border-slate-200 p-4 backdrop-blur-sm">
<Section>
<Img
src={getAssetUrl('/static/logo.png')}
alt="Documenso Logo"
className="mb-4 h-6"
/>
{branding.brandingEnabled && branding.brandingLogo ? (
<Img src={branding.brandingLogo} alt="Branding Logo" className="mb-4 h-6" />
) : (
<Img
src={getAssetUrl('/static/logo.png')}
alt="Documenso Logo"
className="mb-4 h-6"
/>
)}
<TemplateDocumentDelete
reason={reason}

View File

@ -2,6 +2,7 @@ import { msg } from '@lingui/macro';
import { useLingui } from '@lingui/react';
import { Body, Container, Head, Html, Img, Preview, Section } from '../components';
import { useBranding } from '../providers/branding';
import { TemplateFooter } from '../template-components/template-footer';
import type { TemplateForgotPasswordProps } from '../template-components/template-forgot-password';
import { TemplateForgotPassword } from '../template-components/template-forgot-password';
@ -13,6 +14,7 @@ export const ForgotPasswordTemplate = ({
assetBaseUrl = 'http://localhost:3002',
}: ForgotPasswordTemplateProps) => {
const { _ } = useLingui();
const branding = useBranding();
const previewText = msg`Password Reset Requested`;
@ -29,11 +31,15 @@ export const ForgotPasswordTemplate = ({
<Section>
<Container className="mx-auto mb-2 mt-8 max-w-xl rounded-lg border border-solid border-slate-200 p-4 backdrop-blur-sm">
<Section>
<Img
src={getAssetUrl('/static/logo.png')}
alt="Documenso Logo"
className="mb-4 h-6"
/>
{branding.brandingEnabled && branding.brandingLogo ? (
<Img src={branding.brandingLogo} alt="Branding Logo" className="mb-4 h-6" />
) : (
<Img
src={getAssetUrl('/static/logo.png')}
alt="Documenso Logo"
className="mb-4 h-6"
/>
)}
<TemplateForgotPassword
resetPasswordLink={resetPasswordLink}

View File

@ -1,7 +1,8 @@
import { msg } from '@lingui/macro';
import { Trans, msg } from '@lingui/macro';
import { useLingui } from '@lingui/react';
import { Body, Container, Head, Hr, Html, Img, Preview, Section, Text } from '../components';
import { useBranding } from '../providers/branding';
import type { TemplateDocumentCancelProps } from '../template-components/template-document-cancel';
import TemplateDocumentImage from '../template-components/template-document-image';
import { TemplateFooter } from '../template-components/template-footer';
@ -14,6 +15,7 @@ export const RecipientRemovedFromDocumentTemplate = ({
assetBaseUrl = 'http://localhost:3002',
}: DocumentCancelEmailTemplateProps) => {
const { _ } = useLingui();
const branding = useBranding();
const previewText = msg`${inviterName} has removed you from the document ${documentName}.`;
@ -30,18 +32,24 @@ export const RecipientRemovedFromDocumentTemplate = ({
<Section>
<Container className="mx-auto mb-2 mt-8 max-w-xl rounded-lg border border-solid border-slate-200 p-4 backdrop-blur-sm">
<Section>
<Img
src={getAssetUrl('/static/logo.png')}
alt="Documenso Logo"
className="mb-4 h-6"
/>
{branding.brandingEnabled && branding.brandingLogo ? (
<Img src={branding.brandingLogo} alt="Branding Logo" className="mb-4 h-6" />
) : (
<Img
src={getAssetUrl('/static/logo.png')}
alt="Documenso Logo"
className="mb-4 h-6"
/>
)}
<TemplateDocumentImage className="mt-6" assetBaseUrl={assetBaseUrl} />
<Section>
<Text className="text-primary mx-auto mb-0 max-w-[80%] text-center text-lg font-semibold">
{inviterName} has removed you from the document
<br />"{documentName}"
<Trans>
{inviterName} has removed you from the document
<br />"{documentName}"
</Trans>
</Text>
</Section>
</Section>

View File

@ -2,6 +2,7 @@ import { Trans, msg } from '@lingui/macro';
import { useLingui } from '@lingui/react';
import { Body, Container, Head, Hr, Html, Img, Link, Preview, Section, Text } from '../components';
import { useBranding } from '../providers/branding';
import { TemplateFooter } from '../template-components/template-footer';
import type { TemplateResetPasswordProps } from '../template-components/template-reset-password';
import { TemplateResetPassword } from '../template-components/template-reset-password';
@ -14,6 +15,7 @@ export const ResetPasswordTemplate = ({
assetBaseUrl = 'http://localhost:3002',
}: ResetPasswordTemplateProps) => {
const { _ } = useLingui();
const branding = useBranding();
const previewText = msg`Password Reset Successful`;
@ -30,11 +32,15 @@ export const ResetPasswordTemplate = ({
<Section>
<Container className="mx-auto mb-2 mt-8 max-w-xl rounded-lg border border-solid border-slate-200 p-4 backdrop-blur-sm">
<Section>
<Img
src={getAssetUrl('/static/logo.png')}
alt="Documenso Logo"
className="mb-4 h-6"
/>
{branding.brandingEnabled && branding.brandingLogo ? (
<Img src={branding.brandingLogo} alt="Branding Logo" className="mb-4 h-6" />
) : (
<Img
src={getAssetUrl('/static/logo.png')}
alt="Documenso Logo"
className="mb-4 h-6"
/>
)}
<TemplateResetPassword
userName={userName}

View File

@ -3,7 +3,8 @@ import { useLingui } from '@lingui/react';
import { formatTeamUrl } from '@documenso/lib/utils/teams';
import { Body, Container, Head, Hr, Html, Preview, Section, Text } from '../components';
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';
@ -21,6 +22,7 @@ export const TeamDeleteEmailTemplate = ({
isOwner = false,
}: TeamDeleteEmailProps) => {
const { _ } = useLingui();
const branding = useBranding();
const previewText = isOwner
? msg`Your team has been deleted`
@ -42,11 +44,15 @@ export const TeamDeleteEmailTemplate = ({
<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 p-2 backdrop-blur-sm">
<TemplateImage
assetBaseUrl={assetBaseUrl}
className="mb-4 h-6 p-2"
staticAsset="logo.png"
/>
{branding.brandingEnabled && branding.brandingLogo ? (
<Img src={branding.brandingLogo} alt="Branding Logo" className="mb-4 h-6 p-2" />
) : (
<TemplateImage
assetBaseUrl={assetBaseUrl}
className="mb-4 h-6 p-2"
staticAsset="logo.png"
/>
)}
<Section>
<TemplateImage

View File

@ -3,7 +3,8 @@ import { useLingui } from '@lingui/react';
import { formatTeamUrl } from '@documenso/lib/utils/teams';
import { Body, Container, Head, Hr, Html, Preview, Section, Text } from '../components';
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';
@ -23,6 +24,7 @@ export const TeamEmailRemovedTemplate = ({
teamUrl = 'demo',
}: TeamEmailRemovedTemplateProps) => {
const { _ } = useLingui();
const branding = useBranding();
const previewText = msg`Team email removed for ${teamName} on Documenso`;
@ -34,11 +36,15 @@ export const TeamEmailRemovedTemplate = ({
<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"
/>
{branding.brandingEnabled && branding.brandingLogo ? (
<Img src={branding.brandingLogo} alt="Branding Logo" className="mb-4 h-6 p-2" />
) : (
<TemplateImage
assetBaseUrl={assetBaseUrl}
className="mb-4 h-6 p-2"
staticAsset="logo.png"
/>
)}
<Section>
<TemplateImage

View File

@ -3,7 +3,19 @@ import { useLingui } from '@lingui/react';
import { formatTeamUrl } from '@documenso/lib/utils/teams';
import { Body, Button, Container, Head, Hr, Html, Preview, Section, Text } from '../components';
import {
Body,
Button,
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';
@ -25,6 +37,7 @@ export const TeamInviteEmailTemplate = ({
token = '',
}: TeamInviteEmailProps) => {
const { _ } = useLingui();
const branding = useBranding();
const previewText = msg`Accept invitation to join a team on Documenso`;
@ -36,11 +49,15 @@ export const TeamInviteEmailTemplate = ({
<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 p-2 backdrop-blur-sm">
<TemplateImage
assetBaseUrl={assetBaseUrl}
className="mb-4 h-6 p-2"
staticAsset="logo.png"
/>
{branding.brandingEnabled && branding.brandingLogo ? (
<Img src={branding.brandingLogo} alt="Branding Logo" className="mb-4 h-6 p-2" />
) : (
<TemplateImage
assetBaseUrl={assetBaseUrl}
className="mb-4 h-6 p-2"
staticAsset="logo.png"
/>
)}
<Section>
<TemplateImage

View File

@ -3,7 +3,8 @@ import { useLingui } from '@lingui/react';
import { formatTeamUrl } from '@documenso/lib/utils/teams';
import { Body, Container, Head, Hr, Html, Preview, Section, Text } from '../components';
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';
@ -25,6 +26,7 @@ export const TeamJoinEmailTemplate = ({
teamUrl = 'demo',
}: TeamJoinEmailProps) => {
const { _ } = useLingui();
const branding = useBranding();
const previewText = msg`A team member has joined a team on Documenso`;
@ -36,11 +38,15 @@ export const TeamJoinEmailTemplate = ({
<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 p-2 backdrop-blur-sm">
<TemplateImage
assetBaseUrl={assetBaseUrl}
className="mb-4 h-6 p-2"
staticAsset="logo.png"
/>
{branding.brandingEnabled && branding.brandingLogo ? (
<Img src={branding.brandingLogo} alt="Branding Logo" className="mb-4 h-6 p-2" />
) : (
<TemplateImage
assetBaseUrl={assetBaseUrl}
className="mb-4 h-6 p-2"
staticAsset="logo.png"
/>
)}
<Section>
<TemplateImage

View File

@ -3,7 +3,8 @@ import { useLingui } from '@lingui/react';
import { formatTeamUrl } from '@documenso/lib/utils/teams';
import { Body, Container, Head, Hr, Html, Preview, Section, Text } from '../components';
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';
@ -25,6 +26,7 @@ export const TeamLeaveEmailTemplate = ({
teamUrl = 'demo',
}: TeamLeaveEmailProps) => {
const { _ } = useLingui();
const branding = useBranding();
const previewText = msg`A team member has left a team on Documenso`;
@ -36,11 +38,15 @@ export const TeamLeaveEmailTemplate = ({
<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 p-2 backdrop-blur-sm">
<TemplateImage
assetBaseUrl={assetBaseUrl}
className="mb-4 h-6 p-2"
staticAsset="logo.png"
/>
{branding.brandingEnabled && branding.brandingLogo ? (
<Img src={branding.brandingLogo} alt="Branding Logo" className="mb-4 h-6 p-2" />
) : (
<TemplateImage
assetBaseUrl={assetBaseUrl}
className="mb-4 h-6 p-2"
staticAsset="logo.png"
/>
)}
<Section>
<TemplateImage

View File

@ -1,7 +1,7 @@
import type { MessageDescriptor } from '@lingui/core';
import { msg } from '@lingui/macro';
import { DocumentStatus } from '@documenso/prisma/client';
import { DocumentDistributionMethod, DocumentStatus } from '@documenso/prisma/client';
export const DOCUMENT_STATUS: {
[status in DocumentStatus]: { description: MessageDescriptor };
@ -16,3 +16,19 @@ export const DOCUMENT_STATUS: {
description: msg`Pending`,
},
};
type DocumentDistributionMethodTypeData = {
value: DocumentDistributionMethod;
description: MessageDescriptor;
};
export const DOCUMENT_DISTRIBUTION_METHODS: Record<string, DocumentDistributionMethodTypeData> = {
[DocumentDistributionMethod.EMAIL]: {
value: DocumentDistributionMethod.EMAIL,
description: msg`Email`,
},
[DocumentDistributionMethod.NONE]: {
value: DocumentDistributionMethod.NONE,
description: msg`None`,
},
} satisfies Record<DocumentDistributionMethod, DocumentDistributionMethodTypeData>;

View File

@ -9,59 +9,27 @@ export const RECIPIENT_ROLES_DESCRIPTION = {
actioned: msg`Approved`,
progressiveVerb: msg`Approving`,
roleName: msg`Approver`,
roleNamePlural: msg`Approvers`,
},
[RecipientRole.CC]: {
actionVerb: msg`CC`,
actioned: msg`CC'd`,
progressiveVerb: msg`CC`,
roleName: msg`Cc`,
roleNamePlural: msg`Ccers`,
},
[RecipientRole.SIGNER]: {
actionVerb: msg`Sign`,
actioned: msg`Signed`,
progressiveVerb: msg`Signing`,
roleName: msg`Signer`,
roleNamePlural: msg`Signers`,
},
[RecipientRole.VIEWER]: {
actionVerb: msg`View`,
actioned: msg`Viewed`,
progressiveVerb: msg`Viewing`,
roleName: msg`Viewer`,
},
} satisfies Record<keyof typeof RecipientRole, unknown>;
/**
* Raw english descriptions for emails.
*
* Todo: Handle i18n for emails.
*/
export const RECIPIENT_ROLES_DESCRIPTION_ENG = {
[RecipientRole.APPROVER]: {
actionVerb: `Approve`,
actioned: `Approved`,
progressiveVerb: `Approving`,
roleName: `Approver`,
roleNamePlural: msg`Approvers`,
},
[RecipientRole.CC]: {
actionVerb: `CC`,
actioned: `CC'd`,
progressiveVerb: `CC`,
roleName: `Cc`,
roleNamePlural: msg`Ccers`,
},
[RecipientRole.SIGNER]: {
actionVerb: `Sign`,
actioned: `Signed`,
progressiveVerb: `Signing`,
roleName: `Signer`,
roleNamePlural: msg`Signers`,
},
[RecipientRole.VIEWER]: {
actionVerb: `View`,
actioned: `Viewed`,
progressiveVerb: `Viewing`,
roleName: `Viewer`,
roleNamePlural: msg`Viewers`,
},
} satisfies Record<keyof typeof RecipientRole, unknown>;

View File

@ -169,7 +169,7 @@ export class LocalJobProvider extends BaseJobProvider {
},
});
} catch (error) {
console.error(`[JOBS]: Job ${options.name} failed`, error);
console.log(`[JOBS]: Job ${options.name} failed`, error);
const taskHasExceededRetries = error instanceof BackgroundTaskExceededRetriesError;
const jobHasExceededRetries =
@ -295,7 +295,7 @@ export class LocalJobProvider extends BaseJobProvider {
});
return result;
} catch {
} catch (err) {
task = await prisma.backgroundJobTask.update({
where: {
id: task.id,
@ -309,6 +309,8 @@ export class LocalJobProvider extends BaseJobProvider {
},
});
console.log(`[JOBS:${task.id}] Task failed`, err);
throw new BackgroundTaskFailedError('Task failed');
}
},

View File

@ -17,14 +17,16 @@ import { getI18nInstance } from '../../../client-only/providers/i18n.server';
import { NEXT_PUBLIC_WEBAPP_URL } from '../../../constants/app';
import { FROM_ADDRESS, FROM_NAME } from '../../../constants/email';
import {
RECIPIENT_ROLES_DESCRIPTION_ENG,
RECIPIENT_ROLES_DESCRIPTION,
RECIPIENT_ROLE_TO_EMAIL_TYPE,
} from '../../../constants/recipient-roles';
import { DOCUMENT_AUDIT_LOG_TYPE } from '../../../types/document-audit-logs';
import { extractDerivedDocumentEmailSettings } from '../../../types/document-email';
import { ZRequestMetadataSchema } from '../../../universal/extract-request-metadata';
import { createDocumentAuditLogData } from '../../../utils/document-audit-logs';
import { renderCustomEmailTemplate } from '../../../utils/render-custom-email-template';
import { renderEmailWithI18N } from '../../../utils/render-email-with-i18n';
import { teamGlobalSettingsToBranding } from '../../../utils/team-global-settings-to-branding';
import { type JobDefinition } from '../../client/_internal/job';
const SEND_SIGNING_EMAIL_JOB_DEFINITION_ID = 'send.signing.requested.email';
@ -64,6 +66,7 @@ export const SEND_SIGNING_EMAIL_JOB_DEFINITION = {
select: {
teamEmail: true,
name: true,
teamGlobalSettings: true,
},
},
},
@ -81,6 +84,14 @@ export const SEND_SIGNING_EMAIL_JOB_DEFINITION = {
return;
}
const isRecipientSigningRequestEmailEnabled = extractDerivedDocumentEmailSettings(
document.documentMeta,
).recipientSigningRequest;
if (!isRecipientSigningRequestEmailEnabled) {
return;
}
const customEmail = document?.documentMeta;
const isDirectTemplate = document.source === DocumentSource.TEMPLATE_DIRECT_LINK;
const isTeamDocument = document.teamId !== null;
@ -89,11 +100,13 @@ export const SEND_SIGNING_EMAIL_JOB_DEFINITION = {
const { email, name } = recipient;
const selfSigner = email === user.email;
const recipientActionVerb =
RECIPIENT_ROLES_DESCRIPTION_ENG[recipient.role].actionVerb.toLowerCase();
const i18n = await getI18nInstance(documentMeta?.language);
const recipientActionVerb = i18n
._(RECIPIENT_ROLES_DESCRIPTION[recipient.role].actionVerb)
.toLowerCase();
let emailMessage = customEmail?.message || '';
let emailSubject = i18n._(msg`Please ${recipientActionVerb} this document`);
@ -115,11 +128,15 @@ export const SEND_SIGNING_EMAIL_JOB_DEFINITION = {
if (isTeamDocument && team) {
emailSubject = i18n._(msg`${team.name} invited you to ${recipientActionVerb} a document`);
emailMessage =
customEmail?.message ||
i18n._(
msg`${user.name} on behalf of ${team.name} has invited you to ${recipientActionVerb} the document "${document.title}".`,
emailMessage = customEmail?.message ?? '';
if (!emailMessage) {
emailMessage = i18n._(
team.teamGlobalSettings?.includeSenderDetails
? msg`${user.name} on behalf of ${team.name} has invited you to ${recipientActionVerb} the document "${document.title}".`
: msg`${team.name} has invited you to ${recipientActionVerb} the document "${document.title}".`,
);
}
}
const customEmailTemplate = {
@ -143,13 +160,19 @@ export const SEND_SIGNING_EMAIL_JOB_DEFINITION = {
isTeamInvite: isTeamDocument,
teamName: team?.name,
teamEmail: team?.teamEmail?.email,
includeSenderDetails: team?.teamGlobalSettings?.includeSenderDetails,
});
await io.runTask('send-signing-email', async () => {
const branding = document.team?.teamGlobalSettings
? teamGlobalSettingsToBranding(document.team.teamGlobalSettings)
: undefined;
const [html, text] = await Promise.all([
renderEmailWithI18N(template, { lang: documentMeta?.language }),
renderEmailWithI18N(template, { lang: documentMeta?.language, branding }),
renderEmailWithI18N(template, {
lang: documentMeta?.language,
branding,
plainText: true,
}),
]);

View File

@ -1,5 +1,7 @@
import { z } from 'zod';
import { DocumentVisibility } from '@documenso/prisma/client';
import { sendTeamDeleteEmail } from '../../../server-only/team/delete-team';
import type { JobDefinition } from '../../client/_internal/job';
@ -10,6 +12,19 @@ const SEND_TEAM_DELETED_EMAIL_JOB_DEFINITION_SCHEMA = z.object({
name: z.string(),
url: z.string(),
ownerUserId: z.number(),
teamGlobalSettings: z
.object({
documentVisibility: z.nativeEnum(DocumentVisibility),
documentLanguage: z.string(),
includeSenderDetails: z.boolean(),
brandingEnabled: z.boolean(),
brandingLogo: z.string(),
brandingUrl: z.string(),
brandingCompanyDetails: z.string(),
brandingHidePoweredBy: z.boolean(),
teamId: z.number(),
})
.nullish(),
}),
members: z.array(
z.object({
@ -35,8 +50,7 @@ export const SEND_TEAM_DELETED_EMAIL_JOB_DEFINITION = {
await io.runTask(`send-team-deleted-email--${team.url}_${member.id}`, async () => {
await sendTeamDeleteEmail({
email: member.email,
teamName: team.name,
teamUrl: team.url,
team,
isOwner: member.id === team.ownerUserId,
});
});

View File

@ -1,3 +1,5 @@
import { createElement } from 'react';
import { msg } from '@lingui/macro';
import { z } from 'zod';
@ -10,6 +12,7 @@ import { getI18nInstance } from '../../../client-only/providers/i18n.server';
import { WEBAPP_BASE_URL } from '../../../constants/app';
import { FROM_ADDRESS, FROM_NAME } from '../../../constants/email';
import { renderEmailWithI18N } from '../../../utils/render-email-with-i18n';
import { teamGlobalSettingsToBranding } from '../../../utils/team-global-settings-to-branding';
import type { JobDefinition } from '../../client/_internal/job';
const SEND_TEAM_MEMBER_JOINED_EMAIL_JOB_DEFINITION_ID = 'send.team-member-joined.email';
@ -43,6 +46,7 @@ export const SEND_TEAM_MEMBER_JOINED_EMAIL_JOB_DEFINITION = {
user: true,
},
},
teamGlobalSettings: true,
},
});
@ -64,7 +68,7 @@ export const SEND_TEAM_MEMBER_JOINED_EMAIL_JOB_DEFINITION = {
await io.runTask(
`send-team-member-joined-email--${invitedMember.id}_${member.id}`,
async () => {
const emailContent = TeamJoinEmailTemplate({
const emailContent = createElement(TeamJoinEmailTemplate, {
assetBaseUrl: WEBAPP_BASE_URL,
baseUrl: WEBAPP_BASE_URL,
memberName: invitedMember.user.name || '',
@ -73,13 +77,26 @@ export const SEND_TEAM_MEMBER_JOINED_EMAIL_JOB_DEFINITION = {
teamUrl: team.url,
});
const branding = team.teamGlobalSettings
? teamGlobalSettingsToBranding(team.teamGlobalSettings)
: undefined;
const lang = team.teamGlobalSettings?.documentLanguage;
// !: Replace with the actual language of the recipient later
const [html, text] = await Promise.all([
renderEmailWithI18N(emailContent),
renderEmailWithI18N(emailContent, { plainText: true }),
renderEmailWithI18N(emailContent, {
lang,
branding,
}),
renderEmailWithI18N(emailContent, {
lang,
branding,
plainText: true,
}),
]);
const i18n = await getI18nInstance();
const i18n = await getI18nInstance(lang);
await mailer.sendMail({
to: member.user.email,

View File

@ -1,3 +1,5 @@
import { createElement } from 'react';
import { msg } from '@lingui/macro';
import { z } from 'zod';
@ -10,6 +12,7 @@ import { getI18nInstance } from '../../../client-only/providers/i18n.server';
import { WEBAPP_BASE_URL } from '../../../constants/app';
import { FROM_ADDRESS, FROM_NAME } from '../../../constants/email';
import { renderEmailWithI18N } from '../../../utils/render-email-with-i18n';
import { teamGlobalSettingsToBranding } from '../../../utils/team-global-settings-to-branding';
import type { JobDefinition } from '../../client/_internal/job';
const SEND_TEAM_MEMBER_LEFT_EMAIL_JOB_DEFINITION_ID = 'send.team-member-left.email';
@ -43,6 +46,7 @@ export const SEND_TEAM_MEMBER_LEFT_EMAIL_JOB_DEFINITION = {
user: true,
},
},
teamGlobalSettings: true,
},
});
@ -54,7 +58,7 @@ export const SEND_TEAM_MEMBER_LEFT_EMAIL_JOB_DEFINITION = {
for (const member of team.members) {
await io.runTask(`send-team-member-left-email--${oldMember.id}_${member.id}`, async () => {
const emailContent = TeamJoinEmailTemplate({
const emailContent = createElement(TeamJoinEmailTemplate, {
assetBaseUrl: WEBAPP_BASE_URL,
baseUrl: WEBAPP_BASE_URL,
memberName: oldMember.name || '',
@ -63,12 +67,25 @@ export const SEND_TEAM_MEMBER_LEFT_EMAIL_JOB_DEFINITION = {
teamUrl: team.url,
});
const branding = team.teamGlobalSettings
? teamGlobalSettingsToBranding(team.teamGlobalSettings)
: undefined;
const lang = team.teamGlobalSettings?.documentLanguage;
const [html, text] = await Promise.all([
renderEmailWithI18N(emailContent),
renderEmailWithI18N(emailContent, { plainText: true }),
renderEmailWithI18N(emailContent, {
lang,
branding,
}),
renderEmailWithI18N(emailContent, {
lang,
branding,
plainText: true,
}),
]);
const i18n = await getI18nInstance();
const i18n = await getI18nInstance(lang);
await mailer.sendMail({
to: member.user.email,

View File

@ -7,9 +7,10 @@ import {
diffDocumentMetaChanges,
} from '@documenso/lib/utils/document-audit-logs';
import { prisma } from '@documenso/prisma';
import type { DocumentSigningOrder } from '@documenso/prisma/client';
import type { DocumentDistributionMethod, DocumentSigningOrder } from '@documenso/prisma/client';
import type { SupportedLanguageCodes } from '../../constants/i18n';
import type { TDocumentEmailSettings } from '../../types/document-email';
export type CreateDocumentMetaOptions = {
documentId: number;
@ -19,7 +20,9 @@ export type CreateDocumentMetaOptions = {
password?: string;
dateFormat?: string;
redirectUrl?: string;
emailSettings?: TDocumentEmailSettings;
signingOrder?: DocumentSigningOrder;
distributionMethod?: DocumentDistributionMethod;
typedSignatureEnabled?: boolean;
language?: SupportedLanguageCodes;
userId: number;
@ -36,6 +39,8 @@ export const upsertDocumentMeta = async ({
userId,
redirectUrl,
signingOrder,
emailSettings,
distributionMethod,
typedSignatureEnabled,
language,
requestMetadata,
@ -88,6 +93,8 @@ export const upsertDocumentMeta = async ({
documentId,
redirectUrl,
signingOrder,
emailSettings,
distributionMethod,
typedSignatureEnabled,
language,
},
@ -99,6 +106,8 @@ export const upsertDocumentMeta = async ({
timezone,
redirectUrl,
signingOrder,
emailSettings,
distributionMethod,
typedSignatureEnabled,
language,
},

View File

@ -5,7 +5,9 @@ import { DOCUMENT_AUDIT_LOG_TYPE } from '@documenso/lib/types/document-audit-log
import type { RequestMetadata } from '@documenso/lib/universal/extract-request-metadata';
import { createDocumentAuditLogData } from '@documenso/lib/utils/document-audit-logs';
import { prisma } from '@documenso/prisma';
import { DocumentSource, WebhookTriggerEvents } from '@documenso/prisma/client';
import { DocumentSource, DocumentVisibility, WebhookTriggerEvents } from '@documenso/prisma/client';
import type { Team, TeamGlobalSettings } from '@documenso/prisma/client';
import { TeamMemberRole } from '@documenso/prisma/client';
import { triggerWebhook } from '../webhooks/trigger/trigger-webhook';
@ -48,6 +50,51 @@ export const createDocument = async ({
throw new AppError(AppErrorCode.NOT_FOUND, 'Team not found');
}
let team: (Team & { teamGlobalSettings: TeamGlobalSettings | null }) | null = null;
let userTeamRole: TeamMemberRole | undefined;
if (teamId) {
const teamWithUserRole = await prisma.team.findFirstOrThrow({
where: {
id: teamId,
},
include: {
teamGlobalSettings: true,
members: {
where: {
userId: userId,
},
select: {
role: true,
},
},
},
});
team = teamWithUserRole;
userTeamRole = teamWithUserRole.members[0]?.role;
}
const determineVisibility = (
globalVisibility: DocumentVisibility | null | undefined,
userRole: TeamMemberRole,
): DocumentVisibility => {
const defaultVisibility = globalVisibility ?? DocumentVisibility.EVERYONE;
if (userRole === TeamMemberRole.ADMIN) {
return defaultVisibility;
}
if (userRole === TeamMemberRole.MANAGER) {
if (defaultVisibility === DocumentVisibility.ADMIN) {
return DocumentVisibility.MANAGER_AND_ABOVE;
}
return defaultVisibility;
}
return DocumentVisibility.EVERYONE;
};
return await prisma.$transaction(async (tx) => {
const document = await tx.document.create({
data: {
@ -56,8 +103,17 @@ export const createDocument = async ({
documentDataId,
userId,
teamId,
visibility: determineVisibility(
team?.teamGlobalSettings?.documentVisibility,
userTeamRole ?? TeamMemberRole.MEMBER,
),
formValues,
source: DocumentSource.DOCUMENT,
documentMeta: {
create: {
language: team?.teamGlobalSettings?.documentLanguage,
},
},
},
});

View File

@ -7,16 +7,25 @@ import { msg } from '@lingui/macro';
import { mailer } from '@documenso/email/mailer';
import DocumentCancelTemplate from '@documenso/email/templates/document-cancel';
import { prisma } from '@documenso/prisma';
import type { Document, DocumentMeta, Recipient, User } from '@documenso/prisma/client';
import type {
Document,
DocumentMeta,
Recipient,
Team,
TeamGlobalSettings,
User,
} from '@documenso/prisma/client';
import { DocumentStatus, SendStatus } from '@documenso/prisma/client';
import { getI18nInstance } from '../../client-only/providers/i18n.server';
import { NEXT_PUBLIC_WEBAPP_URL } from '../../constants/app';
import { FROM_ADDRESS, FROM_NAME } from '../../constants/email';
import { DOCUMENT_AUDIT_LOG_TYPE } from '../../types/document-audit-logs';
import { extractDerivedDocumentEmailSettings } from '../../types/document-email';
import type { RequestMetadata } from '../../universal/extract-request-metadata';
import { createDocumentAuditLogData } from '../../utils/document-audit-logs';
import { renderEmailWithI18N } from '../../utils/render-email-with-i18n';
import { teamGlobalSettingsToBranding } from '../../utils/team-global-settings-to-branding';
export type DeleteDocumentOptions = {
id: number;
@ -49,8 +58,9 @@ export const deleteDocument = async ({
Recipient: true,
documentMeta: true,
team: {
select: {
include: {
members: true,
teamGlobalSettings: true,
},
},
},
@ -73,6 +83,7 @@ export const deleteDocument = async ({
await handleDocumentOwnerDelete({
document,
user,
team: document.team,
requestMetadata,
});
}
@ -113,6 +124,11 @@ type HandleDocumentOwnerDeleteOptions = {
Recipient: Recipient[];
documentMeta: DocumentMeta | null;
};
team?:
| (Team & {
teamGlobalSettings?: TeamGlobalSettings | null;
})
| null;
user: User;
requestMetadata?: RequestMetadata;
};
@ -120,6 +136,7 @@ type HandleDocumentOwnerDeleteOptions = {
const handleDocumentOwnerDelete = async ({
document,
user,
team,
requestMetadata,
}: HandleDocumentOwnerDeleteOptions) => {
if (document.deletedAt) {
@ -178,6 +195,14 @@ const handleDocumentOwnerDelete = async ({
});
});
const isDocumentDeleteEmailEnabled = extractDerivedDocumentEmailSettings(
document.documentMeta,
).documentDeleted;
if (!isDocumentDeleteEmailEnabled) {
return deletedDocument;
}
// Send cancellation emails to recipients.
await Promise.all(
document.Recipient.map(async (recipient) => {
@ -194,9 +219,17 @@ const handleDocumentOwnerDelete = async ({
assetBaseUrl,
});
const branding = team?.teamGlobalSettings
? teamGlobalSettingsToBranding(team.teamGlobalSettings)
: undefined;
const [html, text] = await Promise.all([
renderEmailWithI18N(template, { lang: document.documentMeta?.language }),
renderEmailWithI18N(template, { lang: document.documentMeta?.language, plainText: true }),
renderEmailWithI18N(template, { lang: document.documentMeta?.language, branding }),
renderEmailWithI18N(template, {
lang: document.documentMeta?.language,
branding,
plainText: true,
}),
]);
const i18n = await getI18nInstance(document.documentMeta?.language);

View File

@ -124,11 +124,18 @@ export const findDocuments = async ({
}))
.otherwise(() => ({ visibility: DocumentVisibility.EVERYONE })),
{
Recipient: {
some: {
email: user.email,
OR: [
{
Recipient: {
some: {
email: user.email,
},
},
},
},
{
userId: user.id,
},
],
},
];

View File

@ -143,11 +143,18 @@ export const getDocumentWhereInput = async ({
])
.otherwise(() => [{ visibility: DocumentVisibility.EVERYONE }]),
{
Recipient: {
some: {
email: user.email,
OR: [
{
Recipient: {
some: {
email: user.email,
},
},
},
},
{
userId: user.id,
},
],
},
];

View File

@ -6,11 +6,10 @@ import { prisma } from '@documenso/prisma';
import { TeamMemberRole } from '@documenso/prisma/client';
import type { Prisma, User } from '@documenso/prisma/client';
import { SigningStatus } from '@documenso/prisma/client';
import { DocumentVisibility } from '@documenso/prisma/client';
import { isExtendedDocumentStatus } from '@documenso/prisma/guards/is-extended-document-status';
import { ExtendedDocumentStatus } from '@documenso/prisma/types/extended-document-status';
import { DocumentVisibility } from '../../types/document-visibility';
export type GetStatsInput = {
user: User;
team?: Omit<GetTeamCountsOption, 'createdAt'>;
@ -207,47 +206,45 @@ const getTeamCounts = async (options: GetTeamCountsOption) => {
let notSignedCountsGroupByArgs = null;
let hasSignedCountsGroupByArgs = null;
const visibilityFilters = [
...match(options.currentTeamMemberRole)
.with(TeamMemberRole.ADMIN, () => [
{ visibility: DocumentVisibility.EVERYONE },
{ visibility: DocumentVisibility.MANAGER_AND_ABOVE },
{ visibility: DocumentVisibility.ADMIN },
])
.with(TeamMemberRole.MANAGER, () => [
{ visibility: DocumentVisibility.EVERYONE },
{ visibility: DocumentVisibility.MANAGER_AND_ABOVE },
])
.otherwise(() => [{ visibility: DocumentVisibility.EVERYONE }]),
];
ownerCountsWhereInput = {
...ownerCountsWhereInput,
OR: [
const visibilityFiltersWhereInput: Prisma.DocumentWhereInput = {
AND: [
{ deletedAt: null },
{
AND: [
{
visibility: {
in: visibilityFilters.map((filter) => filter.visibility),
},
},
{
Recipient: {
none: {
email: options.currentUserEmail,
OR: [
match(options.currentTeamMemberRole)
.with(TeamMemberRole.ADMIN, () => ({
visibility: {
in: [
DocumentVisibility.EVERYONE,
DocumentVisibility.MANAGER_AND_ABOVE,
DocumentVisibility.ADMIN,
],
},
},
}))
.with(TeamMemberRole.MANAGER, () => ({
visibility: {
in: [DocumentVisibility.EVERYONE, DocumentVisibility.MANAGER_AND_ABOVE],
},
}))
.otherwise(() => ({
visibility: {
equals: DocumentVisibility.EVERYONE,
},
})),
{
OR: [
{ userId: options.userId },
{ Recipient: { some: { email: options.currentUserEmail } } },
],
},
],
},
{
Recipient: {
some: {
email: options.currentUserEmail,
},
},
},
],
};
ownerCountsWhereInput = {
...ownerCountsWhereInput,
...visibilityFiltersWhereInput,
...searchFilter,
};

View File

@ -19,7 +19,9 @@ import type { Prisma } from '@documenso/prisma/client';
import { getI18nInstance } from '../../client-only/providers/i18n.server';
import { NEXT_PUBLIC_WEBAPP_URL } from '../../constants/app';
import { extractDerivedDocumentEmailSettings } from '../../types/document-email';
import { renderEmailWithI18N } from '../../utils/render-email-with-i18n';
import { teamGlobalSettingsToBranding } from '../../utils/team-global-settings-to-branding';
import { getDocumentWhereInput } from './get-document-by-id';
export type ResendDocumentOptions = {
@ -65,6 +67,7 @@ export const resendDocument = async ({
select: {
teamEmail: true,
name: true,
teamGlobalSettings: true,
},
},
},
@ -89,6 +92,14 @@ export const resendDocument = async ({
throw new Error('Can not send completed document');
}
const isRecipientSigningRequestEmailEnabled = extractDerivedDocumentEmailSettings(
document.documentMeta,
).recipientSigningRequest;
if (!isRecipientSigningRequestEmailEnabled) {
return;
}
await Promise.all(
document.Recipient.map(async (recipient) => {
if (recipient.role === RecipientRole.CC) {
@ -149,12 +160,20 @@ export const resendDocument = async ({
teamName: document.team?.name,
});
const branding = document.team?.teamGlobalSettings
? teamGlobalSettingsToBranding(document.team.teamGlobalSettings)
: undefined;
await prisma.$transaction(
async (tx) => {
const [html, text] = await Promise.all([
renderEmailWithI18N(template, { lang: document.documentMeta?.language }),
renderEmailWithI18N(template, {
lang: document.documentMeta?.language,
branding,
}),
renderEmailWithI18N(template, {
lang: document.documentMeta?.language,
branding,
plainText: true,
}),
]);

View File

@ -10,11 +10,13 @@ import { DocumentSource } from '@documenso/prisma/client';
import { getI18nInstance } from '../../client-only/providers/i18n.server';
import { NEXT_PUBLIC_WEBAPP_URL } from '../../constants/app';
import { DOCUMENT_AUDIT_LOG_TYPE } from '../../types/document-audit-logs';
import { extractDerivedDocumentEmailSettings } from '../../types/document-email';
import type { RequestMetadata } from '../../universal/extract-request-metadata';
import { getFile } from '../../universal/upload/get-file';
import { createDocumentAuditLogData } from '../../utils/document-audit-logs';
import { renderCustomEmailTemplate } from '../../utils/render-custom-email-template';
import { renderEmailWithI18N } from '../../utils/render-email-with-i18n';
import { teamGlobalSettingsToBranding } from '../../utils/team-global-settings-to-branding';
export interface SendDocumentOptions {
documentId: number;
@ -35,6 +37,7 @@ export const sendCompletedEmail = async ({ documentId, requestMetadata }: SendDo
select: {
id: true,
url: true,
teamGlobalSettings: true,
},
},
},
@ -66,17 +69,32 @@ export const sendCompletedEmail = async ({ documentId, requestMetadata }: SendDo
const i18n = await getI18nInstance(document.documentMeta?.language);
// If the document owner is not a recipient then send the email to them separately
if (!document.Recipient.find((recipient) => recipient.email === owner.email)) {
const isDocumentCompletedEmailEnabled = extractDerivedDocumentEmailSettings(
document.documentMeta,
).documentCompleted;
// If the document owner is not a recipient, OR recipient emails are disabled, then send the email to them separately.
if (
!document.Recipient.find((recipient) => recipient.email === owner.email) ||
!isDocumentCompletedEmailEnabled
) {
const template = createElement(DocumentCompletedEmailTemplate, {
documentName: document.title,
assetBaseUrl,
downloadLink: documentOwnerDownloadLink,
});
const branding = document.team?.teamGlobalSettings
? teamGlobalSettingsToBranding(document.team.teamGlobalSettings)
: undefined;
const [html, text] = await Promise.all([
renderEmailWithI18N(template, { lang: document.documentMeta?.language }),
renderEmailWithI18N(template, { lang: document.documentMeta?.language, plainText: true }),
renderEmailWithI18N(template, { lang: document.documentMeta?.language, branding }),
renderEmailWithI18N(template, {
lang: document.documentMeta?.language,
branding,
plainText: true,
}),
]);
await mailer.sendMail({
@ -119,6 +137,10 @@ export const sendCompletedEmail = async ({ documentId, requestMetadata }: SendDo
});
}
if (!isDocumentCompletedEmailEnabled) {
return;
}
await Promise.all(
document.Recipient.map(async (recipient) => {
const customEmailTemplate = {
@ -139,9 +161,17 @@ export const sendCompletedEmail = async ({ documentId, requestMetadata }: SendDo
: undefined,
});
const branding = document.team?.teamGlobalSettings
? teamGlobalSettingsToBranding(document.team.teamGlobalSettings)
: undefined;
const [html, text] = await Promise.all([
renderEmailWithI18N(template, { lang: document.documentMeta?.language }),
renderEmailWithI18N(template, { lang: document.documentMeta?.language, plainText: true }),
renderEmailWithI18N(template, { lang: document.documentMeta?.language, branding }),
renderEmailWithI18N(template, {
lang: document.documentMeta?.language,
branding,
plainText: true,
}),
]);
await mailer.sendMail({

View File

@ -8,7 +8,9 @@ import { prisma } from '@documenso/prisma';
import { getI18nInstance } from '../../client-only/providers/i18n.server';
import { NEXT_PUBLIC_WEBAPP_URL } from '../../constants/app';
import { extractDerivedDocumentEmailSettings } from '../../types/document-email';
import { renderEmailWithI18N } from '../../utils/render-email-with-i18n';
import { teamGlobalSettingsToBranding } from '../../utils/team-global-settings-to-branding';
export interface SendDeleteEmailOptions {
documentId: number;
@ -22,6 +24,12 @@ export const sendDeleteEmail = async ({ documentId, reason }: SendDeleteEmailOpt
},
include: {
User: true,
documentMeta: true,
team: {
include: {
teamGlobalSettings: true,
},
},
},
});
@ -29,6 +37,14 @@ export const sendDeleteEmail = async ({ documentId, reason }: SendDeleteEmailOpt
throw new Error('Document not found');
}
const isDocumentDeletedEmailEnabled = extractDerivedDocumentEmailSettings(
document.documentMeta,
).documentDeleted;
if (!isDocumentDeletedEmailEnabled) {
return;
}
const { email, name } = document.User;
const assetBaseUrl = NEXT_PUBLIC_WEBAPP_URL() || 'http://localhost:3000';
@ -39,9 +55,17 @@ export const sendDeleteEmail = async ({ documentId, reason }: SendDeleteEmailOpt
assetBaseUrl,
});
const branding = document.team?.teamGlobalSettings
? teamGlobalSettingsToBranding(document.team.teamGlobalSettings)
: undefined;
const [html, text] = await Promise.all([
renderEmailWithI18N(template),
renderEmailWithI18N(template, { plainText: true }),
renderEmailWithI18N(template, { lang: document.documentMeta?.language, branding }),
renderEmailWithI18N(template, {
lang: document.documentMeta?.language,
branding,
plainText: true,
}),
]);
const i18n = await getI18nInstance();

View File

@ -13,6 +13,7 @@ import {
import { WebhookTriggerEvents } from '@documenso/prisma/client';
import { jobs } from '../../jobs/client';
import { extractDerivedDocumentEmailSettings } from '../../types/document-email';
import { getFile } from '../../universal/upload/get-file';
import { insertFormValuesInPdf } from '../pdf/insert-form-values-in-pdf';
import { triggerWebhook } from '../webhooks/trigger/trigger-webhook';
@ -29,7 +30,7 @@ export const sendDocument = async ({
documentId,
userId,
teamId,
sendEmail = true,
sendEmail,
requestMetadata,
}: SendDocumentOptions) => {
const user = await prisma.user.findFirstOrThrow({
@ -156,7 +157,14 @@ export const sendDocument = async ({
// throw new Error('Some signers have not been assigned a signature field.');
// }
if (sendEmail) {
const isRecipientSigningRequestEmailEnabled = extractDerivedDocumentEmailSettings(
document.documentMeta,
).recipientSigningRequest;
// Only send email if one of the following is true:
// - It is explicitly set
// - The email is enabled for signing requests AND sendEmail is undefined
if (sendEmail || (isRecipientSigningRequestEmailEnabled && sendEmail === undefined)) {
await Promise.all(
recipientsToNotify.map(async (recipient) => {
if (recipient.sendStatus === SendStatus.SENT || recipient.role === RecipientRole.CC) {

View File

@ -8,7 +8,9 @@ import { prisma } from '@documenso/prisma';
import { getI18nInstance } from '../../client-only/providers/i18n.server';
import { NEXT_PUBLIC_WEBAPP_URL } from '../../constants/app';
import { extractDerivedDocumentEmailSettings } from '../../types/document-email';
import { renderEmailWithI18N } from '../../utils/render-email-with-i18n';
import { teamGlobalSettingsToBranding } from '../../utils/team-global-settings-to-branding';
export interface SendPendingEmailOptions {
documentId: number;
@ -32,6 +34,11 @@ export const sendPendingEmail = async ({ documentId, recipientId }: SendPendingE
},
},
documentMeta: true,
team: {
include: {
teamGlobalSettings: true,
},
},
},
});
@ -43,6 +50,14 @@ export const sendPendingEmail = async ({ documentId, recipientId }: SendPendingE
throw new Error('Document has no recipients');
}
const isDocumentPendingEmailEnabled = extractDerivedDocumentEmailSettings(
document.documentMeta,
).documentPending;
if (!isDocumentPendingEmailEnabled) {
return;
}
const [recipient] = document.Recipient;
const { email, name } = recipient;
@ -54,12 +69,20 @@ export const sendPendingEmail = async ({ documentId, recipientId }: SendPendingE
assetBaseUrl,
});
const branding = document.team?.teamGlobalSettings
? teamGlobalSettingsToBranding(document.team.teamGlobalSettings)
: undefined;
const [html, text] = await Promise.all([
renderEmailWithI18N(template, { lang: document.documentMeta?.language }),
renderEmailWithI18N(template, { lang: document.documentMeta?.language, plainText: true }),
renderEmailWithI18N(template, { lang: document.documentMeta?.language, branding }),
renderEmailWithI18N(template, {
lang: document.documentMeta?.language,
branding,
plainText: true,
}),
]);
const i18n = await getI18nInstance();
const i18n = await getI18nInstance(document.documentMeta?.language);
await mailer.sendMail({
to: {

View File

@ -13,9 +13,11 @@ import { getI18nInstance } from '../../client-only/providers/i18n.server';
import { NEXT_PUBLIC_WEBAPP_URL } from '../../constants/app';
import { FROM_ADDRESS, FROM_NAME } from '../../constants/email';
import { DOCUMENT_AUDIT_LOG_TYPE } from '../../types/document-audit-logs';
import { extractDerivedDocumentEmailSettings } from '../../types/document-email';
import type { RequestMetadata } from '../../universal/extract-request-metadata';
import { createDocumentAuditLogData } from '../../utils/document-audit-logs';
import { renderEmailWithI18N } from '../../utils/render-email-with-i18n';
import { teamGlobalSettingsToBranding } from '../../utils/team-global-settings-to-branding';
export type SuperDeleteDocumentOptions = {
id: number;
@ -31,6 +33,11 @@ export const superDeleteDocument = async ({ id, requestMetadata }: SuperDeleteDo
Recipient: true,
documentMeta: true,
User: true,
team: {
include: {
teamGlobalSettings: true,
},
},
},
});
@ -40,8 +47,16 @@ export const superDeleteDocument = async ({ id, requestMetadata }: SuperDeleteDo
const { status, User: user } = document;
const isDocumentDeletedEmailEnabled = extractDerivedDocumentEmailSettings(
document.documentMeta,
).documentDeleted;
// if the document is pending, send cancellation emails to all recipients
if (status === DocumentStatus.PENDING && document.Recipient.length > 0) {
if (
status === DocumentStatus.PENDING &&
document.Recipient.length > 0 &&
isDocumentDeletedEmailEnabled
) {
await Promise.all(
document.Recipient.map(async (recipient) => {
if (recipient.sendStatus !== SendStatus.SENT) {
@ -56,9 +71,17 @@ export const superDeleteDocument = async ({ id, requestMetadata }: SuperDeleteDo
assetBaseUrl,
});
const branding = document.team?.teamGlobalSettings
? teamGlobalSettingsToBranding(document.team.teamGlobalSettings)
: undefined;
const [html, text] = await Promise.all([
renderEmailWithI18N(template, { lang: document.documentMeta?.language }),
renderEmailWithI18N(template, { lang: document.documentMeta?.language, plainText: true }),
renderEmailWithI18N(template, { lang: document.documentMeta?.language, branding }),
renderEmailWithI18N(template, {
lang: document.documentMeta?.language,
branding,
plainText: true,
}),
]);
const i18n = await getI18nInstance(document.documentMeta?.language);

View File

@ -1,13 +1,15 @@
'use server';
import { match } from 'ts-pattern';
import { isUserEnterprise } from '@documenso/ee/server-only/util/is-document-enterprise';
import { DOCUMENT_AUDIT_LOG_TYPE } from '@documenso/lib/types/document-audit-logs';
import type { RequestMetadata } from '@documenso/lib/universal/extract-request-metadata';
import type { CreateDocumentAuditLogDataResponse } from '@documenso/lib/utils/document-audit-logs';
import { createDocumentAuditLogData } from '@documenso/lib/utils/document-audit-logs';
import { prisma } from '@documenso/prisma';
import type { DocumentVisibility } from '@documenso/prisma/client';
import { DocumentStatus } from '@documenso/prisma/client';
import { DocumentVisibility } from '@documenso/prisma/client';
import { DocumentStatus, TeamMemberRole } from '@documenso/prisma/client';
import { AppError, AppErrorCode } from '../../errors/app-error';
import type { TDocumentAccessAuthTypes, TDocumentActionAuthTypes } from '../../types/document-auth';
@ -20,7 +22,7 @@ export type UpdateDocumentSettingsOptions = {
data: {
title?: string;
externalId?: string | null;
visibility?: string | null;
visibility?: DocumentVisibility | null;
globalAccessAuth?: TDocumentAccessAuthTypes | null;
globalActionAuth?: TDocumentActionAuthTypes | null;
};
@ -63,8 +65,62 @@ export const updateDocumentSettings = async ({
teamId: null,
}),
},
include: {
team: {
select: {
members: {
where: {
userId,
},
select: {
role: true,
},
},
},
},
},
});
if (teamId) {
const currentUserRole = document.team?.members[0]?.role;
match(currentUserRole)
.with(TeamMemberRole.ADMIN, () => true)
.with(TeamMemberRole.MANAGER, () => {
const allowedVisibilities: DocumentVisibility[] = [
DocumentVisibility.EVERYONE,
DocumentVisibility.MANAGER_AND_ABOVE,
];
if (
!allowedVisibilities.includes(document.visibility) ||
(data.visibility && !allowedVisibilities.includes(data.visibility))
) {
throw new AppError(
AppErrorCode.UNAUTHORIZED,
'You do not have permission to update the document visibility',
);
}
})
.with(TeamMemberRole.MEMBER, () => {
if (
document.visibility !== DocumentVisibility.EVERYONE ||
(data.visibility && data.visibility !== DocumentVisibility.EVERYONE)
) {
throw new AppError(
AppErrorCode.UNAUTHORIZED,
'You do not have permission to update the document visibility',
);
}
})
.otherwise(() => {
throw new AppError(
AppErrorCode.UNAUTHORIZED,
'You do not have permission to update the document',
);
});
}
const { documentAuthOption } = extractDocumentAuthMethods({
documentAuth: document.authOptions,
});

View File

@ -26,8 +26,10 @@ import { getI18nInstance } from '../../client-only/providers/i18n.server';
import { NEXT_PUBLIC_WEBAPP_URL } from '../../constants/app';
import { FROM_ADDRESS, FROM_NAME } from '../../constants/email';
import { AppError, AppErrorCode } from '../../errors/app-error';
import { extractDerivedDocumentEmailSettings } from '../../types/document-email';
import { canRecipientBeModified } from '../../utils/recipients';
import { renderEmailWithI18N } from '../../utils/render-email-with-i18n';
import { teamGlobalSettingsToBranding } from '../../utils/team-global-settings-to-branding';
export interface SetRecipientsForDocumentOptions {
userId: number;
@ -66,6 +68,11 @@ export const setRecipientsForDocument = async ({
include: {
Field: true,
documentMeta: true,
team: {
include: {
teamGlobalSettings: true,
},
},
},
});
@ -280,10 +287,14 @@ export const setRecipientsForDocument = async ({
});
});
const isRecipientRemovedEmailEnabled = extractDerivedDocumentEmailSettings(
document.documentMeta,
).recipientRemoved;
// Send emails to deleted recipients.
await Promise.all(
removedRecipients.map(async (recipient) => {
if (recipient.sendStatus !== SendStatus.SENT) {
if (recipient.sendStatus !== SendStatus.SENT || !isRecipientRemovedEmailEnabled) {
return;
}
@ -295,6 +306,10 @@ export const setRecipientsForDocument = async ({
assetBaseUrl,
});
const branding = document.team?.teamGlobalSettings
? teamGlobalSettingsToBranding(document.team.teamGlobalSettings)
: undefined;
const [html, text] = await Promise.all([
renderEmailWithI18N(template, { lang: document.documentMeta?.language }),
renderEmailWithI18N(template, { lang: document.documentMeta?.language, plainText: true }),

View File

@ -11,10 +11,12 @@ import { TEAM_MEMBER_ROLE_PERMISSIONS_MAP } from '@documenso/lib/constants/teams
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
import { createTokenVerification } from '@documenso/lib/utils/token-verification';
import { prisma } from '@documenso/prisma';
import type { Team, TeamGlobalSettings } from '@documenso/prisma/client';
import { Prisma } from '@documenso/prisma/client';
import { getI18nInstance } from '../../client-only/providers/i18n.server';
import { renderEmailWithI18N } from '../../utils/render-email-with-i18n';
import { teamGlobalSettingsToBranding } from '../../utils/team-global-settings-to-branding';
export type CreateTeamEmailVerificationOptions = {
userId: number;
@ -48,6 +50,7 @@ export const createTeamEmailVerification = async ({
include: {
teamEmail: true,
emailVerification: true,
teamGlobalSettings: true,
},
});
@ -80,7 +83,7 @@ export const createTeamEmailVerification = async ({
},
});
await sendTeamEmailVerificationEmail(data.email, token, team.name, team.url);
await sendTeamEmailVerificationEmail(data.email, token, team);
},
{ timeout: 30_000 },
);
@ -112,25 +115,36 @@ export const createTeamEmailVerification = async ({
export const sendTeamEmailVerificationEmail = async (
email: string,
token: string,
teamName: string,
teamUrl: string,
team: Team & {
teamGlobalSettings?: TeamGlobalSettings | null;
},
) => {
const assetBaseUrl = process.env.NEXT_PUBLIC_WEBAPP_URL || 'http://localhost:3000';
const template = createElement(ConfirmTeamEmailTemplate, {
assetBaseUrl,
baseUrl: WEBAPP_BASE_URL,
teamName,
teamUrl,
teamName: team.name,
teamUrl: team.url,
token,
});
const branding = team.teamGlobalSettings
? teamGlobalSettingsToBranding(team.teamGlobalSettings)
: undefined;
const lang = team.teamGlobalSettings?.documentLanguage;
const [html, text] = await Promise.all([
renderEmailWithI18N(template),
renderEmailWithI18N(template, { plainText: true }),
renderEmailWithI18N(template, { lang, branding }),
renderEmailWithI18N(template, {
lang,
branding,
plainText: true,
}),
]);
const i18n = await getI18nInstance();
const i18n = await getI18nInstance(lang);
await mailer.sendMail({
to: email,
@ -139,7 +153,7 @@ export const sendTeamEmailVerificationEmail = async (
address: FROM_ADDRESS,
},
subject: i18n._(
msg`A request to use your email has been initiated by ${teamName} on Documenso`,
msg`A request to use your email has been initiated by ${team.name} on Documenso`,
),
html,
text,

View File

@ -4,7 +4,6 @@ import { msg } from '@lingui/macro';
import { nanoid } from 'nanoid';
import { mailer } from '@documenso/email/mailer';
import type { TeamInviteEmailProps } from '@documenso/email/templates/team-invite';
import { TeamInviteEmailTemplate } from '@documenso/email/templates/team-invite';
import { WEBAPP_BASE_URL } from '@documenso/lib/constants/app';
import { FROM_ADDRESS, FROM_NAME } from '@documenso/lib/constants/email';
@ -12,11 +11,13 @@ import { TEAM_MEMBER_ROLE_PERMISSIONS_MAP } from '@documenso/lib/constants/teams
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
import { isTeamRoleWithinUserHierarchy } from '@documenso/lib/utils/teams';
import { prisma } from '@documenso/prisma';
import type { Team, TeamGlobalSettings } from '@documenso/prisma/client';
import { TeamMemberInviteStatus } from '@documenso/prisma/client';
import type { TCreateTeamMemberInvitesMutationSchema } from '@documenso/trpc/server/team-router/schema';
import { getI18nInstance } from '../../client-only/providers/i18n.server';
import { renderEmailWithI18N } from '../../utils/render-email-with-i18n';
import { teamGlobalSettingsToBranding } from '../../utils/team-global-settings-to-branding';
export type CreateTeamMemberInvitesOptions = {
userId: number;
@ -59,6 +60,7 @@ export const createTeamMemberInvites = async ({
},
},
invites: true,
teamGlobalSettings: true,
},
});
@ -112,8 +114,7 @@ export const createTeamMemberInvites = async ({
sendTeamMemberInviteEmail({
email,
token,
teamName: team.name,
teamUrl: team.url,
team,
senderName: userName,
}),
),
@ -134,8 +135,13 @@ export const createTeamMemberInvites = async ({
}
};
type SendTeamMemberInviteEmailOptions = Omit<TeamInviteEmailProps, 'baseUrl' | 'assetBaseUrl'> & {
type SendTeamMemberInviteEmailOptions = {
email: string;
senderName: string;
token: string;
team: Team & {
teamGlobalSettings?: TeamGlobalSettings | null;
};
};
/**
@ -143,20 +149,33 @@ type SendTeamMemberInviteEmailOptions = Omit<TeamInviteEmailProps, 'baseUrl' | '
*/
export const sendTeamMemberInviteEmail = async ({
email,
...emailTemplateOptions
senderName,
token,
team,
}: SendTeamMemberInviteEmailOptions) => {
const template = createElement(TeamInviteEmailTemplate, {
assetBaseUrl: WEBAPP_BASE_URL,
baseUrl: WEBAPP_BASE_URL,
...emailTemplateOptions,
senderName,
token,
teamName: team.name,
teamUrl: team.url,
});
const branding = team.teamGlobalSettings
? teamGlobalSettingsToBranding(team.teamGlobalSettings)
: undefined;
const [html, text] = await Promise.all([
renderEmailWithI18N(template),
renderEmailWithI18N(template, { plainText: true }),
renderEmailWithI18N(template, { lang: team.teamGlobalSettings?.documentLanguage, branding }),
renderEmailWithI18N(template, {
lang: team.teamGlobalSettings?.documentLanguage,
branding,
plainText: true,
}),
]);
const i18n = await getI18nInstance();
const i18n = await getI18nInstance(team.teamGlobalSettings?.documentLanguage);
await mailer.sendMail({
to: email,
@ -164,9 +183,7 @@ export const sendTeamMemberInviteEmail = async ({
name: FROM_NAME,
address: FROM_ADDRESS,
},
subject: i18n._(
msg`You have been invited to join ${emailTemplateOptions.teamName} on Documenso`,
),
subject: i18n._(msg`You have been invited to join ${team.name} on Documenso`),
html,
text,
});

View File

@ -11,6 +11,7 @@ import { prisma } from '@documenso/prisma';
import { getI18nInstance } from '../../client-only/providers/i18n.server';
import { renderEmailWithI18N } from '../../utils/render-email-with-i18n';
import { teamGlobalSettingsToBranding } from '../../utils/team-global-settings-to-branding';
export type DeleteTeamEmailOptions = {
userId: number;
@ -54,6 +55,7 @@ export const deleteTeamEmail = async ({ userId, userEmail, teamId }: DeleteTeamE
email: true,
},
},
teamGlobalSettings: true,
},
});
@ -77,12 +79,18 @@ export const deleteTeamEmail = async ({ userId, userEmail, teamId }: DeleteTeamE
teamUrl: team.url,
});
const branding = team.teamGlobalSettings
? teamGlobalSettingsToBranding(team.teamGlobalSettings)
: undefined;
const lang = team.teamGlobalSettings?.documentLanguage;
const [html, text] = await Promise.all([
renderEmailWithI18N(template),
renderEmailWithI18N(template, { plainText: true }),
renderEmailWithI18N(template, { lang, branding }),
renderEmailWithI18N(template, { lang, branding, plainText: true }),
]);
const i18n = await getI18nInstance();
const i18n = await getI18nInstance(lang);
await mailer.sendMail({
to: {

View File

@ -1,16 +1,20 @@
import { createElement } from 'react';
import { msg } from '@lingui/macro';
import { mailer } from '@documenso/email/mailer';
import type { TeamDeleteEmailProps } from '@documenso/email/templates/team-delete';
import { TeamDeleteEmailTemplate } from '@documenso/email/templates/team-delete';
import { WEBAPP_BASE_URL } from '@documenso/lib/constants/app';
import { FROM_ADDRESS, FROM_NAME } from '@documenso/lib/constants/email';
import { AppError } from '@documenso/lib/errors/app-error';
import { stripe } from '@documenso/lib/server-only/stripe';
import { prisma } from '@documenso/prisma';
import type { Team, TeamGlobalSettings } from '@documenso/prisma/client';
import { getI18nInstance } from '../../client-only/providers/i18n.server';
import { jobs } from '../../jobs/client';
import { renderEmailWithI18N } from '../../utils/render-email-with-i18n';
import { teamGlobalSettingsToBranding } from '../../utils/team-global-settings-to-branding';
export type DeleteTeamOptions = {
userId: number;
@ -38,6 +42,7 @@ export const deleteTeam = async ({ userId, teamId }: DeleteTeamOptions) => {
},
},
},
teamGlobalSettings: true,
},
});
@ -60,6 +65,7 @@ export const deleteTeam = async ({ userId, teamId }: DeleteTeamOptions) => {
name: team.name,
url: team.url,
ownerUserId: team.ownerUserId,
teamGlobalSettings: team.teamGlobalSettings,
},
members: team.members.map((member) => ({
id: member.user.id,
@ -80,33 +86,42 @@ export const deleteTeam = async ({ userId, teamId }: DeleteTeamOptions) => {
);
};
type SendTeamDeleteEmailOptions = Omit<TeamDeleteEmailProps, 'baseUrl' | 'assetBaseUrl'> & {
type SendTeamDeleteEmailOptions = {
email: string;
teamName: string;
team: Pick<Team, 'url' | 'name'> & {
teamGlobalSettings?: TeamGlobalSettings | null;
};
isOwner: boolean;
};
export const sendTeamDeleteEmail = async ({
email,
...emailTemplateOptions
}: SendTeamDeleteEmailOptions) => {
export const sendTeamDeleteEmail = async ({ email, isOwner, team }: SendTeamDeleteEmailOptions) => {
const template = createElement(TeamDeleteEmailTemplate, {
assetBaseUrl: WEBAPP_BASE_URL,
baseUrl: WEBAPP_BASE_URL,
...emailTemplateOptions,
teamUrl: team.url,
isOwner,
});
const branding = team.teamGlobalSettings
? teamGlobalSettingsToBranding(team.teamGlobalSettings)
: undefined;
const lang = team.teamGlobalSettings?.documentLanguage;
const [html, text] = await Promise.all([
renderEmailWithI18N(template),
renderEmailWithI18N(template, { plainText: true }),
renderEmailWithI18N(template, { lang, branding }),
renderEmailWithI18N(template, { lang, branding, plainText: true }),
]);
const i18n = await getI18nInstance(lang);
await mailer.sendMail({
to: email,
from: {
name: FROM_NAME,
address: FROM_ADDRESS,
},
subject: `Team "${emailTemplateOptions.teamName}" has been deleted on Documenso`,
subject: i18n._(msg`Team "${team.name}" has been deleted on Documenso`),
html,
text,
});

View File

@ -30,6 +30,7 @@ export const getTeamById = async ({ userId, teamId }: GetTeamByIdOptions) => {
where: whereFilter,
include: {
teamEmail: true,
teamGlobalSettings: true,
members: {
where: {
userId,
@ -89,6 +90,7 @@ export const getTeamByUrl = async ({ userId, teamUrl }: GetTeamByUrlOptions) =>
},
},
subscription: true,
teamGlobalSettings: true,
members: {
where: {
userId,

View File

@ -33,6 +33,7 @@ export const resendTeamEmailVerification = async ({
},
include: {
emailVerification: true,
teamGlobalSettings: true,
},
});
@ -61,7 +62,7 @@ export const resendTeamEmailVerification = async ({
},
});
await sendTeamEmailVerificationEmail(emailVerification.email, token, team.name, team.url);
await sendTeamEmailVerificationEmail(emailVerification.email, token, team);
},
{ timeout: 30_000 },
);

View File

@ -49,6 +49,9 @@ export const resendTeamMemberInvitation = async ({
},
},
},
include: {
teamGlobalSettings: true,
},
});
if (!team) {
@ -69,9 +72,8 @@ export const resendTeamMemberInvitation = async ({
await sendTeamMemberInviteEmail({
email: teamMemberInvite.email,
token: teamMemberInvite.token,
teamName: team.name,
teamUrl: team.url,
senderName: userName,
team,
});
},
{ timeout: 30_000 },

View File

@ -0,0 +1,52 @@
import { prisma } from '@documenso/prisma';
import { TeamMemberRole } from '@documenso/prisma/client';
export type UpdateTeamBrandingSettingsOptions = {
userId: number;
teamId: number;
settings: {
brandingEnabled: boolean;
brandingLogo: string;
brandingUrl: string;
brandingCompanyDetails: string;
};
};
export const updateTeamBrandingSettings = async ({
userId,
teamId,
settings,
}: UpdateTeamBrandingSettingsOptions) => {
const { brandingEnabled, brandingLogo, brandingUrl, brandingCompanyDetails } = settings;
const member = await prisma.teamMember.findFirst({
where: {
userId,
teamId,
},
});
if (!member || member.role !== TeamMemberRole.ADMIN) {
throw new Error('You do not have permission to update this team.');
}
return await prisma.teamGlobalSettings.upsert({
where: {
teamId,
},
create: {
teamId,
brandingEnabled,
brandingLogo,
brandingUrl,
brandingCompanyDetails,
},
update: {
brandingEnabled,
brandingLogo,
brandingUrl,
brandingCompanyDetails,
},
});
};

View File

@ -0,0 +1,52 @@
import { prisma } from '@documenso/prisma';
import type { DocumentVisibility } from '@documenso/prisma/client';
import { TeamMemberRole } from '@documenso/prisma/client';
import type { SupportedLanguageCodes } from '../../constants/i18n';
export type UpdateTeamDocumentSettingsOptions = {
userId: number;
teamId: number;
settings: {
documentVisibility: DocumentVisibility;
documentLanguage: SupportedLanguageCodes;
includeSenderDetails: boolean;
};
};
export const updateTeamDocumentSettings = async ({
userId,
teamId,
settings,
}: UpdateTeamDocumentSettingsOptions) => {
const { documentVisibility, documentLanguage, includeSenderDetails } = settings;
const member = await prisma.teamMember.findFirst({
where: {
userId,
teamId,
},
});
if (!member || member.role !== TeamMemberRole.ADMIN) {
throw new Error('You do not have permission to update this team.');
}
return await prisma.teamGlobalSettings.upsert({
where: {
teamId,
},
create: {
teamId,
documentVisibility,
documentLanguage,
includeSenderDetails,
},
update: {
documentVisibility,
documentLanguage,
includeSenderDetails,
},
});
};

View File

@ -4,6 +4,7 @@ import { TEAM_MEMBER_ROLE_PERMISSIONS_MAP } from '@documenso/lib/constants/teams
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
import { prisma } from '@documenso/prisma';
import { Prisma } from '@documenso/prisma/client';
import type { DocumentVisibility } from '@documenso/prisma/client';
export type UpdateTeamOptions = {
userId: number;
@ -11,6 +12,8 @@ export type UpdateTeamOptions = {
data: {
name?: string;
url?: string;
documentVisibility?: DocumentVisibility;
includeSenderDetails?: boolean;
};
};
@ -42,6 +45,18 @@ export const updateTeam = async ({ userId, teamId, data }: UpdateTeamOptions) =>
data: {
url: data.url,
name: data.name,
teamGlobalSettings: {
upsert: {
create: {
documentVisibility: data.documentVisibility,
includeSenderDetails: data.includeSenderDetails,
},
update: {
documentVisibility: data.documentVisibility,
includeSenderDetails: data.includeSenderDetails,
},
},
},
},
});

View File

@ -40,6 +40,7 @@ import {
extractDocumentAuthMethods,
} from '../../utils/document-auth';
import { renderEmailWithI18N } from '../../utils/render-email-with-i18n';
import { teamGlobalSettingsToBranding } from '../../utils/team-global-settings-to-branding';
import { formatDocumentsPath } from '../../utils/teams';
import { sendDocument } from '../document/send-document';
import { validateFieldAuth } from '../document/validate-field-auth';
@ -91,6 +92,11 @@ export const createDocumentFromDirectTemplate = async ({
templateDocumentData: true,
templateMeta: true,
User: true,
team: {
include: {
teamGlobalSettings: true,
},
},
},
});
@ -145,7 +151,8 @@ export const createDocumentFromDirectTemplate = async ({
const metaDateFormat = template.templateMeta?.dateFormat || DEFAULT_DOCUMENT_DATE_FORMAT;
const metaEmailMessage = template.templateMeta?.message || '';
const metaEmailSubject = template.templateMeta?.subject || '';
const metaLanguage = template.templateMeta?.language;
const metaLanguage =
template.templateMeta?.language ?? template.team?.teamGlobalSettings?.documentLanguage;
const metaSigningOrder = template.templateMeta?.signingOrder || DocumentSigningOrder.PARALLEL;
// Associate, validate and map to a query every direct template recipient field with the provided fields.
@ -237,6 +244,7 @@ export const createDocumentFromDirectTemplate = async ({
createdAt: initialRequestTime,
status: DocumentStatus.PENDING,
externalId: directTemplateExternalId,
visibility: template.team?.teamGlobalSettings?.documentVisibility,
documentDataId: documentData.id,
authOptions: createDocumentAuthOptions({
globalAccessAuth: templateAuthOptions.globalAccessAuth,
@ -275,6 +283,7 @@ export const createDocumentFromDirectTemplate = async ({
subject: metaEmailSubject,
language: metaLanguage,
signingOrder: metaSigningOrder,
distributionMethod: template.templateMeta?.distributionMethod,
},
},
},
@ -533,9 +542,13 @@ export const createDocumentFromDirectTemplate = async ({
assetBaseUrl: NEXT_PUBLIC_WEBAPP_URL() || 'http://localhost:3000',
});
const branding = template.team?.teamGlobalSettings
? teamGlobalSettingsToBranding(template.team.teamGlobalSettings)
: undefined;
const [html, text] = await Promise.all([
renderEmailWithI18N(emailTemplate, { lang: metaLanguage }),
renderEmailWithI18N(emailTemplate, { lang: metaLanguage, plainText: true }),
renderEmailWithI18N(emailTemplate, { lang: metaLanguage, branding }),
renderEmailWithI18N(emailTemplate, { lang: metaLanguage, branding, plainText: true }),
]);
const i18n = await getI18nInstance(metaLanguage);

View File

@ -47,6 +47,11 @@ export const createDocumentFromTemplateLegacy = async ({
Field: true,
templateDocumentData: true,
templateMeta: true,
team: {
include: {
teamGlobalSettings: true,
},
},
},
});
@ -69,6 +74,7 @@ export const createDocumentFromTemplateLegacy = async ({
userId,
teamId: template.teamId,
title: template.title,
visibility: template.team?.teamGlobalSettings?.documentVisibility,
documentDataId: documentData.id,
Recipient: {
create: template.Recipient.map((recipient) => ({
@ -87,7 +93,8 @@ export const createDocumentFromTemplateLegacy = async ({
dateFormat: template.templateMeta?.dateFormat,
redirectUrl: template.templateMeta?.redirectUrl,
signingOrder: template.templateMeta?.signingOrder ?? undefined,
language: template.templateMeta?.language,
language:
template.templateMeta?.language || template.team?.teamGlobalSettings?.documentLanguage,
},
},
},

View File

@ -1,5 +1,6 @@
import { nanoid } from '@documenso/lib/universal/id';
import { prisma } from '@documenso/prisma';
import type { DocumentDistributionMethod } from '@documenso/prisma/client';
import {
DocumentSigningOrder,
DocumentSource,
@ -62,6 +63,7 @@ export type CreateDocumentFromTemplateOptions = {
redirectUrl?: string;
signingOrder?: DocumentSigningOrder;
language?: SupportedLanguageCodes;
distributionMethod?: DocumentDistributionMethod;
};
requestMetadata?: RequestMetadata;
};
@ -108,6 +110,11 @@ export const createDocumentFromTemplate = async ({
},
templateDocumentData: true,
templateMeta: true,
team: {
include: {
teamGlobalSettings: true,
},
},
},
});
@ -169,6 +176,7 @@ export const createDocumentFromTemplate = async ({
globalAccessAuth: templateAuthOptions.globalAccessAuth,
globalActionAuth: templateAuthOptions.globalActionAuth,
}),
visibility: template.team?.teamGlobalSettings?.documentVisibility,
documentMeta: {
create: {
subject: override?.subject || template.templateMeta?.subject,
@ -177,11 +185,17 @@ export const createDocumentFromTemplate = async ({
password: override?.password || template.templateMeta?.password,
dateFormat: override?.dateFormat || template.templateMeta?.dateFormat,
redirectUrl: override?.redirectUrl || template.templateMeta?.redirectUrl,
distributionMethod:
override?.distributionMethod || template.templateMeta?.distributionMethod,
emailSettings: template.templateMeta?.emailSettings || undefined,
signingOrder:
override?.signingOrder ||
template.templateMeta?.signingOrder ||
DocumentSigningOrder.PARALLEL,
language: override?.language || template.templateMeta?.language,
language:
override?.language ||
template.templateMeta?.language ||
template.team?.teamGlobalSettings?.documentLanguage,
},
},
Recipient: {

View File

@ -60,7 +60,10 @@ export const duplicateTemplate = async ({
if (template.templateMeta) {
templateMeta = {
create: omit(template.templateMeta, ['id', 'templateId']),
create: {
...omit(template.templateMeta, ['id', 'templateId']),
emailSettings: template.templateMeta.emailSettings || undefined,
},
};
}

View File

@ -54,6 +54,7 @@ export const findTemplates = async ({
templateMeta: {
select: {
signingOrder: true,
distributionMethod: true,
},
},
directLink: {

View File

@ -112,9 +112,11 @@ export const updateTemplateSettings = async ({
},
create: {
...meta,
emailSettings: meta?.emailSettings || undefined,
},
update: {
...meta,
emailSettings: meta?.emailSettings || undefined,
},
},
},

File diff suppressed because it is too large Load Diff

View File

@ -18,6 +18,10 @@ msgstr ""
"X-Crowdin-File: web.po\n"
"X-Crowdin-File-ID: 8\n"
#: apps/web/src/app/(teams)/t/[teamUrl]/settings/preferences/document-preferences.tsx:211
msgid "\"{0}\" has invited you to sign \"example document\"."
msgstr ""
#: apps/web/src/app/(signing)/sign/[token]/date-field.tsx:69
msgid "\"{0}\" will appear on the document as it has a timezone of \"{timezone}\"."
msgstr "\"{0}\" wird im Dokument erscheinen, da es eine Zeitzone von \"{timezone}\" hat."
@ -26,6 +30,20 @@ msgstr "\"{0}\" wird im Dokument erscheinen, da es eine Zeitzone von \"{timezone
msgid "\"{documentTitle}\" has been successfully deleted"
msgstr "\"{documentTitle}\" wurde erfolgreich gelöscht"
#: apps/web/src/components/(teams)/forms/update-team-form.tsx:234
msgid "\"{email}\" on behalf of \"{teamName}\" has invited you to sign \"example document\"."
msgstr ""
#: apps/web/src/app/(teams)/t/[teamUrl]/settings/preferences/document-preferences.tsx:209
msgid ""
"\"{placeholderEmail}\" on behalf of \"{0}\" has invited you to sign \"example\n"
"document\"."
msgstr ""
#: apps/web/src/components/(teams)/forms/update-team-form.tsx:241
msgid "\"{teamUrl}\" has invited you to sign \"example document\"."
msgstr ""
#: apps/web/src/app/(signing)/sign/[token]/signing-page-view.tsx:78
msgid "({0}) has invited you to approve this document"
msgstr "({0}) hat dich eingeladen, dieses Dokument zu genehmigen"
@ -77,8 +95,8 @@ msgid "{0} direct signing templates"
msgstr "{0} direkte Signaturvorlagen"
#: apps/web/src/app/(recipient)/d/[token]/direct-template.tsx:66
msgid "{0} document"
msgstr "{0} Dokument"
#~ msgid "{0} document"
#~ msgstr "{0} Dokument"
#: apps/web/src/app/(dashboard)/documents/upload-document.tsx:146
msgid "{0} of {1} documents remaining this month."
@ -89,8 +107,8 @@ msgid "{0} Recipient(s)"
msgstr "{0} Empfänger(in)"
#: apps/web/src/app/(recipient)/d/[token]/direct-template.tsx:67
msgid "{0} the document to complete the process."
msgstr "{0} das Dokument, um den Prozess abzuschließen."
#~ msgid "{0} the document to complete the process."
#~ msgstr "{0} das Dokument, um den Prozess abzuschließen."
#: apps/web/src/app/(signing)/sign/[token]/text-field.tsx:292
msgid "{charactersRemaining, plural, one {1 character remaining} other {{charactersRemaining} characters remaining}}"
@ -104,6 +122,14 @@ msgstr "{formattedTeamMemberQuanity} • Monatlich • Erneuert: {formattedDate}
msgid "{numberOfSeats, plural, one {# member} other {# members}}"
msgstr "{numberOfSeats, plural, one {# Mitglied} other {# Mitglieder}}"
#: apps/web/src/app/(recipient)/d/[token]/direct-template.tsx:67
msgid "{recipientActionVerb} document"
msgstr ""
#: apps/web/src/app/(recipient)/d/[token]/direct-template.tsx:68
msgid "{recipientActionVerb} the document to complete the process."
msgstr ""
#: apps/web/src/components/forms/public-profile-form.tsx:231
#: apps/web/src/components/templates/manage-public-template-dialog.tsx:389
msgid "{remaningLength, plural, one {# character remaining} other {# characters remaining}}"
@ -161,7 +187,7 @@ msgstr "Eine Bestätigungs-E-Mail wurde gesendet, und sie sollte in Kürze in de
msgid "A device capable of accessing, opening, and reading documents"
msgstr "Ein Gerät, das in der Lage ist, Dokumente zuzugreifen, zu öffnen und zu lesen"
#: apps/web/src/app/(dashboard)/templates/use-template-dialog.tsx:207
#: apps/web/src/app/(dashboard)/templates/use-template-dialog.tsx:218
msgid "A draft document will be created"
msgstr "Ein Entwurf wird erstellt"
@ -200,7 +226,7 @@ msgid "A unique URL to access your profile"
msgstr "Eine eindeutige URL, um auf dein Profil zuzugreifen"
#: apps/web/src/components/(teams)/dialogs/create-team-dialog.tsx:206
#: apps/web/src/components/(teams)/forms/update-team-form.tsx:139
#: apps/web/src/components/(teams)/forms/update-team-form.tsx:179
msgid "A unique URL to identify your team"
msgstr "Eine eindeutige URL, um dein Team zu identifizieren"
@ -256,7 +282,7 @@ msgstr "Aktion"
msgid "Actions"
msgstr "Aktionen"
#: apps/web/src/app/(teams)/t/[teamUrl]/settings/page.tsx:101
#: apps/web/src/app/(teams)/t/[teamUrl]/settings/page.tsx:107
#: apps/web/src/components/(teams)/tables/teams-member-page-data-table.tsx:76
#: apps/web/src/components/(teams)/tables/user-settings-teams-page-data-table.tsx:71
msgid "Active"
@ -270,7 +296,7 @@ msgstr "Aktive Abonnements"
msgid "Add"
msgstr "Hinzufügen"
#: apps/web/src/app/(dashboard)/documents/[id]/edit-document.tsx:176
#: apps/web/src/app/(dashboard)/documents/[id]/edit-document.tsx:177
#: apps/web/src/app/(dashboard)/templates/[id]/edit/edit-template.tsx:88
msgid "Add all relevant fields for each recipient."
msgstr "Fügen Sie alle relevanten Felder für jeden Empfänger hinzu."
@ -291,7 +317,7 @@ msgstr "Fügen Sie einen Authenticator hinzu, um als sekundäre Authentifizierun
msgid "Add email"
msgstr "E-Mail hinzufügen"
#: apps/web/src/app/(dashboard)/documents/[id]/edit-document.tsx:175
#: apps/web/src/app/(dashboard)/documents/[id]/edit-document.tsx:176
#: apps/web/src/app/(dashboard)/templates/[id]/edit/edit-template.tsx:87
msgid "Add Fields"
msgstr "Felder hinzufügen"
@ -309,34 +335,38 @@ msgstr "Passkey hinzufügen"
msgid "Add Placeholders"
msgstr "Platzhalter hinzufügen"
#: apps/web/src/app/(dashboard)/documents/[id]/edit-document.tsx:170
#: apps/web/src/app/(dashboard)/documents/[id]/edit-document.tsx:171
msgid "Add Signers"
msgstr "Unterzeichner hinzufügen"
#: apps/web/src/app/(dashboard)/documents/[id]/edit-document.tsx:180
msgid "Add Subject"
msgstr "Betreff hinzufügen"
#~ msgid "Add Subject"
#~ msgstr "Betreff hinzufügen"
#: apps/web/src/components/(teams)/dialogs/add-team-email-dialog.tsx:133
msgid "Add team email"
msgstr "Team-E-Mail hinzufügen"
#: apps/web/src/app/(dashboard)/documents/[id]/edit-document.tsx:171
#: apps/web/src/app/(dashboard)/documents/[id]/edit-document.tsx:172
msgid "Add the people who will sign the document."
msgstr "Fügen Sie die Personen hinzu, die das Dokument unterschreiben werden."
#: apps/web/src/app/(dashboard)/templates/use-template-dialog.tsx:209
#: apps/web/src/app/(dashboard)/templates/use-template-dialog.tsx:220
msgid "Add the recipients to create the document with"
msgstr "Fügen Sie die Empfänger hinzu, um das Dokument zu erstellen"
#: apps/web/src/app/(dashboard)/documents/[id]/edit-document.tsx:181
msgid "Add the subject and message you wish to send to signers."
msgstr "Fügen Sie den Betreff und die Nachricht hinzu, die Sie den Unterzeichnern senden möchten."
#~ msgid "Add the subject and message you wish to send to signers."
#~ msgstr "Fügen Sie den Betreff und die Nachricht hinzu, die Sie den Unterzeichnern senden möchten."
#: apps/web/src/components/(teams)/dialogs/create-team-checkout-dialog.tsx:152
msgid "Adding and removing seats will adjust your invoice accordingly."
msgstr "Das Hinzufügen und Entfernen von Sitzplätzen wird Ihre Rechnung entsprechend anpassen."
#: apps/web/src/app/(teams)/t/[teamUrl]/settings/preferences/branding-preferences.tsx:303
msgid "Additional brand information to display at the bottom of emails"
msgstr ""
#: apps/web/src/app/(dashboard)/admin/documents/[id]/page.tsx:59
msgid "Admin Actions"
msgstr "Admin-Aktionen"
@ -428,17 +458,17 @@ msgstr "Eine E-Mail, in der die Übertragung dieses Teams angefordert wird, wurd
msgid "An error occurred"
msgstr "Ein Fehler ist aufgetreten"
#: apps/web/src/app/(dashboard)/documents/[id]/edit-document.tsx:268
#: apps/web/src/app/(dashboard)/documents/[id]/edit-document.tsx:269
#: apps/web/src/app/(dashboard)/templates/[id]/edit/edit-template.tsx:201
#: apps/web/src/app/(dashboard)/templates/[id]/edit/edit-template.tsx:235
msgid "An error occurred while adding signers."
msgstr "Ein Fehler ist aufgetreten, während Unterzeichner hinzugefügt wurden."
#: apps/web/src/app/(dashboard)/documents/[id]/edit-document.tsx:303
#: apps/web/src/app/(dashboard)/documents/[id]/edit-document.tsx:304
msgid "An error occurred while adding the fields."
msgstr "Ein Fehler ist aufgetreten, während die Felder hinzugefügt wurden."
#: apps/web/src/app/(dashboard)/templates/use-template-dialog.tsx:165
#: apps/web/src/app/(dashboard)/templates/use-template-dialog.tsx:176
msgid "An error occurred while creating document from template."
msgstr "Ein Fehler ist aufgetreten, während das Dokument aus der Vorlage erstellt wurde."
@ -496,7 +526,7 @@ msgstr "Ein Fehler ist aufgetreten, während die Unterschrift entfernt wurde."
msgid "An error occurred while removing the text."
msgstr "Ein Fehler ist aufgetreten, während der Text entfernt wurde."
#: apps/web/src/app/(dashboard)/documents/[id]/edit-document.tsx:334
#: apps/web/src/app/(dashboard)/documents/[id]/edit-document.tsx:350
msgid "An error occurred while sending the document."
msgstr "Ein Fehler ist aufgetreten, während das Dokument gesendet wurde."
@ -521,11 +551,15 @@ msgstr "Ein Fehler ist aufgetreten, während das Dokument unterzeichnet wurde."
msgid "An error occurred while trying to create a checkout session."
msgstr "Ein Fehler ist aufgetreten, während versucht wurde, eine Checkout-Sitzung zu erstellen."
#: apps/web/src/app/(dashboard)/documents/[id]/edit-document.tsx:234
#: apps/web/src/app/(dashboard)/documents/[id]/edit-document.tsx:235
#: apps/web/src/app/(dashboard)/templates/[id]/edit/edit-template.tsx:170
msgid "An error occurred while updating the document settings."
msgstr "Ein Fehler ist aufgetreten, während die Dokumenteinstellungen aktualisiert wurden."
#: apps/web/src/components/forms/team-document-settings.tsx:78
#~ msgid "An error occurred while updating the global team settings."
#~ msgstr ""
#: apps/web/src/app/(signing)/sign/[token]/checkbox-field.tsx:213
msgid "An error occurred while updating the signature."
msgstr "Ein Fehler ist aufgetreten, während die Unterschrift aktualisiert wurde."
@ -556,7 +590,7 @@ msgstr "Ein Fehler ist aufgetreten, während dein Dokument hochgeladen wurde."
#: apps/web/src/components/(teams)/dialogs/transfer-team-dialog.tsx:116
#: apps/web/src/components/(teams)/dialogs/update-team-email-dialog.tsx:89
#: apps/web/src/components/(teams)/dialogs/update-team-member-dialog.tsx:100
#: apps/web/src/components/(teams)/forms/update-team-form.tsx:94
#: apps/web/src/components/(teams)/forms/update-team-form.tsx:134
#: apps/web/src/components/forms/avatar-image.tsx:94
#: apps/web/src/components/forms/avatar-image.tsx:122
#: apps/web/src/components/forms/password.tsx:84
@ -598,8 +632,8 @@ msgstr "Jeder Status"
#: apps/web/src/app/(teams)/t/[teamUrl]/settings/tokens/page.tsx:56
#: apps/web/src/components/(dashboard)/settings/layout/desktop-nav.tsx:90
#: apps/web/src/components/(dashboard)/settings/layout/mobile-nav.tsx:93
#: apps/web/src/components/(teams)/settings/layout/desktop-nav.tsx:81
#: apps/web/src/components/(teams)/settings/layout/mobile-nav.tsx:89
#: apps/web/src/components/(teams)/settings/layout/desktop-nav.tsx:96
#: apps/web/src/components/(teams)/settings/layout/mobile-nav.tsx:105
msgid "API Tokens"
msgstr "API-Token"
@ -669,7 +703,7 @@ msgstr "Avatar"
msgid "Avatar Updated"
msgstr "Avatar aktualisiert"
#: apps/web/src/app/(teams)/t/[teamUrl]/settings/page.tsx:121
#: apps/web/src/app/(teams)/t/[teamUrl]/settings/page.tsx:127
msgid "Awaiting email confirmation"
msgstr "Warte auf E-Mail-Bestätigung"
@ -708,11 +742,19 @@ msgstr "Basisdetails"
#: apps/web/src/app/(teams)/t/[teamUrl]/settings/billing/page.tsx:61
#: apps/web/src/components/(dashboard)/settings/layout/desktop-nav.tsx:117
#: apps/web/src/components/(dashboard)/settings/layout/mobile-nav.tsx:120
#: apps/web/src/components/(teams)/settings/layout/desktop-nav.tsx:108
#: apps/web/src/components/(teams)/settings/layout/mobile-nav.tsx:116
#: apps/web/src/components/(teams)/settings/layout/desktop-nav.tsx:123
#: apps/web/src/components/(teams)/settings/layout/mobile-nav.tsx:132
msgid "Billing"
msgstr "Abrechnung"
#: apps/web/src/app/(teams)/t/[teamUrl]/settings/preferences/page.tsx:42
msgid "Branding Preferences"
msgstr ""
#: apps/web/src/app/(teams)/t/[teamUrl]/settings/preferences/branding-preferences.tsx:102
msgid "Branding preferences updated"
msgstr ""
#: apps/web/src/app/(dashboard)/settings/security/activity/user-security-activity-data-table.tsx:99
#: apps/web/src/app/(internal)/%5F%5Fhtmltopdf/audit-log/data-table.tsx:48
msgid "Browser"
@ -798,6 +840,10 @@ msgstr "Vom Benutzer abgebrochen"
msgid "Charts"
msgstr "Diagramme"
#: apps/web/src/app/(teams)/t/[teamUrl]/settings/documents/page.tsx:32
#~ msgid "Check out the documentaton for the <0>global team settings</0>."
#~ msgstr ""
#: apps/web/src/components/(teams)/dialogs/create-team-checkout-dialog.tsx:179
msgid "Checkout"
msgstr "Abrechnung"
@ -810,6 +856,10 @@ msgstr "Wählen Sie einen vorhandenen Empfänger unten aus, um fortzufahren"
msgid "Choose Direct Link Recipient"
msgstr "Wählen Sie den direkten Link Empfänger"
#: apps/web/src/app/(dashboard)/documents/[id]/edit-document.tsx:182
msgid "Choose how the document will reach recipients"
msgstr ""
#: apps/web/src/components/forms/token.tsx:200
msgid "Choose..."
msgstr "Wählen..."
@ -858,7 +908,7 @@ msgid "Click to insert field"
msgstr "Klicken Sie, um das Feld einzufügen"
#: apps/web/src/app/(dashboard)/templates/new-template-dialog.tsx:126
#: apps/web/src/app/(dashboard)/templates/use-template-dialog.tsx:345
#: apps/web/src/app/(dashboard)/templates/use-template-dialog.tsx:389
#: apps/web/src/app/(signing)/sign/[token]/document-action-auth-2fa.tsx:125
#: apps/web/src/app/(signing)/sign/[token]/document-action-auth-passkey.tsx:138
#: apps/web/src/app/(teams)/t/[teamUrl]/settings/team-transfer-status.tsx:121
@ -903,7 +953,7 @@ msgstr "Abgeschlossene Dokumente"
msgid "Completed Documents"
msgstr "Abgeschlossene Dokumente"
#: apps/web/src/app/(dashboard)/documents/[id]/edit-document.tsx:166
#: apps/web/src/app/(dashboard)/documents/[id]/edit-document.tsx:167
msgid "Configure general settings for the document."
msgstr "Konfigurieren Sie die allgemeinen Einstellungen für das Dokument."
@ -971,6 +1021,18 @@ msgstr "Fortfahren"
msgid "Continue to login"
msgstr "Weiter zum Login"
#: apps/web/src/app/(teams)/t/[teamUrl]/settings/preferences/document-preferences.tsx:173
msgid "Controls the default language of an uploaded document. This will be used as the language in email communications with the recipients."
msgstr ""
#: apps/web/src/app/(teams)/t/[teamUrl]/settings/preferences/document-preferences.tsx:141
msgid "Controls the default visibility of an uploaded document."
msgstr ""
#: apps/web/src/app/(teams)/t/[teamUrl]/settings/preferences/document-preferences.tsx:216
msgid "Controls the formatting of the message that will be sent when inviting a recipient to sign a document. If a custom message has been provided while configuring the document, it will be used instead."
msgstr ""
#: apps/web/src/components/document/document-recipient-link-copy-dialog.tsx:128
msgid "Copied"
msgstr ""
@ -1024,14 +1086,18 @@ msgstr "Ein Team erstellen, um mit Ihren Teammitgliedern zusammenzuarbeiten."
msgid "Create account"
msgstr "Konto erstellen"
#: apps/web/src/app/(dashboard)/templates/use-template-dialog.tsx:351
#: apps/web/src/app/(dashboard)/templates/use-template-dialog.tsx:397
msgid "Create and send"
msgstr "Erstellen und senden"
#: apps/web/src/app/(dashboard)/templates/use-template-dialog.tsx:353
#: apps/web/src/app/(dashboard)/templates/use-template-dialog.tsx:395
msgid "Create as draft"
msgstr "Als Entwurf erstellen"
#: apps/web/src/app/(dashboard)/templates/use-template-dialog.tsx:355
msgid "Create as pending"
msgstr ""
#: apps/web/src/app/(dashboard)/templates/[id]/template-direct-link-dialog-wrapper.tsx:37
msgid "Create Direct Link"
msgstr "Direkten Link erstellen"
@ -1040,7 +1106,7 @@ msgstr "Direkten Link erstellen"
msgid "Create Direct Signing Link"
msgstr "Direkten Signatur-Link erstellen"
#: apps/web/src/app/(dashboard)/templates/use-template-dialog.tsx:203
#: apps/web/src/app/(dashboard)/templates/use-template-dialog.tsx:214
msgid "Create document from template"
msgstr "Dokument aus der Vorlage erstellen"
@ -1052,6 +1118,10 @@ msgstr "Jetzt erstellen"
msgid "Create one automatically"
msgstr "Einen automatisch erstellen"
#: apps/web/src/app/(dashboard)/templates/use-template-dialog.tsx:399
msgid "Create signing links"
msgstr ""
#: apps/web/src/components/(dashboard)/layout/menu-switcher.tsx:181
#: apps/web/src/components/(dashboard)/layout/menu-switcher.tsx:251
#: apps/web/src/components/(teams)/dialogs/create-team-dialog.tsx:138
@ -1063,6 +1133,10 @@ msgstr "Team erstellen"
msgid "Create Team"
msgstr "Team erstellen"
#: apps/web/src/app/(dashboard)/templates/use-template-dialog.tsx:362
msgid "Create the document as pending and ready to sign."
msgstr ""
#: apps/web/src/components/forms/token.tsx:250
#: apps/web/src/components/forms/token.tsx:259
msgid "Create token"
@ -1155,6 +1229,15 @@ msgstr "Ablehnen"
msgid "Declined team invitation"
msgstr "Team-Einladung abgelehnt"
#: apps/web/src/app/(teams)/t/[teamUrl]/settings/preferences/document-preferences.tsx:153
msgid "Default Document Language"
msgstr ""
#: apps/web/src/app/(teams)/t/[teamUrl]/settings/preferences/document-preferences.tsx:117
#: apps/web/src/components/(teams)/forms/update-team-form.tsx:195
msgid "Default Document Visibility"
msgstr ""
#: apps/web/src/app/(dashboard)/documents/delete-document-dialog.tsx:90
msgid "delete"
msgstr "löschen"
@ -1212,7 +1295,7 @@ msgstr "Dokument löschen"
msgid "Delete passkey"
msgstr "Passkey löschen"
#: apps/web/src/app/(teams)/t/[teamUrl]/settings/page.tsx:191
#: apps/web/src/app/(teams)/t/[teamUrl]/settings/page.tsx:197
#: apps/web/src/components/(teams)/dialogs/delete-team-dialog.tsx:118
msgid "Delete team"
msgstr "Team löschen"
@ -1325,6 +1408,10 @@ msgstr "Das Deaktivieren der direkten Link-Signatur verhindert, dass jemand auf
msgid "Display your name and email in documents"
msgstr "Zeigen Sie Ihren Namen und Ihre E-Mail in Dokumenten an"
#: apps/web/src/app/(dashboard)/documents/[id]/edit-document.tsx:181
msgid "Distribute Document"
msgstr ""
#: apps/web/src/app/(dashboard)/templates/delete-template-dialog.tsx:63
msgid "Do you want to delete this template?"
msgstr "Möchten Sie diese Vorlage löschen?"
@ -1362,7 +1449,7 @@ msgstr "Dokument abgeschlossen"
msgid "Document Completed!"
msgstr "Dokument abgeschlossen!"
#: apps/web/src/app/(dashboard)/templates/use-template-dialog.tsx:154
#: apps/web/src/app/(dashboard)/templates/use-template-dialog.tsx:156
msgid "Document created"
msgstr "Dokument erstellt"
@ -1402,7 +1489,7 @@ msgstr "Dokument-ID"
msgid "Document inbox"
msgstr "Dokumenten-Posteingang"
#: apps/web/src/app/(dashboard)/templates/data-table-templates.tsx:179
#: apps/web/src/app/(dashboard)/templates/data-table-templates.tsx:180
msgid "Document Limit Exceeded!"
msgstr "Dokumentenlimit überschritten!"
@ -1422,6 +1509,10 @@ msgstr "Dokument steht nicht mehr zur Unterschrift zur Verfügung"
msgid "Document pending"
msgstr "Dokument ausstehend"
#: apps/web/src/app/(teams)/t/[teamUrl]/settings/preferences/document-preferences.tsx:91
msgid "Document preferences updated"
msgstr ""
#: apps/web/src/app/(dashboard)/documents/_action-items/resend-document.tsx:97
msgid "Document re-sent"
msgstr "Dokument erneut gesendet"
@ -1430,10 +1521,14 @@ msgstr "Dokument erneut gesendet"
msgid "Document resealed"
msgstr "Dokument wieder versiegelt"
#: apps/web/src/app/(dashboard)/documents/[id]/edit-document.tsx:323
#: apps/web/src/app/(dashboard)/documents/[id]/edit-document.tsx:327
msgid "Document sent"
msgstr "Dokument gesendet"
#: apps/web/src/app/(teams)/t/[teamUrl]/settings/documents/page.tsx:26
#~ msgid "Document Settings"
#~ msgstr ""
#: apps/web/src/app/(signing)/sign/[token]/complete/page.tsx:132
msgid "Document Signed"
msgstr "Dokument signiert"
@ -1575,8 +1670,8 @@ msgstr "Offenlegung der elektronischen Unterschrift"
#: apps/web/src/app/(dashboard)/admin/documents/[id]/recipient-item.tsx:166
#: apps/web/src/app/(dashboard)/admin/users/[id]/page.tsx:114
#: apps/web/src/app/(dashboard)/admin/users/data-table-users.tsx:71
#: apps/web/src/app/(dashboard)/templates/use-template-dialog.tsx:254
#: apps/web/src/app/(dashboard)/templates/use-template-dialog.tsx:261
#: apps/web/src/app/(dashboard)/templates/use-template-dialog.tsx:265
#: apps/web/src/app/(dashboard)/templates/use-template-dialog.tsx:272
#: apps/web/src/app/(internal)/%5F%5Fhtmltopdf/certificate/page.tsx:122
#: apps/web/src/app/(internal)/%5F%5Fhtmltopdf/certificate/page.tsx:129
#: apps/web/src/app/(recipient)/d/[token]/configure-direct-template.tsx:118
@ -1631,6 +1726,10 @@ msgstr "2FA aktivieren"
msgid "Enable Authenticator App"
msgstr "Authenticator-App aktivieren"
#: apps/web/src/app/(teams)/t/[teamUrl]/settings/preferences/branding-preferences.tsx:170
msgid "Enable custom branding for all documents in this team."
msgstr ""
#: apps/web/src/app/(dashboard)/templates/template-direct-link-dialog.tsx:251
msgid "Enable direct link signing"
msgstr "Direktlinksignierung aktivieren"
@ -1656,6 +1755,10 @@ msgstr "Beigefügte Dokument"
msgid "Ends On"
msgstr "Endet am"
#: apps/web/src/app/(teams)/t/[teamUrl]/settings/preferences/branding-preferences.tsx:295
msgid "Enter your brand details"
msgstr ""
#: apps/web/src/app/(signing)/sign/[token]/complete/claim-account.tsx:137
msgid "Enter your email"
msgstr "Geben Sie Ihre E-Mail-Adresse ein"
@ -1674,10 +1777,10 @@ msgstr "Geben Sie hier Ihren Text ein"
#: apps/web/src/app/(dashboard)/admin/documents/[id]/admin-actions.tsx:41
#: apps/web/src/app/(dashboard)/admin/users/[id]/page.tsx:78
#: apps/web/src/app/(dashboard)/documents/[id]/edit-document.tsx:233
#: apps/web/src/app/(dashboard)/documents/[id]/edit-document.tsx:267
#: apps/web/src/app/(dashboard)/documents/[id]/edit-document.tsx:302
#: apps/web/src/app/(dashboard)/documents/[id]/edit-document.tsx:333
#: apps/web/src/app/(dashboard)/documents/[id]/edit-document.tsx:234
#: apps/web/src/app/(dashboard)/documents/[id]/edit-document.tsx:268
#: apps/web/src/app/(dashboard)/documents/[id]/edit-document.tsx:303
#: apps/web/src/app/(dashboard)/documents/[id]/edit-document.tsx:349
#: apps/web/src/app/(dashboard)/documents/move-document-dialog.tsx:57
#: apps/web/src/app/(dashboard)/documents/upload-document.tsx:106
#: apps/web/src/app/(dashboard)/documents/upload-document.tsx:112
@ -1686,7 +1789,7 @@ msgstr "Geben Sie hier Ihren Text ein"
#: apps/web/src/app/(dashboard)/templates/[id]/edit/edit-template.tsx:234
#: apps/web/src/app/(dashboard)/templates/duplicate-template-dialog.tsx:51
#: apps/web/src/app/(dashboard)/templates/move-template-dialog.tsx:56
#: apps/web/src/app/(dashboard)/templates/use-template-dialog.tsx:164
#: apps/web/src/app/(dashboard)/templates/use-template-dialog.tsx:175
#: apps/web/src/app/(signing)/sign/[token]/checkbox-field.tsx:122
#: apps/web/src/app/(signing)/sign/[token]/checkbox-field.tsx:151
#: apps/web/src/app/(signing)/sign/[token]/checkbox-field.tsx:212
@ -1713,6 +1816,14 @@ msgstr "Geben Sie hier Ihren Text ein"
msgid "Error"
msgstr "Fehler"
#: apps/web/src/components/forms/team-document-settings.tsx:77
#~ msgid "Error updating global team settings"
#~ msgstr ""
#: apps/web/src/app/(teams)/t/[teamUrl]/settings/preferences/document-preferences.tsx:128
msgid "Everyone can access and view the document"
msgstr ""
#: apps/web/src/app/(signing)/sign/[token]/complete/page.tsx:142
msgid "Everyone has signed"
msgstr "Alle haben unterschrieben"
@ -1725,7 +1836,7 @@ msgstr "Alle haben unterschrieben! Sie werden eine E-Mail-Kopie des unterzeichne
msgid "Exceeded timeout"
msgstr "Zeitüberschreitung überschritten"
#: apps/web/src/app/(teams)/t/[teamUrl]/settings/page.tsx:114
#: apps/web/src/app/(teams)/t/[teamUrl]/settings/page.tsx:120
msgid "Expired"
msgstr "Abgelaufen"
@ -1774,14 +1885,23 @@ msgstr "Haben Sie Ihr Passwort vergessen?"
msgid "Full Name"
msgstr "Vollständiger Name"
#: apps/web/src/app/(dashboard)/documents/[id]/edit-document.tsx:165
#: apps/web/src/app/(dashboard)/documents/[id]/edit-document.tsx:166
#: apps/web/src/app/(dashboard)/templates/[id]/edit/edit-template.tsx:77
#: apps/web/src/app/(recipient)/d/[token]/direct-template.tsx:60
#: apps/web/src/components/(teams)/settings/layout/desktop-nav.tsx:43
#: apps/web/src/components/(teams)/settings/layout/mobile-nav.tsx:51
#: apps/web/src/app/(recipient)/d/[token]/direct-template.tsx:62
#: apps/web/src/components/(teams)/settings/layout/desktop-nav.tsx:44
#: apps/web/src/components/(teams)/settings/layout/mobile-nav.tsx:52
msgid "General"
msgstr "Allgemein"
#: apps/web/src/components/(teams)/settings/layout/desktop-nav.tsx:57
#: apps/web/src/components/(teams)/settings/layout/mobile-nav.tsx:65
#~ msgid "Global Settings"
#~ msgstr ""
#: apps/web/src/components/forms/team-document-settings.tsx:69
#~ msgid "Global Team Settings Updated"
#~ msgstr ""
#: apps/web/src/app/(profile)/p/[url]/not-found.tsx:30
#: apps/web/src/app/(recipient)/d/[token]/not-found.tsx:33
#: apps/web/src/app/(teams)/t/[teamUrl]/error.tsx:51
@ -1818,6 +1938,14 @@ msgstr "Hier können Sie Ihre persönlichen Daten bearbeiten."
msgid "Here you can manage your password and security settings."
msgstr "Hier können Sie Ihre Passwort- und Sicherheitseinstellungen verwalten."
#: apps/web/src/app/(teams)/t/[teamUrl]/settings/preferences/page.tsx:43
msgid "Here you can set preferences and defaults for branding."
msgstr ""
#: apps/web/src/app/(teams)/t/[teamUrl]/settings/preferences/page.tsx:34
msgid "Here you can set preferences and defaults for your team."
msgstr ""
#: apps/web/src/app/(dashboard)/templates/template-direct-link-dialog.tsx:206
msgid "Here's how it works:"
msgstr "So funktioniert es:"
@ -1869,6 +1997,10 @@ msgstr "Posteingang"
msgid "Inbox documents"
msgstr "Posteingang Dokumente"
#: apps/web/src/components/forms/team-document-settings.tsx:132
#~ msgid "Include Sender Details"
#~ msgstr ""
#: apps/web/src/app/(dashboard)/documents/[id]/document-page-view-information.tsx:53
#: apps/web/src/app/(dashboard)/templates/[id]/template-page-view-information.tsx:50
msgid "Information"
@ -2039,6 +2171,10 @@ msgstr "Möchten Sie Ihr eigenes öffentliches Profil mit Vereinbarungen haben?"
msgid "Link template"
msgstr "Vorlage verlinken"
#: apps/web/src/app/(dashboard)/documents/[id]/edit-document.tsx:338
msgid "Links Generated"
msgstr ""
#: apps/web/src/app/(dashboard)/settings/webhooks/page.tsx:79
#: apps/web/src/app/(teams)/t/[teamUrl]/settings/webhooks/page.tsx:84
msgid "Listening to {0}"
@ -2163,8 +2299,8 @@ msgid "Member Since"
msgstr "Mitglied seit"
#: apps/web/src/app/(teams)/t/[teamUrl]/settings/members/page.tsx:31
#: apps/web/src/components/(teams)/settings/layout/desktop-nav.tsx:71
#: apps/web/src/components/(teams)/settings/layout/mobile-nav.tsx:79
#: apps/web/src/components/(teams)/settings/layout/desktop-nav.tsx:86
#: apps/web/src/components/(teams)/settings/layout/mobile-nav.tsx:95
msgid "Members"
msgstr "Mitglieder"
@ -2218,8 +2354,8 @@ msgstr "Meine Vorlagen"
#: apps/web/src/app/(dashboard)/admin/users/data-table-users.tsx:66
#: apps/web/src/app/(dashboard)/settings/security/passkeys/user-passkeys-data-table-actions.tsx:144
#: apps/web/src/app/(dashboard)/settings/security/passkeys/user-passkeys-data-table.tsx:61
#: apps/web/src/app/(dashboard)/templates/use-template-dialog.tsx:276
#: apps/web/src/app/(dashboard)/templates/use-template-dialog.tsx:283
#: apps/web/src/app/(dashboard)/templates/use-template-dialog.tsx:287
#: apps/web/src/app/(dashboard)/templates/use-template-dialog.tsx:294
#: apps/web/src/app/(signing)/sign/[token]/complete/claim-account.tsx:119
#: apps/web/src/app/(signing)/sign/[token]/name-field.tsx:170
#: apps/web/src/components/(teams)/dialogs/add-team-email-dialog.tsx:153
@ -2353,6 +2489,14 @@ msgstr "Sobald dies bestätigt ist, wird Folgendes geschehen:"
msgid "Once you have scanned the QR code or entered the code manually, enter the code provided by your authenticator app below."
msgstr "Sobald Sie den QR-Code gescannt oder den Code manuell eingegeben haben, geben Sie den von Ihrer Authentifizierungs-App bereitgestellten Code unten ein."
#: apps/web/src/app/(teams)/t/[teamUrl]/settings/preferences/document-preferences.tsx:134
msgid "Only admins can access and view the document"
msgstr ""
#: apps/web/src/app/(teams)/t/[teamUrl]/settings/preferences/document-preferences.tsx:131
msgid "Only managers and above can access and view the document"
msgstr ""
#: apps/web/src/app/(profile)/p/[url]/not-found.tsx:19
#: apps/web/src/app/(recipient)/d/[token]/not-found.tsx:19
#: apps/web/src/app/(teams)/t/[teamUrl]/error.tsx:37
@ -2376,7 +2520,7 @@ msgstr "Oder"
msgid "Or continue with"
msgstr "Oder fahren Sie fort mit"
#: apps/web/src/app/(dashboard)/templates/use-template-dialog.tsx:330
#: apps/web/src/app/(dashboard)/templates/use-template-dialog.tsx:341
msgid "Otherwise, the document will be created as a draft."
msgstr "Andernfalls wird das Dokument als Entwurf erstellt."
@ -2582,13 +2726,23 @@ msgid "Please type <0>{0}</0> to confirm."
msgstr "Bitte geben Sie <0>{0}</0> ein, um zu bestätigen."
#: apps/web/src/components/(dashboard)/common/command-menu.tsx:214
#: apps/web/src/components/(teams)/settings/layout/desktop-nav.tsx:58
#: apps/web/src/components/(teams)/settings/layout/mobile-nav.tsx:67
msgid "Preferences"
msgstr "Einstellungen"
#: apps/web/src/app/(recipient)/d/[token]/direct-template.tsx:61
#: apps/web/src/app/(teams)/t/[teamUrl]/settings/preferences/document-preferences.tsx:204
msgid "Preview"
msgstr ""
#: apps/web/src/app/(recipient)/d/[token]/direct-template.tsx:63
msgid "Preview and configure template."
msgstr "Vorschau und Vorlagen konfigurieren."
#: apps/web/src/app/(teams)/t/[teamUrl]/settings/preferences/document-preferences.tsx:130
#~ msgid "Preview: {0}"
#~ msgstr ""
#: apps/web/src/app/(dashboard)/templates/data-table-templates.tsx:105
#: apps/web/src/components/formatter/template-type.tsx:22
msgid "Private"
@ -2626,8 +2780,8 @@ msgstr "Öffentlich"
#: apps/web/src/app/(dashboard)/settings/public-profile/public-profile-page-view.tsx:42
#: apps/web/src/components/(dashboard)/settings/layout/desktop-nav.tsx:50
#: apps/web/src/components/(dashboard)/settings/layout/mobile-nav.tsx:53
#: apps/web/src/components/(teams)/settings/layout/desktop-nav.tsx:57
#: apps/web/src/components/(teams)/settings/layout/mobile-nav.tsx:65
#: apps/web/src/components/(teams)/settings/layout/desktop-nav.tsx:72
#: apps/web/src/components/(teams)/settings/layout/mobile-nav.tsx:81
msgid "Public Profile"
msgstr "Öffentliches Profil"
@ -2719,6 +2873,7 @@ msgstr "Haben Sie Ihr Passwort vergessen? <0>Einloggen</0>"
#: apps/web/src/app/(dashboard)/templates/template-direct-link-dialog.tsx:431
#: apps/web/src/app/(signing)/sign/[token]/signing-field-container.tsx:156
#: apps/web/src/app/(signing)/sign/[token]/signing-field-container.tsx:180
#: apps/web/src/app/(teams)/t/[teamUrl]/settings/preferences/branding-preferences.tsx:250
#: apps/web/src/app/(teams)/t/[teamUrl]/settings/team-email-dropdown.tsx:89
#: apps/web/src/components/(teams)/dialogs/remove-team-email-dialog.tsx:159
#: apps/web/src/components/(teams)/tables/pending-user-teams-data-table-actions.tsx:54
@ -2762,7 +2917,7 @@ msgstr "Bestätigungs-E-Mail erneut senden"
msgid "Resend verification"
msgstr "Bestätigung erneut senden"
#: apps/web/src/components/(teams)/forms/update-team-form.tsx:164
#: apps/web/src/components/(teams)/forms/update-team-form.tsx:266
#: apps/web/src/components/forms/public-profile-form.tsx:267
msgid "Reset"
msgstr "Zurücksetzen"
@ -2842,6 +2997,8 @@ msgstr "Rollen"
#: apps/web/src/app/(dashboard)/templates/template-direct-link-dialog.tsx:446
#: apps/web/src/app/(signing)/sign/[token]/number-field.tsx:336
#: apps/web/src/app/(signing)/sign/[token]/text-field.tsx:342
#: apps/web/src/app/(teams)/t/[teamUrl]/settings/preferences/branding-preferences.tsx:312
#: apps/web/src/app/(teams)/t/[teamUrl]/settings/preferences/document-preferences.tsx:228
msgid "Save"
msgstr "Speichern"
@ -2913,10 +3070,15 @@ msgstr "Passkey auswählen"
msgid "Send confirmation email"
msgstr "Bestätigungs-E-Mail senden"
#: apps/web/src/app/(dashboard)/templates/use-template-dialog.tsx:314
#: apps/web/src/app/(dashboard)/templates/use-template-dialog.tsx:326
msgid "Send document"
msgstr "Dokument senden"
#: apps/web/src/app/(teams)/t/[teamUrl]/settings/preferences/document-preferences.tsx:188
#: apps/web/src/components/(teams)/forms/update-team-form.tsx:220
msgid "Send on Behalf of Team"
msgstr ""
#: apps/web/src/app/(dashboard)/documents/_action-items/resend-document.tsx:191
msgid "Send reminder"
msgstr "Erinnerung senden"
@ -3108,15 +3270,23 @@ msgstr "Anmeldung..."
msgid "Signing Links"
msgstr ""
#: apps/web/src/app/(dashboard)/documents/[id]/edit-document.tsx:339
msgid "Signing links have been generated for this document."
msgstr ""
#: apps/web/src/components/forms/signup.tsx:235
msgid "Signing up..."
msgstr "Registrierung..."
#: apps/web/src/app/(dashboard)/admin/leaderboard/data-table-leaderboard.tsx:84
#: apps/web/src/app/(dashboard)/admin/leaderboard/page.tsx:46
#: apps/web/src/app/(dashboard)/admin/leaderboard/page.tsx:55
msgid "Signing Volume"
msgstr ""
#: apps/web/src/app/(dashboard)/admin/leaderboard/page.tsx:68
msgid "Signing Volume 2"
msgstr ""
#: apps/web/src/app/(profile)/p/[url]/page.tsx:109
msgid "Since {0}"
msgstr "Seit {0}"
@ -3152,10 +3322,11 @@ msgstr "Website Einstellungen"
#: apps/web/src/app/(dashboard)/templates/template-direct-link-dialog.tsx:104
#: apps/web/src/app/(dashboard)/templates/template-direct-link-dialog.tsx:127
#: apps/web/src/app/(dashboard)/templates/template-direct-link-dialog.tsx:151
#: apps/web/src/app/(recipient)/d/[token]/direct-template.tsx:117
#: apps/web/src/app/(recipient)/d/[token]/direct-template.tsx:118
#: apps/web/src/app/(recipient)/d/[token]/signing-auth-page.tsx:27
#: apps/web/src/app/(signing)/sign/[token]/signing-auth-page.tsx:38
#: apps/web/src/app/(teams)/t/[teamUrl]/layout-billing-banner.tsx:53
#: apps/web/src/app/(teams)/t/[teamUrl]/settings/preferences/branding-preferences.tsx:107
#: apps/web/src/app/(teams)/t/[teamUrl]/settings/team-email-dropdown.tsx:39
#: apps/web/src/app/(unauthenticated)/verify-email/[token]/page.tsx:61
#: apps/web/src/app/embed/direct/[[...url]]/client.tsx:243
@ -3192,6 +3363,10 @@ msgstr "Etwas ist schiefgelaufen beim Senden der Bestätigungs-E-Mail."
msgid "Something went wrong while updating the team billing subscription, please contact support."
msgstr "Etwas ist schiefgelaufen beim Aktualisieren des Abonnements für die Team-Zahlung. Bitte kontaktieren Sie den Support."
#: apps/web/src/app/(teams)/t/[teamUrl]/settings/preferences/document-preferences.tsx:96
msgid "Something went wrong!"
msgstr ""
#: apps/web/src/app/(dashboard)/settings/security/passkeys/create-passkey-dialog.tsx:240
#: apps/web/src/components/forms/2fa/view-recovery-codes-dialog.tsx:154
msgid "Something went wrong. Please try again or contact support."
@ -3256,7 +3431,7 @@ msgstr "Abonnements"
#: apps/web/src/components/(teams)/dialogs/transfer-team-dialog.tsx:108
#: apps/web/src/components/(teams)/dialogs/update-team-email-dialog.tsx:79
#: apps/web/src/components/(teams)/dialogs/update-team-member-dialog.tsx:92
#: apps/web/src/components/(teams)/forms/update-team-form.tsx:68
#: apps/web/src/components/(teams)/forms/update-team-form.tsx:106
#: apps/web/src/components/(teams)/tables/pending-user-teams-data-table-actions.tsx:27
#: apps/web/src/components/(teams)/tables/team-member-invites-data-table.tsx:62
#: apps/web/src/components/(teams)/tables/team-member-invites-data-table.tsx:79
@ -3287,8 +3462,8 @@ msgstr "Team"
msgid "Team checkout"
msgstr "Teameinkaufs-Prüfung"
#: apps/web/src/app/(teams)/t/[teamUrl]/settings/page.tsx:61
#: apps/web/src/app/(teams)/t/[teamUrl]/settings/page.tsx:140
#: apps/web/src/app/(teams)/t/[teamUrl]/settings/page.tsx:67
#: apps/web/src/app/(teams)/t/[teamUrl]/settings/page.tsx:146
msgid "Team email"
msgstr "Team E-Mail"
@ -3331,7 +3506,7 @@ msgid "Team Member"
msgstr "Teammitglied"
#: apps/web/src/components/(teams)/dialogs/create-team-dialog.tsx:166
#: apps/web/src/components/(teams)/forms/update-team-form.tsx:113
#: apps/web/src/components/(teams)/forms/update-team-form.tsx:153
msgid "Team Name"
msgstr "Teamname"
@ -3355,6 +3530,10 @@ msgstr "Team-Eigentumsübertragung bereits abgeschlossen!"
msgid "Team ownership transferred!"
msgstr "Team-Eigentum übertragen!"
#: apps/web/src/app/(teams)/t/[teamUrl]/settings/preferences/page.tsx:33
msgid "Team Preferences"
msgstr ""
#: apps/web/src/app/(dashboard)/settings/public-profile/public-profile-page-view.tsx:49
msgid "Team Public Profile"
msgstr "Öffentliches Profil des Teams"
@ -3380,7 +3559,7 @@ msgid "Team transfer request expired"
msgstr "Der Antrag auf Teamübertragung ist abgelaufen"
#: apps/web/src/components/(teams)/dialogs/create-team-dialog.tsx:196
#: apps/web/src/components/(teams)/forms/update-team-form.tsx:129
#: apps/web/src/components/(teams)/forms/update-team-form.tsx:169
msgid "Team URL"
msgstr "Team-URL"
@ -3481,7 +3660,7 @@ msgstr "Das Dokument wurde erfolgreich in das ausgewählte Team verschoben."
msgid "The document is now completed, please follow any instructions provided within the parent application."
msgstr "Das Dokument ist jetzt abgeschlossen. Bitte folgen Sie allen Anweisungen, die in der übergeordneten Anwendung bereitgestellt werden."
#: apps/web/src/app/(dashboard)/templates/use-template-dialog.tsx:171
#: apps/web/src/app/(dashboard)/templates/use-template-dialog.tsx:182
msgid "The document was created but could not be sent to recipients."
msgstr "Das Dokument wurde erstellt, konnte aber nicht an die Empfänger versendet werden."
@ -3489,7 +3668,7 @@ msgstr "Das Dokument wurde erstellt, konnte aber nicht an die Empfänger versend
msgid "The document will be hidden from your account"
msgstr "Das Dokument wird von Ihrem Konto verborgen werden"
#: apps/web/src/app/(dashboard)/templates/use-template-dialog.tsx:322
#: apps/web/src/app/(dashboard)/templates/use-template-dialog.tsx:334
msgid "The document will be immediately sent to recipients if this is checked."
msgstr "Das Dokument wird sofort an die Empfänger gesendet, wenn dies angehakt ist."
@ -3499,6 +3678,10 @@ msgstr "Das Dokument wird sofort an die Empfänger gesendet, wenn dies angehakt
msgid "The events that will trigger a webhook to be sent to your URL."
msgstr "Die Ereignisse, die einen Webhook auslösen, der an Ihre URL gesendet wird."
#: apps/web/src/app/(teams)/t/[teamUrl]/settings/documents/page.tsx:27
#~ msgid "The global settings for the documents in your team account."
#~ msgstr ""
#: apps/web/src/app/(unauthenticated)/team/verify/transfer/[token]/page.tsx:114
msgid "The ownership of team <0>{0}</0> has been successfully transferred to you."
msgstr "Die Inhaberschaft des Teams <0>{0}</0> wurde erfolgreich auf Sie übertragen."
@ -3678,7 +3861,7 @@ msgstr "Dieser Preis beinhaltet mindestens 5 Plätze."
msgid "This session has expired. Please try again."
msgstr "Diese Sitzung ist abgelaufen. Bitte versuchen Sie es erneut."
#: apps/web/src/app/(teams)/t/[teamUrl]/settings/page.tsx:195
#: apps/web/src/app/(teams)/t/[teamUrl]/settings/page.tsx:201
msgid "This team, and any associated data excluding billing invoices will be permanently deleted."
msgstr "Dieses Team und alle zugehörigen Daten, ausgenommen Rechnungen, werden permanent gelöscht."
@ -3695,7 +3878,7 @@ msgid "This token is invalid or has expired. Please contact your team for a new
msgstr "Dieser Token ist ungültig oder abgelaufen. Bitte kontaktieren Sie Ihr Team für eine neue Einladung."
#: apps/web/src/components/(teams)/dialogs/create-team-dialog.tsx:98
#: apps/web/src/components/(teams)/forms/update-team-form.tsx:87
#: apps/web/src/components/(teams)/forms/update-team-form.tsx:127
msgid "This URL is already in use."
msgstr "Diese URL wird bereits verwendet."
@ -3828,13 +4011,13 @@ msgstr "übertragen {teamName}"
msgid "Transfer ownership of this team to a selected team member."
msgstr "Übertragen Sie die Inhaberschaft dieses Teams auf ein ausgewähltes Teammitglied."
#: apps/web/src/app/(teams)/t/[teamUrl]/settings/page.tsx:169
#: apps/web/src/app/(teams)/t/[teamUrl]/settings/page.tsx:175
#: apps/web/src/components/(teams)/dialogs/transfer-team-dialog.tsx:147
#: apps/web/src/components/(teams)/dialogs/transfer-team-dialog.tsx:156
msgid "Transfer team"
msgstr "Team übertragen"
#: apps/web/src/app/(teams)/t/[teamUrl]/settings/page.tsx:173
#: apps/web/src/app/(teams)/t/[teamUrl]/settings/page.tsx:179
msgid "Transfer the ownership of the team to another team member."
msgstr "Übertragen Sie das Eigentum des Teams auf ein anderes Teammitglied."
@ -4025,7 +4208,7 @@ msgstr "Empfänger aktualisieren"
msgid "Update role"
msgstr "Rolle aktualisieren"
#: apps/web/src/components/(teams)/forms/update-team-form.tsx:176
#: apps/web/src/components/(teams)/forms/update-team-form.tsx:278
msgid "Update team"
msgstr "Team aktualisieren"
@ -4064,6 +4247,10 @@ msgstr "Aktualisierung Ihrer Informationen"
msgid "Upload Avatar"
msgstr "Avatar hochladen"
#: apps/web/src/app/(teams)/t/[teamUrl]/settings/preferences/branding-preferences.tsx:256
msgid "Upload your brand logo (max 5MB, JPG, PNG, or WebP)"
msgstr ""
#: apps/web/src/app/(dashboard)/documents/[id]/document-page-view-information.tsx:31
#: apps/web/src/app/(dashboard)/templates/[id]/template-page-view-information.tsx:30
msgid "Uploaded by"
@ -4095,7 +4282,7 @@ msgstr "Authenticator verwenden"
msgid "Use Backup Code"
msgstr "Backup-Code verwenden"
#: apps/web/src/app/(dashboard)/templates/use-template-dialog.tsx:196
#: apps/web/src/app/(dashboard)/templates/use-template-dialog.tsx:207
msgid "Use Template"
msgstr "Vorlage verwenden"
@ -4189,7 +4376,7 @@ msgstr "Codes ansehen"
msgid "View Document"
msgstr "Dokument anzeigen"
#: apps/web/src/app/(teams)/t/[teamUrl]/settings/page.tsx:150
#: apps/web/src/app/(teams)/t/[teamUrl]/settings/page.tsx:156
msgid "View documents associated with this email"
msgstr "Dokumente ansehen, die mit dieser E-Mail verknüpft sind"
@ -4375,7 +4562,7 @@ msgstr "Wir sind auf einen unbekannten Fehler gestoßen, während wir versucht h
msgid "We encountered an unknown error while attempting to update your public profile. Please try again later."
msgstr "Wir sind auf einen unbekannten Fehler gestoßen, während wir versucht haben, Ihr öffentliches Profil zu aktualisieren. Bitte versuchen Sie es später erneut."
#: apps/web/src/components/(teams)/forms/update-team-form.tsx:96
#: apps/web/src/components/(teams)/forms/update-team-form.tsx:136
msgid "We encountered an unknown error while attempting to update your team. Please try again later."
msgstr "Wir sind auf einen unbekannten Fehler gestoßen, während wir versucht haben, Ihr Team zu aktualisieren. Bitte versuchen Sie es später erneut."
@ -4417,12 +4604,20 @@ msgstr "Wir konnten Ihr öffentliches Profil nicht auf öffentlich setzen. Bitte
msgid "We were unable to setup two-factor authentication for your account. Please ensure that you have entered your code correctly and try again."
msgstr "Wir konnten die Zwei-Faktor-Authentifizierung für Ihr Konto nicht einrichten. Bitte stellen Sie sicher, dass Sie den Code korrekt eingegeben haben und versuchen Sie es erneut."
#: apps/web/src/app/(recipient)/d/[token]/direct-template.tsx:119
#: apps/web/src/app/(recipient)/d/[token]/direct-template.tsx:120
#: apps/web/src/app/embed/direct/[[...url]]/client.tsx:245
#: apps/web/src/app/embed/sign/[[...url]]/client.tsx:127
msgid "We were unable to submit this document at this time. Please try again later."
msgstr "Wir konnten dieses Dokument zurzeit nicht einreichen. Bitte versuchen Sie es später erneut."
#: apps/web/src/app/(teams)/t/[teamUrl]/settings/preferences/branding-preferences.tsx:109
msgid "We were unable to update your branding preferences at this time, please try again later"
msgstr ""
#: apps/web/src/app/(teams)/t/[teamUrl]/settings/preferences/document-preferences.tsx:98
msgid "We were unable to update your document preferences at this time, please try again later"
msgstr ""
#: apps/web/src/app/(signing)/sign/[token]/document-action-auth-2fa.tsx:169
msgid "We were unable to verify your details. Please try again or contact support"
msgstr "Wir konnten Ihre Angaben nicht verifizieren. Bitte versuchen Sie es erneut oder kontaktieren Sie den Support"
@ -4431,6 +4626,14 @@ msgstr "Wir konnten Ihre Angaben nicht verifizieren. Bitte versuchen Sie es erne
msgid "We were unable to verify your email. If your email is not verified already, please try again."
msgstr "Wir konnten Ihre E-Mail nicht bestätigen. Wenn Ihre E-Mail noch nicht bestätigt wurde, versuchen Sie es bitte erneut."
#: apps/web/src/app/(dashboard)/templates/use-template-dialog.tsx:370
msgid "We will generate signing links for you, which you can send to the recipients through your method of choice."
msgstr ""
#: apps/web/src/app/(dashboard)/templates/use-template-dialog.tsx:366
msgid "We won't send anything to notify recipients."
msgstr ""
#: apps/web/src/app/(dashboard)/documents/empty-state.tsx:29
#: apps/web/src/app/(dashboard)/templates/empty-state.tsx:11
msgid "We're all empty"
@ -4462,8 +4665,8 @@ msgstr "Webhook-URL"
#: apps/web/src/app/(teams)/t/[teamUrl]/settings/webhooks/page.tsx:33
#: apps/web/src/components/(dashboard)/settings/layout/desktop-nav.tsx:103
#: apps/web/src/components/(dashboard)/settings/layout/mobile-nav.tsx:106
#: apps/web/src/components/(teams)/settings/layout/desktop-nav.tsx:94
#: apps/web/src/components/(teams)/settings/layout/mobile-nav.tsx:102
#: apps/web/src/components/(teams)/settings/layout/desktop-nav.tsx:109
#: apps/web/src/components/(teams)/settings/layout/mobile-nav.tsx:118
msgid "Webhooks"
msgstr "Webhooks"
@ -4592,7 +4795,7 @@ msgstr ""
msgid "You can update the profile URL by updating the team URL in the general settings page."
msgstr "Sie können die Profil-URL aktualisieren, indem Sie die Team-URL auf der Seite mit den allgemeinen Einstellungen aktualisieren."
#: apps/web/src/app/(teams)/t/[teamUrl]/settings/page.tsx:65
#: apps/web/src/app/(teams)/t/[teamUrl]/settings/page.tsx:71
msgid "You can view documents associated with this email and use this identity when sending documents."
msgstr "Sie können Dokumente ansehen, die mit dieser E-Mail verknüpft sind, und diese Identität beim Senden von Dokumenten verwenden."
@ -4654,7 +4857,7 @@ msgstr "Sie haben das maximale Limit von {0} direkten Vorlagen erreicht. <0>Upgr
msgid "You have reached your document limit."
msgstr "Sie haben Ihr Dokumentenlimit erreicht."
#: apps/web/src/app/(dashboard)/templates/data-table-templates.tsx:182
#: apps/web/src/app/(dashboard)/templates/data-table-templates.tsx:183
msgid "You have reached your document limit. <0>Upgrade your account to continue!</0>"
msgstr "Sie haben Ihr Dokumentenlimit erreicht. <0>Upgrade your account to continue!</0>"
@ -4742,6 +4945,14 @@ msgstr "Ihr Avatar wurde erfolgreich aktualisiert."
msgid "Your banner has been updated successfully."
msgstr "Ihr Banner wurde erfolgreich aktualisiert."
#: apps/web/src/app/(teams)/t/[teamUrl]/settings/preferences/branding-preferences.tsx:280
msgid "Your brand website URL"
msgstr ""
#: apps/web/src/app/(teams)/t/[teamUrl]/settings/preferences/branding-preferences.tsx:103
msgid "Your branding preferences have been updated"
msgstr ""
#: apps/web/src/app/(dashboard)/settings/billing/page.tsx:119
msgid "Your current plan is past due. Please update your payment information."
msgstr "Ihr aktueller Plan ist überfällig. Bitte aktualisieren Sie Ihre Zahlungsinformationen."
@ -4754,7 +4965,7 @@ msgstr "Ihre direkten Unterzeichnungsvorlagen"
msgid "Your document failed to upload."
msgstr "Ihr Dokument konnte nicht hochgeladen werden."
#: apps/web/src/app/(dashboard)/templates/use-template-dialog.tsx:155
#: apps/web/src/app/(dashboard)/templates/use-template-dialog.tsx:157
msgid "Your document has been created from the template successfully."
msgstr "Ihr Dokument wurde erfolgreich aus der Vorlage erstellt."
@ -4762,7 +4973,7 @@ msgstr "Ihr Dokument wurde erfolgreich aus der Vorlage erstellt."
msgid "Your document has been re-sent successfully."
msgstr "Ihr Dokument wurde erfolgreich erneut gesendet."
#: apps/web/src/app/(dashboard)/documents/[id]/edit-document.tsx:324
#: apps/web/src/app/(dashboard)/documents/[id]/edit-document.tsx:328
msgid "Your document has been sent successfully."
msgstr "Ihr Dokument wurde erfolgreich gesendet."
@ -4778,6 +4989,10 @@ msgstr "Ihr Dokument wurde erfolgreich hochgeladen."
msgid "Your document has been uploaded successfully. You will be redirected to the template page."
msgstr "Ihr Dokument wurde erfolgreich hochgeladen. Sie werden zur Vorlagenseite weitergeleitet."
#: apps/web/src/app/(teams)/t/[teamUrl]/settings/preferences/document-preferences.tsx:92
msgid "Your document preferences have been updated"
msgstr ""
#: apps/web/src/components/(dashboard)/common/command-menu.tsx:223
msgid "Your documents"
msgstr "Ihre Dokumente"
@ -4795,6 +5010,10 @@ msgstr "Ihre E-Mail wird derzeit von Team <0>{0}</0> ({1}) verwendet."
msgid "Your existing tokens"
msgstr "Ihre vorhandenen Tokens"
#: apps/web/src/components/forms/team-document-settings.tsx:70
#~ msgid "Your global team document settings has been updated successfully."
#~ msgstr ""
#: apps/web/src/components/forms/password.tsx:72
#: apps/web/src/components/forms/reset-password.tsx:73
msgid "Your password has been updated successfully."
@ -4837,7 +5056,7 @@ msgstr "Ihr Team wurde erstellt."
msgid "Your team has been successfully deleted."
msgstr "Ihr Team wurde erfolgreich gelöscht."
#: apps/web/src/components/(teams)/forms/update-team-form.tsx:69
#: apps/web/src/components/(teams)/forms/update-team-form.tsx:107
msgid "Your team has been successfully updated."
msgstr "Ihr Team wurde erfolgreich aktualisiert."

File diff suppressed because it is too large Load Diff

View File

@ -13,6 +13,10 @@ msgstr ""
"Language-Team: \n"
"Plural-Forms: \n"
#: apps/web/src/app/(teams)/t/[teamUrl]/settings/preferences/document-preferences.tsx:211
msgid "\"{0}\" has invited you to sign \"example document\"."
msgstr "\"{0}\" has invited you to sign \"example document\"."
#: apps/web/src/app/(signing)/sign/[token]/date-field.tsx:69
msgid "\"{0}\" will appear on the document as it has a timezone of \"{timezone}\"."
msgstr "\"{0}\" will appear on the document as it has a timezone of \"{timezone}\"."
@ -21,6 +25,22 @@ msgstr "\"{0}\" will appear on the document as it has a timezone of \"{timezone}
msgid "\"{documentTitle}\" has been successfully deleted"
msgstr "\"{documentTitle}\" has been successfully deleted"
#: apps/web/src/components/(teams)/forms/update-team-form.tsx:234
msgid "\"{email}\" on behalf of \"{teamName}\" has invited you to sign \"example document\"."
msgstr "\"{email}\" on behalf of \"{teamName}\" has invited you to sign \"example document\"."
#: apps/web/src/app/(teams)/t/[teamUrl]/settings/preferences/document-preferences.tsx:209
msgid ""
"\"{placeholderEmail}\" on behalf of \"{0}\" has invited you to sign \"example\n"
"document\"."
msgstr ""
"\"{placeholderEmail}\" on behalf of \"{0}\" has invited you to sign \"example\n"
"document\"."
#: apps/web/src/components/(teams)/forms/update-team-form.tsx:241
msgid "\"{teamUrl}\" has invited you to sign \"example document\"."
msgstr "\"{teamUrl}\" has invited you to sign \"example document\"."
#: apps/web/src/app/(signing)/sign/[token]/signing-page-view.tsx:78
msgid "({0}) has invited you to approve this document"
msgstr "({0}) has invited you to approve this document"
@ -72,8 +92,8 @@ msgid "{0} direct signing templates"
msgstr "{0} direct signing templates"
#: apps/web/src/app/(recipient)/d/[token]/direct-template.tsx:66
msgid "{0} document"
msgstr "{0} document"
#~ msgid "{0} document"
#~ msgstr "{0} document"
#: apps/web/src/app/(dashboard)/documents/upload-document.tsx:146
msgid "{0} of {1} documents remaining this month."
@ -84,8 +104,8 @@ msgid "{0} Recipient(s)"
msgstr "{0} Recipient(s)"
#: apps/web/src/app/(recipient)/d/[token]/direct-template.tsx:67
msgid "{0} the document to complete the process."
msgstr "{0} the document to complete the process."
#~ msgid "{0} the document to complete the process."
#~ msgstr "{0} the document to complete the process."
#: apps/web/src/app/(signing)/sign/[token]/text-field.tsx:292
msgid "{charactersRemaining, plural, one {1 character remaining} other {{charactersRemaining} characters remaining}}"
@ -99,6 +119,14 @@ msgstr "{formattedTeamMemberQuanity} • Monthly • Renews: {formattedDate}"
msgid "{numberOfSeats, plural, one {# member} other {# members}}"
msgstr "{numberOfSeats, plural, one {# member} other {# members}}"
#: apps/web/src/app/(recipient)/d/[token]/direct-template.tsx:67
msgid "{recipientActionVerb} document"
msgstr "{recipientActionVerb} document"
#: apps/web/src/app/(recipient)/d/[token]/direct-template.tsx:68
msgid "{recipientActionVerb} the document to complete the process."
msgstr "{recipientActionVerb} the document to complete the process."
#: apps/web/src/components/forms/public-profile-form.tsx:231
#: apps/web/src/components/templates/manage-public-template-dialog.tsx:389
msgid "{remaningLength, plural, one {# character remaining} other {# characters remaining}}"
@ -156,7 +184,7 @@ msgstr "A confirmation email has been sent, and it should arrive in your inbox s
msgid "A device capable of accessing, opening, and reading documents"
msgstr "A device capable of accessing, opening, and reading documents"
#: apps/web/src/app/(dashboard)/templates/use-template-dialog.tsx:207
#: apps/web/src/app/(dashboard)/templates/use-template-dialog.tsx:218
msgid "A draft document will be created"
msgstr "A draft document will be created"
@ -195,7 +223,7 @@ msgid "A unique URL to access your profile"
msgstr "A unique URL to access your profile"
#: apps/web/src/components/(teams)/dialogs/create-team-dialog.tsx:206
#: apps/web/src/components/(teams)/forms/update-team-form.tsx:139
#: apps/web/src/components/(teams)/forms/update-team-form.tsx:179
msgid "A unique URL to identify your team"
msgstr "A unique URL to identify your team"
@ -251,7 +279,7 @@ msgstr "Action"
msgid "Actions"
msgstr "Actions"
#: apps/web/src/app/(teams)/t/[teamUrl]/settings/page.tsx:101
#: apps/web/src/app/(teams)/t/[teamUrl]/settings/page.tsx:107
#: apps/web/src/components/(teams)/tables/teams-member-page-data-table.tsx:76
#: apps/web/src/components/(teams)/tables/user-settings-teams-page-data-table.tsx:71
msgid "Active"
@ -265,7 +293,7 @@ msgstr "Active Subscriptions"
msgid "Add"
msgstr "Add"
#: apps/web/src/app/(dashboard)/documents/[id]/edit-document.tsx:176
#: apps/web/src/app/(dashboard)/documents/[id]/edit-document.tsx:177
#: apps/web/src/app/(dashboard)/templates/[id]/edit/edit-template.tsx:88
msgid "Add all relevant fields for each recipient."
msgstr "Add all relevant fields for each recipient."
@ -286,7 +314,7 @@ msgstr "Add an authenticator to serve as a secondary authentication method when
msgid "Add email"
msgstr "Add email"
#: apps/web/src/app/(dashboard)/documents/[id]/edit-document.tsx:175
#: apps/web/src/app/(dashboard)/documents/[id]/edit-document.tsx:176
#: apps/web/src/app/(dashboard)/templates/[id]/edit/edit-template.tsx:87
msgid "Add Fields"
msgstr "Add Fields"
@ -304,34 +332,38 @@ msgstr "Add passkey"
msgid "Add Placeholders"
msgstr "Add Placeholders"
#: apps/web/src/app/(dashboard)/documents/[id]/edit-document.tsx:170
#: apps/web/src/app/(dashboard)/documents/[id]/edit-document.tsx:171
msgid "Add Signers"
msgstr "Add Signers"
#: apps/web/src/app/(dashboard)/documents/[id]/edit-document.tsx:180
msgid "Add Subject"
msgstr "Add Subject"
#~ msgid "Add Subject"
#~ msgstr "Add Subject"
#: apps/web/src/components/(teams)/dialogs/add-team-email-dialog.tsx:133
msgid "Add team email"
msgstr "Add team email"
#: apps/web/src/app/(dashboard)/documents/[id]/edit-document.tsx:171
#: apps/web/src/app/(dashboard)/documents/[id]/edit-document.tsx:172
msgid "Add the people who will sign the document."
msgstr "Add the people who will sign the document."
#: apps/web/src/app/(dashboard)/templates/use-template-dialog.tsx:209
#: apps/web/src/app/(dashboard)/templates/use-template-dialog.tsx:220
msgid "Add the recipients to create the document with"
msgstr "Add the recipients to create the document with"
#: apps/web/src/app/(dashboard)/documents/[id]/edit-document.tsx:181
msgid "Add the subject and message you wish to send to signers."
msgstr "Add the subject and message you wish to send to signers."
#~ msgid "Add the subject and message you wish to send to signers."
#~ msgstr "Add the subject and message you wish to send to signers."
#: apps/web/src/components/(teams)/dialogs/create-team-checkout-dialog.tsx:152
msgid "Adding and removing seats will adjust your invoice accordingly."
msgstr "Adding and removing seats will adjust your invoice accordingly."
#: apps/web/src/app/(teams)/t/[teamUrl]/settings/preferences/branding-preferences.tsx:303
msgid "Additional brand information to display at the bottom of emails"
msgstr "Additional brand information to display at the bottom of emails"
#: apps/web/src/app/(dashboard)/admin/documents/[id]/page.tsx:59
msgid "Admin Actions"
msgstr "Admin Actions"
@ -423,17 +455,17 @@ msgstr "An email requesting the transfer of this team has been sent."
msgid "An error occurred"
msgstr "An error occurred"
#: apps/web/src/app/(dashboard)/documents/[id]/edit-document.tsx:268
#: apps/web/src/app/(dashboard)/documents/[id]/edit-document.tsx:269
#: apps/web/src/app/(dashboard)/templates/[id]/edit/edit-template.tsx:201
#: apps/web/src/app/(dashboard)/templates/[id]/edit/edit-template.tsx:235
msgid "An error occurred while adding signers."
msgstr "An error occurred while adding signers."
#: apps/web/src/app/(dashboard)/documents/[id]/edit-document.tsx:303
#: apps/web/src/app/(dashboard)/documents/[id]/edit-document.tsx:304
msgid "An error occurred while adding the fields."
msgstr "An error occurred while adding the fields."
#: apps/web/src/app/(dashboard)/templates/use-template-dialog.tsx:165
#: apps/web/src/app/(dashboard)/templates/use-template-dialog.tsx:176
msgid "An error occurred while creating document from template."
msgstr "An error occurred while creating document from template."
@ -491,7 +523,7 @@ msgstr "An error occurred while removing the signature."
msgid "An error occurred while removing the text."
msgstr "An error occurred while removing the text."
#: apps/web/src/app/(dashboard)/documents/[id]/edit-document.tsx:334
#: apps/web/src/app/(dashboard)/documents/[id]/edit-document.tsx:350
msgid "An error occurred while sending the document."
msgstr "An error occurred while sending the document."
@ -516,11 +548,15 @@ msgstr "An error occurred while signing the document."
msgid "An error occurred while trying to create a checkout session."
msgstr "An error occurred while trying to create a checkout session."
#: apps/web/src/app/(dashboard)/documents/[id]/edit-document.tsx:234
#: apps/web/src/app/(dashboard)/documents/[id]/edit-document.tsx:235
#: apps/web/src/app/(dashboard)/templates/[id]/edit/edit-template.tsx:170
msgid "An error occurred while updating the document settings."
msgstr "An error occurred while updating the document settings."
#: apps/web/src/components/forms/team-document-settings.tsx:78
#~ msgid "An error occurred while updating the global team settings."
#~ msgstr "An error occurred while updating the global team settings."
#: apps/web/src/app/(signing)/sign/[token]/checkbox-field.tsx:213
msgid "An error occurred while updating the signature."
msgstr "An error occurred while updating the signature."
@ -551,7 +587,7 @@ msgstr "An error occurred while uploading your document."
#: apps/web/src/components/(teams)/dialogs/transfer-team-dialog.tsx:116
#: apps/web/src/components/(teams)/dialogs/update-team-email-dialog.tsx:89
#: apps/web/src/components/(teams)/dialogs/update-team-member-dialog.tsx:100
#: apps/web/src/components/(teams)/forms/update-team-form.tsx:94
#: apps/web/src/components/(teams)/forms/update-team-form.tsx:134
#: apps/web/src/components/forms/avatar-image.tsx:94
#: apps/web/src/components/forms/avatar-image.tsx:122
#: apps/web/src/components/forms/password.tsx:84
@ -593,8 +629,8 @@ msgstr "Any Status"
#: apps/web/src/app/(teams)/t/[teamUrl]/settings/tokens/page.tsx:56
#: apps/web/src/components/(dashboard)/settings/layout/desktop-nav.tsx:90
#: apps/web/src/components/(dashboard)/settings/layout/mobile-nav.tsx:93
#: apps/web/src/components/(teams)/settings/layout/desktop-nav.tsx:81
#: apps/web/src/components/(teams)/settings/layout/mobile-nav.tsx:89
#: apps/web/src/components/(teams)/settings/layout/desktop-nav.tsx:96
#: apps/web/src/components/(teams)/settings/layout/mobile-nav.tsx:105
msgid "API Tokens"
msgstr "API Tokens"
@ -664,7 +700,7 @@ msgstr "Avatar"
msgid "Avatar Updated"
msgstr "Avatar Updated"
#: apps/web/src/app/(teams)/t/[teamUrl]/settings/page.tsx:121
#: apps/web/src/app/(teams)/t/[teamUrl]/settings/page.tsx:127
msgid "Awaiting email confirmation"
msgstr "Awaiting email confirmation"
@ -703,11 +739,19 @@ msgstr "Basic details"
#: apps/web/src/app/(teams)/t/[teamUrl]/settings/billing/page.tsx:61
#: apps/web/src/components/(dashboard)/settings/layout/desktop-nav.tsx:117
#: apps/web/src/components/(dashboard)/settings/layout/mobile-nav.tsx:120
#: apps/web/src/components/(teams)/settings/layout/desktop-nav.tsx:108
#: apps/web/src/components/(teams)/settings/layout/mobile-nav.tsx:116
#: apps/web/src/components/(teams)/settings/layout/desktop-nav.tsx:123
#: apps/web/src/components/(teams)/settings/layout/mobile-nav.tsx:132
msgid "Billing"
msgstr "Billing"
#: apps/web/src/app/(teams)/t/[teamUrl]/settings/preferences/page.tsx:42
msgid "Branding Preferences"
msgstr "Branding Preferences"
#: apps/web/src/app/(teams)/t/[teamUrl]/settings/preferences/branding-preferences.tsx:102
msgid "Branding preferences updated"
msgstr "Branding preferences updated"
#: apps/web/src/app/(dashboard)/settings/security/activity/user-security-activity-data-table.tsx:99
#: apps/web/src/app/(internal)/%5F%5Fhtmltopdf/audit-log/data-table.tsx:48
msgid "Browser"
@ -793,6 +837,10 @@ msgstr "Cancelled by user"
msgid "Charts"
msgstr "Charts"
#: apps/web/src/app/(teams)/t/[teamUrl]/settings/documents/page.tsx:32
#~ msgid "Check out the documentaton for the <0>global team settings</0>."
#~ msgstr "Check out the documentaton for the <0>global team settings</0>."
#: apps/web/src/components/(teams)/dialogs/create-team-checkout-dialog.tsx:179
msgid "Checkout"
msgstr "Checkout"
@ -805,6 +853,10 @@ msgstr "Choose an existing recipient from below to continue"
msgid "Choose Direct Link Recipient"
msgstr "Choose Direct Link Recipient"
#: apps/web/src/app/(dashboard)/documents/[id]/edit-document.tsx:182
msgid "Choose how the document will reach recipients"
msgstr "Choose how the document will reach recipients"
#: apps/web/src/components/forms/token.tsx:200
msgid "Choose..."
msgstr "Choose..."
@ -853,7 +905,7 @@ msgid "Click to insert field"
msgstr "Click to insert field"
#: apps/web/src/app/(dashboard)/templates/new-template-dialog.tsx:126
#: apps/web/src/app/(dashboard)/templates/use-template-dialog.tsx:345
#: apps/web/src/app/(dashboard)/templates/use-template-dialog.tsx:389
#: apps/web/src/app/(signing)/sign/[token]/document-action-auth-2fa.tsx:125
#: apps/web/src/app/(signing)/sign/[token]/document-action-auth-passkey.tsx:138
#: apps/web/src/app/(teams)/t/[teamUrl]/settings/team-transfer-status.tsx:121
@ -898,7 +950,7 @@ msgstr "Completed documents"
msgid "Completed Documents"
msgstr "Completed Documents"
#: apps/web/src/app/(dashboard)/documents/[id]/edit-document.tsx:166
#: apps/web/src/app/(dashboard)/documents/[id]/edit-document.tsx:167
msgid "Configure general settings for the document."
msgstr "Configure general settings for the document."
@ -966,6 +1018,18 @@ msgstr "Continue"
msgid "Continue to login"
msgstr "Continue to login"
#: apps/web/src/app/(teams)/t/[teamUrl]/settings/preferences/document-preferences.tsx:173
msgid "Controls the default language of an uploaded document. This will be used as the language in email communications with the recipients."
msgstr "Controls the default language of an uploaded document. This will be used as the language in email communications with the recipients."
#: apps/web/src/app/(teams)/t/[teamUrl]/settings/preferences/document-preferences.tsx:141
msgid "Controls the default visibility of an uploaded document."
msgstr "Controls the default visibility of an uploaded document."
#: apps/web/src/app/(teams)/t/[teamUrl]/settings/preferences/document-preferences.tsx:216
msgid "Controls the formatting of the message that will be sent when inviting a recipient to sign a document. If a custom message has been provided while configuring the document, it will be used instead."
msgstr "Controls the formatting of the message that will be sent when inviting a recipient to sign a document. If a custom message has been provided while configuring the document, it will be used instead."
#: apps/web/src/components/document/document-recipient-link-copy-dialog.tsx:128
msgid "Copied"
msgstr "Copied"
@ -1019,14 +1083,18 @@ msgstr "Create a team to collaborate with your team members."
msgid "Create account"
msgstr "Create account"
#: apps/web/src/app/(dashboard)/templates/use-template-dialog.tsx:351
#: apps/web/src/app/(dashboard)/templates/use-template-dialog.tsx:397
msgid "Create and send"
msgstr "Create and send"
#: apps/web/src/app/(dashboard)/templates/use-template-dialog.tsx:353
#: apps/web/src/app/(dashboard)/templates/use-template-dialog.tsx:395
msgid "Create as draft"
msgstr "Create as draft"
#: apps/web/src/app/(dashboard)/templates/use-template-dialog.tsx:355
msgid "Create as pending"
msgstr "Create as pending"
#: apps/web/src/app/(dashboard)/templates/[id]/template-direct-link-dialog-wrapper.tsx:37
msgid "Create Direct Link"
msgstr "Create Direct Link"
@ -1035,7 +1103,7 @@ msgstr "Create Direct Link"
msgid "Create Direct Signing Link"
msgstr "Create Direct Signing Link"
#: apps/web/src/app/(dashboard)/templates/use-template-dialog.tsx:203
#: apps/web/src/app/(dashboard)/templates/use-template-dialog.tsx:214
msgid "Create document from template"
msgstr "Create document from template"
@ -1047,6 +1115,10 @@ msgstr "Create now"
msgid "Create one automatically"
msgstr "Create one automatically"
#: apps/web/src/app/(dashboard)/templates/use-template-dialog.tsx:399
msgid "Create signing links"
msgstr "Create signing links"
#: apps/web/src/components/(dashboard)/layout/menu-switcher.tsx:181
#: apps/web/src/components/(dashboard)/layout/menu-switcher.tsx:251
#: apps/web/src/components/(teams)/dialogs/create-team-dialog.tsx:138
@ -1058,6 +1130,10 @@ msgstr "Create team"
msgid "Create Team"
msgstr "Create Team"
#: apps/web/src/app/(dashboard)/templates/use-template-dialog.tsx:362
msgid "Create the document as pending and ready to sign."
msgstr "Create the document as pending and ready to sign."
#: apps/web/src/components/forms/token.tsx:250
#: apps/web/src/components/forms/token.tsx:259
msgid "Create token"
@ -1150,6 +1226,15 @@ msgstr "Decline"
msgid "Declined team invitation"
msgstr "Declined team invitation"
#: apps/web/src/app/(teams)/t/[teamUrl]/settings/preferences/document-preferences.tsx:153
msgid "Default Document Language"
msgstr "Default Document Language"
#: apps/web/src/app/(teams)/t/[teamUrl]/settings/preferences/document-preferences.tsx:117
#: apps/web/src/components/(teams)/forms/update-team-form.tsx:195
msgid "Default Document Visibility"
msgstr "Default Document Visibility"
#: apps/web/src/app/(dashboard)/documents/delete-document-dialog.tsx:90
msgid "delete"
msgstr "delete"
@ -1207,7 +1292,7 @@ msgstr "Delete Document"
msgid "Delete passkey"
msgstr "Delete passkey"
#: apps/web/src/app/(teams)/t/[teamUrl]/settings/page.tsx:191
#: apps/web/src/app/(teams)/t/[teamUrl]/settings/page.tsx:197
#: apps/web/src/components/(teams)/dialogs/delete-team-dialog.tsx:118
msgid "Delete team"
msgstr "Delete team"
@ -1320,6 +1405,10 @@ msgstr "Disabling direct link signing will prevent anyone from accessing the lin
msgid "Display your name and email in documents"
msgstr "Display your name and email in documents"
#: apps/web/src/app/(dashboard)/documents/[id]/edit-document.tsx:181
msgid "Distribute Document"
msgstr "Distribute Document"
#: apps/web/src/app/(dashboard)/templates/delete-template-dialog.tsx:63
msgid "Do you want to delete this template?"
msgstr "Do you want to delete this template?"
@ -1357,7 +1446,7 @@ msgstr "Document completed"
msgid "Document Completed!"
msgstr "Document Completed!"
#: apps/web/src/app/(dashboard)/templates/use-template-dialog.tsx:154
#: apps/web/src/app/(dashboard)/templates/use-template-dialog.tsx:156
msgid "Document created"
msgstr "Document created"
@ -1397,7 +1486,7 @@ msgstr "Document ID"
msgid "Document inbox"
msgstr "Document inbox"
#: apps/web/src/app/(dashboard)/templates/data-table-templates.tsx:179
#: apps/web/src/app/(dashboard)/templates/data-table-templates.tsx:180
msgid "Document Limit Exceeded!"
msgstr "Document Limit Exceeded!"
@ -1417,6 +1506,10 @@ msgstr "Document no longer available to sign"
msgid "Document pending"
msgstr "Document pending"
#: apps/web/src/app/(teams)/t/[teamUrl]/settings/preferences/document-preferences.tsx:91
msgid "Document preferences updated"
msgstr "Document preferences updated"
#: apps/web/src/app/(dashboard)/documents/_action-items/resend-document.tsx:97
msgid "Document re-sent"
msgstr "Document re-sent"
@ -1425,10 +1518,14 @@ msgstr "Document re-sent"
msgid "Document resealed"
msgstr "Document resealed"
#: apps/web/src/app/(dashboard)/documents/[id]/edit-document.tsx:323
#: apps/web/src/app/(dashboard)/documents/[id]/edit-document.tsx:327
msgid "Document sent"
msgstr "Document sent"
#: apps/web/src/app/(teams)/t/[teamUrl]/settings/documents/page.tsx:26
#~ msgid "Document Settings"
#~ msgstr "Document Settings"
#: apps/web/src/app/(signing)/sign/[token]/complete/page.tsx:132
msgid "Document Signed"
msgstr "Document Signed"
@ -1570,8 +1667,8 @@ msgstr "Electronic Signature Disclosure"
#: apps/web/src/app/(dashboard)/admin/documents/[id]/recipient-item.tsx:166
#: apps/web/src/app/(dashboard)/admin/users/[id]/page.tsx:114
#: apps/web/src/app/(dashboard)/admin/users/data-table-users.tsx:71
#: apps/web/src/app/(dashboard)/templates/use-template-dialog.tsx:254
#: apps/web/src/app/(dashboard)/templates/use-template-dialog.tsx:261
#: apps/web/src/app/(dashboard)/templates/use-template-dialog.tsx:265
#: apps/web/src/app/(dashboard)/templates/use-template-dialog.tsx:272
#: apps/web/src/app/(internal)/%5F%5Fhtmltopdf/certificate/page.tsx:122
#: apps/web/src/app/(internal)/%5F%5Fhtmltopdf/certificate/page.tsx:129
#: apps/web/src/app/(recipient)/d/[token]/configure-direct-template.tsx:118
@ -1626,6 +1723,10 @@ msgstr "Enable 2FA"
msgid "Enable Authenticator App"
msgstr "Enable Authenticator App"
#: apps/web/src/app/(teams)/t/[teamUrl]/settings/preferences/branding-preferences.tsx:170
msgid "Enable custom branding for all documents in this team."
msgstr "Enable custom branding for all documents in this team."
#: apps/web/src/app/(dashboard)/templates/template-direct-link-dialog.tsx:251
msgid "Enable direct link signing"
msgstr "Enable direct link signing"
@ -1651,6 +1752,10 @@ msgstr "Enclosed Document"
msgid "Ends On"
msgstr "Ends On"
#: apps/web/src/app/(teams)/t/[teamUrl]/settings/preferences/branding-preferences.tsx:295
msgid "Enter your brand details"
msgstr "Enter your brand details"
#: apps/web/src/app/(signing)/sign/[token]/complete/claim-account.tsx:137
msgid "Enter your email"
msgstr "Enter your email"
@ -1669,10 +1774,10 @@ msgstr "Enter your text here"
#: apps/web/src/app/(dashboard)/admin/documents/[id]/admin-actions.tsx:41
#: apps/web/src/app/(dashboard)/admin/users/[id]/page.tsx:78
#: apps/web/src/app/(dashboard)/documents/[id]/edit-document.tsx:233
#: apps/web/src/app/(dashboard)/documents/[id]/edit-document.tsx:267
#: apps/web/src/app/(dashboard)/documents/[id]/edit-document.tsx:302
#: apps/web/src/app/(dashboard)/documents/[id]/edit-document.tsx:333
#: apps/web/src/app/(dashboard)/documents/[id]/edit-document.tsx:234
#: apps/web/src/app/(dashboard)/documents/[id]/edit-document.tsx:268
#: apps/web/src/app/(dashboard)/documents/[id]/edit-document.tsx:303
#: apps/web/src/app/(dashboard)/documents/[id]/edit-document.tsx:349
#: apps/web/src/app/(dashboard)/documents/move-document-dialog.tsx:57
#: apps/web/src/app/(dashboard)/documents/upload-document.tsx:106
#: apps/web/src/app/(dashboard)/documents/upload-document.tsx:112
@ -1681,7 +1786,7 @@ msgstr "Enter your text here"
#: apps/web/src/app/(dashboard)/templates/[id]/edit/edit-template.tsx:234
#: apps/web/src/app/(dashboard)/templates/duplicate-template-dialog.tsx:51
#: apps/web/src/app/(dashboard)/templates/move-template-dialog.tsx:56
#: apps/web/src/app/(dashboard)/templates/use-template-dialog.tsx:164
#: apps/web/src/app/(dashboard)/templates/use-template-dialog.tsx:175
#: apps/web/src/app/(signing)/sign/[token]/checkbox-field.tsx:122
#: apps/web/src/app/(signing)/sign/[token]/checkbox-field.tsx:151
#: apps/web/src/app/(signing)/sign/[token]/checkbox-field.tsx:212
@ -1708,6 +1813,14 @@ msgstr "Enter your text here"
msgid "Error"
msgstr "Error"
#: apps/web/src/components/forms/team-document-settings.tsx:77
#~ msgid "Error updating global team settings"
#~ msgstr "Error updating global team settings"
#: apps/web/src/app/(teams)/t/[teamUrl]/settings/preferences/document-preferences.tsx:128
msgid "Everyone can access and view the document"
msgstr "Everyone can access and view the document"
#: apps/web/src/app/(signing)/sign/[token]/complete/page.tsx:142
msgid "Everyone has signed"
msgstr "Everyone has signed"
@ -1720,7 +1833,7 @@ msgstr "Everyone has signed! You will receive an Email copy of the signed docume
msgid "Exceeded timeout"
msgstr "Exceeded timeout"
#: apps/web/src/app/(teams)/t/[teamUrl]/settings/page.tsx:114
#: apps/web/src/app/(teams)/t/[teamUrl]/settings/page.tsx:120
msgid "Expired"
msgstr "Expired"
@ -1769,14 +1882,23 @@ msgstr "Forgot your password?"
msgid "Full Name"
msgstr "Full Name"
#: apps/web/src/app/(dashboard)/documents/[id]/edit-document.tsx:165
#: apps/web/src/app/(dashboard)/documents/[id]/edit-document.tsx:166
#: apps/web/src/app/(dashboard)/templates/[id]/edit/edit-template.tsx:77
#: apps/web/src/app/(recipient)/d/[token]/direct-template.tsx:60
#: apps/web/src/components/(teams)/settings/layout/desktop-nav.tsx:43
#: apps/web/src/components/(teams)/settings/layout/mobile-nav.tsx:51
#: apps/web/src/app/(recipient)/d/[token]/direct-template.tsx:62
#: apps/web/src/components/(teams)/settings/layout/desktop-nav.tsx:44
#: apps/web/src/components/(teams)/settings/layout/mobile-nav.tsx:52
msgid "General"
msgstr "General"
#: apps/web/src/components/(teams)/settings/layout/desktop-nav.tsx:57
#: apps/web/src/components/(teams)/settings/layout/mobile-nav.tsx:65
#~ msgid "Global Settings"
#~ msgstr "Global Settings"
#: apps/web/src/components/forms/team-document-settings.tsx:69
#~ msgid "Global Team Settings Updated"
#~ msgstr "Global Team Settings Updated"
#: apps/web/src/app/(profile)/p/[url]/not-found.tsx:30
#: apps/web/src/app/(recipient)/d/[token]/not-found.tsx:33
#: apps/web/src/app/(teams)/t/[teamUrl]/error.tsx:51
@ -1813,6 +1935,14 @@ msgstr "Here you can edit your personal details."
msgid "Here you can manage your password and security settings."
msgstr "Here you can manage your password and security settings."
#: apps/web/src/app/(teams)/t/[teamUrl]/settings/preferences/page.tsx:43
msgid "Here you can set preferences and defaults for branding."
msgstr "Here you can set preferences and defaults for branding."
#: apps/web/src/app/(teams)/t/[teamUrl]/settings/preferences/page.tsx:34
msgid "Here you can set preferences and defaults for your team."
msgstr "Here you can set preferences and defaults for your team."
#: apps/web/src/app/(dashboard)/templates/template-direct-link-dialog.tsx:206
msgid "Here's how it works:"
msgstr "Here's how it works:"
@ -1864,6 +1994,10 @@ msgstr "Inbox"
msgid "Inbox documents"
msgstr "Inbox documents"
#: apps/web/src/components/forms/team-document-settings.tsx:132
#~ msgid "Include Sender Details"
#~ msgstr "Include Sender Details"
#: apps/web/src/app/(dashboard)/documents/[id]/document-page-view-information.tsx:53
#: apps/web/src/app/(dashboard)/templates/[id]/template-page-view-information.tsx:50
msgid "Information"
@ -2034,6 +2168,10 @@ msgstr "Like to have your own public profile with agreements?"
msgid "Link template"
msgstr "Link template"
#: apps/web/src/app/(dashboard)/documents/[id]/edit-document.tsx:338
msgid "Links Generated"
msgstr "Links Generated"
#: apps/web/src/app/(dashboard)/settings/webhooks/page.tsx:79
#: apps/web/src/app/(teams)/t/[teamUrl]/settings/webhooks/page.tsx:84
msgid "Listening to {0}"
@ -2158,8 +2296,8 @@ msgid "Member Since"
msgstr "Member Since"
#: apps/web/src/app/(teams)/t/[teamUrl]/settings/members/page.tsx:31
#: apps/web/src/components/(teams)/settings/layout/desktop-nav.tsx:71
#: apps/web/src/components/(teams)/settings/layout/mobile-nav.tsx:79
#: apps/web/src/components/(teams)/settings/layout/desktop-nav.tsx:86
#: apps/web/src/components/(teams)/settings/layout/mobile-nav.tsx:95
msgid "Members"
msgstr "Members"
@ -2213,8 +2351,8 @@ msgstr "My templates"
#: apps/web/src/app/(dashboard)/admin/users/data-table-users.tsx:66
#: apps/web/src/app/(dashboard)/settings/security/passkeys/user-passkeys-data-table-actions.tsx:144
#: apps/web/src/app/(dashboard)/settings/security/passkeys/user-passkeys-data-table.tsx:61
#: apps/web/src/app/(dashboard)/templates/use-template-dialog.tsx:276
#: apps/web/src/app/(dashboard)/templates/use-template-dialog.tsx:283
#: apps/web/src/app/(dashboard)/templates/use-template-dialog.tsx:287
#: apps/web/src/app/(dashboard)/templates/use-template-dialog.tsx:294
#: apps/web/src/app/(signing)/sign/[token]/complete/claim-account.tsx:119
#: apps/web/src/app/(signing)/sign/[token]/name-field.tsx:170
#: apps/web/src/components/(teams)/dialogs/add-team-email-dialog.tsx:153
@ -2348,6 +2486,14 @@ msgstr "Once confirmed, the following will occur:"
msgid "Once you have scanned the QR code or entered the code manually, enter the code provided by your authenticator app below."
msgstr "Once you have scanned the QR code or entered the code manually, enter the code provided by your authenticator app below."
#: apps/web/src/app/(teams)/t/[teamUrl]/settings/preferences/document-preferences.tsx:134
msgid "Only admins can access and view the document"
msgstr "Only admins can access and view the document"
#: apps/web/src/app/(teams)/t/[teamUrl]/settings/preferences/document-preferences.tsx:131
msgid "Only managers and above can access and view the document"
msgstr "Only managers and above can access and view the document"
#: apps/web/src/app/(profile)/p/[url]/not-found.tsx:19
#: apps/web/src/app/(recipient)/d/[token]/not-found.tsx:19
#: apps/web/src/app/(teams)/t/[teamUrl]/error.tsx:37
@ -2371,7 +2517,7 @@ msgstr "Or"
msgid "Or continue with"
msgstr "Or continue with"
#: apps/web/src/app/(dashboard)/templates/use-template-dialog.tsx:330
#: apps/web/src/app/(dashboard)/templates/use-template-dialog.tsx:341
msgid "Otherwise, the document will be created as a draft."
msgstr "Otherwise, the document will be created as a draft."
@ -2577,13 +2723,23 @@ msgid "Please type <0>{0}</0> to confirm."
msgstr "Please type <0>{0}</0> to confirm."
#: apps/web/src/components/(dashboard)/common/command-menu.tsx:214
#: apps/web/src/components/(teams)/settings/layout/desktop-nav.tsx:58
#: apps/web/src/components/(teams)/settings/layout/mobile-nav.tsx:67
msgid "Preferences"
msgstr "Preferences"
#: apps/web/src/app/(recipient)/d/[token]/direct-template.tsx:61
#: apps/web/src/app/(teams)/t/[teamUrl]/settings/preferences/document-preferences.tsx:204
msgid "Preview"
msgstr "Preview"
#: apps/web/src/app/(recipient)/d/[token]/direct-template.tsx:63
msgid "Preview and configure template."
msgstr "Preview and configure template."
#: apps/web/src/app/(teams)/t/[teamUrl]/settings/preferences/document-preferences.tsx:130
#~ msgid "Preview: {0}"
#~ msgstr "Preview: {0}"
#: apps/web/src/app/(dashboard)/templates/data-table-templates.tsx:105
#: apps/web/src/components/formatter/template-type.tsx:22
msgid "Private"
@ -2621,8 +2777,8 @@ msgstr "Public"
#: apps/web/src/app/(dashboard)/settings/public-profile/public-profile-page-view.tsx:42
#: apps/web/src/components/(dashboard)/settings/layout/desktop-nav.tsx:50
#: apps/web/src/components/(dashboard)/settings/layout/mobile-nav.tsx:53
#: apps/web/src/components/(teams)/settings/layout/desktop-nav.tsx:57
#: apps/web/src/components/(teams)/settings/layout/mobile-nav.tsx:65
#: apps/web/src/components/(teams)/settings/layout/desktop-nav.tsx:72
#: apps/web/src/components/(teams)/settings/layout/mobile-nav.tsx:81
msgid "Public Profile"
msgstr "Public Profile"
@ -2714,6 +2870,7 @@ msgstr "Remembered your password? <0>Sign In</0>"
#: apps/web/src/app/(dashboard)/templates/template-direct-link-dialog.tsx:431
#: apps/web/src/app/(signing)/sign/[token]/signing-field-container.tsx:156
#: apps/web/src/app/(signing)/sign/[token]/signing-field-container.tsx:180
#: apps/web/src/app/(teams)/t/[teamUrl]/settings/preferences/branding-preferences.tsx:250
#: apps/web/src/app/(teams)/t/[teamUrl]/settings/team-email-dropdown.tsx:89
#: apps/web/src/components/(teams)/dialogs/remove-team-email-dialog.tsx:159
#: apps/web/src/components/(teams)/tables/pending-user-teams-data-table-actions.tsx:54
@ -2757,7 +2914,7 @@ msgstr "Resend Confirmation Email"
msgid "Resend verification"
msgstr "Resend verification"
#: apps/web/src/components/(teams)/forms/update-team-form.tsx:164
#: apps/web/src/components/(teams)/forms/update-team-form.tsx:266
#: apps/web/src/components/forms/public-profile-form.tsx:267
msgid "Reset"
msgstr "Reset"
@ -2837,6 +2994,8 @@ msgstr "Roles"
#: apps/web/src/app/(dashboard)/templates/template-direct-link-dialog.tsx:446
#: apps/web/src/app/(signing)/sign/[token]/number-field.tsx:336
#: apps/web/src/app/(signing)/sign/[token]/text-field.tsx:342
#: apps/web/src/app/(teams)/t/[teamUrl]/settings/preferences/branding-preferences.tsx:312
#: apps/web/src/app/(teams)/t/[teamUrl]/settings/preferences/document-preferences.tsx:228
msgid "Save"
msgstr "Save"
@ -2908,10 +3067,15 @@ msgstr "Select passkey"
msgid "Send confirmation email"
msgstr "Send confirmation email"
#: apps/web/src/app/(dashboard)/templates/use-template-dialog.tsx:314
#: apps/web/src/app/(dashboard)/templates/use-template-dialog.tsx:326
msgid "Send document"
msgstr "Send document"
#: apps/web/src/app/(teams)/t/[teamUrl]/settings/preferences/document-preferences.tsx:188
#: apps/web/src/components/(teams)/forms/update-team-form.tsx:220
msgid "Send on Behalf of Team"
msgstr "Send on Behalf of Team"
#: apps/web/src/app/(dashboard)/documents/_action-items/resend-document.tsx:191
msgid "Send reminder"
msgstr "Send reminder"
@ -3103,15 +3267,23 @@ msgstr "Signing in..."
msgid "Signing Links"
msgstr "Signing Links"
#: apps/web/src/app/(dashboard)/documents/[id]/edit-document.tsx:339
msgid "Signing links have been generated for this document."
msgstr "Signing links have been generated for this document."
#: apps/web/src/components/forms/signup.tsx:235
msgid "Signing up..."
msgstr "Signing up..."
#: apps/web/src/app/(dashboard)/admin/leaderboard/data-table-leaderboard.tsx:84
#: apps/web/src/app/(dashboard)/admin/leaderboard/page.tsx:46
#: apps/web/src/app/(dashboard)/admin/leaderboard/page.tsx:55
msgid "Signing Volume"
msgstr "Signing Volume"
#: apps/web/src/app/(dashboard)/admin/leaderboard/page.tsx:68
msgid "Signing Volume 2"
msgstr "Signing Volume 2"
#: apps/web/src/app/(profile)/p/[url]/page.tsx:109
msgid "Since {0}"
msgstr "Since {0}"
@ -3147,10 +3319,11 @@ msgstr "Site Settings"
#: apps/web/src/app/(dashboard)/templates/template-direct-link-dialog.tsx:104
#: apps/web/src/app/(dashboard)/templates/template-direct-link-dialog.tsx:127
#: apps/web/src/app/(dashboard)/templates/template-direct-link-dialog.tsx:151
#: apps/web/src/app/(recipient)/d/[token]/direct-template.tsx:117
#: apps/web/src/app/(recipient)/d/[token]/direct-template.tsx:118
#: apps/web/src/app/(recipient)/d/[token]/signing-auth-page.tsx:27
#: apps/web/src/app/(signing)/sign/[token]/signing-auth-page.tsx:38
#: apps/web/src/app/(teams)/t/[teamUrl]/layout-billing-banner.tsx:53
#: apps/web/src/app/(teams)/t/[teamUrl]/settings/preferences/branding-preferences.tsx:107
#: apps/web/src/app/(teams)/t/[teamUrl]/settings/team-email-dropdown.tsx:39
#: apps/web/src/app/(unauthenticated)/verify-email/[token]/page.tsx:61
#: apps/web/src/app/embed/direct/[[...url]]/client.tsx:243
@ -3187,6 +3360,10 @@ msgstr "Something went wrong while sending the confirmation email."
msgid "Something went wrong while updating the team billing subscription, please contact support."
msgstr "Something went wrong while updating the team billing subscription, please contact support."
#: apps/web/src/app/(teams)/t/[teamUrl]/settings/preferences/document-preferences.tsx:96
msgid "Something went wrong!"
msgstr "Something went wrong!"
#: apps/web/src/app/(dashboard)/settings/security/passkeys/create-passkey-dialog.tsx:240
#: apps/web/src/components/forms/2fa/view-recovery-codes-dialog.tsx:154
msgid "Something went wrong. Please try again or contact support."
@ -3251,7 +3428,7 @@ msgstr "Subscriptions"
#: apps/web/src/components/(teams)/dialogs/transfer-team-dialog.tsx:108
#: apps/web/src/components/(teams)/dialogs/update-team-email-dialog.tsx:79
#: apps/web/src/components/(teams)/dialogs/update-team-member-dialog.tsx:92
#: apps/web/src/components/(teams)/forms/update-team-form.tsx:68
#: apps/web/src/components/(teams)/forms/update-team-form.tsx:106
#: apps/web/src/components/(teams)/tables/pending-user-teams-data-table-actions.tsx:27
#: apps/web/src/components/(teams)/tables/team-member-invites-data-table.tsx:62
#: apps/web/src/components/(teams)/tables/team-member-invites-data-table.tsx:79
@ -3282,8 +3459,8 @@ msgstr "Team"
msgid "Team checkout"
msgstr "Team checkout"
#: apps/web/src/app/(teams)/t/[teamUrl]/settings/page.tsx:61
#: apps/web/src/app/(teams)/t/[teamUrl]/settings/page.tsx:140
#: apps/web/src/app/(teams)/t/[teamUrl]/settings/page.tsx:67
#: apps/web/src/app/(teams)/t/[teamUrl]/settings/page.tsx:146
msgid "Team email"
msgstr "Team email"
@ -3326,7 +3503,7 @@ msgid "Team Member"
msgstr "Team Member"
#: apps/web/src/components/(teams)/dialogs/create-team-dialog.tsx:166
#: apps/web/src/components/(teams)/forms/update-team-form.tsx:113
#: apps/web/src/components/(teams)/forms/update-team-form.tsx:153
msgid "Team Name"
msgstr "Team Name"
@ -3350,6 +3527,10 @@ msgstr "Team ownership transfer already completed!"
msgid "Team ownership transferred!"
msgstr "Team ownership transferred!"
#: apps/web/src/app/(teams)/t/[teamUrl]/settings/preferences/page.tsx:33
msgid "Team Preferences"
msgstr "Team Preferences"
#: apps/web/src/app/(dashboard)/settings/public-profile/public-profile-page-view.tsx:49
msgid "Team Public Profile"
msgstr "Team Public Profile"
@ -3375,7 +3556,7 @@ msgid "Team transfer request expired"
msgstr "Team transfer request expired"
#: apps/web/src/components/(teams)/dialogs/create-team-dialog.tsx:196
#: apps/web/src/components/(teams)/forms/update-team-form.tsx:129
#: apps/web/src/components/(teams)/forms/update-team-form.tsx:169
msgid "Team URL"
msgstr "Team URL"
@ -3476,7 +3657,7 @@ msgstr "The document has been successfully moved to the selected team."
msgid "The document is now completed, please follow any instructions provided within the parent application."
msgstr "The document is now completed, please follow any instructions provided within the parent application."
#: apps/web/src/app/(dashboard)/templates/use-template-dialog.tsx:171
#: apps/web/src/app/(dashboard)/templates/use-template-dialog.tsx:182
msgid "The document was created but could not be sent to recipients."
msgstr "The document was created but could not be sent to recipients."
@ -3484,7 +3665,7 @@ msgstr "The document was created but could not be sent to recipients."
msgid "The document will be hidden from your account"
msgstr "The document will be hidden from your account"
#: apps/web/src/app/(dashboard)/templates/use-template-dialog.tsx:322
#: apps/web/src/app/(dashboard)/templates/use-template-dialog.tsx:334
msgid "The document will be immediately sent to recipients if this is checked."
msgstr "The document will be immediately sent to recipients if this is checked."
@ -3494,6 +3675,10 @@ msgstr "The document will be immediately sent to recipients if this is checked."
msgid "The events that will trigger a webhook to be sent to your URL."
msgstr "The events that will trigger a webhook to be sent to your URL."
#: apps/web/src/app/(teams)/t/[teamUrl]/settings/documents/page.tsx:27
#~ msgid "The global settings for the documents in your team account."
#~ msgstr "The global settings for the documents in your team account."
#: apps/web/src/app/(unauthenticated)/team/verify/transfer/[token]/page.tsx:114
msgid "The ownership of team <0>{0}</0> has been successfully transferred to you."
msgstr "The ownership of team <0>{0}</0> has been successfully transferred to you."
@ -3673,7 +3858,7 @@ msgstr "This price includes minimum 5 seats."
msgid "This session has expired. Please try again."
msgstr "This session has expired. Please try again."
#: apps/web/src/app/(teams)/t/[teamUrl]/settings/page.tsx:195
#: apps/web/src/app/(teams)/t/[teamUrl]/settings/page.tsx:201
msgid "This team, and any associated data excluding billing invoices will be permanently deleted."
msgstr "This team, and any associated data excluding billing invoices will be permanently deleted."
@ -3690,7 +3875,7 @@ msgid "This token is invalid or has expired. Please contact your team for a new
msgstr "This token is invalid or has expired. Please contact your team for a new invitation."
#: apps/web/src/components/(teams)/dialogs/create-team-dialog.tsx:98
#: apps/web/src/components/(teams)/forms/update-team-form.tsx:87
#: apps/web/src/components/(teams)/forms/update-team-form.tsx:127
msgid "This URL is already in use."
msgstr "This URL is already in use."
@ -3823,13 +4008,13 @@ msgstr "transfer {teamName}"
msgid "Transfer ownership of this team to a selected team member."
msgstr "Transfer ownership of this team to a selected team member."
#: apps/web/src/app/(teams)/t/[teamUrl]/settings/page.tsx:169
#: apps/web/src/app/(teams)/t/[teamUrl]/settings/page.tsx:175
#: apps/web/src/components/(teams)/dialogs/transfer-team-dialog.tsx:147
#: apps/web/src/components/(teams)/dialogs/transfer-team-dialog.tsx:156
msgid "Transfer team"
msgstr "Transfer team"
#: apps/web/src/app/(teams)/t/[teamUrl]/settings/page.tsx:173
#: apps/web/src/app/(teams)/t/[teamUrl]/settings/page.tsx:179
msgid "Transfer the ownership of the team to another team member."
msgstr "Transfer the ownership of the team to another team member."
@ -4020,7 +4205,7 @@ msgstr "Update Recipient"
msgid "Update role"
msgstr "Update role"
#: apps/web/src/components/(teams)/forms/update-team-form.tsx:176
#: apps/web/src/components/(teams)/forms/update-team-form.tsx:278
msgid "Update team"
msgstr "Update team"
@ -4059,6 +4244,10 @@ msgstr "Updating Your Information"
msgid "Upload Avatar"
msgstr "Upload Avatar"
#: apps/web/src/app/(teams)/t/[teamUrl]/settings/preferences/branding-preferences.tsx:256
msgid "Upload your brand logo (max 5MB, JPG, PNG, or WebP)"
msgstr "Upload your brand logo (max 5MB, JPG, PNG, or WebP)"
#: apps/web/src/app/(dashboard)/documents/[id]/document-page-view-information.tsx:31
#: apps/web/src/app/(dashboard)/templates/[id]/template-page-view-information.tsx:30
msgid "Uploaded by"
@ -4090,7 +4279,7 @@ msgstr "Use Authenticator"
msgid "Use Backup Code"
msgstr "Use Backup Code"
#: apps/web/src/app/(dashboard)/templates/use-template-dialog.tsx:196
#: apps/web/src/app/(dashboard)/templates/use-template-dialog.tsx:207
msgid "Use Template"
msgstr "Use Template"
@ -4184,7 +4373,7 @@ msgstr "View Codes"
msgid "View Document"
msgstr "View Document"
#: apps/web/src/app/(teams)/t/[teamUrl]/settings/page.tsx:150
#: apps/web/src/app/(teams)/t/[teamUrl]/settings/page.tsx:156
msgid "View documents associated with this email"
msgstr "View documents associated with this email"
@ -4370,7 +4559,7 @@ msgstr "We encountered an unknown error while attempting to update your password
msgid "We encountered an unknown error while attempting to update your public profile. Please try again later."
msgstr "We encountered an unknown error while attempting to update your public profile. Please try again later."
#: apps/web/src/components/(teams)/forms/update-team-form.tsx:96
#: apps/web/src/components/(teams)/forms/update-team-form.tsx:136
msgid "We encountered an unknown error while attempting to update your team. Please try again later."
msgstr "We encountered an unknown error while attempting to update your team. Please try again later."
@ -4412,12 +4601,20 @@ msgstr "We were unable to set your public profile to public. Please try again."
msgid "We were unable to setup two-factor authentication for your account. Please ensure that you have entered your code correctly and try again."
msgstr "We were unable to setup two-factor authentication for your account. Please ensure that you have entered your code correctly and try again."
#: apps/web/src/app/(recipient)/d/[token]/direct-template.tsx:119
#: apps/web/src/app/(recipient)/d/[token]/direct-template.tsx:120
#: apps/web/src/app/embed/direct/[[...url]]/client.tsx:245
#: apps/web/src/app/embed/sign/[[...url]]/client.tsx:127
msgid "We were unable to submit this document at this time. Please try again later."
msgstr "We were unable to submit this document at this time. Please try again later."
#: apps/web/src/app/(teams)/t/[teamUrl]/settings/preferences/branding-preferences.tsx:109
msgid "We were unable to update your branding preferences at this time, please try again later"
msgstr "We were unable to update your branding preferences at this time, please try again later"
#: apps/web/src/app/(teams)/t/[teamUrl]/settings/preferences/document-preferences.tsx:98
msgid "We were unable to update your document preferences at this time, please try again later"
msgstr "We were unable to update your document preferences at this time, please try again later"
#: apps/web/src/app/(signing)/sign/[token]/document-action-auth-2fa.tsx:169
msgid "We were unable to verify your details. Please try again or contact support"
msgstr "We were unable to verify your details. Please try again or contact support"
@ -4426,6 +4623,14 @@ msgstr "We were unable to verify your details. Please try again or contact suppo
msgid "We were unable to verify your email. If your email is not verified already, please try again."
msgstr "We were unable to verify your email. If your email is not verified already, please try again."
#: apps/web/src/app/(dashboard)/templates/use-template-dialog.tsx:370
msgid "We will generate signing links for you, which you can send to the recipients through your method of choice."
msgstr "We will generate signing links for you, which you can send to the recipients through your method of choice."
#: apps/web/src/app/(dashboard)/templates/use-template-dialog.tsx:366
msgid "We won't send anything to notify recipients."
msgstr "We won't send anything to notify recipients."
#: apps/web/src/app/(dashboard)/documents/empty-state.tsx:29
#: apps/web/src/app/(dashboard)/templates/empty-state.tsx:11
msgid "We're all empty"
@ -4457,8 +4662,8 @@ msgstr "Webhook URL"
#: apps/web/src/app/(teams)/t/[teamUrl]/settings/webhooks/page.tsx:33
#: apps/web/src/components/(dashboard)/settings/layout/desktop-nav.tsx:103
#: apps/web/src/components/(dashboard)/settings/layout/mobile-nav.tsx:106
#: apps/web/src/components/(teams)/settings/layout/desktop-nav.tsx:94
#: apps/web/src/components/(teams)/settings/layout/mobile-nav.tsx:102
#: apps/web/src/components/(teams)/settings/layout/desktop-nav.tsx:109
#: apps/web/src/components/(teams)/settings/layout/mobile-nav.tsx:118
msgid "Webhooks"
msgstr "Webhooks"
@ -4587,7 +4792,7 @@ msgstr "You can copy and share these links to recipients so they can action the
msgid "You can update the profile URL by updating the team URL in the general settings page."
msgstr "You can update the profile URL by updating the team URL in the general settings page."
#: apps/web/src/app/(teams)/t/[teamUrl]/settings/page.tsx:65
#: apps/web/src/app/(teams)/t/[teamUrl]/settings/page.tsx:71
msgid "You can view documents associated with this email and use this identity when sending documents."
msgstr "You can view documents associated with this email and use this identity when sending documents."
@ -4649,7 +4854,7 @@ msgstr "You have reached the maximum limit of {0} direct templates. <0>Upgrade y
msgid "You have reached your document limit."
msgstr "You have reached your document limit."
#: apps/web/src/app/(dashboard)/templates/data-table-templates.tsx:182
#: apps/web/src/app/(dashboard)/templates/data-table-templates.tsx:183
msgid "You have reached your document limit. <0>Upgrade your account to continue!</0>"
msgstr "You have reached your document limit. <0>Upgrade your account to continue!</0>"
@ -4737,6 +4942,14 @@ msgstr "Your avatar has been updated successfully."
msgid "Your banner has been updated successfully."
msgstr "Your banner has been updated successfully."
#: apps/web/src/app/(teams)/t/[teamUrl]/settings/preferences/branding-preferences.tsx:280
msgid "Your brand website URL"
msgstr "Your brand website URL"
#: apps/web/src/app/(teams)/t/[teamUrl]/settings/preferences/branding-preferences.tsx:103
msgid "Your branding preferences have been updated"
msgstr "Your branding preferences have been updated"
#: apps/web/src/app/(dashboard)/settings/billing/page.tsx:119
msgid "Your current plan is past due. Please update your payment information."
msgstr "Your current plan is past due. Please update your payment information."
@ -4749,7 +4962,7 @@ msgstr "Your direct signing templates"
msgid "Your document failed to upload."
msgstr "Your document failed to upload."
#: apps/web/src/app/(dashboard)/templates/use-template-dialog.tsx:155
#: apps/web/src/app/(dashboard)/templates/use-template-dialog.tsx:157
msgid "Your document has been created from the template successfully."
msgstr "Your document has been created from the template successfully."
@ -4757,7 +4970,7 @@ msgstr "Your document has been created from the template successfully."
msgid "Your document has been re-sent successfully."
msgstr "Your document has been re-sent successfully."
#: apps/web/src/app/(dashboard)/documents/[id]/edit-document.tsx:324
#: apps/web/src/app/(dashboard)/documents/[id]/edit-document.tsx:328
msgid "Your document has been sent successfully."
msgstr "Your document has been sent successfully."
@ -4773,6 +4986,10 @@ msgstr "Your document has been uploaded successfully."
msgid "Your document has been uploaded successfully. You will be redirected to the template page."
msgstr "Your document has been uploaded successfully. You will be redirected to the template page."
#: apps/web/src/app/(teams)/t/[teamUrl]/settings/preferences/document-preferences.tsx:92
msgid "Your document preferences have been updated"
msgstr "Your document preferences have been updated"
#: apps/web/src/components/(dashboard)/common/command-menu.tsx:223
msgid "Your documents"
msgstr "Your documents"
@ -4790,6 +5007,10 @@ msgstr "Your email is currently being used by team <0>{0}</0> ({1})."
msgid "Your existing tokens"
msgstr "Your existing tokens"
#: apps/web/src/components/forms/team-document-settings.tsx:70
#~ msgid "Your global team document settings has been updated successfully."
#~ msgstr "Your global team document settings has been updated successfully."
#: apps/web/src/components/forms/password.tsx:72
#: apps/web/src/components/forms/reset-password.tsx:73
msgid "Your password has been updated successfully."
@ -4832,7 +5053,7 @@ msgstr "Your team has been created."
msgid "Your team has been successfully deleted."
msgstr "Your team has been successfully deleted."
#: apps/web/src/components/(teams)/forms/update-team-form.tsx:69
#: apps/web/src/components/(teams)/forms/update-team-form.tsx:107
msgid "Your team has been successfully updated."
msgstr "Your team has been successfully updated."

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,52 @@
import { z } from 'zod';
import type { DocumentMeta } from '@documenso/prisma/client';
import { DocumentDistributionMethod } from '@documenso/prisma/client';
export enum DocumentEmailEvents {
RecipientSigningRequest = 'recipientSigningRequest',
RecipientRemoved = 'recipientRemoved',
DocumentPending = 'documentPending',
DocumentCompleted = 'documentCompleted',
DocumentDeleted = 'documentDeleted',
}
export const ZDocumentEmailSettingsSchema = z
.object({
recipientSigningRequest: z.boolean().default(true),
recipientRemoved: z.boolean().default(true),
documentPending: z.boolean().default(true),
documentCompleted: z.boolean().default(true),
documentDeleted: z.boolean().default(true),
})
.strip()
.catch(() => ({
recipientSigningRequest: true,
recipientRemoved: true,
documentPending: true,
documentCompleted: true,
documentDeleted: true,
}));
export type TDocumentEmailSettings = z.infer<typeof ZDocumentEmailSettingsSchema>;
export const extractDerivedDocumentEmailSettings = (
documentMeta?: DocumentMeta | null,
): TDocumentEmailSettings => {
const emailSettings = ZDocumentEmailSettingsSchema.parse(documentMeta?.emailSettings ?? {});
if (
!documentMeta?.distributionMethod ||
documentMeta?.distributionMethod === DocumentDistributionMethod.EMAIL
) {
return emailSettings;
}
return {
recipientSigningRequest: false,
recipientRemoved: false,
documentPending: false,
documentCompleted: false,
documentDeleted: false,
};
};

View File

@ -1,5 +1,6 @@
import { I18nProvider } from '@lingui/react';
import type { RenderOptions } from '@documenso/email/render';
import { render } from '@documenso/email/render';
import { getI18nInstance } from '../client-only/providers/i18n.server';
@ -11,14 +12,13 @@ import {
export const renderEmailWithI18N = async (
component: React.ReactElement,
options?: {
plainText?: boolean;
options?: RenderOptions & {
// eslint-disable-next-line @typescript-eslint/ban-types
lang?: SupportedLanguageCodes | (string & {});
},
) => {
try {
const providedLang = options?.lang;
const { lang: providedLang, ...otherOptions } = options ?? {};
const lang = isValidLanguageCode(providedLang) ? providedLang : APP_I18N_OPTIONS.sourceLang;
@ -26,9 +26,7 @@ export const renderEmailWithI18N = async (
i18n.activate(lang);
return render(<I18nProvider i18n={i18n}>{component}</I18nProvider>, {
plainText: options?.plainText,
});
return render(<I18nProvider i18n={i18n}>{component}</I18nProvider>, otherOptions);
} catch (err) {
console.error(err);
throw new Error('Failed to render email');

View File

@ -0,0 +1,13 @@
import type { TeamGlobalSettings } from '@documenso/prisma/client';
import { NEXT_PUBLIC_WEBAPP_URL } from '../constants/app';
export const teamGlobalSettingsToBranding = (teamGlobalSettings: TeamGlobalSettings) => {
return {
...teamGlobalSettings,
brandingLogo:
teamGlobalSettings.brandingEnabled && teamGlobalSettings.brandingLogo
? `${NEXT_PUBLIC_WEBAPP_URL()}/api/branding/logo/team/${teamGlobalSettings.teamId}`
: '',
};
};

View File

@ -0,0 +1,12 @@
-- CreateTable
CREATE TABLE "TeamGlobalSettings" (
"teamId" INTEGER NOT NULL,
"documentVisibility" "DocumentVisibility" NOT NULL DEFAULT 'EVERYONE',
"includeSenderDetails" BOOLEAN NOT NULL DEFAULT true
);
-- CreateIndex
CREATE UNIQUE INDEX "TeamGlobalSettings_teamId_key" ON "TeamGlobalSettings"("teamId");
-- AddForeignKey
ALTER TABLE "TeamGlobalSettings" ADD CONSTRAINT "TeamGlobalSettings_teamId_fkey" FOREIGN KEY ("teamId") REFERENCES "Team"("id") ON DELETE CASCADE ON UPDATE CASCADE;

View File

@ -0,0 +1,6 @@
-- AlterTable
ALTER TABLE "TeamGlobalSettings" ADD COLUMN "brandingCompanyDetails" TEXT NOT NULL DEFAULT '',
ADD COLUMN "brandingEnabled" BOOLEAN NOT NULL DEFAULT false,
ADD COLUMN "brandingHidePoweredBy" BOOLEAN NOT NULL DEFAULT false,
ADD COLUMN "brandingLogo" TEXT NOT NULL DEFAULT '',
ADD COLUMN "brandingUrl" TEXT NOT NULL DEFAULT '';

View File

@ -0,0 +1,10 @@
-- CreateEnum
CREATE TYPE "DocumentDistributionMethod" AS ENUM ('EMAIL', 'NONE');
-- AlterTable
ALTER TABLE "DocumentMeta" ADD COLUMN "distributionMethod" "DocumentDistributionMethod" NOT NULL DEFAULT 'EMAIL',
ADD COLUMN "emailSettings" JSONB;
-- AlterTable
ALTER TABLE "TemplateMeta" ADD COLUMN "distributionMethod" "DocumentDistributionMethod" NOT NULL DEFAULT 'EMAIL',
ADD COLUMN "emailSettings" JSONB;

View File

@ -0,0 +1,2 @@
-- AlterTable
ALTER TABLE "TeamGlobalSettings" ADD COLUMN "documentLanguage" TEXT NOT NULL DEFAULT 'en';

View File

@ -358,19 +358,26 @@ model DocumentData {
Template Template?
}
enum DocumentDistributionMethod {
EMAIL
NONE
}
model DocumentMeta {
id String @id @default(cuid())
id String @id @default(cuid())
subject String?
message String?
timezone String? @default("Etc/UTC") @db.Text
timezone String? @default("Etc/UTC") @db.Text
password String?
dateFormat String? @default("yyyy-MM-dd hh:mm a") @db.Text
documentId Int @unique
document Document @relation(fields: [documentId], references: [id], onDelete: Cascade)
dateFormat String? @default("yyyy-MM-dd hh:mm a") @db.Text
documentId Int @unique
document Document @relation(fields: [documentId], references: [id], onDelete: Cascade)
redirectUrl String?
signingOrder DocumentSigningOrder @default(PARALLEL)
typedSignatureEnabled Boolean @default(false)
language String @default("en")
signingOrder DocumentSigningOrder @default(PARALLEL)
typedSignatureEnabled Boolean @default(false)
language String @default("en")
distributionMethod DocumentDistributionMethod @default(EMAIL)
emailSettings Json?
}
enum ReadStatus {
@ -501,6 +508,21 @@ enum TeamMemberInviteStatus {
DECLINED
}
model TeamGlobalSettings {
teamId Int @unique
documentVisibility DocumentVisibility @default(EVERYONE)
documentLanguage String @default("en")
includeSenderDetails Boolean @default(true)
brandingEnabled Boolean @default(false)
brandingLogo String @default("")
brandingUrl String @default("")
brandingCompanyDetails String @default("")
brandingHidePoweredBy Boolean @default(false)
team Team @relation(fields: [teamId], references: [id], onDelete: Cascade)
}
model Team {
id Int @id @default(autoincrement())
name String
@ -515,6 +537,7 @@ model Team {
teamEmail TeamEmail?
emailVerification TeamEmailVerification?
transferVerification TeamTransferVerification?
teamGlobalSettings TeamGlobalSettings?
avatarImage AvatarImage? @relation(fields: [avatarImageId], references: [id], onDelete: SetNull)
profile TeamProfile?
@ -603,17 +626,19 @@ enum TemplateType {
}
model TemplateMeta {
id String @id @default(cuid())
subject String?
message String?
timezone String? @default("Etc/UTC") @db.Text
password String?
dateFormat String? @default("yyyy-MM-dd hh:mm a") @db.Text
signingOrder DocumentSigningOrder? @default(PARALLEL)
templateId Int @unique
template Template @relation(fields: [templateId], references: [id], onDelete: Cascade)
redirectUrl String?
language String @default("en")
id String @id @default(cuid())
subject String?
message String?
timezone String? @default("Etc/UTC") @db.Text
password String?
dateFormat String? @default("yyyy-MM-dd hh:mm a") @db.Text
signingOrder DocumentSigningOrder? @default(PARALLEL)
templateId Int @unique
template Template @relation(fields: [templateId], references: [id], onDelete: Cascade)
redirectUrl String?
language String @default("en")
distributionMethod DocumentDistributionMethod @default(EMAIL)
emailSettings Json?
}
model Template {

View File

@ -413,7 +413,15 @@ export const documentRouter = router({
try {
const { documentId, teamId, meta } = input;
if (meta.message || meta.subject || meta.timezone || meta.dateFormat || meta.redirectUrl) {
if (
meta.message ||
meta.subject ||
meta.timezone ||
meta.dateFormat ||
meta.redirectUrl ||
meta.distributionMethod ||
meta.emailSettings
) {
await upsertDocumentMeta({
documentId,
subject: meta.subject,
@ -421,7 +429,9 @@ export const documentRouter = router({
dateFormat: meta.dateFormat,
timezone: meta.timezone,
redirectUrl: meta.redirectUrl,
distributionMethod: meta.distributionMethod,
userId: ctx.user.id,
emailSettings: meta.emailSettings,
requestMetadata: extractNextApiRequestMetadata(ctx.req),
});
}

View File

@ -5,12 +5,15 @@ import {
ZDocumentAccessAuthTypesSchema,
ZDocumentActionAuthTypesSchema,
} from '@documenso/lib/types/document-auth';
import { ZDocumentEmailSettingsSchema } from '@documenso/lib/types/document-email';
import { ZBaseTableSearchParamsSchema } from '@documenso/lib/types/search-params';
import { isValidRedirectUrl } from '@documenso/lib/utils/is-valid-redirect-url';
import {
DocumentDistributionMethod,
DocumentSigningOrder,
DocumentSource,
DocumentStatus,
DocumentVisibility,
FieldType,
RecipientRole,
} from '@documenso/prisma/client';
@ -80,7 +83,7 @@ export const ZSetSettingsForDocumentMutationSchema = z.object({
data: z.object({
title: z.string().min(1).optional(),
externalId: z.string().nullish(),
visibility: z.string().optional(),
visibility: z.nativeEnum(DocumentVisibility).optional(),
globalAccessAuth: ZDocumentAccessAuthTypesSchema.nullable().optional(),
globalActionAuth: ZDocumentActionAuthTypesSchema.nullable().optional(),
}),
@ -155,6 +158,7 @@ export const ZSendDocumentMutationSchema = z.object({
message: z.string(),
timezone: z.string().optional(),
dateFormat: z.string().optional(),
distributionMethod: z.nativeEnum(DocumentDistributionMethod).optional(),
redirectUrl: z
.string()
.optional()
@ -162,6 +166,7 @@ export const ZSendDocumentMutationSchema = z.object({
message:
'Please enter a valid URL, make sure you include http:// or https:// part of the url.',
}),
emailSettings: ZDocumentEmailSettingsSchema.optional(),
}),
});

View File

@ -31,6 +31,8 @@ import { requestTeamOwnershipTransfer } from '@documenso/lib/server-only/team/re
import { resendTeamEmailVerification } from '@documenso/lib/server-only/team/resend-team-email-verification';
import { resendTeamMemberInvitation } from '@documenso/lib/server-only/team/resend-team-member-invitation';
import { updateTeam } from '@documenso/lib/server-only/team/update-team';
import { updateTeamBrandingSettings } from '@documenso/lib/server-only/team/update-team-branding-settings';
import { updateTeamDocumentSettings } from '@documenso/lib/server-only/team/update-team-document-settings';
import { updateTeamEmail } from '@documenso/lib/server-only/team/update-team-email';
import { updateTeamMember } from '@documenso/lib/server-only/team/update-team-member';
import { updateTeamPublicProfile } from '@documenso/lib/server-only/team/update-team-public-profile';
@ -62,6 +64,8 @@ import {
ZRequestTeamOwnerhsipTransferMutationSchema,
ZResendTeamEmailVerificationMutationSchema,
ZResendTeamMemberInvitationMutationSchema,
ZUpdateTeamBrandingSettingsMutationSchema,
ZUpdateTeamDocumentSettingsMutationSchema,
ZUpdateTeamEmailMutationSchema,
ZUpdateTeamMemberMutationSchema,
ZUpdateTeamMutationSchema,
@ -556,6 +560,42 @@ export const teamRouter = router({
} catch (err) {
console.error(err);
throw AppError.parseErrorToTRPCError(err);
}
}),
updateTeamBrandingSettings: authenticatedProcedure
.input(ZUpdateTeamBrandingSettingsMutationSchema)
.mutation(async ({ ctx, input }) => {
const { teamId, settings } = input;
try {
return await updateTeamBrandingSettings({
userId: ctx.user.id,
teamId,
settings,
});
} catch (err) {
console.error(err);
throw AppError.parseErrorToTRPCError(err);
}
}),
updateTeamDocumentSettings: authenticatedProcedure
.input(ZUpdateTeamDocumentSettingsMutationSchema)
.mutation(async ({ ctx, input }) => {
const { teamId, settings } = input;
try {
return await updateTeamDocumentSettings({
userId: ctx.user.id,
teamId,
settings,
});
} catch (err) {
console.error(err);
throw AppError.parseErrorToTRPCError(err);
}
}),

View File

@ -1,7 +1,8 @@
import { z } from 'zod';
import { SUPPORTED_LANGUAGE_CODES } from '@documenso/lib/constants/i18n';
import { PROTECTED_TEAM_URLS } from '@documenso/lib/constants/teams';
import { TeamMemberRole } from '@documenso/prisma/client';
import { DocumentVisibility, TeamMemberRole } from '@documenso/prisma/client';
import { ZUpdatePublicProfileMutationSchema } from '../profile-router/schema';
@ -150,6 +151,8 @@ export const ZUpdateTeamMutationSchema = z.object({
data: z.object({
name: ZTeamNameSchema,
url: ZTeamUrlSchema,
documentVisibility: z.nativeEnum(DocumentVisibility).optional(),
includeSenderDetails: z.boolean().optional(),
}),
});
@ -190,6 +193,28 @@ export const ZResendTeamMemberInvitationMutationSchema = z.object({
invitationId: z.number(),
});
export const ZUpdateTeamBrandingSettingsMutationSchema = z.object({
teamId: z.number(),
settings: z.object({
brandingEnabled: z.boolean().optional().default(false),
brandingLogo: z.string().optional().default(''),
brandingUrl: z.string().optional().default(''),
brandingCompanyDetails: z.string().optional().default(''),
}),
});
export const ZUpdateTeamDocumentSettingsMutationSchema = z.object({
teamId: z.number(),
settings: z.object({
documentVisibility: z
.nativeEnum(DocumentVisibility)
.optional()
.default(DocumentVisibility.EVERYONE),
documentLanguage: z.enum(SUPPORTED_LANGUAGE_CODES).optional().default('en'),
includeSenderDetails: z.boolean().optional().default(false),
}),
});
export type TCreateTeamMutationSchema = z.infer<typeof ZCreateTeamMutationSchema>;
export type TCreateTeamEmailVerificationMutationSchema = z.infer<
typeof ZCreateTeamEmailVerificationMutationSchema
@ -225,3 +250,9 @@ export type TResendTeamEmailVerificationMutationSchema = z.infer<
export type TResendTeamMemberInvitationMutationSchema = z.infer<
typeof ZResendTeamMemberInvitationMutationSchema
>;
export type TUpdateTeamBrandingSettingsMutationSchema = z.infer<
typeof ZUpdateTeamBrandingSettingsMutationSchema
>;
export type TUpdateTeamDocumentSettingsMutationSchema = z.infer<
typeof ZUpdateTeamDocumentSettingsMutationSchema
>;

View File

@ -120,7 +120,7 @@ export const templateRouter = router({
requestMetadata,
});
if (input.sendDocument) {
if (input.distributeDocument) {
document = await sendDocument({
documentId: document.id,
userId: ctx.user.id,

View File

@ -5,9 +5,14 @@ import {
ZDocumentAccessAuthTypesSchema,
ZDocumentActionAuthTypesSchema,
} from '@documenso/lib/types/document-auth';
import { ZDocumentEmailSettingsSchema } from '@documenso/lib/types/document-email';
import { ZBaseTableSearchParamsSchema } from '@documenso/lib/types/search-params';
import { isValidRedirectUrl } from '@documenso/lib/utils/is-valid-redirect-url';
import { DocumentSigningOrder, TemplateType } from '@documenso/prisma/client';
import {
DocumentDistributionMethod,
DocumentSigningOrder,
TemplateType,
} from '@documenso/prisma/client';
import { ZSignFieldWithTokenMutationSchema } from '../field-router/schema';
@ -41,7 +46,7 @@ export const ZCreateDocumentFromTemplateMutationSchema = z.object({
const emails = recipients.map((signer) => signer.email);
return new Set(emails).size === emails.length;
}, 'Recipients must have unique emails'),
sendDocument: z.boolean().optional(),
distributeDocument: z.boolean().optional(),
});
export const ZDuplicateTemplateMutationSchema = z.object({
@ -99,6 +104,8 @@ export const ZUpdateTemplateSettingsMutationSchema = z.object({
message: z.string(),
timezone: z.string(),
dateFormat: z.string(),
distributionMethod: z.nativeEnum(DocumentDistributionMethod),
emailSettings: ZDocumentEmailSettingsSchema,
redirectUrl: z
.string()
.optional()

View File

@ -0,0 +1,222 @@
import { Trans } from '@lingui/macro';
import { InfoIcon } from 'lucide-react';
import { DocumentEmailEvents } from '@documenso/lib/types/document-email';
import { Tooltip, TooltipContent, TooltipTrigger } from '@documenso/ui/primitives/tooltip';
import { cn } from '../../lib/utils';
import { Checkbox } from '../../primitives/checkbox';
type Value = Record<DocumentEmailEvents, boolean>;
type DocumentEmailCheckboxesProps = {
value: Value;
onChange: (value: Value) => void;
className?: string;
};
export const DocumentEmailCheckboxes = ({
value,
onChange,
className,
}: DocumentEmailCheckboxesProps) => {
return (
<div className={cn('space-y-3', className)}>
<div className="flex flex-row items-center">
<Checkbox
id={DocumentEmailEvents.RecipientSigningRequest}
className="h-5 w-5"
checkClassName="dark:text-white text-primary"
checked={value.recipientSigningRequest}
onCheckedChange={(checked) =>
onChange({ ...value, [DocumentEmailEvents.RecipientSigningRequest]: Boolean(checked) })
}
/>
<label
className="text-muted-foreground ml-2 flex flex-row items-center text-sm"
htmlFor={DocumentEmailEvents.RecipientSigningRequest}
>
<Trans>Send recipient signing request email</Trans>
<Tooltip>
<TooltipTrigger>
<InfoIcon className="mx-2 h-4 w-4" />
</TooltipTrigger>
<TooltipContent className="text-foreground max-w-md space-y-2 p-4">
<h2>
<strong>
<Trans>Recipient signing request email</Trans>
</strong>
</h2>
<p>
<Trans>
This email is sent to the recipient requesting them to sign the document.
</Trans>
</p>
</TooltipContent>
</Tooltip>
</label>
</div>
<div className="flex flex-row items-center">
<Checkbox
id={DocumentEmailEvents.RecipientRemoved}
className="h-5 w-5"
checkClassName="dark:text-white text-primary"
checked={value.recipientRemoved}
onCheckedChange={(checked) =>
onChange({ ...value, [DocumentEmailEvents.RecipientRemoved]: Boolean(checked) })
}
/>
<label
className="text-muted-foreground ml-2 flex flex-row items-center text-sm"
htmlFor={DocumentEmailEvents.RecipientRemoved}
>
<Trans>Send recipient removed email</Trans>
<Tooltip>
<TooltipTrigger>
<InfoIcon className="mx-2 h-4 w-4" />
</TooltipTrigger>
<TooltipContent className="text-foreground max-w-md space-y-2 p-4">
<h2>
<strong>
<Trans>Recipient removed email</Trans>
</strong>
</h2>
<p>
<Trans>
This email is sent to the recipient if they are removed from a pending document.
</Trans>
</p>
</TooltipContent>
</Tooltip>
</label>
</div>
<div className="flex flex-row items-center">
<Checkbox
id={DocumentEmailEvents.DocumentPending}
className="h-5 w-5"
checkClassName="dark:text-white text-primary"
checked={value.documentPending}
onCheckedChange={(checked) =>
onChange({ ...value, [DocumentEmailEvents.DocumentPending]: Boolean(checked) })
}
/>
<label
className="text-muted-foreground ml-2 flex flex-row items-center text-sm"
htmlFor={DocumentEmailEvents.DocumentPending}
>
<Trans>Send document pending email</Trans>
<Tooltip>
<TooltipTrigger>
<InfoIcon className="mx-2 h-4 w-4" />
</TooltipTrigger>
<TooltipContent className="text-foreground max-w-md space-y-2 p-4">
<h2>
<strong>
<Trans>Document pending email</Trans>
</strong>
</h2>
<p>
<Trans>
This email will be sent to the recipient who has just signed the document, if
there are still other recipients who have not signed yet.
</Trans>
</p>
</TooltipContent>
</Tooltip>
</label>
</div>
<div className="flex flex-row items-center">
<Checkbox
id={DocumentEmailEvents.DocumentCompleted}
className="h-5 w-5"
checkClassName="dark:text-white text-primary"
checked={value.documentCompleted}
onCheckedChange={(checked) =>
onChange({ ...value, [DocumentEmailEvents.DocumentCompleted]: Boolean(checked) })
}
/>
<label
className="text-muted-foreground ml-2 flex flex-row items-center text-sm"
htmlFor={DocumentEmailEvents.DocumentCompleted}
>
<Trans>Send document completed email</Trans>
<Tooltip>
<TooltipTrigger>
<InfoIcon className="mx-2 h-4 w-4" />
</TooltipTrigger>
<TooltipContent className="text-foreground max-w-md space-y-2 p-4">
<h2>
<strong>
<Trans>Document completed email</Trans>
</strong>
</h2>
<p>
<Trans>
This will be sent to all recipients once the document has been fully completed.
</Trans>
</p>
</TooltipContent>
</Tooltip>
</label>
</div>
<div className="flex flex-row items-center">
<Checkbox
id={DocumentEmailEvents.DocumentDeleted}
className="h-5 w-5"
checkClassName="dark:text-white text-primary"
checked={value.documentDeleted}
onCheckedChange={(checked) =>
onChange({ ...value, [DocumentEmailEvents.DocumentDeleted]: Boolean(checked) })
}
/>
<label
className="text-muted-foreground ml-2 flex flex-row items-center text-sm"
htmlFor={DocumentEmailEvents.DocumentDeleted}
>
<Trans>Send document deleted email</Trans>
<Tooltip>
<TooltipTrigger>
<InfoIcon className="mx-2 h-4 w-4" />
</TooltipTrigger>
<TooltipContent className="text-foreground max-w-md space-y-2 p-4">
<h2>
<strong>
<Trans>Document deleted email</Trans>
</strong>
</h2>
<p>
<Trans>
This will be sent to all recipients if a pending document has been deleted.
</Trans>
</p>
</TooltipContent>
</Tooltip>
</label>
</div>
</div>
);
};

View File

@ -16,14 +16,17 @@ import { Tooltip, TooltipContent, TooltipTrigger } from '@documenso/ui/primitive
export type DocumentVisibilitySelectType = SelectProps & {
currentMemberRole?: string;
isTeamSettings?: boolean;
disabled?: boolean;
};
export const DocumentVisibilitySelect = forwardRef<HTMLButtonElement, DocumentVisibilitySelectType>(
({ currentMemberRole, ...props }, ref) => {
const canUpdateVisibility = currentMemberRole === 'ADMIN' || currentMemberRole === 'MANAGER';
({ currentMemberRole, isTeamSettings = false, disabled, ...props }, ref) => {
const canUpdateVisibility =
currentMemberRole === 'ADMIN' || currentMemberRole === 'MANAGER' || isTeamSettings;
return (
<Select {...props} disabled={!canUpdateVisibility}>
<Select {...props} disabled={(!canUpdateVisibility && !isTeamSettings) || disabled}>
<SelectTrigger ref={ref} className="bg-background text-muted-foreground">
<SelectValue data-testid="documentVisibilitySelectValue" placeholder="Everyone" />
</SelectTrigger>
@ -32,18 +35,15 @@ export const DocumentVisibilitySelect = forwardRef<HTMLButtonElement, DocumentVi
<SelectItem value={DocumentVisibility.EVERYONE}>
{DOCUMENT_VISIBILITY.EVERYONE.value}
</SelectItem>
{(currentMemberRole === 'ADMIN' || currentMemberRole === 'MANAGER') && (
<SelectItem value={DocumentVisibility.MANAGER_AND_ABOVE}>
{DOCUMENT_VISIBILITY.MANAGER_AND_ABOVE.value}
</SelectItem>
)}
{currentMemberRole === 'ADMIN' && (
<SelectItem value={DocumentVisibility.ADMIN}>
{DOCUMENT_VISIBILITY.ADMIN.value}
</SelectItem>
)}
<SelectItem value={DocumentVisibility.MANAGER_AND_ABOVE} disabled={!canUpdateVisibility}>
{DOCUMENT_VISIBILITY.MANAGER_AND_ABOVE.value}
</SelectItem>
<SelectItem
value={DocumentVisibility.ADMIN}
disabled={currentMemberRole !== 'ADMIN' && !isTeamSettings}
>
{DOCUMENT_VISIBILITY.ADMIN.value}
</SelectItem>
</SelectContent>
</Select>
);

View File

@ -28,7 +28,7 @@ import { prop, sortBy } from 'remeda';
import { getBoundingClientRect } from '@documenso/lib/client-only/get-bounding-client-rect';
import { useDocumentElement } from '@documenso/lib/client-only/hooks/use-document-element';
import { PDF_VIEWER_PAGE_SELECTOR } from '@documenso/lib/constants/pdf-viewer';
import { RECIPIENT_ROLES_DESCRIPTION_ENG } from '@documenso/lib/constants/recipient-roles';
import { RECIPIENT_ROLES_DESCRIPTION } from '@documenso/lib/constants/recipient-roles';
import {
type TFieldMetaSchema as FieldMeta,
ZFieldMetaSchema,
@ -690,7 +690,7 @@ export const AddFieldsFormPartial = ({
{recipientsByRoleToDisplay.map(([role, roleRecipients], roleIndex) => (
<CommandGroup key={roleIndex}>
<div className="text-muted-foreground mb-1 ml-2 mt-2 text-xs font-medium">
{_(RECIPIENT_ROLES_DESCRIPTION_ENG[role].roleNamePlural)}
{_(RECIPIENT_ROLES_DESCRIPTION[role].roleNamePlural)}
</div>
{roleRecipients.length === 0 && (

View File

@ -8,6 +8,7 @@ import {
ZDocumentActionAuthTypesSchema,
} from '@documenso/lib/types/document-auth';
import { isValidRedirectUrl } from '@documenso/lib/utils/is-valid-redirect-url';
import { DocumentVisibility } from '@documenso/prisma/client';
export const ZMapNegativeOneToUndefinedSchema = z
.string()
@ -23,7 +24,7 @@ export const ZMapNegativeOneToUndefinedSchema = z
export const ZAddSettingsFormSchema = z.object({
title: z.string().trim().min(1, { message: "Title can't be empty" }),
externalId: z.string().optional(),
visibility: z.string().optional(),
visibility: z.nativeEnum(DocumentVisibility).optional(),
globalAccessAuth: ZMapNegativeOneToUndefinedSchema.pipe(
ZDocumentAccessAuthTypesSchema.optional(),
),

View File

@ -2,18 +2,32 @@
import { zodResolver } from '@hookform/resolvers/zod';
import { Trans, msg } from '@lingui/macro';
import { useLingui } from '@lingui/react';
import { AnimatePresence, motion } from 'framer-motion';
import { useForm } from 'react-hook-form';
import { RECIPIENT_ROLES_DESCRIPTION } from '@documenso/lib/constants/recipient-roles';
import { ZDocumentEmailSettingsSchema } from '@documenso/lib/types/document-email';
import { formatSigningLink } from '@documenso/lib/utils/recipients';
import type { Field, Recipient } from '@documenso/prisma/client';
import { DocumentStatus } from '@documenso/prisma/client';
import {
DocumentDistributionMethod,
DocumentStatus,
RecipientRole,
} from '@documenso/prisma/client';
import type { DocumentWithData } from '@documenso/prisma/types/document-with-data';
import { DocumentSendEmailMessageHelper } from '@documenso/ui/components/document/document-send-email-message-helper';
import { Tabs, TabsList, TabsTrigger } from '@documenso/ui/primitives/tabs';
import { CopyTextButton } from '../../components/common/copy-text-button';
import { DocumentEmailCheckboxes } from '../../components/document/document-email-checkboxes';
import { AvatarWithText } from '../avatar';
import { FormErrorMessage } from '../form/form-error-message';
import { Input } from '../input';
import { Label } from '../label';
import { useStep } from '../stepper';
import { Textarea } from '../textarea';
import { toast } from '../use-toast';
import { type TAddSubjectFormSchema, ZAddSubjectFormSchema } from './add-subject.types';
import {
DocumentFlowFormContainerActions,
@ -42,20 +56,45 @@ export const AddSubjectFormPartial = ({
onSubmit,
isDocumentPdfLoaded,
}: AddSubjectFormProps) => {
const { _ } = useLingui();
const {
register,
handleSubmit,
setValue,
watch,
formState: { errors, isSubmitting },
} = useForm<TAddSubjectFormSchema>({
defaultValues: {
meta: {
subject: document.documentMeta?.subject ?? '',
message: document.documentMeta?.message ?? '',
distributionMethod:
document.documentMeta?.distributionMethod || DocumentDistributionMethod.EMAIL,
emailSettings: ZDocumentEmailSettingsSchema.parse(document?.documentMeta?.emailSettings),
},
},
resolver: zodResolver(ZAddSubjectFormSchema),
});
const GoNextLabel = {
[DocumentDistributionMethod.EMAIL]: {
[DocumentStatus.DRAFT]: msg`Send`,
[DocumentStatus.PENDING]: recipients.some((recipient) => recipient.sendStatus === 'SENT')
? msg`Resend`
: msg`Send`,
[DocumentStatus.COMPLETED]: msg`Update`,
},
[DocumentDistributionMethod.NONE]: {
[DocumentStatus.DRAFT]: msg`Generate Links`,
[DocumentStatus.PENDING]: msg`View Document`,
[DocumentStatus.COMPLETED]: msg`View Document`,
},
};
const distributionMethod = watch('meta.distributionMethod');
const emailSettings = watch('meta.emailSettings');
const onFormSubmit = handleSubmit(onSubmit);
const { currentStep, totalSteps, previousStep } = useStep();
@ -72,46 +111,158 @@ export const AddSubjectFormPartial = ({
<ShowFieldItem key={index} field={field} recipients={recipients} />
))}
<div className="flex flex-col gap-y-4">
<div>
<Label htmlFor="subject">
<Trans>
Subject <span className="text-muted-foreground">(Optional)</span>
</Trans>
</Label>
<Tabs
onValueChange={(value) =>
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
setValue('meta.distributionMethod', value as DocumentDistributionMethod)
}
value={distributionMethod}
className="mb-2"
>
<TabsList className="w-full">
<TabsTrigger className="w-full" value={DocumentDistributionMethod.EMAIL}>
Email
</TabsTrigger>
<TabsTrigger className="w-full" value={DocumentDistributionMethod.NONE}>
None
</TabsTrigger>
</TabsList>
</Tabs>
<Input
id="subject"
className="bg-background mt-2"
disabled={isSubmitting}
{...register('meta.subject')}
/>
<AnimatePresence mode="wait">
{distributionMethod === DocumentDistributionMethod.EMAIL && (
<motion.div
key={'Emails'}
initial={{ opacity: 0, y: 15 }}
animate={{ opacity: 1, y: 0, transition: { duration: 0.3 } }}
exit={{ opacity: 0, transition: { duration: 0.15 } }}
className="flex flex-col gap-y-4 rounded-lg border p-4"
>
<div>
<Label htmlFor="subject">
<Trans>
Subject <span className="text-muted-foreground">(Optional)</span>
</Trans>
</Label>
<FormErrorMessage className="mt-2" error={errors.meta?.subject} />
</div>
<Input
id="subject"
className="bg-background mt-2"
disabled={isSubmitting}
{...register('meta.subject')}
/>
<div>
<Label htmlFor="message">
<Trans>
Message <span className="text-muted-foreground">(Optional)</span>
</Trans>
</Label>
<FormErrorMessage className="mt-2" error={errors.meta?.subject} />
</div>
<Textarea
id="message"
className="bg-background mt-2 h-32 resize-none"
disabled={isSubmitting}
{...register('meta.message')}
/>
<div>
<Label htmlFor="message">
<Trans>
Message <span className="text-muted-foreground">(Optional)</span>
</Trans>
</Label>
<FormErrorMessage
className="mt-2"
error={typeof errors.meta?.message !== 'string' ? errors.meta?.message : undefined}
/>
</div>
<Textarea
id="message"
className="bg-background mt-2 h-32 resize-none"
disabled={isSubmitting}
{...register('meta.message')}
/>
<DocumentSendEmailMessageHelper />
</div>
<FormErrorMessage
className="mt-2"
error={
typeof errors.meta?.message !== 'string' ? errors.meta?.message : undefined
}
/>
</div>
<DocumentSendEmailMessageHelper />
<DocumentEmailCheckboxes
className="mt-2"
value={emailSettings}
onChange={(value) => setValue('meta.emailSettings', value)}
/>
</motion.div>
)}
{distributionMethod === DocumentDistributionMethod.NONE && (
<motion.div
key={'Links'}
initial={{ opacity: 0, y: 15 }}
animate={{ opacity: 1, y: 0, transition: { duration: 0.3 } }}
exit={{ opacity: 0, transition: { duration: 0.15 } }}
className="rounded-lg border"
>
{document.status === DocumentStatus.DRAFT ? (
<div className="text-muted-foreground py-16 text-center text-sm">
<p>
<Trans>We won't send anything to notify recipients.</Trans>
</p>
<p className="mt-2">
<Trans>
We will generate signing links for with you, which you can send to the
recipients through your method of choice.
</Trans>
</p>
</div>
) : (
<ul className="text-muted-foreground divide-y">
{recipients.length === 0 && (
<li className="flex flex-col items-center justify-center py-6 text-sm">
<Trans>No recipients</Trans>
</li>
)}
{recipients.map((recipient) => (
<li
key={recipient.id}
className="flex items-center justify-between px-4 py-3 text-sm"
>
<AvatarWithText
avatarFallback={recipient.email.slice(0, 1).toUpperCase()}
primaryText={
<p className="text-muted-foreground text-sm">{recipient.email}</p>
}
secondaryText={
<p className="text-muted-foreground/70 text-xs">
{_(RECIPIENT_ROLES_DESCRIPTION[recipient.role].roleName)}
</p>
}
/>
{recipient.role !== RecipientRole.CC && (
<CopyTextButton
value={formatSigningLink(recipient.token)}
onCopySuccess={() => {
toast({
title: _(msg`Copied to clipboard`),
description: _(
msg`The signing link has been copied to your clipboard.`,
),
});
}}
badgeContentUncopied={
<p className="ml-1 text-xs">
<Trans>Copy</Trans>
</p>
}
badgeContentCopied={
<p className="ml-1 text-xs">
<Trans>Copied</Trans>
</p>
}
/>
)}
</li>
))}
</ul>
)}
</motion.div>
)}
</AnimatePresence>
</div>
</DocumentFlowFormContainerContent>
@ -121,7 +272,7 @@ export const AddSubjectFormPartial = ({
<DocumentFlowFormContainerActions
loading={isSubmitting}
disabled={isSubmitting}
goNextLabel={document.status === DocumentStatus.DRAFT ? msg`Send` : msg`Update`}
goNextLabel={GoNextLabel[distributionMethod][document.status]}
onGoBackClick={previousStep}
onGoNextClick={() => void onFormSubmit()}
/>

View File

@ -1,9 +1,18 @@
import { z } from 'zod';
import { ZDocumentEmailSettingsSchema } from '@documenso/lib/types/document-email';
import { DocumentDistributionMethod } from '.prisma/client';
export const ZAddSubjectFormSchema = z.object({
meta: z.object({
subject: z.string(),
message: z.string(),
distributionMethod: z
.nativeEnum(DocumentDistributionMethod)
.optional()
.default(DocumentDistributionMethod.EMAIL),
emailSettings: ZDocumentEmailSettingsSchema,
}),
});

View File

@ -23,7 +23,7 @@ import { useFieldArray, useForm } from 'react-hook-form';
import { getBoundingClientRect } from '@documenso/lib/client-only/get-bounding-client-rect';
import { useDocumentElement } from '@documenso/lib/client-only/hooks/use-document-element';
import { PDF_VIEWER_PAGE_SELECTOR } from '@documenso/lib/constants/pdf-viewer';
import { RECIPIENT_ROLES_DESCRIPTION_ENG } from '@documenso/lib/constants/recipient-roles';
import { RECIPIENT_ROLES_DESCRIPTION } from '@documenso/lib/constants/recipient-roles';
import {
type TFieldMetaSchema as FieldMeta,
ZFieldMetaSchema,
@ -508,7 +508,7 @@ export const AddTemplateFieldsFormPartial = ({
{recipientsByRoleToDisplay.map(([role, roleRecipients], roleIndex) => (
<CommandGroup key={roleIndex}>
<div className="text-muted-foreground mb-1 ml-2 mt-2 text-xs font-medium">
{_(RECIPIENT_ROLES_DESCRIPTION_ENG[role].roleNamePlural)}
{_(RECIPIENT_ROLES_DESCRIPTION[role].roleNamePlural)}
</div>
{roleRecipients.length === 0 && (

View File

@ -4,14 +4,17 @@ import { useEffect } from 'react';
import { zodResolver } from '@hookform/resolvers/zod';
import { Trans } from '@lingui/macro';
import { useLingui } from '@lingui/react';
import { InfoIcon } from 'lucide-react';
import { useForm } from 'react-hook-form';
import { DATE_FORMATS, DEFAULT_DOCUMENT_DATE_FORMAT } from '@documenso/lib/constants/date-formats';
import { DOCUMENT_DISTRIBUTION_METHODS } from '@documenso/lib/constants/document';
import { SUPPORTED_LANGUAGES } from '@documenso/lib/constants/i18n';
import { DEFAULT_DOCUMENT_TIME_ZONE, TIME_ZONES } from '@documenso/lib/constants/time-zones';
import { ZDocumentEmailSettingsSchema } from '@documenso/lib/types/document-email';
import { extractDocumentAuthMethods } from '@documenso/lib/utils/document-auth';
import { type Field, type Recipient } from '@documenso/prisma/client';
import { DocumentDistributionMethod, type Field, type Recipient } from '@documenso/prisma/client';
import type { TemplateWithData } from '@documenso/prisma/types/template';
import {
DocumentGlobalAuthAccessSelect,
@ -37,6 +40,7 @@ import {
FormMessage,
} from '@documenso/ui/primitives/form/form';
import { DocumentEmailCheckboxes } from '../../components/document/document-email-checkboxes';
import { Combobox } from '../combobox';
import {
DocumentFlowFormContainerActions,
@ -74,6 +78,8 @@ export const AddTemplateSettingsFormPartial = ({
template,
onSubmit,
}: AddTemplateSettingsFormProps) => {
const { _ } = useLingui();
const { documentAuthOption } = extractDocumentAuthMethods({
documentAuth: template.authOptions,
});
@ -90,14 +96,20 @@ export const AddTemplateSettingsFormPartial = ({
message: template.templateMeta?.message ?? '',
timezone: template.templateMeta?.timezone ?? DEFAULT_DOCUMENT_TIME_ZONE,
dateFormat: template.templateMeta?.dateFormat ?? DEFAULT_DOCUMENT_DATE_FORMAT,
distributionMethod:
template.templateMeta?.distributionMethod || DocumentDistributionMethod.EMAIL,
redirectUrl: template.templateMeta?.redirectUrl ?? '',
language: template.templateMeta?.language ?? 'en',
emailSettings: ZDocumentEmailSettingsSchema.parse(template?.templateMeta?.emailSettings),
},
},
});
const { stepIndex, currentStep, totalSteps, previousStep } = useStep();
const distributionMethod = form.watch('meta.distributionMethod');
const emailSettings = form.watch('meta.emailSettings');
// We almost always want to set the timezone to the user's local timezone to avoid confusion
// when the document is signed.
useEffect(() => {
@ -198,6 +210,77 @@ export const AddTemplateSettingsFormPartial = ({
)}
/>
<FormField
control={form.control}
name="meta.distributionMethod"
render={({ field }) => (
<FormItem>
<FormLabel className="flex flex-row items-center">
<Trans>Document Distribution Method</Trans>
<Tooltip>
<TooltipTrigger>
<InfoIcon className="mx-2 h-4 w-4" />
</TooltipTrigger>
<TooltipContent className="text-foreground max-w-md space-y-2 p-4">
<h2>
<strong>
<Trans>Document Distribution Method</Trans>
</strong>
</h2>
<p>
<Trans>
This is how the document will reach the recipients once the document is
ready for signing.
</Trans>
</p>
<ul className="ml-3.5 list-outside list-disc space-y-0.5 py-2">
<li>
<Trans>
<strong>Email</strong> - The recipient will be emailed the document to
sign, approve, etc.
</Trans>
</li>
<li>
<Trans>
<strong>Links</strong> - We will generate links which you can send to
the recipients manually.
</Trans>
</li>
</ul>
<Trans>
<strong>Note</strong> - If you use Links in combination with direct
templates, you will need to manually send the links to the remaining
recipients.
</Trans>
</TooltipContent>
</Tooltip>
</FormLabel>
<FormControl>
<Select {...field} onValueChange={field.onChange}>
<SelectTrigger className="bg-background text-muted-foreground">
<SelectValue data-testid="documentDistributionMethodSelectValue" />
</SelectTrigger>
<SelectContent position="popper">
{Object.values(DOCUMENT_DISTRIBUTION_METHODS).map(
({ value, description }) => (
<SelectItem key={value} value={value}>
{_(description)}
</SelectItem>
),
)}
</SelectContent>
</Select>
</FormControl>
</FormItem>
)}
/>
{isEnterprise && (
<FormField
control={form.control}
@ -217,59 +300,66 @@ export const AddTemplateSettingsFormPartial = ({
/>
)}
<Accordion type="multiple">
<AccordionItem value="email-options" className="border-none">
<AccordionTrigger className="text-foreground rounded border px-3 py-2 text-left hover:bg-neutral-200/30 hover:no-underline">
<Trans>Email Options</Trans>
</AccordionTrigger>
{distributionMethod === DocumentDistributionMethod.EMAIL && (
<Accordion type="multiple">
<AccordionItem value="email-options" className="border-none">
<AccordionTrigger className="text-foreground rounded border px-3 py-2 text-left hover:bg-neutral-200/30 hover:no-underline">
<Trans>Email Options</Trans>
</AccordionTrigger>
<AccordionContent className="text-muted-foreground -mx-1 px-1 pt-4 text-sm leading-relaxed [&>div]:pb-0">
<div className="flex flex-col space-y-6">
<FormField
control={form.control}
name="meta.subject"
render={({ field }) => (
<FormItem>
<FormLabel>
<Trans>
Subject <span className="text-muted-foreground">(Optional)</span>
</Trans>
</FormLabel>
<AccordionContent className="text-muted-foreground -mx-1 px-1 pt-4 text-sm leading-relaxed [&>div]:pb-0">
<div className="flex flex-col space-y-6">
<FormField
control={form.control}
name="meta.subject"
render={({ field }) => (
<FormItem>
<FormLabel>
<Trans>
Subject <span className="text-muted-foreground">(Optional)</span>
</Trans>
</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
<FormControl>
<Input {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="meta.message"
render={({ field }) => (
<FormItem>
<FormLabel>
<Trans>
Message <span className="text-muted-foreground">(Optional)</span>
</Trans>
</FormLabel>
<FormField
control={form.control}
name="meta.message"
render={({ field }) => (
<FormItem>
<FormLabel>
<Trans>
Message <span className="text-muted-foreground">(Optional)</span>
</Trans>
</FormLabel>
<FormControl>
<Textarea className="bg-background h-32 resize-none" {...field} />
</FormControl>
<FormControl>
<Textarea className="bg-background h-32 resize-none" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormMessage />
</FormItem>
)}
/>
<DocumentSendEmailMessageHelper />
</div>
</AccordionContent>
</AccordionItem>
</Accordion>
<DocumentSendEmailMessageHelper />
<DocumentEmailCheckboxes
value={emailSettings}
onChange={(value) => form.setValue('meta.emailSettings', value)}
/>
</div>
</AccordionContent>
</AccordionItem>
</Accordion>
)}
<Accordion type="multiple">
<AccordionItem value="advanced-options" className="border-none">

View File

@ -7,9 +7,11 @@ import {
ZDocumentAccessAuthTypesSchema,
ZDocumentActionAuthTypesSchema,
} from '@documenso/lib/types/document-auth';
import { ZDocumentEmailSettingsSchema } from '@documenso/lib/types/document-email';
import { isValidRedirectUrl } from '@documenso/lib/utils/is-valid-redirect-url';
import { ZMapNegativeOneToUndefinedSchema } from '../document-flow/add-settings.types';
import { DocumentDistributionMethod } from '.prisma/client';
export const ZAddTemplateSettingsFormSchema = z.object({
title: z.string().trim().min(1, { message: "Title can't be empty" }),
@ -25,6 +27,10 @@ export const ZAddTemplateSettingsFormSchema = z.object({
message: z.string(),
timezone: z.string().optional().default(DEFAULT_DOCUMENT_TIME_ZONE),
dateFormat: z.string().optional().default(DEFAULT_DOCUMENT_DATE_FORMAT),
distributionMethod: z
.nativeEnum(DocumentDistributionMethod)
.optional()
.default(DocumentDistributionMethod.EMAIL),
redirectUrl: z
.string()
.optional()
@ -36,6 +42,7 @@ export const ZAddTemplateSettingsFormSchema = z.object({
.union([z.string(), z.enum(SUPPORTED_LANGUAGE_CODES)])
.optional()
.default('en'),
emailSettings: ZDocumentEmailSettingsSchema,
}),
});