mirror of
https://github.com/documenso/documenso.git
synced 2025-11-17 02:01:33 +10:00
Merge branch 'main' into staging
This commit is contained in:
@ -301,6 +301,7 @@ export const ApiContractV1Implementation = createNextRoute(ApiContractV1, {
|
||||
dateFormat: dateFormat?.value,
|
||||
redirectUrl: body.meta.redirectUrl,
|
||||
signingOrder: body.meta.signingOrder,
|
||||
language: body.meta.language,
|
||||
requestMetadata: extractNextApiRequestMetadata(args.req),
|
||||
});
|
||||
|
||||
|
||||
@ -2,6 +2,7 @@ import { extendZodWithOpenApi } from '@anatine/zod-openapi';
|
||||
import { z } from 'zod';
|
||||
|
||||
import { DATE_FORMATS, DEFAULT_DOCUMENT_DATE_FORMAT } from '@documenso/lib/constants/date-formats';
|
||||
import { SUPPORTED_LANGUAGE_CODES } from '@documenso/lib/constants/i18n';
|
||||
import '@documenso/lib/constants/time-zones';
|
||||
import { DEFAULT_DOCUMENT_TIME_ZONE, TIME_ZONES } from '@documenso/lib/constants/time-zones';
|
||||
import { ZUrlSchema } from '@documenso/lib/schemas/common';
|
||||
@ -127,6 +128,7 @@ export const ZCreateDocumentMutationSchema = z.object({
|
||||
}),
|
||||
redirectUrl: z.string(),
|
||||
signingOrder: z.nativeEnum(DocumentSigningOrder).optional(),
|
||||
language: z.enum(SUPPORTED_LANGUAGE_CODES).optional(),
|
||||
})
|
||||
.partial(),
|
||||
authOptions: z
|
||||
@ -181,6 +183,7 @@ export const ZCreateDocumentFromTemplateMutationSchema = z.object({
|
||||
dateFormat: z.string(),
|
||||
redirectUrl: z.string(),
|
||||
signingOrder: z.nativeEnum(DocumentSigningOrder).optional(),
|
||||
language: z.enum(SUPPORTED_LANGUAGE_CODES).optional(),
|
||||
})
|
||||
.partial()
|
||||
.optional(),
|
||||
@ -247,6 +250,7 @@ export const ZGenerateDocumentFromTemplateMutationSchema = z.object({
|
||||
dateFormat: z.string(),
|
||||
redirectUrl: ZUrlSchema,
|
||||
signingOrder: z.nativeEnum(DocumentSigningOrder).optional(),
|
||||
language: z.enum(SUPPORTED_LANGUAGE_CODES).optional(),
|
||||
})
|
||||
.partial()
|
||||
.optional(),
|
||||
|
||||
@ -1 +0,0 @@
|
||||
export { render, renderAsync } from '@react-email/render';
|
||||
39
packages/email/render.tsx
Normal file
39
packages/email/render.tsx
Normal file
@ -0,0 +1,39 @@
|
||||
import * as reactEmail from '@react-email/render';
|
||||
|
||||
import config from '@documenso/tailwind-config';
|
||||
|
||||
import { Tailwind } from './components';
|
||||
|
||||
export const render: typeof reactEmail.render = (element, options) => {
|
||||
return reactEmail.render(
|
||||
<Tailwind
|
||||
config={{
|
||||
theme: {
|
||||
extend: {
|
||||
colors: config.theme.extend.colors,
|
||||
},
|
||||
},
|
||||
}}
|
||||
>
|
||||
{element}
|
||||
</Tailwind>,
|
||||
options,
|
||||
);
|
||||
};
|
||||
|
||||
export const renderAsync: typeof reactEmail.renderAsync = async (element, options) => {
|
||||
return reactEmail.renderAsync(
|
||||
<Tailwind
|
||||
config={{
|
||||
theme: {
|
||||
extend: {
|
||||
colors: config.theme.extend.colors,
|
||||
},
|
||||
},
|
||||
}}
|
||||
>
|
||||
{element}
|
||||
</Tailwind>,
|
||||
options,
|
||||
);
|
||||
};
|
||||
@ -1,3 +1,6 @@
|
||||
import { Trans } from '@lingui/macro';
|
||||
import { useLingui } from '@lingui/react';
|
||||
|
||||
import { Button, Section, Text } from '../components';
|
||||
import { TemplateDocumentImage } from './template-document-image';
|
||||
|
||||
@ -10,17 +13,21 @@ export const TemplateConfirmationEmail = ({
|
||||
confirmationLink,
|
||||
assetBaseUrl,
|
||||
}: TemplateConfirmationEmailProps) => {
|
||||
const { _ } = useLingui();
|
||||
|
||||
return (
|
||||
<>
|
||||
<TemplateDocumentImage className="mt-6" assetBaseUrl={assetBaseUrl} />
|
||||
|
||||
<Section className="flex-row items-center justify-center">
|
||||
<Text className="text-primary mx-auto mb-0 max-w-[80%] text-center text-lg font-semibold">
|
||||
Welcome to Documenso!
|
||||
<Trans>Welcome to Documenso!</Trans>
|
||||
</Text>
|
||||
|
||||
<Text className="my-1 text-center text-base text-slate-400">
|
||||
Before you get started, please confirm your email address by clicking the button below:
|
||||
<Trans>
|
||||
Before you get started, please confirm your email address by clicking the button below:
|
||||
</Trans>
|
||||
</Text>
|
||||
|
||||
<Section className="mb-6 mt-8 text-center">
|
||||
@ -28,11 +35,13 @@ export const TemplateConfirmationEmail = ({
|
||||
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={confirmationLink}
|
||||
>
|
||||
Confirm email
|
||||
<Trans>Confirm email</Trans>
|
||||
</Button>
|
||||
<Text className="mt-8 text-center text-sm italic text-slate-400">
|
||||
You can also copy and paste this link into your browser: {confirmationLink} (link
|
||||
expires in 1 hour)
|
||||
<Trans>
|
||||
You can also copy and paste this link into your browser: {confirmationLink} (link
|
||||
expires in 1 hour)
|
||||
</Trans>
|
||||
</Text>
|
||||
</Section>
|
||||
</Section>
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
import { Trans } from '@lingui/macro';
|
||||
|
||||
import { Section, Text } from '../components';
|
||||
import { TemplateDocumentImage } from './template-document-image';
|
||||
|
||||
@ -19,16 +21,18 @@ export const TemplateDocumentCancel = ({
|
||||
|
||||
<Section>
|
||||
<Text className="text-primary mx-auto mb-0 max-w-[80%] text-center text-lg font-semibold">
|
||||
{inviterName} has cancelled the document
|
||||
<br />"{documentName}"
|
||||
<Trans>
|
||||
{inviterName} has cancelled the document
|
||||
<br />"{documentName}"
|
||||
</Trans>
|
||||
</Text>
|
||||
|
||||
<Text className="my-1 text-center text-base text-slate-400">
|
||||
All signatures have been voided.
|
||||
<Trans>All signatures have been voided.</Trans>
|
||||
</Text>
|
||||
|
||||
<Text className="my-1 text-center text-base text-slate-400">
|
||||
You don't need to sign it anymore.
|
||||
<Trans>You don't need to sign it anymore.</Trans>
|
||||
</Text>
|
||||
</Section>
|
||||
</>
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
import { Trans } from '@lingui/macro';
|
||||
|
||||
import { Button, Column, Img, Section, Text } from '../components';
|
||||
import { TemplateDocumentImage } from './template-document-image';
|
||||
|
||||
@ -30,17 +32,17 @@ export const TemplateDocumentCompleted = ({
|
||||
src={getAssetUrl('/static/completed.png')}
|
||||
className="-mt-0.5 mr-2 inline h-7 w-7 align-middle"
|
||||
/>
|
||||
Completed
|
||||
<Trans>Completed</Trans>
|
||||
</Text>
|
||||
</Column>
|
||||
</Section>
|
||||
|
||||
<Text className="text-primary mb-0 text-center text-lg font-semibold">
|
||||
{customBody ?? `“${documentName}” was signed by all signers`}
|
||||
<Trans>{customBody ?? `“${documentName}” was signed by all signers`}</Trans>
|
||||
</Text>
|
||||
|
||||
<Text className="my-1 text-center text-base text-slate-400">
|
||||
Continue by downloading the document.
|
||||
<Trans>Continue by downloading the document.</Trans>
|
||||
</Text>
|
||||
|
||||
<Section className="mb-6 mt-8 text-center">
|
||||
@ -59,7 +61,7 @@ export const TemplateDocumentCompleted = ({
|
||||
src={getAssetUrl('/static/download.png')}
|
||||
className="mb-0.5 mr-2 inline h-5 w-5 align-middle"
|
||||
/>
|
||||
Download
|
||||
<Trans>Download</Trans>
|
||||
</Button>
|
||||
</Section>
|
||||
</Section>
|
||||
|
||||
@ -1,3 +1,6 @@
|
||||
import { Trans } from '@lingui/macro';
|
||||
import { useLingui } from '@lingui/react';
|
||||
|
||||
import { RECIPIENT_ROLES_DESCRIPTION_ENG } from '@documenso/lib/constants/recipient-roles';
|
||||
import type { RecipientRole } from '@documenso/prisma/client';
|
||||
|
||||
@ -26,6 +29,8 @@ export const TemplateDocumentInvite = ({
|
||||
isTeamInvite,
|
||||
teamName,
|
||||
}: TemplateDocumentInviteProps) => {
|
||||
const { _ } = useLingui();
|
||||
|
||||
const { actionVerb, progressiveVerb } = RECIPIENT_ROLES_DESCRIPTION_ENG[role];
|
||||
|
||||
return (
|
||||
@ -35,28 +40,30 @@ export const TemplateDocumentInvite = ({
|
||||
<Section>
|
||||
<Text className="text-primary mx-auto mb-0 max-w-[80%] text-center text-lg font-semibold">
|
||||
{selfSigner ? (
|
||||
<>
|
||||
{`Please ${actionVerb.toLowerCase()} your document`}
|
||||
<Trans>
|
||||
{`Please ${_(actionVerb).toLowerCase()} your document`}
|
||||
<br />
|
||||
{`"${documentName}"`}
|
||||
</>
|
||||
</Trans>
|
||||
) : isTeamInvite ? (
|
||||
<>
|
||||
{`${inviterName} on behalf of ${teamName} has invited you to ${actionVerb.toLowerCase()}`}
|
||||
<Trans>
|
||||
{`${inviterName} on behalf of ${teamName} has invited you to ${_(
|
||||
actionVerb,
|
||||
).toLowerCase()}`}
|
||||
<br />
|
||||
{`"${documentName}"`}
|
||||
</>
|
||||
</Trans>
|
||||
) : (
|
||||
<>
|
||||
{`${inviterName} has invited you to ${actionVerb.toLowerCase()}`}
|
||||
<Trans>
|
||||
{`${inviterName} has invited you to ${_(actionVerb).toLowerCase()}`}
|
||||
<br />
|
||||
{`"${documentName}"`}
|
||||
</>
|
||||
</Trans>
|
||||
)}
|
||||
</Text>
|
||||
|
||||
<Text className="my-1 text-center text-base text-slate-400">
|
||||
Continue by {progressiveVerb.toLowerCase()} the document.
|
||||
<Trans>Continue by {_(progressiveVerb).toLowerCase()} the document.</Trans>
|
||||
</Text>
|
||||
|
||||
<Section className="mb-6 mt-8 text-center">
|
||||
@ -64,7 +71,7 @@ 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}
|
||||
>
|
||||
{actionVerb} Document
|
||||
<Trans>{_(actionVerb)} Document</Trans>
|
||||
</Button>
|
||||
</Section>
|
||||
</Section>
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
import { Trans } from '@lingui/macro';
|
||||
|
||||
import { Column, Img, Section, Text } from '../components';
|
||||
import { TemplateDocumentImage } from './template-document-image';
|
||||
|
||||
@ -26,19 +28,21 @@ export const TemplateDocumentPending = ({
|
||||
src={getAssetUrl('/static/clock.png')}
|
||||
className="-mt-0.5 mr-2 inline h-7 w-7 align-middle"
|
||||
/>
|
||||
Waiting for others
|
||||
<Trans>Waiting for others</Trans>
|
||||
</Text>
|
||||
</Column>
|
||||
</Section>
|
||||
|
||||
<Text className="text-primary mb-0 text-center text-lg font-semibold">
|
||||
“{documentName}” has been signed
|
||||
<Trans>“{documentName}” has been signed</Trans>
|
||||
</Text>
|
||||
|
||||
<Text className="mx-auto mb-6 mt-1 max-w-[80%] text-center text-base text-slate-400">
|
||||
We're still waiting for other signers to sign this document.
|
||||
<br />
|
||||
We'll notify you as soon as it's ready.
|
||||
<Trans>
|
||||
We're still waiting for other signers to sign this document.
|
||||
<br />
|
||||
We'll notify you as soon as it's ready.
|
||||
</Trans>
|
||||
</Text>
|
||||
</Section>
|
||||
</>
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import { Trans } from '@lingui/macro';
|
||||
import { env } from 'next-runtime-env';
|
||||
|
||||
import { Button, Column, Img, Link, Section, Text } from '../components';
|
||||
@ -32,25 +33,27 @@ export const TemplateDocumentSelfSigned = ({
|
||||
src={getAssetUrl('/static/completed.png')}
|
||||
className="-mt-0.5 mr-2 inline h-7 w-7 align-middle"
|
||||
/>
|
||||
Completed
|
||||
<Trans>Completed</Trans>
|
||||
</Text>
|
||||
</Column>
|
||||
</Section>
|
||||
|
||||
<Text className="text-primary mb-0 mt-6 text-center text-lg font-semibold">
|
||||
You have signed “{documentName}”
|
||||
<Trans>You have signed “{documentName}”</Trans>
|
||||
</Text>
|
||||
|
||||
<Text className="mx-auto mb-6 mt-1 max-w-[80%] text-center text-base text-slate-400">
|
||||
Create a{' '}
|
||||
<Link
|
||||
href={signUpUrl}
|
||||
target="_blank"
|
||||
className="text-documenso-700 hover:text-documenso-600 whitespace-nowrap"
|
||||
>
|
||||
free account
|
||||
</Link>{' '}
|
||||
to access your signed documents at any time.
|
||||
<Trans>
|
||||
Create a{' '}
|
||||
<Link
|
||||
href={signUpUrl}
|
||||
target="_blank"
|
||||
className="text-documenso-700 hover:text-documenso-600 whitespace-nowrap"
|
||||
>
|
||||
free account
|
||||
</Link>{' '}
|
||||
to access your signed documents at any time.
|
||||
</Trans>
|
||||
</Text>
|
||||
|
||||
<Section className="mb-6 mt-8 text-center">
|
||||
@ -62,7 +65,7 @@ export const TemplateDocumentSelfSigned = ({
|
||||
src={getAssetUrl('/static/user-plus.png')}
|
||||
className="mb-0.5 mr-2 inline h-5 w-5 align-middle"
|
||||
/>
|
||||
Create account
|
||||
<Trans>Create account</Trans>
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
@ -73,7 +76,7 @@ export const TemplateDocumentSelfSigned = ({
|
||||
src={getAssetUrl('/static/review.png')}
|
||||
className="mb-0.5 mr-2 inline h-5 w-5 align-middle"
|
||||
/>
|
||||
View plans
|
||||
<Trans>View plans</Trans>
|
||||
</Button>
|
||||
</Section>
|
||||
</Section>
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
import { Trans } from '@lingui/macro';
|
||||
|
||||
import { Section, Text } from '../components';
|
||||
import { TemplateDocumentImage } from './template-document-image';
|
||||
|
||||
@ -18,20 +20,22 @@ export const TemplateDocumentDelete = ({
|
||||
|
||||
<Section>
|
||||
<Text className="text-primary mb-0 mt-6 text-left text-lg font-semibold">
|
||||
Your document has been deleted by an admin!
|
||||
<Trans>Your document has been deleted by an admin!</Trans>
|
||||
</Text>
|
||||
|
||||
<Text className="mx-auto mb-6 mt-1 text-left text-base text-slate-400">
|
||||
"{documentName}" has been deleted by an admin.
|
||||
<Trans>"{documentName}" has been deleted by an admin.</Trans>
|
||||
</Text>
|
||||
|
||||
<Text className="mx-auto mb-6 mt-1 text-left text-base text-slate-400">
|
||||
This document can not be recovered, if you would like to dispute the reason for future
|
||||
documents please contact support.
|
||||
<Trans>
|
||||
This document can not be recovered, if you would like to dispute the reason for future
|
||||
documents please contact support.
|
||||
</Trans>
|
||||
</Text>
|
||||
|
||||
<Text className="mx-auto mt-1 text-left text-base text-slate-400">
|
||||
The reason provided for deletion is the following:
|
||||
<Trans>The reason provided for deletion is the following:</Trans>
|
||||
</Text>
|
||||
|
||||
<Text className="mx-auto mb-6 mt-1 text-left text-base italic text-slate-400">
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
import { Trans } from '@lingui/macro';
|
||||
|
||||
import { Link, Section, Text } from '../components';
|
||||
|
||||
export type TemplateFooterProps = {
|
||||
@ -9,10 +11,12 @@ export const TemplateFooter = ({ isDocument = true }: TemplateFooterProps) => {
|
||||
<Section>
|
||||
{isDocument && (
|
||||
<Text className="my-4 text-base text-slate-400">
|
||||
This document was sent using{' '}
|
||||
<Link className="text-[#7AC455]" href="https://documen.so/mail-footer">
|
||||
Documenso.
|
||||
</Link>
|
||||
<Trans>
|
||||
This document was sent using{' '}
|
||||
<Link className="text-[#7AC455]" href="https://documen.so/mail-footer">
|
||||
Documenso.
|
||||
</Link>
|
||||
</Trans>
|
||||
</Text>
|
||||
)}
|
||||
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
import { Trans } from '@lingui/macro';
|
||||
|
||||
import { Button, Section, Text } from '../components';
|
||||
import { TemplateDocumentImage } from './template-document-image';
|
||||
|
||||
@ -16,11 +18,11 @@ export const TemplateForgotPassword = ({
|
||||
|
||||
<Section className="flex-row items-center justify-center">
|
||||
<Text className="text-primary mx-auto mb-0 max-w-[80%] text-center text-lg font-semibold">
|
||||
Forgot your password?
|
||||
<Trans>Forgot your password?</Trans>
|
||||
</Text>
|
||||
|
||||
<Text className="my-1 text-center text-base text-slate-400">
|
||||
That's okay, it happens! Click the button below to reset your password.
|
||||
<Trans>That's okay, it happens! Click the button below to reset your password.</Trans>
|
||||
</Text>
|
||||
|
||||
<Section className="mb-6 mt-8 text-center">
|
||||
@ -28,7 +30,7 @@ export const TemplateForgotPassword = ({
|
||||
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={resetPasswordLink}
|
||||
>
|
||||
Reset Password
|
||||
<Trans>Reset Password</Trans>
|
||||
</Button>
|
||||
</Section>
|
||||
</Section>
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import { Trans } from '@lingui/macro';
|
||||
import { env } from 'next-runtime-env';
|
||||
|
||||
import { Button, Section, Text } from '../components';
|
||||
@ -18,11 +19,11 @@ export const TemplateResetPassword = ({ assetBaseUrl }: TemplateResetPasswordPro
|
||||
|
||||
<Section className="flex-row items-center justify-center">
|
||||
<Text className="text-primary mx-auto mb-0 max-w-[80%] text-center text-lg font-semibold">
|
||||
Password updated!
|
||||
<Trans>Password updated!</Trans>
|
||||
</Text>
|
||||
|
||||
<Text className="my-1 text-center text-base text-slate-400">
|
||||
Your password has been updated.
|
||||
<Trans>Your password has been updated.</Trans>
|
||||
</Text>
|
||||
|
||||
<Section className="mb-6 mt-8 text-center">
|
||||
@ -30,7 +31,7 @@ export const TemplateResetPassword = ({ assetBaseUrl }: TemplateResetPasswordPro
|
||||
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={`${NEXT_PUBLIC_WEBAPP_URL ?? 'http://localhost:3000'}/signin`}
|
||||
>
|
||||
Sign In
|
||||
<Trans>Sign In</Trans>
|
||||
</Button>
|
||||
</Section>
|
||||
</Section>
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import config from '@documenso/tailwind-config';
|
||||
import { msg } from '@lingui/macro';
|
||||
import { useLingui } from '@lingui/react';
|
||||
|
||||
import { Body, Container, Head, Html, Img, Preview, Section, Tailwind } from '../components';
|
||||
import { Body, Container, Head, Html, Img, Preview, Section } from '../components';
|
||||
import type { TemplateConfirmationEmailProps } from '../template-components/template-confirmation-email';
|
||||
import { TemplateConfirmationEmail } from '../template-components/template-confirmation-email';
|
||||
import { TemplateFooter } from '../template-components/template-footer';
|
||||
@ -9,7 +10,9 @@ export const ConfirmEmailTemplate = ({
|
||||
confirmationLink,
|
||||
assetBaseUrl = 'http://localhost:3002',
|
||||
}: TemplateConfirmationEmailProps) => {
|
||||
const previewText = `Please confirm your email address`;
|
||||
const { _ } = useLingui();
|
||||
|
||||
const previewText = msg`Please confirm your email address`;
|
||||
|
||||
const getAssetUrl = (path: string) => {
|
||||
return new URL(path, assetBaseUrl).toString();
|
||||
@ -18,40 +21,30 @@ export const ConfirmEmailTemplate = ({
|
||||
return (
|
||||
<Html>
|
||||
<Head />
|
||||
<Preview>{previewText}</Preview>
|
||||
<Tailwind
|
||||
config={{
|
||||
theme: {
|
||||
extend: {
|
||||
colors: config.theme.extend.colors,
|
||||
},
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Body className="mx-auto my-auto bg-white font-sans">
|
||||
<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"
|
||||
/>
|
||||
<Preview>{_(previewText)}</Preview>
|
||||
<Body className="mx-auto my-auto bg-white font-sans">
|
||||
<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"
|
||||
/>
|
||||
|
||||
<TemplateConfirmationEmail
|
||||
confirmationLink={confirmationLink}
|
||||
assetBaseUrl={assetBaseUrl}
|
||||
/>
|
||||
</Section>
|
||||
</Container>
|
||||
<div className="mx-auto mt-12 max-w-xl" />
|
||||
<TemplateConfirmationEmail
|
||||
confirmationLink={confirmationLink}
|
||||
assetBaseUrl={assetBaseUrl}
|
||||
/>
|
||||
</Section>
|
||||
</Container>
|
||||
<div className="mx-auto mt-12 max-w-xl" />
|
||||
|
||||
<Container className="mx-auto max-w-xl">
|
||||
<TemplateFooter isDocument={false} />
|
||||
</Container>
|
||||
</Section>
|
||||
</Body>
|
||||
</Tailwind>
|
||||
<Container className="mx-auto max-w-xl">
|
||||
<TemplateFooter isDocument={false} />
|
||||
</Container>
|
||||
</Section>
|
||||
</Body>
|
||||
</Html>
|
||||
);
|
||||
};
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
import { msg } from '@lingui/macro';
|
||||
import { useLingui } from '@lingui/react';
|
||||
|
||||
import { formatTeamUrl } from '@documenso/lib/utils/teams';
|
||||
import config from '@documenso/tailwind-config';
|
||||
|
||||
import {
|
||||
Body,
|
||||
@ -11,7 +13,6 @@ import {
|
||||
Link,
|
||||
Preview,
|
||||
Section,
|
||||
Tailwind,
|
||||
Text,
|
||||
} from '../components';
|
||||
import { TemplateFooter } from '../template-components/template-footer';
|
||||
@ -32,97 +33,90 @@ export const ConfirmTeamEmailTemplate = ({
|
||||
teamUrl = 'demo',
|
||||
token = '',
|
||||
}: ConfirmTeamEmailProps) => {
|
||||
const previewText = `Accept team email request for ${teamName} on Documenso`;
|
||||
const { _ } = useLingui();
|
||||
|
||||
const previewText = msg`Accept team email request for ${teamName} on Documenso`;
|
||||
|
||||
return (
|
||||
<Html>
|
||||
<Head />
|
||||
<Preview>{previewText}</Preview>
|
||||
<Tailwind
|
||||
config={{
|
||||
theme: {
|
||||
extend: {
|
||||
colors: config.theme.extend.colors,
|
||||
},
|
||||
},
|
||||
}}
|
||||
>
|
||||
<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">
|
||||
<Preview>{_(previewText)}</Preview>
|
||||
|
||||
<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"
|
||||
/>
|
||||
|
||||
<Section>
|
||||
<TemplateImage
|
||||
className="mx-auto"
|
||||
assetBaseUrl={assetBaseUrl}
|
||||
className="mb-4 h-6 p-2"
|
||||
staticAsset="logo.png"
|
||||
staticAsset="mail-open.png"
|
||||
/>
|
||||
</Section>
|
||||
|
||||
<Section>
|
||||
<TemplateImage
|
||||
className="mx-auto"
|
||||
assetBaseUrl={assetBaseUrl}
|
||||
staticAsset="mail-open.png"
|
||||
/>
|
||||
</Section>
|
||||
<Section className="p-2 text-slate-500">
|
||||
<Text className="text-center text-lg font-medium text-black">
|
||||
Verify your team email address
|
||||
</Text>
|
||||
|
||||
<Section className="p-2 text-slate-500">
|
||||
<Text className="text-center text-lg font-medium text-black">
|
||||
Verify your team email address
|
||||
<Text className="text-center text-base">
|
||||
<span className="font-bold">{teamName}</span> has requested to use your email
|
||||
address for their team on Documenso.
|
||||
</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">
|
||||
{formatTeamUrl(teamUrl, baseUrl)}
|
||||
</div>
|
||||
|
||||
<Section className="mt-6">
|
||||
<Text className="my-0 text-sm">
|
||||
By accepting this request, you will be granting <strong>{teamName}</strong> access
|
||||
to:
|
||||
</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.
|
||||
<ul className="mb-0 mt-2">
|
||||
<li className="text-sm">
|
||||
View all documents sent to and from this email address
|
||||
</li>
|
||||
<li className="mt-1 text-sm">
|
||||
Allow document recipients to reply directly to this email address
|
||||
</li>
|
||||
<li className="mt-1 text-sm">
|
||||
Send documents on behalf of the team using the email address
|
||||
</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>
|
||||
</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">
|
||||
{formatTeamUrl(teamUrl, baseUrl)}
|
||||
</div>
|
||||
|
||||
<Section className="mt-6">
|
||||
<Text className="my-0 text-sm">
|
||||
By accepting this request, you will be granting <strong>{teamName}</strong>{' '}
|
||||
access to:
|
||||
</Text>
|
||||
|
||||
<ul className="mb-0 mt-2">
|
||||
<li className="text-sm">
|
||||
View all documents sent to and from this email address
|
||||
</li>
|
||||
<li className="mt-1 text-sm">
|
||||
Allow document recipients to reply directly to this email address
|
||||
</li>
|
||||
<li className="mt-1 text-sm">
|
||||
Send documents on behalf of the team using the email address
|
||||
</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>
|
||||
</Text>
|
||||
</Section>
|
||||
|
||||
<Section className="mb-6 mt-8 text-center">
|
||||
<Button
|
||||
className="bg-documenso-500 inline-flex items-center justify-center rounded-lg px-6 py-3 text-center text-sm font-medium text-black no-underline"
|
||||
href={`${baseUrl}/team/verify/email/${token}`}
|
||||
>
|
||||
Accept
|
||||
</Button>
|
||||
</Section>
|
||||
</Section>
|
||||
|
||||
<Text className="text-center text-xs text-slate-500">Link expires in 1 hour.</Text>
|
||||
</Container>
|
||||
<Section className="mb-6 mt-8 text-center">
|
||||
<Button
|
||||
className="bg-documenso-500 inline-flex items-center justify-center rounded-lg px-6 py-3 text-center text-sm font-medium text-black no-underline"
|
||||
href={`${baseUrl}/team/verify/email/${token}`}
|
||||
>
|
||||
Accept
|
||||
</Button>
|
||||
</Section>
|
||||
</Section>
|
||||
|
||||
<Hr className="mx-auto mt-12 max-w-xl" />
|
||||
<Text className="text-center text-xs text-slate-500">Link expires in 1 hour.</Text>
|
||||
</Container>
|
||||
|
||||
<Container className="mx-auto max-w-xl">
|
||||
<TemplateFooter isDocument={false} />
|
||||
</Container>
|
||||
</Section>
|
||||
</Body>
|
||||
</Tailwind>
|
||||
<Hr className="mx-auto mt-12 max-w-xl" />
|
||||
|
||||
<Container className="mx-auto max-w-xl">
|
||||
<TemplateFooter isDocument={false} />
|
||||
</Container>
|
||||
</Section>
|
||||
</Body>
|
||||
</Html>
|
||||
);
|
||||
};
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import config from '@documenso/tailwind-config';
|
||||
import { msg } from '@lingui/macro';
|
||||
import { useLingui } from '@lingui/react';
|
||||
|
||||
import { Body, Container, Head, Hr, Html, Img, Preview, Section, Tailwind } from '../components';
|
||||
import { Body, Container, Head, Hr, Html, Img, Preview, Section } from '../components';
|
||||
import type { TemplateDocumentCancelProps } from '../template-components/template-document-cancel';
|
||||
import { TemplateDocumentCancel } from '../template-components/template-document-cancel';
|
||||
import { TemplateFooter } from '../template-components/template-footer';
|
||||
@ -13,7 +14,9 @@ export const DocumentCancelTemplate = ({
|
||||
documentName = 'Open Source Pledge.pdf',
|
||||
assetBaseUrl = 'http://localhost:3002',
|
||||
}: DocumentCancelEmailTemplateProps) => {
|
||||
const previewText = `${inviterName} has cancelled the document ${documentName}, you don't need to sign it anymore.`;
|
||||
const { _ } = useLingui();
|
||||
|
||||
const previewText = msg`${inviterName} has cancelled the document ${documentName}, you don't need to sign it anymore.`;
|
||||
|
||||
const getAssetUrl = (path: string) => {
|
||||
return new URL(path, assetBaseUrl).toString();
|
||||
@ -22,43 +25,34 @@ export const DocumentCancelTemplate = ({
|
||||
return (
|
||||
<Html>
|
||||
<Head />
|
||||
<Preview>{previewText}</Preview>
|
||||
<Tailwind
|
||||
config={{
|
||||
theme: {
|
||||
extend: {
|
||||
colors: config.theme.extend.colors,
|
||||
},
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Body className="mx-auto my-auto bg-white font-sans">
|
||||
<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"
|
||||
/>
|
||||
<Preview>{_(previewText)}</Preview>
|
||||
|
||||
<TemplateDocumentCancel
|
||||
inviterName={inviterName}
|
||||
inviterEmail={inviterEmail}
|
||||
documentName={documentName}
|
||||
assetBaseUrl={assetBaseUrl}
|
||||
/>
|
||||
</Section>
|
||||
</Container>
|
||||
<Body className="mx-auto my-auto bg-white font-sans">
|
||||
<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"
|
||||
/>
|
||||
|
||||
<Hr className="mx-auto mt-12 max-w-xl" />
|
||||
<TemplateDocumentCancel
|
||||
inviterName={inviterName}
|
||||
inviterEmail={inviterEmail}
|
||||
documentName={documentName}
|
||||
assetBaseUrl={assetBaseUrl}
|
||||
/>
|
||||
</Section>
|
||||
</Container>
|
||||
|
||||
<Container className="mx-auto max-w-xl">
|
||||
<TemplateFooter />
|
||||
</Container>
|
||||
</Section>
|
||||
</Body>
|
||||
</Tailwind>
|
||||
<Hr className="mx-auto mt-12 max-w-xl" />
|
||||
|
||||
<Container className="mx-auto max-w-xl">
|
||||
<TemplateFooter />
|
||||
</Container>
|
||||
</Section>
|
||||
</Body>
|
||||
</Html>
|
||||
);
|
||||
};
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import config from '@documenso/tailwind-config';
|
||||
import { msg } from '@lingui/macro';
|
||||
import { useLingui } from '@lingui/react';
|
||||
|
||||
import { Body, Container, Head, Html, Img, Preview, Section, Tailwind } from '../components';
|
||||
import { Body, Container, Head, Html, Img, Preview, Section } from '../components';
|
||||
import type { TemplateDocumentCompletedProps } from '../template-components/template-document-completed';
|
||||
import { TemplateDocumentCompleted } from '../template-components/template-document-completed';
|
||||
import { TemplateFooter } from '../template-components/template-footer';
|
||||
@ -15,7 +16,9 @@ export const DocumentCompletedEmailTemplate = ({
|
||||
assetBaseUrl = 'http://localhost:3002',
|
||||
customBody,
|
||||
}: DocumentCompletedEmailTemplateProps) => {
|
||||
const previewText = `Completed Document`;
|
||||
const { _ } = useLingui();
|
||||
|
||||
const previewText = msg`Completed Document`;
|
||||
|
||||
const getAssetUrl = (path: string) => {
|
||||
return new URL(path, assetBaseUrl).toString();
|
||||
@ -24,41 +27,32 @@ export const DocumentCompletedEmailTemplate = ({
|
||||
return (
|
||||
<Html>
|
||||
<Head />
|
||||
<Preview>{previewText}</Preview>
|
||||
<Tailwind
|
||||
config={{
|
||||
theme: {
|
||||
extend: {
|
||||
colors: config.theme.extend.colors,
|
||||
},
|
||||
},
|
||||
}}
|
||||
>
|
||||
<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 p-2 backdrop-blur-sm">
|
||||
<Section className="p-2">
|
||||
<Img
|
||||
src={getAssetUrl('/static/logo.png')}
|
||||
alt="Documenso Logo"
|
||||
className="mb-4 h-6"
|
||||
/>
|
||||
<Preview>{_(previewText)}</Preview>
|
||||
|
||||
<TemplateDocumentCompleted
|
||||
downloadLink={downloadLink}
|
||||
documentName={documentName}
|
||||
assetBaseUrl={assetBaseUrl}
|
||||
customBody={customBody}
|
||||
/>
|
||||
</Section>
|
||||
</Container>
|
||||
<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 p-2 backdrop-blur-sm">
|
||||
<Section className="p-2">
|
||||
<Img
|
||||
src={getAssetUrl('/static/logo.png')}
|
||||
alt="Documenso Logo"
|
||||
className="mb-4 h-6"
|
||||
/>
|
||||
|
||||
<Container className="mx-auto max-w-xl">
|
||||
<TemplateFooter />
|
||||
</Container>
|
||||
</Section>
|
||||
</Body>
|
||||
</Tailwind>
|
||||
<TemplateDocumentCompleted
|
||||
downloadLink={downloadLink}
|
||||
documentName={documentName}
|
||||
assetBaseUrl={assetBaseUrl}
|
||||
customBody={customBody}
|
||||
/>
|
||||
</Section>
|
||||
</Container>
|
||||
|
||||
<Container className="mx-auto max-w-xl">
|
||||
<TemplateFooter />
|
||||
</Container>
|
||||
</Section>
|
||||
</Body>
|
||||
</Html>
|
||||
);
|
||||
};
|
||||
|
||||
@ -1,18 +1,9 @@
|
||||
import { RECIPIENT_ROLES_DESCRIPTION_ENG } from '@documenso/lib/constants/recipient-roles';
|
||||
import config from '@documenso/tailwind-config';
|
||||
import { Trans, msg } from '@lingui/macro';
|
||||
import { useLingui } from '@lingui/react';
|
||||
|
||||
import {
|
||||
Body,
|
||||
Button,
|
||||
Container,
|
||||
Head,
|
||||
Html,
|
||||
Img,
|
||||
Preview,
|
||||
Section,
|
||||
Tailwind,
|
||||
Text,
|
||||
} from '../components';
|
||||
import { RECIPIENT_ROLES_DESCRIPTION_ENG } from '@documenso/lib/constants/recipient-roles';
|
||||
|
||||
import { Body, Button, Container, Head, Html, Img, Preview, Section, Text } from '../components';
|
||||
import TemplateDocumentImage from '../template-components/template-document-image';
|
||||
import { TemplateFooter } from '../template-components/template-footer';
|
||||
import { RecipientRole } from '.prisma/client';
|
||||
@ -32,9 +23,11 @@ export const DocumentCreatedFromDirectTemplateEmailTemplate = ({
|
||||
documentName = 'Open Source Pledge.pdf',
|
||||
assetBaseUrl = 'http://localhost:3002',
|
||||
}: DocumentCompletedEmailTemplateProps) => {
|
||||
const action = RECIPIENT_ROLES_DESCRIPTION_ENG[recipientRole].actioned.toLowerCase();
|
||||
const { _ } = useLingui();
|
||||
|
||||
const previewText = `Document created from direct template`;
|
||||
const action = _(RECIPIENT_ROLES_DESCRIPTION_ENG[recipientRole].actioned).toLowerCase();
|
||||
|
||||
const previewText = msg`Document created from direct template`;
|
||||
|
||||
const getAssetUrl = (path: string) => {
|
||||
return new URL(path, assetBaseUrl).toString();
|
||||
@ -43,55 +36,48 @@ export const DocumentCreatedFromDirectTemplateEmailTemplate = ({
|
||||
return (
|
||||
<Html>
|
||||
<Head />
|
||||
<Preview>{previewText}</Preview>
|
||||
<Tailwind
|
||||
config={{
|
||||
theme: {
|
||||
extend: {
|
||||
colors: config.theme.extend.colors,
|
||||
},
|
||||
},
|
||||
}}
|
||||
>
|
||||
<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 p-2 backdrop-blur-sm">
|
||||
<Section className="p-2">
|
||||
<Img
|
||||
src={getAssetUrl('/static/logo.png')}
|
||||
alt="Documenso Logo"
|
||||
className="mb-4 h-6"
|
||||
/>
|
||||
<Preview>{_(previewText)}</Preview>
|
||||
|
||||
<TemplateDocumentImage className="mt-6" assetBaseUrl={assetBaseUrl} />
|
||||
<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 p-2 backdrop-blur-sm">
|
||||
<Section className="p-2">
|
||||
<Img
|
||||
src={getAssetUrl('/static/logo.png')}
|
||||
alt="Documenso Logo"
|
||||
className="mb-4 h-6"
|
||||
/>
|
||||
|
||||
<Section>
|
||||
<Text className="text-primary mb-0 text-center text-lg font-semibold">
|
||||
<TemplateDocumentImage className="mt-6" assetBaseUrl={assetBaseUrl} />
|
||||
|
||||
<Section>
|
||||
<Text className="text-primary mb-0 text-center text-lg font-semibold">
|
||||
<Trans>
|
||||
{recipientName} {action} a document by using one of your direct links
|
||||
</Text>
|
||||
</Trans>
|
||||
</Text>
|
||||
|
||||
<div className="mx-auto my-2 w-fit rounded-lg bg-gray-50 px-4 py-2 text-sm text-slate-600">
|
||||
{documentName}
|
||||
</div>
|
||||
<div className="mx-auto my-2 w-fit rounded-lg bg-gray-50 px-4 py-2 text-sm text-slate-600">
|
||||
{documentName}
|
||||
</div>
|
||||
|
||||
<Section className="my-6 text-center">
|
||||
<Button
|
||||
className="bg-documenso-500 inline-flex items-center justify-center rounded-lg px-6 py-3 text-center text-sm font-medium text-black no-underline"
|
||||
href={documentLink}
|
||||
>
|
||||
View document
|
||||
</Button>
|
||||
</Section>
|
||||
<Section className="my-6 text-center">
|
||||
<Button
|
||||
className="bg-documenso-500 inline-flex items-center justify-center rounded-lg px-6 py-3 text-center text-sm font-medium text-black no-underline"
|
||||
href={documentLink}
|
||||
>
|
||||
<Trans>View document</Trans>
|
||||
</Button>
|
||||
</Section>
|
||||
</Section>
|
||||
</Container>
|
||||
</Section>
|
||||
</Container>
|
||||
|
||||
<Container className="mx-auto max-w-xl">
|
||||
<TemplateFooter />
|
||||
</Container>
|
||||
</Section>
|
||||
</Body>
|
||||
</Tailwind>
|
||||
<Container className="mx-auto max-w-xl">
|
||||
<TemplateFooter />
|
||||
</Container>
|
||||
</Section>
|
||||
</Body>
|
||||
</Html>
|
||||
);
|
||||
};
|
||||
|
||||
@ -1,20 +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 type { RecipientRole } from '@documenso/prisma/client';
|
||||
import config from '@documenso/tailwind-config';
|
||||
|
||||
import {
|
||||
Body,
|
||||
Container,
|
||||
Head,
|
||||
Hr,
|
||||
Html,
|
||||
Img,
|
||||
Link,
|
||||
Preview,
|
||||
Section,
|
||||
Tailwind,
|
||||
Text,
|
||||
} from '../components';
|
||||
import { Body, Container, Head, Hr, Html, Img, Link, Preview, Section, Text } from '../components';
|
||||
import type { TemplateDocumentInviteProps } from '../template-components/template-document-invite';
|
||||
import { TemplateDocumentInvite } from '../template-components/template-document-invite';
|
||||
import { TemplateFooter } from '../template-components/template-footer';
|
||||
@ -40,13 +30,15 @@ export const DocumentInviteEmailTemplate = ({
|
||||
isTeamInvite = false,
|
||||
teamName,
|
||||
}: DocumentInviteEmailTemplateProps) => {
|
||||
const action = RECIPIENT_ROLES_DESCRIPTION_ENG[role].actionVerb.toLowerCase();
|
||||
const { _ } = useLingui();
|
||||
|
||||
const action = _(RECIPIENT_ROLES_DESCRIPTION_ENG[role].actionVerb).toLowerCase();
|
||||
|
||||
const previewText = selfSigner
|
||||
? `Please ${action} your document ${documentName}`
|
||||
? msg`Please ${action} your document ${documentName}`
|
||||
: isTeamInvite
|
||||
? `${inviterName} on behalf of ${teamName} has invited you to ${action} ${documentName}`
|
||||
: `${inviterName} has invited you to ${action} ${documentName}`;
|
||||
? msg`${inviterName} on behalf of ${teamName} has invited you to ${action} ${documentName}`
|
||||
: msg`${inviterName} has invited you to ${action} ${documentName}`;
|
||||
|
||||
const getAssetUrl = (path: string) => {
|
||||
return new URL(path, assetBaseUrl).toString();
|
||||
@ -55,67 +47,62 @@ export const DocumentInviteEmailTemplate = ({
|
||||
return (
|
||||
<Html>
|
||||
<Head />
|
||||
<Preview>{previewText}</Preview>
|
||||
<Tailwind
|
||||
config={{
|
||||
theme: {
|
||||
extend: {
|
||||
colors: config.theme.extend.colors,
|
||||
},
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Body className="mx-auto my-auto bg-white font-sans">
|
||||
<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"
|
||||
/>
|
||||
<Preview>{_(previewText)}</Preview>
|
||||
|
||||
<TemplateDocumentInvite
|
||||
inviterName={inviterName}
|
||||
inviterEmail={inviterEmail}
|
||||
documentName={documentName}
|
||||
signDocumentLink={signDocumentLink}
|
||||
assetBaseUrl={assetBaseUrl}
|
||||
role={role}
|
||||
selfSigner={selfSigner}
|
||||
isTeamInvite={isTeamInvite}
|
||||
teamName={teamName}
|
||||
/>
|
||||
</Section>
|
||||
</Container>
|
||||
<Body className="mx-auto my-auto bg-white font-sans">
|
||||
<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"
|
||||
/>
|
||||
|
||||
<Container className="mx-auto mt-12 max-w-xl">
|
||||
<Section>
|
||||
<Text className="my-4 text-base font-semibold">
|
||||
<TemplateDocumentInvite
|
||||
inviterName={inviterName}
|
||||
inviterEmail={inviterEmail}
|
||||
documentName={documentName}
|
||||
signDocumentLink={signDocumentLink}
|
||||
assetBaseUrl={assetBaseUrl}
|
||||
role={role}
|
||||
selfSigner={selfSigner}
|
||||
isTeamInvite={isTeamInvite}
|
||||
teamName={teamName}
|
||||
/>
|
||||
</Section>
|
||||
</Container>
|
||||
|
||||
<Container className="mx-auto mt-12 max-w-xl">
|
||||
<Section>
|
||||
<Text className="my-4 text-base font-semibold">
|
||||
<Trans>
|
||||
{inviterName}{' '}
|
||||
<Link className="font-normal text-slate-400" href="mailto:{inviterEmail}">
|
||||
({inviterEmail})
|
||||
</Link>
|
||||
</Text>
|
||||
</Trans>
|
||||
</Text>
|
||||
|
||||
<Text className="mt-2 text-base text-slate-400">
|
||||
{customBody ? (
|
||||
<pre className="font-sans text-base text-slate-400">{customBody}</pre>
|
||||
) : (
|
||||
<Text className="mt-2 text-base text-slate-400">
|
||||
{customBody ? (
|
||||
<pre className="font-sans text-base text-slate-400">{customBody}</pre>
|
||||
) : (
|
||||
<Trans>
|
||||
`${inviterName} has invited you to ${action} the document "${documentName}".`
|
||||
)}
|
||||
</Text>
|
||||
</Section>
|
||||
</Container>
|
||||
</Trans>
|
||||
)}
|
||||
</Text>
|
||||
</Section>
|
||||
</Container>
|
||||
|
||||
<Hr className="mx-auto mt-12 max-w-xl" />
|
||||
<Hr className="mx-auto mt-12 max-w-xl" />
|
||||
|
||||
<Container className="mx-auto max-w-xl">
|
||||
<TemplateFooter />
|
||||
</Container>
|
||||
</Section>
|
||||
</Body>
|
||||
</Tailwind>
|
||||
<Container className="mx-auto max-w-xl">
|
||||
<TemplateFooter />
|
||||
</Container>
|
||||
</Section>
|
||||
</Body>
|
||||
</Html>
|
||||
);
|
||||
};
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import config from '@documenso/tailwind-config';
|
||||
import { msg } from '@lingui/macro';
|
||||
import { useLingui } from '@lingui/react';
|
||||
|
||||
import { Body, Container, Head, Html, Img, Preview, Section, Tailwind } from '../components';
|
||||
import { Body, Container, Head, Html, Img, Preview, Section } from '../components';
|
||||
import type { TemplateDocumentPendingProps } from '../template-components/template-document-pending';
|
||||
import { TemplateDocumentPending } from '../template-components/template-document-pending';
|
||||
import { TemplateFooter } from '../template-components/template-footer';
|
||||
@ -11,7 +12,9 @@ export const DocumentPendingEmailTemplate = ({
|
||||
documentName = 'Open Source Pledge.pdf',
|
||||
assetBaseUrl = 'http://localhost:3002',
|
||||
}: DocumentPendingEmailTemplateProps) => {
|
||||
const previewText = `Pending Document`;
|
||||
const { _ } = useLingui();
|
||||
|
||||
const previewText = msg`Pending Document`;
|
||||
|
||||
const getAssetUrl = (path: string) => {
|
||||
return new URL(path, assetBaseUrl).toString();
|
||||
@ -20,36 +23,27 @@ export const DocumentPendingEmailTemplate = ({
|
||||
return (
|
||||
<Html>
|
||||
<Head />
|
||||
<Preview>{previewText}</Preview>
|
||||
<Tailwind
|
||||
config={{
|
||||
theme: {
|
||||
extend: {
|
||||
colors: config.theme.extend.colors,
|
||||
},
|
||||
},
|
||||
}}
|
||||
>
|
||||
<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 p-4 backdrop-blur-sm">
|
||||
<Section>
|
||||
<Img
|
||||
src={getAssetUrl('/static/logo.png')}
|
||||
alt="Documenso Logo"
|
||||
className="mb-4 h-6"
|
||||
/>
|
||||
<Preview>{_(previewText)}</Preview>
|
||||
|
||||
<TemplateDocumentPending documentName={documentName} assetBaseUrl={assetBaseUrl} />
|
||||
</Section>
|
||||
</Container>
|
||||
<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 p-4 backdrop-blur-sm">
|
||||
<Section>
|
||||
<Img
|
||||
src={getAssetUrl('/static/logo.png')}
|
||||
alt="Documenso Logo"
|
||||
className="mb-4 h-6"
|
||||
/>
|
||||
|
||||
<Container className="mx-auto max-w-xl">
|
||||
<TemplateFooter />
|
||||
</Container>
|
||||
</Section>
|
||||
</Body>
|
||||
</Tailwind>
|
||||
<TemplateDocumentPending documentName={documentName} assetBaseUrl={assetBaseUrl} />
|
||||
</Section>
|
||||
</Container>
|
||||
|
||||
<Container className="mx-auto max-w-xl">
|
||||
<TemplateFooter />
|
||||
</Container>
|
||||
</Section>
|
||||
</Body>
|
||||
</Html>
|
||||
);
|
||||
};
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import config from '@documenso/tailwind-config';
|
||||
import { msg } from '@lingui/macro';
|
||||
import { useLingui } from '@lingui/react';
|
||||
|
||||
import { Body, Container, Head, Html, Img, Preview, Section, Tailwind } from '../components';
|
||||
import { Body, Container, Head, Html, Img, Preview, Section } from '../components';
|
||||
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';
|
||||
@ -11,7 +12,9 @@ export const DocumentSelfSignedEmailTemplate = ({
|
||||
documentName = 'Open Source Pledge.pdf',
|
||||
assetBaseUrl = 'http://localhost:3002',
|
||||
}: DocumentSelfSignedTemplateProps) => {
|
||||
const previewText = `Completed Document`;
|
||||
const { _ } = useLingui();
|
||||
|
||||
const previewText = msg`Completed Document`;
|
||||
|
||||
const getAssetUrl = (path: string) => {
|
||||
return new URL(path, assetBaseUrl).toString();
|
||||
@ -20,39 +23,27 @@ export const DocumentSelfSignedEmailTemplate = ({
|
||||
return (
|
||||
<Html>
|
||||
<Head />
|
||||
<Preview>{previewText}</Preview>
|
||||
<Tailwind
|
||||
config={{
|
||||
theme: {
|
||||
extend: {
|
||||
colors: config.theme.extend.colors,
|
||||
},
|
||||
},
|
||||
}}
|
||||
>
|
||||
<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 p-2 backdrop-blur-sm">
|
||||
<Section className="p-2">
|
||||
<Img
|
||||
src={getAssetUrl('/static/logo.png')}
|
||||
alt="Documenso Logo"
|
||||
className="mb-4 h-6"
|
||||
/>
|
||||
<Preview>{_(previewText)}</Preview>
|
||||
|
||||
<TemplateDocumentSelfSigned
|
||||
documentName={documentName}
|
||||
assetBaseUrl={assetBaseUrl}
|
||||
/>
|
||||
</Section>
|
||||
</Container>
|
||||
<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 p-2 backdrop-blur-sm">
|
||||
<Section className="p-2">
|
||||
<Img
|
||||
src={getAssetUrl('/static/logo.png')}
|
||||
alt="Documenso Logo"
|
||||
className="mb-4 h-6"
|
||||
/>
|
||||
|
||||
<Container className="mx-auto max-w-xl">
|
||||
<TemplateFooter />
|
||||
</Container>
|
||||
</Section>
|
||||
</Body>
|
||||
</Tailwind>
|
||||
<TemplateDocumentSelfSigned documentName={documentName} assetBaseUrl={assetBaseUrl} />
|
||||
</Section>
|
||||
</Container>
|
||||
|
||||
<Container className="mx-auto max-w-xl">
|
||||
<TemplateFooter />
|
||||
</Container>
|
||||
</Section>
|
||||
</Body>
|
||||
</Html>
|
||||
);
|
||||
};
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import config from '@documenso/tailwind-config';
|
||||
import { msg } from '@lingui/macro';
|
||||
import { useLingui } from '@lingui/react';
|
||||
|
||||
import { Body, Container, Head, Hr, Html, Img, Preview, Section, Tailwind } from '../components';
|
||||
import { Body, Container, Head, Hr, Html, Img, Preview, Section } from '../components';
|
||||
import {
|
||||
TemplateDocumentDelete,
|
||||
type TemplateDocumentDeleteProps,
|
||||
@ -14,7 +15,9 @@ export const DocumentSuperDeleteEmailTemplate = ({
|
||||
assetBaseUrl = 'http://localhost:3002',
|
||||
reason = 'Unknown',
|
||||
}: DocumentDeleteEmailTemplateProps) => {
|
||||
const previewText = `An admin has deleted your document "${documentName}".`;
|
||||
const { _ } = useLingui();
|
||||
|
||||
const previewText = msg`An admin has deleted your document "${documentName}".`;
|
||||
|
||||
const getAssetUrl = (path: string) => {
|
||||
return new URL(path, assetBaseUrl).toString();
|
||||
@ -23,42 +26,33 @@ export const DocumentSuperDeleteEmailTemplate = ({
|
||||
return (
|
||||
<Html>
|
||||
<Head />
|
||||
<Preview>{previewText}</Preview>
|
||||
<Tailwind
|
||||
config={{
|
||||
theme: {
|
||||
extend: {
|
||||
colors: config.theme.extend.colors,
|
||||
},
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Body className="mx-auto my-auto bg-white font-sans">
|
||||
<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"
|
||||
/>
|
||||
<Preview>{_(previewText)}</Preview>
|
||||
|
||||
<TemplateDocumentDelete
|
||||
reason={reason}
|
||||
documentName={documentName}
|
||||
assetBaseUrl={assetBaseUrl}
|
||||
/>
|
||||
</Section>
|
||||
</Container>
|
||||
<Body className="mx-auto my-auto bg-white font-sans">
|
||||
<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"
|
||||
/>
|
||||
|
||||
<Hr className="mx-auto mt-12 max-w-xl" />
|
||||
<TemplateDocumentDelete
|
||||
reason={reason}
|
||||
documentName={documentName}
|
||||
assetBaseUrl={assetBaseUrl}
|
||||
/>
|
||||
</Section>
|
||||
</Container>
|
||||
|
||||
<Container className="mx-auto max-w-xl">
|
||||
<TemplateFooter />
|
||||
</Container>
|
||||
</Section>
|
||||
</Body>
|
||||
</Tailwind>
|
||||
<Hr className="mx-auto mt-12 max-w-xl" />
|
||||
|
||||
<Container className="mx-auto max-w-xl">
|
||||
<TemplateFooter />
|
||||
</Container>
|
||||
</Section>
|
||||
</Body>
|
||||
</Html>
|
||||
);
|
||||
};
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import config from '@documenso/tailwind-config';
|
||||
import { msg } from '@lingui/macro';
|
||||
import { useLingui } from '@lingui/react';
|
||||
|
||||
import { Body, Container, Head, Html, Img, Preview, Section, Tailwind } from '../components';
|
||||
import { Body, Container, Head, Html, Img, Preview, Section } from '../components';
|
||||
import { TemplateFooter } from '../template-components/template-footer';
|
||||
import type { TemplateForgotPasswordProps } from '../template-components/template-forgot-password';
|
||||
import { TemplateForgotPassword } from '../template-components/template-forgot-password';
|
||||
@ -11,7 +12,9 @@ export const ForgotPasswordTemplate = ({
|
||||
resetPasswordLink = 'https://documenso.com',
|
||||
assetBaseUrl = 'http://localhost:3002',
|
||||
}: ForgotPasswordTemplateProps) => {
|
||||
const previewText = `Password Reset Requested`;
|
||||
const { _ } = useLingui();
|
||||
|
||||
const previewText = msg`Password Reset Requested`;
|
||||
|
||||
const getAssetUrl = (path: string) => {
|
||||
return new URL(path, assetBaseUrl).toString();
|
||||
@ -20,41 +23,32 @@ export const ForgotPasswordTemplate = ({
|
||||
return (
|
||||
<Html>
|
||||
<Head />
|
||||
<Preview>{previewText}</Preview>
|
||||
<Tailwind
|
||||
config={{
|
||||
theme: {
|
||||
extend: {
|
||||
colors: config.theme.extend.colors,
|
||||
},
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Body className="mx-auto my-auto bg-white font-sans">
|
||||
<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"
|
||||
/>
|
||||
<Preview>{_(previewText)}</Preview>
|
||||
|
||||
<TemplateForgotPassword
|
||||
resetPasswordLink={resetPasswordLink}
|
||||
assetBaseUrl={assetBaseUrl}
|
||||
/>
|
||||
</Section>
|
||||
</Container>
|
||||
<Body className="mx-auto my-auto bg-white font-sans">
|
||||
<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"
|
||||
/>
|
||||
|
||||
<div className="mx-auto mt-12 max-w-xl" />
|
||||
<TemplateForgotPassword
|
||||
resetPasswordLink={resetPasswordLink}
|
||||
assetBaseUrl={assetBaseUrl}
|
||||
/>
|
||||
</Section>
|
||||
</Container>
|
||||
|
||||
<Container className="mx-auto max-w-xl">
|
||||
<TemplateFooter isDocument={false} />
|
||||
</Container>
|
||||
</Section>
|
||||
</Body>
|
||||
</Tailwind>
|
||||
<div className="mx-auto mt-12 max-w-xl" />
|
||||
|
||||
<Container className="mx-auto max-w-xl">
|
||||
<TemplateFooter isDocument={false} />
|
||||
</Container>
|
||||
</Section>
|
||||
</Body>
|
||||
</Html>
|
||||
);
|
||||
};
|
||||
|
||||
@ -1,17 +1,7 @@
|
||||
import config from '@documenso/tailwind-config';
|
||||
import { msg } from '@lingui/macro';
|
||||
import { useLingui } from '@lingui/react';
|
||||
|
||||
import {
|
||||
Body,
|
||||
Container,
|
||||
Head,
|
||||
Hr,
|
||||
Html,
|
||||
Img,
|
||||
Preview,
|
||||
Section,
|
||||
Tailwind,
|
||||
Text,
|
||||
} from '../components';
|
||||
import { Body, Container, Head, Hr, Html, Img, Preview, Section, Text } from '../components';
|
||||
import type { TemplateDocumentCancelProps } from '../template-components/template-document-cancel';
|
||||
import TemplateDocumentImage from '../template-components/template-document-image';
|
||||
import { TemplateFooter } from '../template-components/template-footer';
|
||||
@ -23,7 +13,9 @@ export const RecipientRemovedFromDocumentTemplate = ({
|
||||
documentName = 'Open Source Pledge.pdf',
|
||||
assetBaseUrl = 'http://localhost:3002',
|
||||
}: DocumentCancelEmailTemplateProps) => {
|
||||
const previewText = `${inviterName} has removed you from the document ${documentName}.`;
|
||||
const { _ } = useLingui();
|
||||
|
||||
const previewText = msg`${inviterName} has removed you from the document ${documentName}.`;
|
||||
|
||||
const getAssetUrl = (path: string) => {
|
||||
return new URL(path, assetBaseUrl).toString();
|
||||
@ -32,45 +24,36 @@ export const RecipientRemovedFromDocumentTemplate = ({
|
||||
return (
|
||||
<Html>
|
||||
<Head />
|
||||
<Preview>{previewText}</Preview>
|
||||
<Tailwind
|
||||
config={{
|
||||
theme: {
|
||||
extend: {
|
||||
colors: config.theme.extend.colors,
|
||||
},
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Body className="mx-auto my-auto bg-white font-sans">
|
||||
<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">
|
||||
<Preview>{_(previewText)}</Preview>
|
||||
|
||||
<Body className="mx-auto my-auto bg-white font-sans">
|
||||
<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"
|
||||
/>
|
||||
|
||||
<TemplateDocumentImage className="mt-6" assetBaseUrl={assetBaseUrl} />
|
||||
|
||||
<Section>
|
||||
<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}"
|
||||
</Text>
|
||||
</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}"
|
||||
</Text>
|
||||
</Section>
|
||||
</Container>
|
||||
</Section>
|
||||
</Container>
|
||||
|
||||
<Hr className="mx-auto mt-12 max-w-xl" />
|
||||
<Hr className="mx-auto mt-12 max-w-xl" />
|
||||
|
||||
<Container className="mx-auto max-w-xl">
|
||||
<TemplateFooter />
|
||||
</Container>
|
||||
</Section>
|
||||
</Body>
|
||||
</Tailwind>
|
||||
<Container className="mx-auto max-w-xl">
|
||||
<TemplateFooter />
|
||||
</Container>
|
||||
</Section>
|
||||
</Body>
|
||||
</Html>
|
||||
);
|
||||
};
|
||||
|
||||
@ -1,18 +1,7 @@
|
||||
import config from '@documenso/tailwind-config';
|
||||
import { Trans, msg } from '@lingui/macro';
|
||||
import { useLingui } from '@lingui/react';
|
||||
|
||||
import {
|
||||
Body,
|
||||
Container,
|
||||
Head,
|
||||
Hr,
|
||||
Html,
|
||||
Img,
|
||||
Link,
|
||||
Preview,
|
||||
Section,
|
||||
Tailwind,
|
||||
Text,
|
||||
} from '../components';
|
||||
import { Body, Container, Head, Hr, Html, Img, Link, Preview, Section, Text } from '../components';
|
||||
import { TemplateFooter } from '../template-components/template-footer';
|
||||
import type { TemplateResetPasswordProps } from '../template-components/template-reset-password';
|
||||
import { TemplateResetPassword } from '../template-components/template-reset-password';
|
||||
@ -24,7 +13,9 @@ export const ResetPasswordTemplate = ({
|
||||
userEmail = 'lucas@documenso.com',
|
||||
assetBaseUrl = 'http://localhost:3002',
|
||||
}: ResetPasswordTemplateProps) => {
|
||||
const previewText = `Password Reset Successful`;
|
||||
const { _ } = useLingui();
|
||||
|
||||
const previewText = msg`Password Reset Successful`;
|
||||
|
||||
const getAssetUrl = (path: string) => {
|
||||
return new URL(path, assetBaseUrl).toString();
|
||||
@ -33,65 +24,62 @@ export const ResetPasswordTemplate = ({
|
||||
return (
|
||||
<Html>
|
||||
<Head />
|
||||
<Preview>{previewText}</Preview>
|
||||
<Tailwind
|
||||
config={{
|
||||
theme: {
|
||||
extend: {
|
||||
colors: config.theme.extend.colors,
|
||||
},
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Body className="mx-auto my-auto bg-white font-sans">
|
||||
<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"
|
||||
/>
|
||||
<Preview>{_(previewText)}</Preview>
|
||||
|
||||
<TemplateResetPassword
|
||||
userName={userName}
|
||||
userEmail={userEmail}
|
||||
assetBaseUrl={assetBaseUrl}
|
||||
/>
|
||||
</Section>
|
||||
</Container>
|
||||
<Body className="mx-auto my-auto bg-white font-sans">
|
||||
<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"
|
||||
/>
|
||||
|
||||
<Container className="mx-auto mt-12 max-w-xl">
|
||||
<Section>
|
||||
<Text className="my-4 text-base font-semibold">
|
||||
<TemplateResetPassword
|
||||
userName={userName}
|
||||
userEmail={userEmail}
|
||||
assetBaseUrl={assetBaseUrl}
|
||||
/>
|
||||
</Section>
|
||||
</Container>
|
||||
|
||||
<Container className="mx-auto mt-12 max-w-xl">
|
||||
<Section>
|
||||
<Text className="my-4 text-base font-semibold">
|
||||
<Trans>
|
||||
Hi, {userName}{' '}
|
||||
<Link className="font-normal text-slate-400" href={`mailto:${userEmail}`}>
|
||||
({userEmail})
|
||||
</Link>
|
||||
</Text>
|
||||
</Trans>
|
||||
</Text>
|
||||
|
||||
<Text className="mt-2 text-base text-slate-400">
|
||||
<Text className="mt-2 text-base text-slate-400">
|
||||
<Trans>
|
||||
We've changed your password as you asked. You can now sign in with your new
|
||||
password.
|
||||
</Text>
|
||||
<Text className="mt-2 text-base text-slate-400">
|
||||
</Trans>
|
||||
</Text>
|
||||
<Text className="mt-2 text-base text-slate-400">
|
||||
<Trans>
|
||||
Didn't request a password change? We are here to help you secure your account,
|
||||
just{' '}
|
||||
<Link className="text-documenso-700 font-normal" href="mailto:hi@documenso.com">
|
||||
contact us.
|
||||
</Link>
|
||||
</Text>
|
||||
</Section>
|
||||
</Container>
|
||||
</Trans>
|
||||
</Text>
|
||||
</Section>
|
||||
</Container>
|
||||
|
||||
<Hr className="mx-auto mt-12 max-w-xl" />
|
||||
<Hr className="mx-auto mt-12 max-w-xl" />
|
||||
|
||||
<Container className="mx-auto max-w-xl">
|
||||
<TemplateFooter isDocument={false} />
|
||||
</Container>
|
||||
</Section>
|
||||
</Body>
|
||||
</Tailwind>
|
||||
<Container className="mx-auto max-w-xl">
|
||||
<TemplateFooter isDocument={false} />
|
||||
</Container>
|
||||
</Section>
|
||||
</Body>
|
||||
</Html>
|
||||
);
|
||||
};
|
||||
|
||||
@ -1,7 +1,9 @@
|
||||
import { formatTeamUrl } from '@documenso/lib/utils/teams';
|
||||
import config from '@documenso/tailwind-config';
|
||||
import { msg } from '@lingui/macro';
|
||||
import { useLingui } from '@lingui/react';
|
||||
|
||||
import { Body, Container, Head, Hr, Html, Preview, Section, Tailwind, Text } from '../components';
|
||||
import { formatTeamUrl } from '@documenso/lib/utils/teams';
|
||||
|
||||
import { Body, Container, Head, Hr, Html, Preview, Section, Text } from '../components';
|
||||
import { TemplateFooter } from '../template-components/template-footer';
|
||||
import TemplateImage from '../template-components/template-image';
|
||||
|
||||
@ -18,67 +20,60 @@ export const TeamDeleteEmailTemplate = ({
|
||||
teamUrl = 'demo',
|
||||
isOwner = false,
|
||||
}: TeamDeleteEmailProps) => {
|
||||
const { _ } = useLingui();
|
||||
|
||||
const previewText = isOwner
|
||||
? 'Your team has been deleted'
|
||||
: 'A team you were a part of has been deleted';
|
||||
? msg`Your team has been deleted`
|
||||
: msg`A team you were a part of has been deleted`;
|
||||
|
||||
const title = isOwner
|
||||
? 'Your team has been deleted'
|
||||
: 'A team you were a part of has been deleted';
|
||||
? msg`Your team has been deleted`
|
||||
: msg`A team you were a part of has been deleted`;
|
||||
|
||||
const description = isOwner
|
||||
? 'The following team has been deleted by you'
|
||||
: 'The following team has been deleted by its owner. You will no longer be able to access this team and its documents';
|
||||
? msg`The following team has been deleted by you`
|
||||
: msg`The following team has been deleted by its owner. You will no longer be able to access this team and its documents`;
|
||||
|
||||
return (
|
||||
<Html>
|
||||
<Head />
|
||||
<Preview>{previewText}</Preview>
|
||||
<Tailwind
|
||||
config={{
|
||||
theme: {
|
||||
extend: {
|
||||
colors: config.theme.extend.colors,
|
||||
},
|
||||
},
|
||||
}}
|
||||
>
|
||||
<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">
|
||||
<Preview>{_(previewText)}</Preview>
|
||||
|
||||
<Body className="mx-auto my-auto font-sans">
|
||||
<Section className="bg-white text-slate-500">
|
||||
<Container className="mx-auto mb-2 mt-8 max-w-xl rounded-lg border border-solid border-slate-200 p-2 backdrop-blur-sm">
|
||||
<TemplateImage
|
||||
assetBaseUrl={assetBaseUrl}
|
||||
className="mb-4 h-6 p-2"
|
||||
staticAsset="logo.png"
|
||||
/>
|
||||
|
||||
<Section>
|
||||
<TemplateImage
|
||||
className="mx-auto"
|
||||
assetBaseUrl={assetBaseUrl}
|
||||
className="mb-4 h-6 p-2"
|
||||
staticAsset="logo.png"
|
||||
staticAsset="delete-team.png"
|
||||
/>
|
||||
</Section>
|
||||
|
||||
<Section>
|
||||
<TemplateImage
|
||||
className="mx-auto"
|
||||
assetBaseUrl={assetBaseUrl}
|
||||
staticAsset="delete-team.png"
|
||||
/>
|
||||
</Section>
|
||||
<Section className="p-2 text-slate-500">
|
||||
<Text className="text-center text-lg font-medium text-black">{_(title)}</Text>
|
||||
|
||||
<Section className="p-2 text-slate-500">
|
||||
<Text className="text-center text-lg font-medium text-black">{title}</Text>
|
||||
<Text className="my-1 text-center text-base">{_(description)}</Text>
|
||||
|
||||
<Text className="my-1 text-center text-base">{description}</Text>
|
||||
<div className="mx-auto my-2 w-fit rounded-lg bg-gray-50 px-4 py-2 text-base font-medium text-slate-600">
|
||||
{formatTeamUrl(teamUrl, baseUrl)}
|
||||
</div>
|
||||
</Section>
|
||||
</Container>
|
||||
|
||||
<div className="mx-auto my-2 w-fit rounded-lg bg-gray-50 px-4 py-2 text-base font-medium text-slate-600">
|
||||
{formatTeamUrl(teamUrl, baseUrl)}
|
||||
</div>
|
||||
</Section>
|
||||
</Container>
|
||||
<Hr className="mx-auto mt-12 max-w-xl" />
|
||||
|
||||
<Hr className="mx-auto mt-12 max-w-xl" />
|
||||
|
||||
<Container className="mx-auto max-w-xl">
|
||||
<TemplateFooter isDocument={false} />
|
||||
</Container>
|
||||
</Section>
|
||||
</Body>
|
||||
</Tailwind>
|
||||
<Container className="mx-auto max-w-xl">
|
||||
<TemplateFooter isDocument={false} />
|
||||
</Container>
|
||||
</Section>
|
||||
</Body>
|
||||
</Html>
|
||||
);
|
||||
};
|
||||
|
||||
@ -1,7 +1,9 @@
|
||||
import { formatTeamUrl } from '@documenso/lib/utils/teams';
|
||||
import config from '@documenso/tailwind-config';
|
||||
import { Trans, msg } from '@lingui/macro';
|
||||
import { useLingui } from '@lingui/react';
|
||||
|
||||
import { Body, Container, Head, Hr, Html, Preview, Section, Tailwind, Text } from '../components';
|
||||
import { formatTeamUrl } from '@documenso/lib/utils/teams';
|
||||
|
||||
import { Body, Container, Head, Hr, Html, Preview, Section, Text } from '../components';
|
||||
import { TemplateFooter } from '../template-components/template-footer';
|
||||
import TemplateImage from '../template-components/template-image';
|
||||
|
||||
@ -20,62 +22,57 @@ export const TeamEmailRemovedTemplate = ({
|
||||
teamName = 'Team Name',
|
||||
teamUrl = 'demo',
|
||||
}: TeamEmailRemovedTemplateProps) => {
|
||||
const previewText = `Team email removed for ${teamName} on Documenso`;
|
||||
const { _ } = useLingui();
|
||||
|
||||
const previewText = msg`Team email removed for ${teamName} on Documenso`;
|
||||
|
||||
return (
|
||||
<Html>
|
||||
<Head />
|
||||
<Preview>{previewText}</Preview>
|
||||
<Tailwind
|
||||
config={{
|
||||
theme: {
|
||||
extend: {
|
||||
colors: config.theme.extend.colors,
|
||||
},
|
||||
},
|
||||
}}
|
||||
>
|
||||
<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">
|
||||
<Preview>{_(previewText)}</Preview>
|
||||
|
||||
<Body className="mx-auto my-auto font-sans">
|
||||
<Section className="bg-white text-slate-500">
|
||||
<Container className="mx-auto mb-2 mt-8 max-w-xl rounded-lg border border-solid border-slate-200 px-2 pt-2 backdrop-blur-sm">
|
||||
<TemplateImage
|
||||
assetBaseUrl={assetBaseUrl}
|
||||
className="mb-4 h-6 p-2"
|
||||
staticAsset="logo.png"
|
||||
/>
|
||||
|
||||
<Section>
|
||||
<TemplateImage
|
||||
className="mx-auto"
|
||||
assetBaseUrl={assetBaseUrl}
|
||||
className="mb-4 h-6 p-2"
|
||||
staticAsset="logo.png"
|
||||
staticAsset="mail-open-alert.png"
|
||||
/>
|
||||
</Section>
|
||||
|
||||
<Section>
|
||||
<TemplateImage
|
||||
className="mx-auto"
|
||||
assetBaseUrl={assetBaseUrl}
|
||||
staticAsset="mail-open-alert.png"
|
||||
/>
|
||||
</Section>
|
||||
<Section className="p-2 text-slate-500">
|
||||
<Text className="text-center text-lg font-medium text-black">
|
||||
<Trans>Team email removed</Trans>
|
||||
</Text>
|
||||
|
||||
<Section className="p-2 text-slate-500">
|
||||
<Text className="text-center text-lg font-medium text-black">
|
||||
Team email removed
|
||||
</Text>
|
||||
|
||||
<Text className="my-1 text-center text-base">
|
||||
<Text className="my-1 text-center text-base">
|
||||
<Trans>
|
||||
The team email <span className="font-bold">{teamEmail}</span> has been removed
|
||||
from the following team
|
||||
</Text>
|
||||
</Trans>
|
||||
</Text>
|
||||
|
||||
<div className="mx-auto mb-6 mt-2 w-fit rounded-lg bg-gray-50 px-4 py-2 text-base font-medium text-slate-600">
|
||||
{formatTeamUrl(teamUrl, baseUrl)}
|
||||
</div>
|
||||
</Section>
|
||||
</Container>
|
||||
<div className="mx-auto mb-6 mt-2 w-fit rounded-lg bg-gray-50 px-4 py-2 text-base font-medium text-slate-600">
|
||||
{formatTeamUrl(teamUrl, baseUrl)}
|
||||
</div>
|
||||
</Section>
|
||||
</Container>
|
||||
|
||||
<Hr className="mx-auto mt-12 max-w-xl" />
|
||||
<Hr className="mx-auto mt-12 max-w-xl" />
|
||||
|
||||
<Container className="mx-auto max-w-xl">
|
||||
<TemplateFooter isDocument={false} />
|
||||
</Container>
|
||||
</Section>
|
||||
</Body>
|
||||
</Tailwind>
|
||||
<Container className="mx-auto max-w-xl">
|
||||
<TemplateFooter isDocument={false} />
|
||||
</Container>
|
||||
</Section>
|
||||
</Body>
|
||||
</Html>
|
||||
);
|
||||
};
|
||||
|
||||
@ -1,18 +1,9 @@
|
||||
import { formatTeamUrl } from '@documenso/lib/utils/teams';
|
||||
import config from '@documenso/tailwind-config';
|
||||
import { Trans, msg } from '@lingui/macro';
|
||||
import { useLingui } from '@lingui/react';
|
||||
|
||||
import {
|
||||
Body,
|
||||
Button,
|
||||
Container,
|
||||
Head,
|
||||
Hr,
|
||||
Html,
|
||||
Preview,
|
||||
Section,
|
||||
Tailwind,
|
||||
Text,
|
||||
} from '../components';
|
||||
import { formatTeamUrl } from '@documenso/lib/utils/teams';
|
||||
|
||||
import { Body, Button, Container, Head, Hr, Html, Preview, Section, Text } from '../components';
|
||||
import { TemplateFooter } from '../template-components/template-footer';
|
||||
import TemplateImage from '../template-components/template-image';
|
||||
|
||||
@ -33,80 +24,75 @@ export const TeamInviteEmailTemplate = ({
|
||||
teamUrl = 'demo',
|
||||
token = '',
|
||||
}: TeamInviteEmailProps) => {
|
||||
const previewText = `Accept invitation to join a team on Documenso`;
|
||||
const { _ } = useLingui();
|
||||
|
||||
const previewText = msg`Accept invitation to join a team on Documenso`;
|
||||
|
||||
return (
|
||||
<Html>
|
||||
<Head />
|
||||
<Preview>{previewText}</Preview>
|
||||
<Tailwind
|
||||
config={{
|
||||
theme: {
|
||||
extend: {
|
||||
colors: config.theme.extend.colors,
|
||||
},
|
||||
},
|
||||
}}
|
||||
>
|
||||
<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">
|
||||
<Preview>{_(previewText)}</Preview>
|
||||
|
||||
<Body className="mx-auto my-auto font-sans">
|
||||
<Section className="bg-white text-slate-500">
|
||||
<Container className="mx-auto mb-2 mt-8 max-w-xl rounded-lg border border-solid border-slate-200 p-2 backdrop-blur-sm">
|
||||
<TemplateImage
|
||||
assetBaseUrl={assetBaseUrl}
|
||||
className="mb-4 h-6 p-2"
|
||||
staticAsset="logo.png"
|
||||
/>
|
||||
|
||||
<Section>
|
||||
<TemplateImage
|
||||
className="mx-auto"
|
||||
assetBaseUrl={assetBaseUrl}
|
||||
className="mb-4 h-6 p-2"
|
||||
staticAsset="logo.png"
|
||||
staticAsset="add-user.png"
|
||||
/>
|
||||
</Section>
|
||||
|
||||
<Section>
|
||||
<TemplateImage
|
||||
className="mx-auto"
|
||||
assetBaseUrl={assetBaseUrl}
|
||||
staticAsset="add-user.png"
|
||||
/>
|
||||
</Section>
|
||||
<Section className="p-2 text-slate-500">
|
||||
<Text className="text-center text-lg font-medium text-black">
|
||||
<Trans>Join {teamName} on Documenso</Trans>
|
||||
</Text>
|
||||
|
||||
<Section className="p-2 text-slate-500">
|
||||
<Text className="text-center text-lg font-medium text-black">
|
||||
Join {teamName} on Documenso
|
||||
</Text>
|
||||
<Text className="my-1 text-center text-base">
|
||||
<Trans>You have been invited to join the following team</Trans>
|
||||
</Text>
|
||||
|
||||
<Text className="my-1 text-center text-base">
|
||||
You have been invited to join the following team
|
||||
</Text>
|
||||
<div className="mx-auto my-2 w-fit rounded-lg bg-gray-50 px-4 py-2 text-base font-medium text-slate-600">
|
||||
{formatTeamUrl(teamUrl, baseUrl)}
|
||||
</div>
|
||||
|
||||
<div className="mx-auto my-2 w-fit rounded-lg bg-gray-50 px-4 py-2 text-base font-medium text-slate-600">
|
||||
{formatTeamUrl(teamUrl, baseUrl)}
|
||||
</div>
|
||||
|
||||
<Text className="my-1 text-center text-base">
|
||||
<Text className="my-1 text-center text-base">
|
||||
<Trans>
|
||||
by <span className="text-slate-900">{senderName}</span>
|
||||
</Text>
|
||||
</Trans>
|
||||
</Text>
|
||||
|
||||
<Section className="mb-6 mt-6 text-center">
|
||||
<Button
|
||||
className="bg-documenso-500 inline-flex items-center justify-center rounded-lg px-6 py-3 text-center text-sm font-medium text-black no-underline"
|
||||
href={`${baseUrl}/team/invite/${token}`}
|
||||
>
|
||||
Accept
|
||||
</Button>
|
||||
<Button
|
||||
className="ml-4 inline-flex items-center justify-center rounded-lg bg-gray-50 px-6 py-3 text-center text-sm font-medium text-slate-600 no-underline"
|
||||
href={`${baseUrl}/team/decline/${token}`}
|
||||
>
|
||||
Decline
|
||||
</Button>
|
||||
</Section>
|
||||
<Section className="mb-6 mt-6 text-center">
|
||||
<Button
|
||||
className="bg-documenso-500 inline-flex items-center justify-center rounded-lg px-6 py-3 text-center text-sm font-medium text-black no-underline"
|
||||
href={`${baseUrl}/team/invite/${token}`}
|
||||
>
|
||||
<Trans>Accept</Trans>
|
||||
</Button>
|
||||
<Button
|
||||
className="ml-4 inline-flex items-center justify-center rounded-lg bg-gray-50 px-6 py-3 text-center text-sm font-medium text-slate-600 no-underline"
|
||||
href={`${baseUrl}/team/decline/${token}`}
|
||||
>
|
||||
<Trans>Decline</Trans>
|
||||
</Button>
|
||||
</Section>
|
||||
</Container>
|
||||
</Section>
|
||||
</Container>
|
||||
|
||||
<Hr className="mx-auto mt-12 max-w-xl" />
|
||||
<Hr className="mx-auto mt-12 max-w-xl" />
|
||||
|
||||
<Container className="mx-auto max-w-xl">
|
||||
<TemplateFooter isDocument={false} />
|
||||
</Container>
|
||||
</Section>
|
||||
</Body>
|
||||
</Tailwind>
|
||||
<Container className="mx-auto max-w-xl">
|
||||
<TemplateFooter isDocument={false} />
|
||||
</Container>
|
||||
</Section>
|
||||
</Body>
|
||||
</Html>
|
||||
);
|
||||
};
|
||||
|
||||
@ -1,7 +1,9 @@
|
||||
import { formatTeamUrl } from '@documenso/lib/utils/teams';
|
||||
import config from '@documenso/tailwind-config';
|
||||
import { Trans, msg } from '@lingui/macro';
|
||||
import { useLingui } from '@lingui/react';
|
||||
|
||||
import { Body, Container, Head, Hr, Html, Preview, Section, Tailwind, Text } from '../components';
|
||||
import { formatTeamUrl } from '@documenso/lib/utils/teams';
|
||||
|
||||
import { Body, Container, Head, Hr, Html, Preview, Section, Text } from '../components';
|
||||
import { TemplateFooter } from '../template-components/template-footer';
|
||||
import TemplateImage from '../template-components/template-image';
|
||||
|
||||
@ -22,61 +24,56 @@ export const TeamJoinEmailTemplate = ({
|
||||
teamName = 'Team Name',
|
||||
teamUrl = 'demo',
|
||||
}: TeamJoinEmailProps) => {
|
||||
const previewText = 'A team member has joined a team on Documenso';
|
||||
const { _ } = useLingui();
|
||||
|
||||
const previewText = msg`A team member has joined a team on Documenso`;
|
||||
|
||||
return (
|
||||
<Html>
|
||||
<Head />
|
||||
<Preview>{previewText}</Preview>
|
||||
<Tailwind
|
||||
config={{
|
||||
theme: {
|
||||
extend: {
|
||||
colors: config.theme.extend.colors,
|
||||
},
|
||||
},
|
||||
}}
|
||||
>
|
||||
<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">
|
||||
<Preview>{_(previewText)}</Preview>
|
||||
|
||||
<Body className="mx-auto my-auto font-sans">
|
||||
<Section className="bg-white text-slate-500">
|
||||
<Container className="mx-auto mb-2 mt-8 max-w-xl rounded-lg border border-solid border-slate-200 p-2 backdrop-blur-sm">
|
||||
<TemplateImage
|
||||
assetBaseUrl={assetBaseUrl}
|
||||
className="mb-4 h-6 p-2"
|
||||
staticAsset="logo.png"
|
||||
/>
|
||||
|
||||
<Section>
|
||||
<TemplateImage
|
||||
className="mx-auto"
|
||||
assetBaseUrl={assetBaseUrl}
|
||||
className="mb-4 h-6 p-2"
|
||||
staticAsset="logo.png"
|
||||
staticAsset="add-user.png"
|
||||
/>
|
||||
</Section>
|
||||
|
||||
<Section>
|
||||
<TemplateImage
|
||||
className="mx-auto"
|
||||
assetBaseUrl={assetBaseUrl}
|
||||
staticAsset="add-user.png"
|
||||
/>
|
||||
</Section>
|
||||
|
||||
<Section className="p-2 text-slate-500">
|
||||
<Text className="text-center text-lg font-medium text-black">
|
||||
<Section className="p-2 text-slate-500">
|
||||
<Text className="text-center text-lg font-medium text-black">
|
||||
<Trans>
|
||||
{memberName || memberEmail} joined the team {teamName} on Documenso
|
||||
</Text>
|
||||
</Trans>
|
||||
</Text>
|
||||
|
||||
<Text className="my-1 text-center text-base">
|
||||
{memberEmail} joined the following team
|
||||
</Text>
|
||||
<Text className="my-1 text-center text-base">
|
||||
<Trans>{memberEmail} joined the following team</Trans>
|
||||
</Text>
|
||||
|
||||
<div className="mx-auto my-2 w-fit rounded-lg bg-gray-50 px-4 py-2 text-base font-medium text-slate-600">
|
||||
{formatTeamUrl(teamUrl, baseUrl)}
|
||||
</div>
|
||||
</Section>
|
||||
</Container>
|
||||
<div className="mx-auto my-2 w-fit rounded-lg bg-gray-50 px-4 py-2 text-base font-medium text-slate-600">
|
||||
{formatTeamUrl(teamUrl, baseUrl)}
|
||||
</div>
|
||||
</Section>
|
||||
</Container>
|
||||
|
||||
<Hr className="mx-auto mt-12 max-w-xl" />
|
||||
<Hr className="mx-auto mt-12 max-w-xl" />
|
||||
|
||||
<Container className="mx-auto max-w-xl">
|
||||
<TemplateFooter isDocument={false} />
|
||||
</Container>
|
||||
</Section>
|
||||
</Body>
|
||||
</Tailwind>
|
||||
<Container className="mx-auto max-w-xl">
|
||||
<TemplateFooter isDocument={false} />
|
||||
</Container>
|
||||
</Section>
|
||||
</Body>
|
||||
</Html>
|
||||
);
|
||||
};
|
||||
|
||||
@ -1,7 +1,9 @@
|
||||
import { formatTeamUrl } from '@documenso/lib/utils/teams';
|
||||
import config from '@documenso/tailwind-config';
|
||||
import { Trans, msg } from '@lingui/macro';
|
||||
import { useLingui } from '@lingui/react';
|
||||
|
||||
import { Body, Container, Head, Hr, Html, Preview, Section, Tailwind, Text } from '../components';
|
||||
import { formatTeamUrl } from '@documenso/lib/utils/teams';
|
||||
|
||||
import { Body, Container, Head, Hr, Html, Preview, Section, Text } from '../components';
|
||||
import { TemplateFooter } from '../template-components/template-footer';
|
||||
import TemplateImage from '../template-components/template-image';
|
||||
|
||||
@ -22,61 +24,56 @@ export const TeamLeaveEmailTemplate = ({
|
||||
teamName = 'Team Name',
|
||||
teamUrl = 'demo',
|
||||
}: TeamLeaveEmailProps) => {
|
||||
const previewText = 'A team member has left a team on Documenso';
|
||||
const { _ } = useLingui();
|
||||
|
||||
const previewText = msg`A team member has left a team on Documenso`;
|
||||
|
||||
return (
|
||||
<Html>
|
||||
<Head />
|
||||
<Preview>{previewText}</Preview>
|
||||
<Tailwind
|
||||
config={{
|
||||
theme: {
|
||||
extend: {
|
||||
colors: config.theme.extend.colors,
|
||||
},
|
||||
},
|
||||
}}
|
||||
>
|
||||
<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">
|
||||
<Preview>{_(previewText)}</Preview>
|
||||
|
||||
<Body className="mx-auto my-auto font-sans">
|
||||
<Section className="bg-white text-slate-500">
|
||||
<Container className="mx-auto mb-2 mt-8 max-w-xl rounded-lg border border-solid border-slate-200 p-2 backdrop-blur-sm">
|
||||
<TemplateImage
|
||||
assetBaseUrl={assetBaseUrl}
|
||||
className="mb-4 h-6 p-2"
|
||||
staticAsset="logo.png"
|
||||
/>
|
||||
|
||||
<Section>
|
||||
<TemplateImage
|
||||
className="mx-auto"
|
||||
assetBaseUrl={assetBaseUrl}
|
||||
className="mb-4 h-6 p-2"
|
||||
staticAsset="logo.png"
|
||||
staticAsset="delete-user.png"
|
||||
/>
|
||||
</Section>
|
||||
|
||||
<Section>
|
||||
<TemplateImage
|
||||
className="mx-auto"
|
||||
assetBaseUrl={assetBaseUrl}
|
||||
staticAsset="delete-user.png"
|
||||
/>
|
||||
</Section>
|
||||
|
||||
<Section className="p-2 text-slate-500">
|
||||
<Text className="text-center text-lg font-medium text-black">
|
||||
<Section className="p-2 text-slate-500">
|
||||
<Text className="text-center text-lg font-medium text-black">
|
||||
<Trans>
|
||||
{memberName || memberEmail} left the team {teamName} on Documenso
|
||||
</Text>
|
||||
</Trans>
|
||||
</Text>
|
||||
|
||||
<Text className="my-1 text-center text-base">
|
||||
{memberEmail} left the following team
|
||||
</Text>
|
||||
<Text className="my-1 text-center text-base">
|
||||
<Trans>{memberEmail} left the following team</Trans>
|
||||
</Text>
|
||||
|
||||
<div className="mx-auto my-2 w-fit rounded-lg bg-gray-50 px-4 py-2 text-base font-medium text-slate-600">
|
||||
{formatTeamUrl(teamUrl, baseUrl)}
|
||||
</div>
|
||||
</Section>
|
||||
</Container>
|
||||
<div className="mx-auto my-2 w-fit rounded-lg bg-gray-50 px-4 py-2 text-base font-medium text-slate-600">
|
||||
{formatTeamUrl(teamUrl, baseUrl)}
|
||||
</div>
|
||||
</Section>
|
||||
</Container>
|
||||
|
||||
<Hr className="mx-auto mt-12 max-w-xl" />
|
||||
<Hr className="mx-auto mt-12 max-w-xl" />
|
||||
|
||||
<Container className="mx-auto max-w-xl">
|
||||
<TemplateFooter isDocument={false} />
|
||||
</Container>
|
||||
</Section>
|
||||
</Body>
|
||||
</Tailwind>
|
||||
<Container className="mx-auto max-w-xl">
|
||||
<TemplateFooter isDocument={false} />
|
||||
</Container>
|
||||
</Section>
|
||||
</Body>
|
||||
</Html>
|
||||
);
|
||||
};
|
||||
|
||||
@ -1,18 +1,9 @@
|
||||
import { formatTeamUrl } from '@documenso/lib/utils/teams';
|
||||
import config from '@documenso/tailwind-config';
|
||||
import { Trans, msg } from '@lingui/macro';
|
||||
import { useLingui } from '@lingui/react';
|
||||
|
||||
import {
|
||||
Body,
|
||||
Button,
|
||||
Container,
|
||||
Head,
|
||||
Hr,
|
||||
Html,
|
||||
Preview,
|
||||
Section,
|
||||
Tailwind,
|
||||
Text,
|
||||
} from '../components';
|
||||
import { formatTeamUrl } from '@documenso/lib/utils/teams';
|
||||
|
||||
import { Body, Button, Container, Head, Hr, Html, Preview, Section, Text } from '../components';
|
||||
import { TemplateFooter } from '../template-components/template-footer';
|
||||
import TemplateImage from '../template-components/template-image';
|
||||
|
||||
@ -33,78 +24,77 @@ export const TeamTransferRequestTemplate = ({
|
||||
teamUrl = 'demo',
|
||||
token = '',
|
||||
}: TeamTransferRequestTemplateProps) => {
|
||||
const previewText = 'Accept team transfer request on Documenso';
|
||||
const { _ } = useLingui();
|
||||
|
||||
const previewText = msg`Accept team transfer request on Documenso`;
|
||||
|
||||
return (
|
||||
<Html>
|
||||
<Head />
|
||||
<Preview>{previewText}</Preview>
|
||||
<Tailwind
|
||||
config={{
|
||||
theme: {
|
||||
extend: {
|
||||
colors: config.theme.extend.colors,
|
||||
},
|
||||
},
|
||||
}}
|
||||
>
|
||||
<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">
|
||||
<Preview>{_(previewText)}</Preview>
|
||||
|
||||
<Body className="mx-auto my-auto font-sans">
|
||||
<Section className="bg-white text-slate-500">
|
||||
<Container className="mx-auto mb-2 mt-8 max-w-xl rounded-lg border border-solid border-slate-200 px-2 pt-2 backdrop-blur-sm">
|
||||
<TemplateImage
|
||||
assetBaseUrl={assetBaseUrl}
|
||||
className="mb-4 h-6 p-2"
|
||||
staticAsset="logo.png"
|
||||
/>
|
||||
|
||||
<Section>
|
||||
<TemplateImage
|
||||
className="mx-auto"
|
||||
assetBaseUrl={assetBaseUrl}
|
||||
className="mb-4 h-6 p-2"
|
||||
staticAsset="logo.png"
|
||||
staticAsset="add-user.png"
|
||||
/>
|
||||
</Section>
|
||||
|
||||
<Section>
|
||||
<TemplateImage
|
||||
className="mx-auto"
|
||||
assetBaseUrl={assetBaseUrl}
|
||||
staticAsset="add-user.png"
|
||||
/>
|
||||
</Section>
|
||||
<Section className="p-2 text-slate-500">
|
||||
<Text className="text-center text-lg font-medium text-black">
|
||||
<Trans>{teamName} ownership transfer request</Trans>
|
||||
</Text>
|
||||
|
||||
<Section className="p-2 text-slate-500">
|
||||
<Text className="text-center text-lg font-medium text-black">
|
||||
{teamName} ownership transfer request
|
||||
</Text>
|
||||
|
||||
<Text className="my-1 text-center text-base">
|
||||
<Text className="my-1 text-center text-base">
|
||||
<Trans>
|
||||
<span className="font-bold">{senderName}</span> has requested that you take
|
||||
ownership of the following team
|
||||
</Text>
|
||||
</Trans>
|
||||
</Text>
|
||||
|
||||
<div className="mx-auto my-2 w-fit rounded-lg bg-gray-50 px-4 py-2 text-base font-medium text-slate-600">
|
||||
{formatTeamUrl(teamUrl, baseUrl)}
|
||||
</div>
|
||||
<div className="mx-auto my-2 w-fit rounded-lg bg-gray-50 px-4 py-2 text-base font-medium text-slate-600">
|
||||
{formatTeamUrl(teamUrl, baseUrl)}
|
||||
</div>
|
||||
|
||||
<Text className="text-center text-sm">
|
||||
<Text className="text-center text-sm">
|
||||
<Trans>
|
||||
By accepting this request, you will take responsibility for any billing items
|
||||
associated with this team.
|
||||
</Text>
|
||||
</Trans>
|
||||
</Text>
|
||||
|
||||
<Section className="mb-6 mt-6 text-center">
|
||||
<Button
|
||||
className="bg-documenso-500 ml-2 inline-flex items-center justify-center rounded-lg px-6 py-3 text-center text-sm font-medium text-black no-underline"
|
||||
href={`${baseUrl}/team/verify/transfer/${token}`}
|
||||
>
|
||||
Accept
|
||||
</Button>
|
||||
</Section>
|
||||
<Section className="mb-6 mt-6 text-center">
|
||||
<Button
|
||||
className="bg-documenso-500 ml-2 inline-flex items-center justify-center rounded-lg px-6 py-3 text-center text-sm font-medium text-black no-underline"
|
||||
href={`${baseUrl}/team/verify/transfer/${token}`}
|
||||
>
|
||||
<Trans>Accept</Trans>
|
||||
</Button>
|
||||
</Section>
|
||||
</Section>
|
||||
|
||||
<Text className="text-center text-xs">Link expires in 1 hour.</Text>
|
||||
</Container>
|
||||
<Text className="text-center text-xs">
|
||||
<Trans>Link expires in 1 hour.</Trans>
|
||||
</Text>
|
||||
</Container>
|
||||
|
||||
<Hr className="mx-auto mt-12 max-w-xl" />
|
||||
<Hr className="mx-auto mt-12 max-w-xl" />
|
||||
|
||||
<Container className="mx-auto max-w-xl">
|
||||
<TemplateFooter isDocument={false} />
|
||||
</Container>
|
||||
</Section>
|
||||
</Body>
|
||||
</Tailwind>
|
||||
<Container className="mx-auto max-w-xl">
|
||||
<TemplateFooter isDocument={false} />
|
||||
</Container>
|
||||
</Section>
|
||||
</Body>
|
||||
</Html>
|
||||
);
|
||||
};
|
||||
|
||||
@ -7,58 +7,94 @@ import { setupI18n } from '@lingui/core';
|
||||
import { setI18n } from '@lingui/react/server';
|
||||
|
||||
import { IS_APP_WEB } from '../../constants/app';
|
||||
import { SUPPORTED_LANGUAGE_CODES } from '../../constants/i18n';
|
||||
import {
|
||||
APP_I18N_OPTIONS,
|
||||
SUPPORTED_LANGUAGE_CODES,
|
||||
isValidLanguageCode,
|
||||
} from '../../constants/i18n';
|
||||
import { extractLocaleData } from '../../utils/i18n';
|
||||
import { remember } from '../../utils/remember';
|
||||
|
||||
type SupportedLanguages = (typeof SUPPORTED_LANGUAGE_CODES)[number];
|
||||
|
||||
async function loadCatalog(lang: SupportedLanguages): Promise<{
|
||||
export async function loadCatalog(lang: SupportedLanguages): Promise<{
|
||||
[k: string]: Messages;
|
||||
}> {
|
||||
const extension = process.env.NODE_ENV === 'development' ? 'po' : 'js';
|
||||
const context = IS_APP_WEB ? 'web' : 'marketing';
|
||||
|
||||
const { messages } = await import(`../../translations/${lang}/${context}.${extension}`);
|
||||
let { messages } = await import(`../../translations/${lang}/${context}.${extension}`);
|
||||
|
||||
if (extension === 'po') {
|
||||
const { messages: commonMessages } = await import(
|
||||
`../../translations/${lang}/common.${extension}`
|
||||
);
|
||||
|
||||
messages = { ...messages, ...commonMessages };
|
||||
}
|
||||
|
||||
return {
|
||||
[lang]: messages,
|
||||
};
|
||||
}
|
||||
|
||||
const catalogs = await Promise.all(SUPPORTED_LANGUAGE_CODES.map(loadCatalog));
|
||||
const catalogs = Promise.all(SUPPORTED_LANGUAGE_CODES.map(loadCatalog));
|
||||
|
||||
// transform array of catalogs into a single object
|
||||
export const allMessages = catalogs.reduce((acc, oneCatalog) => {
|
||||
return { ...acc, ...oneCatalog };
|
||||
}, {});
|
||||
const allMessages = async () => {
|
||||
return await catalogs.then((catalogs) =>
|
||||
catalogs.reduce((acc, oneCatalog) => {
|
||||
return {
|
||||
...acc,
|
||||
...oneCatalog,
|
||||
};
|
||||
}, {}),
|
||||
);
|
||||
};
|
||||
|
||||
type AllI18nInstances = { [K in SupportedLanguages]: I18n };
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
||||
export const allI18nInstances = SUPPORTED_LANGUAGE_CODES.reduce((acc, lang) => {
|
||||
const messages = allMessages[lang] ?? {};
|
||||
export const allI18nInstances = remember('i18n.allI18nInstances', async () => {
|
||||
const loadedMessages = await allMessages();
|
||||
|
||||
const i18n = setupI18n({
|
||||
locale: lang,
|
||||
messages: { [lang]: messages },
|
||||
});
|
||||
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
||||
return SUPPORTED_LANGUAGE_CODES.reduce((acc, lang) => {
|
||||
const messages = loadedMessages[lang] ?? {};
|
||||
|
||||
return { ...acc, [lang]: i18n };
|
||||
}, {}) as AllI18nInstances;
|
||||
const i18n = setupI18n({
|
||||
locale: lang,
|
||||
messages: { [lang]: messages },
|
||||
});
|
||||
|
||||
return { ...acc, [lang]: i18n };
|
||||
}, {}) as AllI18nInstances;
|
||||
});
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/ban-types
|
||||
export const getI18nInstance = async (lang?: SupportedLanguages | (string & {})) => {
|
||||
const instances = await allI18nInstances;
|
||||
|
||||
if (!isValidLanguageCode(lang)) {
|
||||
return instances[APP_I18N_OPTIONS.sourceLang];
|
||||
}
|
||||
|
||||
return instances[lang] ?? instances[APP_I18N_OPTIONS.sourceLang];
|
||||
};
|
||||
|
||||
/**
|
||||
* This needs to be run in all layouts and page server components that require i18n.
|
||||
*
|
||||
* https://lingui.dev/tutorials/react-rsc#pages-layouts-and-lingui
|
||||
*/
|
||||
export const setupI18nSSR = () => {
|
||||
export const setupI18nSSR = async () => {
|
||||
const { lang, locales } = extractLocaleData({
|
||||
cookies: cookies(),
|
||||
headers: headers(),
|
||||
});
|
||||
|
||||
// Get and set a ready-made i18n instance for the given language.
|
||||
const i18n = allI18nInstances[lang];
|
||||
const i18n = await getI18nInstance(lang);
|
||||
|
||||
// Reactivate the i18n instance with the locale for date and number formatting.
|
||||
i18n.activate(lang, locales);
|
||||
|
||||
@ -47,3 +47,6 @@ export const SUPPORTED_LANGUAGES: Record<string, SupportedLanguage> = {
|
||||
short: 'es',
|
||||
},
|
||||
} satisfies Record<SupportedLanguageCodes, SupportedLanguage>;
|
||||
|
||||
export const isValidLanguageCode = (code: unknown): code is SupportedLanguageCodes =>
|
||||
SUPPORTED_LANGUAGE_CODES.includes(code as SupportedLanguageCodes);
|
||||
|
||||
@ -41,31 +41,35 @@ export const RECIPIENT_ROLES_DESCRIPTION_ENG = {
|
||||
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>;
|
||||
|
||||
export const RECIPIENT_ROLE_TO_EMAIL_TYPE = {
|
||||
[RecipientRole.SIGNER]: 'SIGNING_REQUEST',
|
||||
[RecipientRole.VIEWER]: 'VIEW_REQUEST',
|
||||
[RecipientRole.APPROVER]: 'APPROVE_REQUEST',
|
||||
[RecipientRole.SIGNER]: `SIGNING_REQUEST`,
|
||||
[RecipientRole.VIEWER]: `VIEW_REQUEST`,
|
||||
[RecipientRole.APPROVER]: `APPROVE_REQUEST`,
|
||||
} as const;
|
||||
|
||||
export const RECIPIENT_ROLE_SIGNING_REASONS = {
|
||||
|
||||
@ -43,18 +43,10 @@ export class LocalJobProvider extends BaseJobProvider {
|
||||
}
|
||||
|
||||
public async triggerJob(options: SimpleTriggerJobOptions) {
|
||||
console.log({ jobDefinitions: this._jobDefinitions });
|
||||
|
||||
const eligibleJobs = Object.values(this._jobDefinitions).filter(
|
||||
(job) => job.trigger.name === options.name,
|
||||
);
|
||||
|
||||
console.log({ options });
|
||||
console.log(
|
||||
'Eligible jobs:',
|
||||
eligibleJobs.map((job) => job.name),
|
||||
);
|
||||
|
||||
await Promise.all(
|
||||
eligibleJobs.map(async (job) => {
|
||||
// Ideally we will change this to a createMany with returning later once we upgrade Prisma
|
||||
|
||||
@ -1,9 +1,9 @@
|
||||
import { createElement } from 'react';
|
||||
|
||||
import { msg } from '@lingui/macro';
|
||||
import { z } from 'zod';
|
||||
|
||||
import { mailer } from '@documenso/email/mailer';
|
||||
import { render } from '@documenso/email/render';
|
||||
import DocumentInviteEmailTemplate from '@documenso/email/templates/document-invite';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import {
|
||||
@ -13,6 +13,7 @@ import {
|
||||
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 {
|
||||
@ -23,6 +24,7 @@ import { DOCUMENT_AUDIT_LOG_TYPE } from '../../../types/document-audit-logs';
|
||||
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 { type JobDefinition } from '../../client/_internal/job';
|
||||
|
||||
const SEND_SIGNING_EMAIL_JOB_DEFINITION_ID = 'send.signing.requested.email';
|
||||
@ -90,22 +92,32 @@ export const SEND_SIGNING_EMAIL_JOB_DEFINITION = {
|
||||
const recipientActionVerb =
|
||||
RECIPIENT_ROLES_DESCRIPTION_ENG[recipient.role].actionVerb.toLowerCase();
|
||||
|
||||
const i18n = await getI18nInstance(documentMeta?.language);
|
||||
|
||||
let emailMessage = customEmail?.message || '';
|
||||
let emailSubject = `Please ${recipientActionVerb} this document`;
|
||||
let emailSubject = i18n._(msg`Please ${recipientActionVerb} this document`);
|
||||
|
||||
if (selfSigner) {
|
||||
emailMessage = `You have initiated the document ${`"${document.title}"`} that requires you to ${recipientActionVerb} it.`;
|
||||
emailSubject = `Please ${recipientActionVerb} your document`;
|
||||
emailMessage = i18n._(
|
||||
msg`You have initiated the document ${`"${document.title}"`} that requires you to ${recipientActionVerb} it.`,
|
||||
);
|
||||
emailSubject = i18n._(msg`Please ${recipientActionVerb} your document`);
|
||||
}
|
||||
|
||||
if (isDirectTemplate) {
|
||||
emailMessage = `A document was created by your direct template that requires you to ${recipientActionVerb} it.`;
|
||||
emailSubject = `Please ${recipientActionVerb} this document created by your direct template`;
|
||||
emailMessage = i18n._(
|
||||
msg`A document was created by your direct template that requires you to ${recipientActionVerb} it.`,
|
||||
);
|
||||
emailSubject = i18n._(
|
||||
msg`Please ${recipientActionVerb} this document created by your direct template`,
|
||||
);
|
||||
}
|
||||
|
||||
if (isTeamDocument && team) {
|
||||
emailSubject = `${team.name} invited you to ${recipientActionVerb} a document`;
|
||||
emailMessage = `${user.name} on behalf of ${team.name} has invited you to ${recipientActionVerb} the document "${document.title}".`;
|
||||
emailSubject = i18n._(msg`${team.name} invited you to ${recipientActionVerb} a document`);
|
||||
emailMessage = i18n._(
|
||||
msg`${user.name} on behalf of ${team.name} has invited you to ${recipientActionVerb} the document "${document.title}".`,
|
||||
);
|
||||
}
|
||||
|
||||
const customEmailTemplate = {
|
||||
@ -132,6 +144,14 @@ export const SEND_SIGNING_EMAIL_JOB_DEFINITION = {
|
||||
});
|
||||
|
||||
await io.runTask('send-signing-email', async () => {
|
||||
const [html, text] = await Promise.all([
|
||||
renderEmailWithI18N(template, { lang: documentMeta?.language }),
|
||||
renderEmailWithI18N(template, {
|
||||
lang: documentMeta?.language,
|
||||
plainText: true,
|
||||
}),
|
||||
]);
|
||||
|
||||
await mailer.sendMail({
|
||||
to: {
|
||||
name: recipient.name,
|
||||
@ -145,8 +165,8 @@ export const SEND_SIGNING_EMAIL_JOB_DEFINITION = {
|
||||
documentMeta?.subject || emailSubject,
|
||||
customEmailTemplate,
|
||||
),
|
||||
html: render(template),
|
||||
text: render(template, { plainText: true }),
|
||||
html,
|
||||
text,
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@ -1,13 +1,15 @@
|
||||
import { msg } from '@lingui/macro';
|
||||
import { z } from 'zod';
|
||||
|
||||
import { mailer } from '@documenso/email/mailer';
|
||||
import { render } from '@documenso/email/render';
|
||||
import TeamJoinEmailTemplate from '@documenso/email/templates/team-join';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import { TeamMemberRole } from '@documenso/prisma/client';
|
||||
|
||||
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 type { JobDefinition } from '../../client/_internal/job';
|
||||
|
||||
const SEND_TEAM_MEMBER_JOINED_EMAIL_JOB_DEFINITION_ID = 'send.team-member-joined.email';
|
||||
@ -71,15 +73,23 @@ export const SEND_TEAM_MEMBER_JOINED_EMAIL_JOB_DEFINITION = {
|
||||
teamUrl: team.url,
|
||||
});
|
||||
|
||||
// !: Replace with the actual language of the recipient later
|
||||
const [html, text] = await Promise.all([
|
||||
renderEmailWithI18N(emailContent),
|
||||
renderEmailWithI18N(emailContent, { plainText: true }),
|
||||
]);
|
||||
|
||||
const i18n = await getI18nInstance();
|
||||
|
||||
await mailer.sendMail({
|
||||
to: member.user.email,
|
||||
from: {
|
||||
name: FROM_NAME,
|
||||
address: FROM_ADDRESS,
|
||||
},
|
||||
subject: 'A new member has joined your team',
|
||||
html: render(emailContent),
|
||||
text: render(emailContent, { plainText: true }),
|
||||
subject: i18n._(msg`A new member has joined your team`),
|
||||
html,
|
||||
text,
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
@ -1,13 +1,15 @@
|
||||
import { msg } from '@lingui/macro';
|
||||
import { z } from 'zod';
|
||||
|
||||
import { mailer } from '@documenso/email/mailer';
|
||||
import { render } from '@documenso/email/render';
|
||||
import TeamJoinEmailTemplate from '@documenso/email/templates/team-join';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import { TeamMemberRole } from '@documenso/prisma/client';
|
||||
|
||||
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 type { JobDefinition } from '../../client/_internal/job';
|
||||
|
||||
const SEND_TEAM_MEMBER_LEFT_EMAIL_JOB_DEFINITION_ID = 'send.team-member-left.email';
|
||||
@ -61,15 +63,22 @@ export const SEND_TEAM_MEMBER_LEFT_EMAIL_JOB_DEFINITION = {
|
||||
teamUrl: team.url,
|
||||
});
|
||||
|
||||
const [html, text] = await Promise.all([
|
||||
renderEmailWithI18N(emailContent),
|
||||
renderEmailWithI18N(emailContent, { plainText: true }),
|
||||
]);
|
||||
|
||||
const i18n = await getI18nInstance();
|
||||
|
||||
await mailer.sendMail({
|
||||
to: member.user.email,
|
||||
from: {
|
||||
name: FROM_NAME,
|
||||
address: FROM_ADDRESS,
|
||||
},
|
||||
subject: `A team member has left ${team.name}`,
|
||||
html: render(emailContent),
|
||||
text: render(emailContent, { plainText: true }),
|
||||
subject: i18n._(msg`A team member has left ${team.name}`),
|
||||
html,
|
||||
text,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@ -25,6 +25,9 @@
|
||||
"@documenso/email": "*",
|
||||
"@documenso/prisma": "*",
|
||||
"@documenso/signing": "*",
|
||||
"@lingui/core": "^4.11.3",
|
||||
"@lingui/macro": "^4.11.3",
|
||||
"@lingui/react": "^4.11.3",
|
||||
"@next-auth/prisma-adapter": "1.0.7",
|
||||
"@noble/ciphers": "0.4.0",
|
||||
"@noble/hashes": "1.3.2",
|
||||
|
||||
@ -1,11 +1,14 @@
|
||||
import { createElement } from 'react';
|
||||
|
||||
import { msg } from '@lingui/macro';
|
||||
|
||||
import { mailer } from '@documenso/email/mailer';
|
||||
import { render } from '@documenso/email/render';
|
||||
import { ConfirmEmailTemplate } from '@documenso/email/templates/confirm-email';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
|
||||
import { getI18nInstance } from '../../client-only/providers/i18n.server';
|
||||
import { NEXT_PUBLIC_WEBAPP_URL } from '../../constants/app';
|
||||
import { renderEmailWithI18N } from '../../utils/render-email-with-i18n';
|
||||
|
||||
export interface SendConfirmationEmailProps {
|
||||
userId: number;
|
||||
@ -45,6 +48,13 @@ export const sendConfirmationEmail = async ({ userId }: SendConfirmationEmailPro
|
||||
confirmationLink,
|
||||
});
|
||||
|
||||
const [html, text] = await Promise.all([
|
||||
renderEmailWithI18N(confirmationTemplate),
|
||||
renderEmailWithI18N(confirmationTemplate, { plainText: true }),
|
||||
]);
|
||||
|
||||
const i18n = await getI18nInstance();
|
||||
|
||||
return mailer.sendMail({
|
||||
to: {
|
||||
address: user.email,
|
||||
@ -54,8 +64,8 @@ export const sendConfirmationEmail = async ({ userId }: SendConfirmationEmailPro
|
||||
name: senderName,
|
||||
address: senderAddress,
|
||||
},
|
||||
subject: 'Please confirm your email',
|
||||
html: render(confirmationTemplate),
|
||||
text: render(confirmationTemplate, { plainText: true }),
|
||||
subject: i18n._(msg`Please confirm your email`),
|
||||
html,
|
||||
text,
|
||||
});
|
||||
};
|
||||
|
||||
@ -1,11 +1,14 @@
|
||||
import { createElement } from 'react';
|
||||
|
||||
import { msg } from '@lingui/macro';
|
||||
|
||||
import { mailer } from '@documenso/email/mailer';
|
||||
import { render } from '@documenso/email/render';
|
||||
import { ForgotPasswordTemplate } from '@documenso/email/templates/forgot-password';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
|
||||
import { getI18nInstance } from '../../client-only/providers/i18n.server';
|
||||
import { NEXT_PUBLIC_WEBAPP_URL } from '../../constants/app';
|
||||
import { renderEmailWithI18N } from '../../utils/render-email-with-i18n';
|
||||
|
||||
export interface SendForgotPasswordOptions {
|
||||
userId: number;
|
||||
@ -39,6 +42,13 @@ export const sendForgotPassword = async ({ userId }: SendForgotPasswordOptions)
|
||||
resetPasswordLink,
|
||||
});
|
||||
|
||||
const [html, text] = await Promise.all([
|
||||
renderEmailWithI18N(template),
|
||||
renderEmailWithI18N(template, { plainText: true }),
|
||||
]);
|
||||
|
||||
const i18n = await getI18nInstance();
|
||||
|
||||
return await mailer.sendMail({
|
||||
to: {
|
||||
address: user.email,
|
||||
@ -48,8 +58,8 @@ export const sendForgotPassword = async ({ userId }: SendForgotPasswordOptions)
|
||||
name: process.env.NEXT_PRIVATE_SMTP_FROM_NAME || 'Documenso',
|
||||
address: process.env.NEXT_PRIVATE_SMTP_FROM_ADDRESS || 'noreply@documenso.com',
|
||||
},
|
||||
subject: 'Forgot Password?',
|
||||
html: render(template),
|
||||
text: render(template, { plainText: true }),
|
||||
subject: i18n._(msg`Forgot Password?`),
|
||||
html,
|
||||
text,
|
||||
});
|
||||
};
|
||||
|
||||
@ -1,11 +1,11 @@
|
||||
import { createElement } from 'react';
|
||||
|
||||
import { mailer } from '@documenso/email/mailer';
|
||||
import { render } from '@documenso/email/render';
|
||||
import { ResetPasswordTemplate } from '@documenso/email/templates/reset-password';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
|
||||
import { NEXT_PUBLIC_WEBAPP_URL } from '../../constants/app';
|
||||
import { renderEmailWithI18N } from '../../utils/render-email-with-i18n';
|
||||
|
||||
export interface SendResetPasswordOptions {
|
||||
userId: number;
|
||||
@ -26,6 +26,11 @@ export const sendResetPassword = async ({ userId }: SendResetPasswordOptions) =>
|
||||
userName: user.name || '',
|
||||
});
|
||||
|
||||
const [html, text] = await Promise.all([
|
||||
renderEmailWithI18N(template),
|
||||
renderEmailWithI18N(template, { plainText: true }),
|
||||
]);
|
||||
|
||||
return await mailer.sendMail({
|
||||
to: {
|
||||
address: user.email,
|
||||
@ -36,7 +41,7 @@ export const sendResetPassword = async ({ userId }: SendResetPasswordOptions) =>
|
||||
address: process.env.NEXT_PRIVATE_SMTP_FROM_ADDRESS || 'noreply@documenso.com',
|
||||
},
|
||||
subject: 'Password Reset Success!',
|
||||
html: render(template),
|
||||
text: render(template, { plainText: true }),
|
||||
html,
|
||||
text,
|
||||
});
|
||||
};
|
||||
|
||||
@ -9,6 +9,8 @@ import {
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import type { DocumentSigningOrder } from '@documenso/prisma/client';
|
||||
|
||||
import type { SupportedLanguageCodes } from '../../constants/i18n';
|
||||
|
||||
export type CreateDocumentMetaOptions = {
|
||||
documentId: number;
|
||||
subject?: string;
|
||||
@ -19,6 +21,7 @@ export type CreateDocumentMetaOptions = {
|
||||
redirectUrl?: string;
|
||||
signingOrder?: DocumentSigningOrder;
|
||||
typedSignatureEnabled?: boolean;
|
||||
language?: SupportedLanguageCodes;
|
||||
userId: number;
|
||||
requestMetadata: RequestMetadata;
|
||||
};
|
||||
@ -34,6 +37,7 @@ export const upsertDocumentMeta = async ({
|
||||
redirectUrl,
|
||||
signingOrder,
|
||||
typedSignatureEnabled,
|
||||
language,
|
||||
requestMetadata,
|
||||
}: CreateDocumentMetaOptions) => {
|
||||
const user = await prisma.user.findFirstOrThrow({
|
||||
@ -85,6 +89,7 @@ export const upsertDocumentMeta = async ({
|
||||
redirectUrl,
|
||||
signingOrder,
|
||||
typedSignatureEnabled,
|
||||
language,
|
||||
},
|
||||
update: {
|
||||
subject,
|
||||
@ -95,6 +100,7 @@ export const upsertDocumentMeta = async ({
|
||||
redirectUrl,
|
||||
signingOrder,
|
||||
typedSignatureEnabled,
|
||||
language,
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@ -3,7 +3,6 @@
|
||||
import { createElement } from 'react';
|
||||
|
||||
import { mailer } from '@documenso/email/mailer';
|
||||
import { render } from '@documenso/email/render';
|
||||
import DocumentCancelTemplate from '@documenso/email/templates/document-cancel';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import type { Document, DocumentMeta, Recipient, User } from '@documenso/prisma/client';
|
||||
@ -14,6 +13,7 @@ import { FROM_ADDRESS, FROM_NAME } from '../../constants/email';
|
||||
import { DOCUMENT_AUDIT_LOG_TYPE } from '../../types/document-audit-logs';
|
||||
import type { RequestMetadata } from '../../universal/extract-request-metadata';
|
||||
import { createDocumentAuditLogData } from '../../utils/document-audit-logs';
|
||||
import { renderEmailWithI18N } from '../../utils/render-email-with-i18n';
|
||||
|
||||
export type DeleteDocumentOptions = {
|
||||
id: number;
|
||||
@ -191,6 +191,11 @@ const handleDocumentOwnerDelete = async ({
|
||||
assetBaseUrl,
|
||||
});
|
||||
|
||||
const [html, text] = await Promise.all([
|
||||
renderEmailWithI18N(template),
|
||||
renderEmailWithI18N(template, { plainText: true }),
|
||||
]);
|
||||
|
||||
await mailer.sendMail({
|
||||
to: {
|
||||
address: recipient.email,
|
||||
@ -201,8 +206,8 @@ const handleDocumentOwnerDelete = async ({
|
||||
address: FROM_ADDRESS,
|
||||
},
|
||||
subject: 'Document Cancelled',
|
||||
html: render(template),
|
||||
text: render(template, { plainText: true }),
|
||||
html,
|
||||
text,
|
||||
});
|
||||
}),
|
||||
);
|
||||
|
||||
@ -1,11 +1,12 @@
|
||||
import { createElement } from 'react';
|
||||
|
||||
import { msg } from '@lingui/macro';
|
||||
|
||||
import { mailer } from '@documenso/email/mailer';
|
||||
import { render } from '@documenso/email/render';
|
||||
import { DocumentInviteEmailTemplate } from '@documenso/email/templates/document-invite';
|
||||
import { FROM_ADDRESS, FROM_NAME } from '@documenso/lib/constants/email';
|
||||
import {
|
||||
RECIPIENT_ROLES_DESCRIPTION_ENG,
|
||||
RECIPIENT_ROLES_DESCRIPTION,
|
||||
RECIPIENT_ROLE_TO_EMAIL_TYPE,
|
||||
} from '@documenso/lib/constants/recipient-roles';
|
||||
import { DOCUMENT_AUDIT_LOG_TYPE } from '@documenso/lib/types/document-audit-logs';
|
||||
@ -16,7 +17,9 @@ import { prisma } from '@documenso/prisma';
|
||||
import { DocumentStatus, RecipientRole, SigningStatus } from '@documenso/prisma/client';
|
||||
import type { Prisma } from '@documenso/prisma/client';
|
||||
|
||||
import { getI18nInstance } from '../../client-only/providers/i18n.server';
|
||||
import { NEXT_PUBLIC_WEBAPP_URL } from '../../constants/app';
|
||||
import { renderEmailWithI18N } from '../../utils/render-email-with-i18n';
|
||||
import { getDocumentWhereInput } from './get-document-by-id';
|
||||
|
||||
export type ResendDocumentOptions = {
|
||||
@ -92,25 +95,28 @@ export const resendDocument = async ({
|
||||
return;
|
||||
}
|
||||
|
||||
const i18n = await getI18nInstance(document.documentMeta?.language);
|
||||
|
||||
const recipientEmailType = RECIPIENT_ROLE_TO_EMAIL_TYPE[recipient.role];
|
||||
|
||||
const { email, name } = recipient;
|
||||
const selfSigner = email === user.email;
|
||||
|
||||
const recipientActionVerb =
|
||||
RECIPIENT_ROLES_DESCRIPTION_ENG[recipient.role].actionVerb.toLowerCase();
|
||||
const recipientActionVerb = i18n
|
||||
._(RECIPIENT_ROLES_DESCRIPTION[recipient.role].actionVerb)
|
||||
.toLowerCase();
|
||||
|
||||
let emailMessage = customEmail?.message || '';
|
||||
let emailSubject = `Reminder: Please ${recipientActionVerb} this document`;
|
||||
let emailMessage = msg`${customEmail?.message || ''}`;
|
||||
let emailSubject = msg`Reminder: Please ${recipientActionVerb} this document`;
|
||||
|
||||
if (selfSigner) {
|
||||
emailMessage = `You have initiated the document ${`"${document.title}"`} that requires you to ${recipientActionVerb} it.`;
|
||||
emailSubject = `Reminder: Please ${recipientActionVerb} your document`;
|
||||
emailMessage = msg`You have initiated the document ${`"${document.title}"`} that requires you to ${recipientActionVerb} it.`;
|
||||
emailSubject = msg`Reminder: Please ${recipientActionVerb} your document`;
|
||||
}
|
||||
|
||||
if (isTeamDocument && document.team) {
|
||||
emailSubject = `Reminder: ${document.team.name} invited you to ${recipientActionVerb} a document`;
|
||||
emailMessage = `${user.name} on behalf of ${document.team.name} has invited you to ${recipientActionVerb} the document "${document.title}".`;
|
||||
emailSubject = msg`Reminder: ${document.team.name} invited you to ${recipientActionVerb} a document`;
|
||||
emailMessage = msg`${user.name} on behalf of ${document.team.name} has invited you to ${recipientActionVerb} the document "${document.title}".`;
|
||||
}
|
||||
|
||||
const customEmailTemplate = {
|
||||
@ -128,7 +134,7 @@ export const resendDocument = async ({
|
||||
inviterEmail: isTeamDocument ? document.team?.teamEmail?.email || user.email : user.email,
|
||||
assetBaseUrl,
|
||||
signDocumentLink,
|
||||
customBody: renderCustomEmailTemplate(emailMessage, customEmailTemplate),
|
||||
customBody: renderCustomEmailTemplate(i18n._(emailMessage), customEmailTemplate),
|
||||
role: recipient.role,
|
||||
selfSigner,
|
||||
isTeamInvite: isTeamDocument,
|
||||
@ -137,6 +143,14 @@ export const resendDocument = async ({
|
||||
|
||||
await prisma.$transaction(
|
||||
async (tx) => {
|
||||
const [html, text] = await Promise.all([
|
||||
renderEmailWithI18N(template, { lang: document.documentMeta?.language }),
|
||||
renderEmailWithI18N(template, {
|
||||
lang: document.documentMeta?.language,
|
||||
plainText: true,
|
||||
}),
|
||||
]);
|
||||
|
||||
await mailer.sendMail({
|
||||
to: {
|
||||
address: email,
|
||||
@ -147,10 +161,13 @@ export const resendDocument = async ({
|
||||
address: FROM_ADDRESS,
|
||||
},
|
||||
subject: customEmail?.subject
|
||||
? renderCustomEmailTemplate(`Reminder: ${customEmail.subject}`, customEmailTemplate)
|
||||
: emailSubject,
|
||||
html: render(template),
|
||||
text: render(template, { plainText: true }),
|
||||
? renderCustomEmailTemplate(
|
||||
i18n._(msg`Reminder: ${customEmail.subject}`),
|
||||
customEmailTemplate,
|
||||
)
|
||||
: i18n._(emailSubject),
|
||||
html,
|
||||
text,
|
||||
});
|
||||
|
||||
await tx.documentAuditLog.create({
|
||||
|
||||
@ -1,17 +1,20 @@
|
||||
import { createElement } from 'react';
|
||||
|
||||
import { msg } from '@lingui/macro';
|
||||
|
||||
import { mailer } from '@documenso/email/mailer';
|
||||
import { render } from '@documenso/email/render';
|
||||
import { DocumentCompletedEmailTemplate } from '@documenso/email/templates/document-completed';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
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 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';
|
||||
|
||||
export interface SendDocumentOptions {
|
||||
documentId: number;
|
||||
@ -61,6 +64,8 @@ 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 template = createElement(DocumentCompletedEmailTemplate, {
|
||||
@ -69,6 +74,11 @@ export const sendCompletedEmail = async ({ documentId, requestMetadata }: SendDo
|
||||
downloadLink: documentOwnerDownloadLink,
|
||||
});
|
||||
|
||||
const [html, text] = await Promise.all([
|
||||
renderEmailWithI18N(template, { lang: document.documentMeta?.language }),
|
||||
renderEmailWithI18N(template, { lang: document.documentMeta?.language, plainText: true }),
|
||||
]);
|
||||
|
||||
await mailer.sendMail({
|
||||
to: [
|
||||
{
|
||||
@ -80,9 +90,9 @@ export const sendCompletedEmail = async ({ documentId, requestMetadata }: SendDo
|
||||
name: process.env.NEXT_PRIVATE_SMTP_FROM_NAME || 'Documenso',
|
||||
address: process.env.NEXT_PRIVATE_SMTP_FROM_ADDRESS || 'noreply@documenso.com',
|
||||
},
|
||||
subject: 'Signing Complete!',
|
||||
html: render(template),
|
||||
text: render(template, { plainText: true }),
|
||||
subject: i18n._(msg`Signing Complete!`),
|
||||
html,
|
||||
text,
|
||||
attachments: [
|
||||
{
|
||||
filename: document.title.endsWith('.pdf') ? document.title : document.title + '.pdf',
|
||||
@ -129,6 +139,11 @@ export const sendCompletedEmail = async ({ documentId, requestMetadata }: SendDo
|
||||
: undefined,
|
||||
});
|
||||
|
||||
const [html, text] = await Promise.all([
|
||||
renderEmailWithI18N(template, { lang: document.documentMeta?.language }),
|
||||
renderEmailWithI18N(template, { lang: document.documentMeta?.language, plainText: true }),
|
||||
]);
|
||||
|
||||
await mailer.sendMail({
|
||||
to: [
|
||||
{
|
||||
@ -143,9 +158,9 @@ export const sendCompletedEmail = async ({ documentId, requestMetadata }: SendDo
|
||||
subject:
|
||||
isDirectTemplate && document.documentMeta?.subject
|
||||
? renderCustomEmailTemplate(document.documentMeta.subject, customEmailTemplate)
|
||||
: 'Signing Complete!',
|
||||
html: render(template),
|
||||
text: render(template, { plainText: true }),
|
||||
: i18n._(msg`Signing Complete!`),
|
||||
html,
|
||||
text,
|
||||
attachments: [
|
||||
{
|
||||
filename: document.title.endsWith('.pdf') ? document.title : document.title + '.pdf',
|
||||
|
||||
@ -1,11 +1,14 @@
|
||||
import { createElement } from 'react';
|
||||
|
||||
import { msg } from '@lingui/macro';
|
||||
|
||||
import { mailer } from '@documenso/email/mailer';
|
||||
import { render } from '@documenso/email/render';
|
||||
import { DocumentSuperDeleteEmailTemplate } from '@documenso/email/templates/document-super-delete';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
|
||||
import { getI18nInstance } from '../../client-only/providers/i18n.server';
|
||||
import { NEXT_PUBLIC_WEBAPP_URL } from '../../constants/app';
|
||||
import { renderEmailWithI18N } from '../../utils/render-email-with-i18n';
|
||||
|
||||
export interface SendDeleteEmailOptions {
|
||||
documentId: number;
|
||||
@ -36,6 +39,13 @@ export const sendDeleteEmail = async ({ documentId, reason }: SendDeleteEmailOpt
|
||||
assetBaseUrl,
|
||||
});
|
||||
|
||||
const [html, text] = await Promise.all([
|
||||
renderEmailWithI18N(template),
|
||||
renderEmailWithI18N(template, { plainText: true }),
|
||||
]);
|
||||
|
||||
const i18n = await getI18nInstance();
|
||||
|
||||
await mailer.sendMail({
|
||||
to: {
|
||||
address: email,
|
||||
@ -45,8 +55,8 @@ export const sendDeleteEmail = async ({ documentId, reason }: SendDeleteEmailOpt
|
||||
name: process.env.NEXT_PRIVATE_SMTP_FROM_NAME || 'Documenso',
|
||||
address: process.env.NEXT_PRIVATE_SMTP_FROM_ADDRESS || 'noreply@documenso.com',
|
||||
},
|
||||
subject: 'Document Deleted!',
|
||||
html: render(template),
|
||||
text: render(template, { plainText: true }),
|
||||
subject: i18n._(msg`Document Deleted!`),
|
||||
html,
|
||||
text,
|
||||
});
|
||||
};
|
||||
|
||||
@ -1,11 +1,14 @@
|
||||
import { createElement } from 'react';
|
||||
|
||||
import { msg } from '@lingui/macro';
|
||||
|
||||
import { mailer } from '@documenso/email/mailer';
|
||||
import { render } from '@documenso/email/render';
|
||||
import { DocumentPendingEmailTemplate } from '@documenso/email/templates/document-pending';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
|
||||
import { getI18nInstance } from '../../client-only/providers/i18n.server';
|
||||
import { NEXT_PUBLIC_WEBAPP_URL } from '../../constants/app';
|
||||
import { renderEmailWithI18N } from '../../utils/render-email-with-i18n';
|
||||
|
||||
export interface SendPendingEmailOptions {
|
||||
documentId: number;
|
||||
@ -28,6 +31,7 @@ export const sendPendingEmail = async ({ documentId, recipientId }: SendPendingE
|
||||
id: recipientId,
|
||||
},
|
||||
},
|
||||
documentMeta: true,
|
||||
},
|
||||
});
|
||||
|
||||
@ -50,6 +54,13 @@ export const sendPendingEmail = async ({ documentId, recipientId }: SendPendingE
|
||||
assetBaseUrl,
|
||||
});
|
||||
|
||||
const [html, text] = await Promise.all([
|
||||
renderEmailWithI18N(template, { lang: document.documentMeta?.language }),
|
||||
renderEmailWithI18N(template, { lang: document.documentMeta?.language, plainText: true }),
|
||||
]);
|
||||
|
||||
const i18n = await getI18nInstance();
|
||||
|
||||
await mailer.sendMail({
|
||||
to: {
|
||||
address: email,
|
||||
@ -59,8 +70,8 @@ export const sendPendingEmail = async ({ documentId, recipientId }: SendPendingE
|
||||
name: process.env.NEXT_PRIVATE_SMTP_FROM_NAME || 'Documenso',
|
||||
address: process.env.NEXT_PRIVATE_SMTP_FROM_ADDRESS || 'noreply@documenso.com',
|
||||
},
|
||||
subject: 'Waiting for others to complete signing.',
|
||||
html: render(template),
|
||||
text: render(template, { plainText: true }),
|
||||
subject: i18n._(msg`Waiting for others to complete signing.`),
|
||||
html,
|
||||
text,
|
||||
});
|
||||
};
|
||||
|
||||
@ -2,17 +2,20 @@
|
||||
|
||||
import { createElement } from 'react';
|
||||
|
||||
import { msg } from '@lingui/macro';
|
||||
|
||||
import { mailer } from '@documenso/email/mailer';
|
||||
import { render } from '@documenso/email/render';
|
||||
import DocumentCancelTemplate from '@documenso/email/templates/document-cancel';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
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 type { RequestMetadata } from '../../universal/extract-request-metadata';
|
||||
import { createDocumentAuditLogData } from '../../utils/document-audit-logs';
|
||||
import { renderEmailWithI18N } from '../../utils/render-email-with-i18n';
|
||||
|
||||
export type SuperDeleteDocumentOptions = {
|
||||
id: number;
|
||||
@ -53,6 +56,13 @@ export const superDeleteDocument = async ({ id, requestMetadata }: SuperDeleteDo
|
||||
assetBaseUrl,
|
||||
});
|
||||
|
||||
const [html, text] = await Promise.all([
|
||||
renderEmailWithI18N(template, { lang: document.documentMeta?.language }),
|
||||
renderEmailWithI18N(template, { lang: document.documentMeta?.language, plainText: true }),
|
||||
]);
|
||||
|
||||
const i18n = await getI18nInstance(document.documentMeta?.language);
|
||||
|
||||
await mailer.sendMail({
|
||||
to: {
|
||||
address: recipient.email,
|
||||
@ -62,9 +72,9 @@ export const superDeleteDocument = async ({ id, requestMetadata }: SuperDeleteDo
|
||||
name: FROM_NAME,
|
||||
address: FROM_ADDRESS,
|
||||
},
|
||||
subject: 'Document Cancelled',
|
||||
html: render(template),
|
||||
text: render(template, { plainText: true }),
|
||||
subject: i18n._(msg`Document Cancelled`),
|
||||
html,
|
||||
text,
|
||||
});
|
||||
}),
|
||||
);
|
||||
|
||||
@ -1,8 +1,9 @@
|
||||
import { createElement } from 'react';
|
||||
|
||||
import { msg } from '@lingui/macro';
|
||||
|
||||
import { isUserEnterprise } from '@documenso/ee/server-only/util/is-document-enterprise';
|
||||
import { mailer } from '@documenso/email/mailer';
|
||||
import { render } from '@documenso/email/render';
|
||||
import RecipientRemovedFromDocumentTemplate from '@documenso/email/templates/recipient-removed-from-document';
|
||||
import { DOCUMENT_AUDIT_LOG_TYPE } from '@documenso/lib/types/document-audit-logs';
|
||||
import {
|
||||
@ -21,10 +22,12 @@ import type { Recipient } from '@documenso/prisma/client';
|
||||
import { RecipientRole } from '@documenso/prisma/client';
|
||||
import { SendStatus, SigningStatus } 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 { AppError, AppErrorCode } from '../../errors/app-error';
|
||||
import { canRecipientBeModified } from '../../utils/recipients';
|
||||
import { renderEmailWithI18N } from '../../utils/render-email-with-i18n';
|
||||
|
||||
export interface SetRecipientsForDocumentOptions {
|
||||
userId: number;
|
||||
@ -62,6 +65,7 @@ export const setRecipientsForDocument = async ({
|
||||
},
|
||||
include: {
|
||||
Field: true,
|
||||
documentMeta: true,
|
||||
},
|
||||
});
|
||||
|
||||
@ -291,6 +295,13 @@ export const setRecipientsForDocument = async ({
|
||||
assetBaseUrl,
|
||||
});
|
||||
|
||||
const [html, text] = await Promise.all([
|
||||
renderEmailWithI18N(template, { lang: document.documentMeta?.language }),
|
||||
renderEmailWithI18N(template, { lang: document.documentMeta?.language, plainText: true }),
|
||||
]);
|
||||
|
||||
const i18n = await getI18nInstance(document.documentMeta?.language);
|
||||
|
||||
await mailer.sendMail({
|
||||
to: {
|
||||
address: recipient.email,
|
||||
@ -300,9 +311,9 @@ export const setRecipientsForDocument = async ({
|
||||
name: FROM_NAME,
|
||||
address: FROM_ADDRESS,
|
||||
},
|
||||
subject: 'You have been removed from a document',
|
||||
html: render(template),
|
||||
text: render(template, { plainText: true }),
|
||||
subject: i18n._(msg`You have been removed from a document`),
|
||||
html,
|
||||
text,
|
||||
});
|
||||
}),
|
||||
);
|
||||
|
||||
@ -81,7 +81,7 @@ export const acceptTeamInvitation = async ({ userId, teamId }: AcceptTeamInvitat
|
||||
await jobs.triggerJob({
|
||||
name: 'send.team-member-joined.email',
|
||||
payload: {
|
||||
teamId: team.id,
|
||||
teamId: teamMember.teamId,
|
||||
memberId: teamMember.id,
|
||||
},
|
||||
});
|
||||
|
||||
@ -1,9 +1,9 @@
|
||||
import { createElement } from 'react';
|
||||
|
||||
import { msg } from '@lingui/macro';
|
||||
import { z } from 'zod';
|
||||
|
||||
import { mailer } from '@documenso/email/mailer';
|
||||
import { render } from '@documenso/email/render';
|
||||
import { ConfirmTeamEmailTemplate } from '@documenso/email/templates/confirm-team-email';
|
||||
import { WEBAPP_BASE_URL } from '@documenso/lib/constants/app';
|
||||
import { FROM_ADDRESS, FROM_NAME } from '@documenso/lib/constants/email';
|
||||
@ -13,6 +13,9 @@ import { createTokenVerification } from '@documenso/lib/utils/token-verification
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import { Prisma } from '@documenso/prisma/client';
|
||||
|
||||
import { getI18nInstance } from '../../client-only/providers/i18n.server';
|
||||
import { renderEmailWithI18N } from '../../utils/render-email-with-i18n';
|
||||
|
||||
export type CreateTeamEmailVerificationOptions = {
|
||||
userId: number;
|
||||
teamId: number;
|
||||
@ -122,14 +125,23 @@ export const sendTeamEmailVerificationEmail = async (
|
||||
token,
|
||||
});
|
||||
|
||||
const [html, text] = await Promise.all([
|
||||
renderEmailWithI18N(template),
|
||||
renderEmailWithI18N(template, { plainText: true }),
|
||||
]);
|
||||
|
||||
const i18n = await getI18nInstance();
|
||||
|
||||
await mailer.sendMail({
|
||||
to: email,
|
||||
from: {
|
||||
name: FROM_NAME,
|
||||
address: FROM_ADDRESS,
|
||||
},
|
||||
subject: `A request to use your email has been initiated by ${teamName} on Documenso`,
|
||||
html: render(template),
|
||||
text: render(template, { plainText: true }),
|
||||
subject: i18n._(
|
||||
msg`A request to use your email has been initiated by ${teamName} on Documenso`,
|
||||
),
|
||||
html,
|
||||
text,
|
||||
});
|
||||
};
|
||||
|
||||
@ -1,9 +1,9 @@
|
||||
import { createElement } from 'react';
|
||||
|
||||
import { msg } from '@lingui/macro';
|
||||
import { nanoid } from 'nanoid';
|
||||
|
||||
import { mailer } from '@documenso/email/mailer';
|
||||
import { render } from '@documenso/email/render';
|
||||
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';
|
||||
@ -15,6 +15,9 @@ import { prisma } from '@documenso/prisma';
|
||||
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';
|
||||
|
||||
export type CreateTeamMemberInvitesOptions = {
|
||||
userId: number;
|
||||
userName: string;
|
||||
@ -148,14 +151,23 @@ export const sendTeamMemberInviteEmail = async ({
|
||||
...emailTemplateOptions,
|
||||
});
|
||||
|
||||
const [html, text] = await Promise.all([
|
||||
renderEmailWithI18N(template),
|
||||
renderEmailWithI18N(template, { plainText: true }),
|
||||
]);
|
||||
|
||||
const i18n = await getI18nInstance();
|
||||
|
||||
await mailer.sendMail({
|
||||
to: email,
|
||||
from: {
|
||||
name: FROM_NAME,
|
||||
address: FROM_ADDRESS,
|
||||
},
|
||||
subject: `You have been invited to join ${emailTemplateOptions.teamName} on Documenso`,
|
||||
html: render(template),
|
||||
text: render(template, { plainText: true }),
|
||||
subject: i18n._(
|
||||
msg`You have been invited to join ${emailTemplateOptions.teamName} on Documenso`,
|
||||
),
|
||||
html,
|
||||
text,
|
||||
});
|
||||
};
|
||||
|
||||
@ -1,13 +1,17 @@
|
||||
import { createElement } from 'react';
|
||||
|
||||
import { msg } from '@lingui/macro';
|
||||
|
||||
import { mailer } from '@documenso/email/mailer';
|
||||
import { render } from '@documenso/email/render';
|
||||
import { TeamEmailRemovedTemplate } from '@documenso/email/templates/team-email-removed';
|
||||
import { WEBAPP_BASE_URL } from '@documenso/lib/constants/app';
|
||||
import { FROM_ADDRESS, FROM_NAME } from '@documenso/lib/constants/email';
|
||||
import { TEAM_MEMBER_ROLE_PERMISSIONS_MAP } from '@documenso/lib/constants/teams';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
|
||||
import { getI18nInstance } from '../../client-only/providers/i18n.server';
|
||||
import { renderEmailWithI18N } from '../../utils/render-email-with-i18n';
|
||||
|
||||
export type DeleteTeamEmailOptions = {
|
||||
userId: number;
|
||||
userEmail: string;
|
||||
@ -73,6 +77,13 @@ export const deleteTeamEmail = async ({ userId, userEmail, teamId }: DeleteTeamE
|
||||
teamUrl: team.url,
|
||||
});
|
||||
|
||||
const [html, text] = await Promise.all([
|
||||
renderEmailWithI18N(template),
|
||||
renderEmailWithI18N(template, { plainText: true }),
|
||||
]);
|
||||
|
||||
const i18n = await getI18nInstance();
|
||||
|
||||
await mailer.sendMail({
|
||||
to: {
|
||||
address: team.owner.email,
|
||||
@ -82,9 +93,9 @@ export const deleteTeamEmail = async ({ userId, userEmail, teamId }: DeleteTeamE
|
||||
name: FROM_NAME,
|
||||
address: FROM_ADDRESS,
|
||||
},
|
||||
subject: `Team email has been revoked for ${team.name}`,
|
||||
html: render(template),
|
||||
text: render(template, { plainText: true }),
|
||||
subject: i18n._(msg`Team email has been revoked for ${team.name}`),
|
||||
html,
|
||||
text,
|
||||
});
|
||||
} catch (e) {
|
||||
// Todo: Teams - Alert us.
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
import { createElement } from 'react';
|
||||
|
||||
import { mailer } from '@documenso/email/mailer';
|
||||
import { render } from '@documenso/email/render';
|
||||
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';
|
||||
@ -11,6 +10,7 @@ import { stripe } from '@documenso/lib/server-only/stripe';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
|
||||
import { jobs } from '../../jobs/client';
|
||||
import { renderEmailWithI18N } from '../../utils/render-email-with-i18n';
|
||||
|
||||
export type DeleteTeamOptions = {
|
||||
userId: number;
|
||||
@ -95,6 +95,11 @@ export const sendTeamDeleteEmail = async ({
|
||||
...emailTemplateOptions,
|
||||
});
|
||||
|
||||
const [html, text] = await Promise.all([
|
||||
renderEmailWithI18N(template),
|
||||
renderEmailWithI18N(template, { plainText: true }),
|
||||
]);
|
||||
|
||||
await mailer.sendMail({
|
||||
to: email,
|
||||
from: {
|
||||
@ -102,7 +107,7 @@ export const sendTeamDeleteEmail = async ({
|
||||
address: FROM_ADDRESS,
|
||||
},
|
||||
subject: `Team "${emailTemplateOptions.teamName}" has been deleted on Documenso`,
|
||||
html: render(template),
|
||||
text: render(template, { plainText: true }),
|
||||
html,
|
||||
text,
|
||||
});
|
||||
};
|
||||
|
||||
@ -1,13 +1,17 @@
|
||||
import { createElement } from 'react';
|
||||
|
||||
import { msg } from '@lingui/macro';
|
||||
|
||||
import { mailer } from '@documenso/email/mailer';
|
||||
import { render } from '@documenso/email/render';
|
||||
import { TeamTransferRequestTemplate } from '@documenso/email/templates/team-transfer-request';
|
||||
import { WEBAPP_BASE_URL } from '@documenso/lib/constants/app';
|
||||
import { FROM_ADDRESS, FROM_NAME } from '@documenso/lib/constants/email';
|
||||
import { createTokenVerification } from '@documenso/lib/utils/token-verification';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
|
||||
import { getI18nInstance } from '../../client-only/providers/i18n.server';
|
||||
import { renderEmailWithI18N } from '../../utils/render-email-with-i18n';
|
||||
|
||||
export type RequestTeamOwnershipTransferOptions = {
|
||||
/**
|
||||
* The ID of the user initiating the transfer.
|
||||
@ -93,15 +97,24 @@ export const requestTeamOwnershipTransfer = async ({
|
||||
token,
|
||||
});
|
||||
|
||||
const [html, text] = await Promise.all([
|
||||
renderEmailWithI18N(template),
|
||||
renderEmailWithI18N(template, { plainText: true }),
|
||||
]);
|
||||
|
||||
const i18n = await getI18nInstance();
|
||||
|
||||
await mailer.sendMail({
|
||||
to: newOwnerUser.email,
|
||||
from: {
|
||||
name: FROM_NAME,
|
||||
address: FROM_ADDRESS,
|
||||
},
|
||||
subject: `You have been requested to take ownership of team ${team.name} on Documenso`,
|
||||
html: render(template),
|
||||
text: render(template, { plainText: true }),
|
||||
subject: i18n._(
|
||||
msg`You have been requested to take ownership of team ${team.name} on Documenso`,
|
||||
),
|
||||
html,
|
||||
text,
|
||||
});
|
||||
},
|
||||
{ timeout: 30_000 },
|
||||
|
||||
@ -1,15 +1,16 @@
|
||||
import { createElement } from 'react';
|
||||
|
||||
import { msg } from '@lingui/macro';
|
||||
import { DateTime } from 'luxon';
|
||||
import { match } from 'ts-pattern';
|
||||
|
||||
import { mailer } from '@documenso/email/mailer';
|
||||
import { render } from '@documenso/email/render';
|
||||
import { DocumentCreatedFromDirectTemplateEmailTemplate } from '@documenso/email/templates/document-created-from-direct-template';
|
||||
import { nanoid } from '@documenso/lib/universal/id';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import type { Field, Signature } from '@documenso/prisma/client';
|
||||
import {
|
||||
DocumentSigningOrder,
|
||||
DocumentSource,
|
||||
DocumentStatus,
|
||||
FieldType,
|
||||
@ -21,6 +22,7 @@ import {
|
||||
} from '@documenso/prisma/client';
|
||||
import type { TSignFieldWithTokenMutationSchema } from '@documenso/trpc/server/field-router/schema';
|
||||
|
||||
import { getI18nInstance } from '../../client-only/providers/i18n.server';
|
||||
import { NEXT_PUBLIC_WEBAPP_URL } from '../../constants/app';
|
||||
import { DEFAULT_DOCUMENT_DATE_FORMAT } from '../../constants/date-formats';
|
||||
import { DEFAULT_DOCUMENT_TIME_ZONE } from '../../constants/time-zones';
|
||||
@ -37,6 +39,7 @@ import {
|
||||
createRecipientAuthOptions,
|
||||
extractDocumentAuthMethods,
|
||||
} from '../../utils/document-auth';
|
||||
import { renderEmailWithI18N } from '../../utils/render-email-with-i18n';
|
||||
import { formatDocumentsPath } from '../../utils/teams';
|
||||
import { sendDocument } from '../document/send-document';
|
||||
import { validateFieldAuth } from '../document/validate-field-auth';
|
||||
@ -142,6 +145,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 metaSigningOrder = template.templateMeta?.signingOrder || DocumentSigningOrder.PARALLEL;
|
||||
|
||||
// Associate, validate and map to a query every direct template recipient field with the provided fields.
|
||||
const createDirectRecipientFieldArgs = await Promise.all(
|
||||
@ -256,6 +261,7 @@ export const createDocumentFromDirectTemplate = async ({
|
||||
recipient.role === RecipientRole.CC
|
||||
? SigningStatus.SIGNED
|
||||
: SigningStatus.NOT_SIGNED,
|
||||
signingOrder: recipient.signingOrder,
|
||||
token: nanoid(),
|
||||
};
|
||||
}),
|
||||
@ -267,6 +273,8 @@ export const createDocumentFromDirectTemplate = async ({
|
||||
dateFormat: metaDateFormat,
|
||||
message: metaEmailMessage,
|
||||
subject: metaEmailSubject,
|
||||
language: metaLanguage,
|
||||
signingOrder: metaSigningOrder,
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -330,6 +338,7 @@ export const createDocumentFromDirectTemplate = async ({
|
||||
signingStatus: SigningStatus.SIGNED,
|
||||
sendStatus: SendStatus.SENT,
|
||||
signedAt: initialRequestTime,
|
||||
signingOrder: directTemplateRecipient.signingOrder,
|
||||
Field: {
|
||||
createMany: {
|
||||
data: directTemplateNonSignatureFields.map(({ templateField, customText }) => ({
|
||||
@ -524,6 +533,13 @@ export const createDocumentFromDirectTemplate = async ({
|
||||
assetBaseUrl: NEXT_PUBLIC_WEBAPP_URL() || 'http://localhost:3000',
|
||||
});
|
||||
|
||||
const [html, text] = await Promise.all([
|
||||
renderEmailWithI18N(emailTemplate, { lang: metaLanguage }),
|
||||
renderEmailWithI18N(emailTemplate, { lang: metaLanguage, plainText: true }),
|
||||
]);
|
||||
|
||||
const i18n = await getI18nInstance(metaLanguage);
|
||||
|
||||
await mailer.sendMail({
|
||||
to: [
|
||||
{
|
||||
@ -535,9 +551,9 @@ export const createDocumentFromDirectTemplate = async ({
|
||||
name: process.env.NEXT_PRIVATE_SMTP_FROM_NAME || 'Documenso',
|
||||
address: process.env.NEXT_PRIVATE_SMTP_FROM_ADDRESS || 'noreply@documenso.com',
|
||||
},
|
||||
subject: 'Document created from direct template',
|
||||
html: render(emailTemplate),
|
||||
text: render(emailTemplate, { plainText: true }),
|
||||
subject: i18n._(msg`Document created from direct template`),
|
||||
html,
|
||||
text,
|
||||
});
|
||||
|
||||
return {
|
||||
|
||||
@ -46,6 +46,7 @@ export const createDocumentFromTemplateLegacy = async ({
|
||||
Recipient: true,
|
||||
Field: true,
|
||||
templateDocumentData: true,
|
||||
templateMeta: true,
|
||||
},
|
||||
});
|
||||
|
||||
@ -78,6 +79,17 @@ export const createDocumentFromTemplateLegacy = async ({
|
||||
token: nanoid(),
|
||||
})),
|
||||
},
|
||||
documentMeta: {
|
||||
create: {
|
||||
subject: template.templateMeta?.subject,
|
||||
message: template.templateMeta?.message,
|
||||
timezone: template.templateMeta?.timezone,
|
||||
dateFormat: template.templateMeta?.dateFormat,
|
||||
redirectUrl: template.templateMeta?.redirectUrl,
|
||||
signingOrder: template.templateMeta?.signingOrder ?? undefined,
|
||||
language: template.templateMeta?.language,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
include: {
|
||||
|
||||
@ -11,6 +11,7 @@ import {
|
||||
WebhookTriggerEvents,
|
||||
} from '@documenso/prisma/client';
|
||||
|
||||
import type { SupportedLanguageCodes } from '../../constants/i18n';
|
||||
import { AppError, AppErrorCode } from '../../errors/app-error';
|
||||
import { DOCUMENT_AUDIT_LOG_TYPE } from '../../types/document-audit-logs';
|
||||
import { ZRecipientAuthOptionsSchema } from '../../types/document-auth';
|
||||
@ -24,7 +25,10 @@ import {
|
||||
} from '../../utils/document-auth';
|
||||
import { triggerWebhook } from '../webhooks/trigger/trigger-webhook';
|
||||
|
||||
type FinalRecipient = Pick<Recipient, 'name' | 'email' | 'role' | 'authOptions'> & {
|
||||
type FinalRecipient = Pick<
|
||||
Recipient,
|
||||
'name' | 'email' | 'role' | 'authOptions' | 'signingOrder'
|
||||
> & {
|
||||
templateRecipientId: number;
|
||||
fields: Field[];
|
||||
};
|
||||
@ -57,6 +61,7 @@ export type CreateDocumentFromTemplateOptions = {
|
||||
dateFormat?: string;
|
||||
redirectUrl?: string;
|
||||
signingOrder?: DocumentSigningOrder;
|
||||
language?: SupportedLanguageCodes;
|
||||
};
|
||||
requestMetadata?: RequestMetadata;
|
||||
};
|
||||
@ -176,6 +181,7 @@ export const createDocumentFromTemplate = async ({
|
||||
override?.signingOrder ||
|
||||
template.templateMeta?.signingOrder ||
|
||||
DocumentSigningOrder.PARALLEL,
|
||||
language: override?.language || template.templateMeta?.language,
|
||||
},
|
||||
},
|
||||
Recipient: {
|
||||
@ -197,6 +203,7 @@ export const createDocumentFromTemplate = async ({
|
||||
recipient.role === RecipientRole.CC
|
||||
? SigningStatus.SIGNED
|
||||
: SigningStatus.NOT_SIGNED,
|
||||
signingOrder: recipient.signingOrder,
|
||||
token: nanoid(),
|
||||
};
|
||||
}),
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
import { omit } from 'remeda';
|
||||
|
||||
import { nanoid } from '@documenso/lib/universal/id';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import type { Prisma } from '@documenso/prisma/client';
|
||||
@ -38,6 +40,7 @@ export const duplicateTemplate = async ({
|
||||
Recipient: true,
|
||||
Field: true,
|
||||
templateDocumentData: true,
|
||||
templateMeta: true,
|
||||
},
|
||||
});
|
||||
|
||||
@ -53,6 +56,14 @@ export const duplicateTemplate = async ({
|
||||
},
|
||||
});
|
||||
|
||||
let templateMeta: Prisma.TemplateCreateArgs['data']['templateMeta'] | undefined = undefined;
|
||||
|
||||
if (template.templateMeta) {
|
||||
templateMeta = {
|
||||
create: omit(template.templateMeta, ['id', 'templateId']),
|
||||
};
|
||||
}
|
||||
|
||||
const duplicatedTemplate = await prisma.template.create({
|
||||
data: {
|
||||
userId,
|
||||
@ -66,8 +77,8 @@ export const duplicateTemplate = async ({
|
||||
token: nanoid(),
|
||||
})),
|
||||
},
|
||||
templateMeta,
|
||||
},
|
||||
|
||||
include: {
|
||||
Recipient: true,
|
||||
},
|
||||
|
||||
@ -39,7 +39,7 @@ export const sendConfirmationToken = async ({
|
||||
mostRecentToken?.createdAt &&
|
||||
DateTime.fromJSDate(mostRecentToken.createdAt).diffNow('minutes').minutes > -5
|
||||
) {
|
||||
return;
|
||||
// return;
|
||||
}
|
||||
|
||||
const createdToken = await prisma.verificationToken.create({
|
||||
@ -64,6 +64,7 @@ export const sendConfirmationToken = async ({
|
||||
|
||||
return { success: true };
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
throw new Error(`Failed to send the confirmation email`);
|
||||
}
|
||||
};
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -8,7 +8,7 @@ msgstr ""
|
||||
"Language: de\n"
|
||||
"Project-Id-Version: documenso-app\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"PO-Revision-Date: 2024-10-18 04:04\n"
|
||||
"PO-Revision-Date: 2024-11-01 02:29\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: German\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
@ -42,7 +42,7 @@ msgstr "Dokument hinzufügen"
|
||||
msgid "Add More Users for {0}"
|
||||
msgstr "Mehr Benutzer hinzufügen für {0}"
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:165
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:164
|
||||
msgid "All our metrics, finances, and learnings are public. We believe in transparency and want to share our journey with you. You can read more about why here: <0>Announcing Open Metrics</0>"
|
||||
msgstr "Alle unsere Kennzahlen, Finanzen und Erkenntnisse sind öffentlich. Wir glauben an Transparenz und möchten unsere Reise mit Ihnen teilen. Mehr erfahren Sie hier: <0>Ankündigung Offene Kennzahlen</0>"
|
||||
|
||||
@ -90,7 +90,7 @@ msgstr "Änderungsprotokoll"
|
||||
msgid "Choose a template from the community app store. Or submit your own template for others to use."
|
||||
msgstr "Wählen Sie eine Vorlage aus dem Community-App-Store. Oder reichen Sie Ihre eigene Vorlage ein, damit andere sie benutzen können."
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:219
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:218
|
||||
msgid "Community"
|
||||
msgstr "Gemeinschaft"
|
||||
|
||||
@ -160,10 +160,6 @@ msgstr "Dokumentation"
|
||||
msgid "Easily embed Documenso into your product. Simply copy and paste our react widget into your application."
|
||||
msgstr "Betten Sie Documenso ganz einfach in Ihr Produkt ein. Kopieren und fügen Sie einfach unser React-Widget in Ihre Anwendung ein."
|
||||
|
||||
#: apps/marketing/src/components/(marketing)/share-connect-paid-widget-bento.tsx:42
|
||||
#~ msgid "Easy Sharing (Soon)."
|
||||
#~ msgstr "Easy Sharing (Soon)."
|
||||
|
||||
#: apps/marketing/src/components/(marketing)/share-connect-paid-widget-bento.tsx:46
|
||||
msgid "Easy Sharing."
|
||||
msgstr "Einfaches Teilen."
|
||||
@ -197,7 +193,7 @@ msgstr "Schnell."
|
||||
msgid "Faster, smarter and more beautiful."
|
||||
msgstr "Schneller, intelligenter und schöner."
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:210
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:209
|
||||
msgid "Finances"
|
||||
msgstr "Finanzen"
|
||||
|
||||
@ -250,15 +246,15 @@ msgstr "Fangen Sie heute an."
|
||||
msgid "Get the latest news from Documenso, including product updates, team announcements and more!"
|
||||
msgstr "Erhalten Sie die neuesten Nachrichten von Documenso, einschließlich Produkt-Updates, Team-Ankündigungen und mehr!"
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:233
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:232
|
||||
msgid "GitHub: Total Merged PRs"
|
||||
msgstr "GitHub: Gesamte PRs zusammengeführt"
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:251
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:250
|
||||
msgid "GitHub: Total Open Issues"
|
||||
msgstr "GitHub: Gesamte offene Issues"
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:225
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:224
|
||||
msgid "GitHub: Total Stars"
|
||||
msgstr "GitHub: Gesamtanzahl Sterne"
|
||||
|
||||
@ -266,7 +262,7 @@ msgstr "GitHub: Gesamtanzahl Sterne"
|
||||
msgid "Global Salary Bands"
|
||||
msgstr "Globale Gehaltsbänder"
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:261
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:260
|
||||
msgid "Growth"
|
||||
msgstr "Wachstum"
|
||||
|
||||
@ -290,7 +286,7 @@ msgstr "Integrierte Zahlungen mit Stripe, sodass Sie sich keine Sorgen ums Bezah
|
||||
msgid "Integrates with all your favourite tools."
|
||||
msgstr "Integriert sich mit all Ihren Lieblingstools."
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:289
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:288
|
||||
msgid "Is there more?"
|
||||
msgstr "Gibt es mehr?"
|
||||
|
||||
@ -314,11 +310,11 @@ msgstr "Standort"
|
||||
msgid "Make it your own through advanced customization and adjustability."
|
||||
msgstr "Machen Sie es zu Ihrem eigenen durch erweiterte Anpassung und Einstellbarkeit."
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:199
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:198
|
||||
msgid "Merged PR's"
|
||||
msgstr "Zusammengeführte PRs"
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:234
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:233
|
||||
msgid "Merged PRs"
|
||||
msgstr "Zusammengeführte PRs"
|
||||
|
||||
@ -349,8 +345,8 @@ msgstr "Keine Kreditkarte erforderlich"
|
||||
msgid "None of these work for you? Try self-hosting!"
|
||||
msgstr "Keines dieser Angebote passt zu Ihnen? Versuchen Sie das Selbst-Hosting!"
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:194
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:252
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:193
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:251
|
||||
msgid "Open Issues"
|
||||
msgstr "Offene Issues"
|
||||
|
||||
@ -358,7 +354,7 @@ msgstr "Offene Issues"
|
||||
msgid "Open Source or Hosted."
|
||||
msgstr "Open Source oder Hosted."
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:161
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:160
|
||||
#: apps/marketing/src/components/(marketing)/footer.tsx:37
|
||||
#: apps/marketing/src/components/(marketing)/header.tsx:64
|
||||
#: apps/marketing/src/components/(marketing)/mobile-navigation.tsx:40
|
||||
@ -377,18 +373,10 @@ msgstr "Unsere benutzerdefinierten Vorlagen verfügen über intelligente Regeln,
|
||||
msgid "Our Enterprise License is great for large organizations looking to switch to Documenso for all their signing needs. It's available for our cloud offering as well as self-hosted setups and offers a wide range of compliance and Adminstration Features."
|
||||
msgstr "Unsere Enterprise-Lizenz ist ideal für große Organisationen, die auf Documenso für all ihre Signaturanforderungen umsteigen möchten. Sie ist sowohl für unser Cloud-Angebot als auch für selbstgehostete Setups verfügbar und bietet eine breite Palette an Compliance- und Verwaltungsfunktionen."
|
||||
|
||||
#: apps/marketing/src/components/(marketing)/enterprise.tsx:20
|
||||
#~ msgid "Our Enterprise License is great large organizations looking to switch to Documenso for all their signing needs. It's availible for our cloud offering as well as self-hosted setups and offer a wide range of compliance and Adminstration Features."
|
||||
#~ msgstr "Our Enterprise License is great large organizations looking to switch to Documenso for all their signing needs. It's availible for our cloud offering as well as self-hosted setups and offer a wide range of compliance and Adminstration Features."
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/pricing/page.tsx:65
|
||||
msgid "Our self-hosted option is great for small teams and individuals who need a simple solution. You can use our docker based setup to get started in minutes. Take control with full customizability and data ownership."
|
||||
msgstr "Unsere selbstgehostete Option ist ideal für kleine Teams und Einzelpersonen, die eine einfache Lösung benötigen. Sie können unser docker-basiertes Setup verwenden, um in wenigen Minuten loszulegen. Übernehmen Sie die Kontrolle mit vollständiger Anpassbarkeit und Datenhoheit."
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/open/data.ts:25
|
||||
#~ msgid "Part-Time"
|
||||
#~ msgstr "Part-Time"
|
||||
|
||||
#: apps/marketing/src/components/(marketing)/pricing-table.tsx:151
|
||||
msgid "Premium Profile Name"
|
||||
msgstr "Premium Profilname"
|
||||
@ -430,10 +418,6 @@ msgstr "Gehalt"
|
||||
msgid "Save $60 or $120"
|
||||
msgstr "Sparen Sie $60 oder $120"
|
||||
|
||||
#: apps/marketing/src/components/(marketing)/i18n-switcher.tsx:47
|
||||
#~ msgid "Search languages..."
|
||||
#~ msgstr "Search languages..."
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/pricing/page.tsx:109
|
||||
msgid "Securely. Our data centers are located in Frankfurt (Germany), giving us the best local privacy laws. We are very aware of the sensitive nature of our data and follow best practices to ensure the security and integrity of the data entrusted to us."
|
||||
msgstr "Sicher. Unsere Rechenzentren befinden sich in Frankfurt (Deutschland) und bieten uns die besten lokalen Datenschutzgesetze. Uns ist die sensible Natur unserer Daten sehr bewusst und wir folgen bewährten Praktiken, um die Sicherheit und Integrität der uns anvertrauten Daten zu gewährleisten."
|
||||
@ -482,7 +466,7 @@ msgstr "Intelligent."
|
||||
msgid "Star on GitHub"
|
||||
msgstr "Auf GitHub favorisieren"
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:226
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:225
|
||||
msgid "Stars"
|
||||
msgstr "Favoriten"
|
||||
|
||||
@ -517,7 +501,7 @@ msgstr "Vorlagen-Shop (Demnächst)."
|
||||
msgid "That's awesome. You can take a look at the current <0>Issues</0> and join our <1>Discord Community</1> to keep up to date, on what the current priorities are. In any case, we are an open community and welcome all input, technical and non-technical ❤️"
|
||||
msgstr "Das ist großartig. Sie können sich die aktuellen <0>Issues</0> ansehen und unserer <1>Discord-Community</1> beitreten, um auf dem neuesten Stand zu bleiben, was die aktuellen Prioritäten sind. In jedem Fall sind wir eine offene Gemeinschaft und begrüßen jegliche Beiträge, technische und nicht-technische ❤️"
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:293
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:292
|
||||
msgid "This page is evolving as we learn what makes a great signing company. We'll update it when we have more to share."
|
||||
msgstr "Diese Seite entwickelt sich weiter, während wir lernen, was ein großartiges Signing-Unternehmen ausmacht. Wir werden sie aktualisieren, wenn wir mehr zu teilen haben."
|
||||
|
||||
@ -530,8 +514,8 @@ msgstr "Titel"
|
||||
msgid "Total Completed Documents"
|
||||
msgstr "Insgesamt Abgeschlossene Dokumente"
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:266
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:267
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:268
|
||||
msgid "Total Customers"
|
||||
msgstr "Insgesamt Kunden"
|
||||
|
||||
@ -618,4 +602,3 @@ msgstr "Sie können Documenso kostenlos selbst hosten oder unsere sofort einsatz
|
||||
#: apps/marketing/src/components/(marketing)/carousel.tsx:272
|
||||
msgid "Your browser does not support the video tag."
|
||||
msgstr "Ihr Browser unterstützt das Video-Tag nicht."
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -37,7 +37,7 @@ msgstr "Add document"
|
||||
msgid "Add More Users for {0}"
|
||||
msgstr "Add More Users for {0}"
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:165
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:164
|
||||
msgid "All our metrics, finances, and learnings are public. We believe in transparency and want to share our journey with you. You can read more about why here: <0>Announcing Open Metrics</0>"
|
||||
msgstr "All our metrics, finances, and learnings are public. We believe in transparency and want to share our journey with you. You can read more about why here: <0>Announcing Open Metrics</0>"
|
||||
|
||||
@ -85,7 +85,7 @@ msgstr "Changelog"
|
||||
msgid "Choose a template from the community app store. Or submit your own template for others to use."
|
||||
msgstr "Choose a template from the community app store. Or submit your own template for others to use."
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:219
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:218
|
||||
msgid "Community"
|
||||
msgstr "Community"
|
||||
|
||||
@ -155,10 +155,6 @@ msgstr "Documentation"
|
||||
msgid "Easily embed Documenso into your product. Simply copy and paste our react widget into your application."
|
||||
msgstr "Easily embed Documenso into your product. Simply copy and paste our react widget into your application."
|
||||
|
||||
#: apps/marketing/src/components/(marketing)/share-connect-paid-widget-bento.tsx:42
|
||||
#~ msgid "Easy Sharing (Soon)."
|
||||
#~ msgstr "Easy Sharing (Soon)."
|
||||
|
||||
#: apps/marketing/src/components/(marketing)/share-connect-paid-widget-bento.tsx:46
|
||||
msgid "Easy Sharing."
|
||||
msgstr "Easy Sharing."
|
||||
@ -192,7 +188,7 @@ msgstr "Fast."
|
||||
msgid "Faster, smarter and more beautiful."
|
||||
msgstr "Faster, smarter and more beautiful."
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:210
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:209
|
||||
msgid "Finances"
|
||||
msgstr "Finances"
|
||||
|
||||
@ -245,15 +241,15 @@ msgstr "Get started today."
|
||||
msgid "Get the latest news from Documenso, including product updates, team announcements and more!"
|
||||
msgstr "Get the latest news from Documenso, including product updates, team announcements and more!"
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:233
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:232
|
||||
msgid "GitHub: Total Merged PRs"
|
||||
msgstr "GitHub: Total Merged PRs"
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:251
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:250
|
||||
msgid "GitHub: Total Open Issues"
|
||||
msgstr "GitHub: Total Open Issues"
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:225
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:224
|
||||
msgid "GitHub: Total Stars"
|
||||
msgstr "GitHub: Total Stars"
|
||||
|
||||
@ -261,7 +257,7 @@ msgstr "GitHub: Total Stars"
|
||||
msgid "Global Salary Bands"
|
||||
msgstr "Global Salary Bands"
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:261
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:260
|
||||
msgid "Growth"
|
||||
msgstr "Growth"
|
||||
|
||||
@ -285,7 +281,7 @@ msgstr "Integrated payments with Stripe so you don’t have to worry about getti
|
||||
msgid "Integrates with all your favourite tools."
|
||||
msgstr "Integrates with all your favourite tools."
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:289
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:288
|
||||
msgid "Is there more?"
|
||||
msgstr "Is there more?"
|
||||
|
||||
@ -309,11 +305,11 @@ msgstr "Location"
|
||||
msgid "Make it your own through advanced customization and adjustability."
|
||||
msgstr "Make it your own through advanced customization and adjustability."
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:199
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:198
|
||||
msgid "Merged PR's"
|
||||
msgstr "Merged PR's"
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:234
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:233
|
||||
msgid "Merged PRs"
|
||||
msgstr "Merged PRs"
|
||||
|
||||
@ -344,8 +340,8 @@ msgstr "No Credit Card required"
|
||||
msgid "None of these work for you? Try self-hosting!"
|
||||
msgstr "None of these work for you? Try self-hosting!"
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:194
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:252
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:193
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:251
|
||||
msgid "Open Issues"
|
||||
msgstr "Open Issues"
|
||||
|
||||
@ -353,7 +349,7 @@ msgstr "Open Issues"
|
||||
msgid "Open Source or Hosted."
|
||||
msgstr "Open Source or Hosted."
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:161
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:160
|
||||
#: apps/marketing/src/components/(marketing)/footer.tsx:37
|
||||
#: apps/marketing/src/components/(marketing)/header.tsx:64
|
||||
#: apps/marketing/src/components/(marketing)/mobile-navigation.tsx:40
|
||||
@ -372,18 +368,10 @@ msgstr "Our custom templates come with smart rules that can help you save time a
|
||||
msgid "Our Enterprise License is great for large organizations looking to switch to Documenso for all their signing needs. It's available for our cloud offering as well as self-hosted setups and offers a wide range of compliance and Adminstration Features."
|
||||
msgstr "Our Enterprise License is great for large organizations looking to switch to Documenso for all their signing needs. It's available for our cloud offering as well as self-hosted setups and offers a wide range of compliance and Adminstration Features."
|
||||
|
||||
#: apps/marketing/src/components/(marketing)/enterprise.tsx:20
|
||||
#~ msgid "Our Enterprise License is great large organizations looking to switch to Documenso for all their signing needs. It's availible for our cloud offering as well as self-hosted setups and offer a wide range of compliance and Adminstration Features."
|
||||
#~ msgstr "Our Enterprise License is great large organizations looking to switch to Documenso for all their signing needs. It's availible for our cloud offering as well as self-hosted setups and offer a wide range of compliance and Adminstration Features."
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/pricing/page.tsx:65
|
||||
msgid "Our self-hosted option is great for small teams and individuals who need a simple solution. You can use our docker based setup to get started in minutes. Take control with full customizability and data ownership."
|
||||
msgstr "Our self-hosted option is great for small teams and individuals who need a simple solution. You can use our docker based setup to get started in minutes. Take control with full customizability and data ownership."
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/open/data.ts:25
|
||||
#~ msgid "Part-Time"
|
||||
#~ msgstr "Part-Time"
|
||||
|
||||
#: apps/marketing/src/components/(marketing)/pricing-table.tsx:151
|
||||
msgid "Premium Profile Name"
|
||||
msgstr "Premium Profile Name"
|
||||
@ -425,10 +413,6 @@ msgstr "Salary"
|
||||
msgid "Save $60 or $120"
|
||||
msgstr "Save $60 or $120"
|
||||
|
||||
#: apps/marketing/src/components/(marketing)/i18n-switcher.tsx:47
|
||||
#~ msgid "Search languages..."
|
||||
#~ msgstr "Search languages..."
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/pricing/page.tsx:109
|
||||
msgid "Securely. Our data centers are located in Frankfurt (Germany), giving us the best local privacy laws. We are very aware of the sensitive nature of our data and follow best practices to ensure the security and integrity of the data entrusted to us."
|
||||
msgstr "Securely. Our data centers are located in Frankfurt (Germany), giving us the best local privacy laws. We are very aware of the sensitive nature of our data and follow best practices to ensure the security and integrity of the data entrusted to us."
|
||||
@ -477,7 +461,7 @@ msgstr "Smart."
|
||||
msgid "Star on GitHub"
|
||||
msgstr "Star on GitHub"
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:226
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:225
|
||||
msgid "Stars"
|
||||
msgstr "Stars"
|
||||
|
||||
@ -512,7 +496,7 @@ msgstr "Template Store (Soon)."
|
||||
msgid "That's awesome. You can take a look at the current <0>Issues</0> and join our <1>Discord Community</1> to keep up to date, on what the current priorities are. In any case, we are an open community and welcome all input, technical and non-technical ❤️"
|
||||
msgstr "That's awesome. You can take a look at the current <0>Issues</0> and join our <1>Discord Community</1> to keep up to date, on what the current priorities are. In any case, we are an open community and welcome all input, technical and non-technical ❤️"
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:293
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:292
|
||||
msgid "This page is evolving as we learn what makes a great signing company. We'll update it when we have more to share."
|
||||
msgstr "This page is evolving as we learn what makes a great signing company. We'll update it when we have more to share."
|
||||
|
||||
@ -525,8 +509,8 @@ msgstr "Title"
|
||||
msgid "Total Completed Documents"
|
||||
msgstr "Total Completed Documents"
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:266
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:267
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:268
|
||||
msgid "Total Customers"
|
||||
msgstr "Total Customers"
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -8,7 +8,7 @@ msgstr ""
|
||||
"Language: es\n"
|
||||
"Project-Id-Version: documenso-app\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"PO-Revision-Date: 2024-10-22 02:25\n"
|
||||
"PO-Revision-Date: 2024-11-01 02:29\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Spanish\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
@ -42,7 +42,7 @@ msgstr "Agregar documento"
|
||||
msgid "Add More Users for {0}"
|
||||
msgstr "Agregar más usuarios por {0}"
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:165
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:164
|
||||
msgid "All our metrics, finances, and learnings are public. We believe in transparency and want to share our journey with you. You can read more about why here: <0>Announcing Open Metrics</0>"
|
||||
msgstr "Todos nuestros métricas, finanzas y aprendizajes son públicos. Creemos en la transparencia y queremos compartir nuestro viaje contigo. Puedes leer más sobre por qué aquí: <0>Anunciando Métricas Abiertas</0>"
|
||||
|
||||
@ -90,7 +90,7 @@ msgstr "Registro de cambios"
|
||||
msgid "Choose a template from the community app store. Or submit your own template for others to use."
|
||||
msgstr "Elige una plantilla de la tienda de aplicaciones de la comunidad. O envía tu propia plantilla para que otros la usen."
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:219
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:218
|
||||
msgid "Community"
|
||||
msgstr "Comunidad"
|
||||
|
||||
@ -160,10 +160,6 @@ msgstr "Documentación"
|
||||
msgid "Easily embed Documenso into your product. Simply copy and paste our react widget into your application."
|
||||
msgstr "Incrusta fácilmente Documenso en tu producto. Simplemente copia y pega nuestro widget de react en tu aplicación."
|
||||
|
||||
#: apps/marketing/src/components/(marketing)/share-connect-paid-widget-bento.tsx:42
|
||||
#~ msgid "Easy Sharing (Soon)."
|
||||
#~ msgstr "Easy Sharing (Soon)."
|
||||
|
||||
#: apps/marketing/src/components/(marketing)/share-connect-paid-widget-bento.tsx:46
|
||||
msgid "Easy Sharing."
|
||||
msgstr "Compartición fácil."
|
||||
@ -197,7 +193,7 @@ msgstr "Rápido."
|
||||
msgid "Faster, smarter and more beautiful."
|
||||
msgstr "Más rápido, más inteligente y más hermoso."
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:210
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:209
|
||||
msgid "Finances"
|
||||
msgstr "Finanzas"
|
||||
|
||||
@ -250,15 +246,15 @@ msgstr "Comienza hoy."
|
||||
msgid "Get the latest news from Documenso, including product updates, team announcements and more!"
|
||||
msgstr "¡Obtén las últimas noticias de Documenso, incluidas actualizaciones de productos, anuncios del equipo y más!"
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:233
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:232
|
||||
msgid "GitHub: Total Merged PRs"
|
||||
msgstr "GitHub: Total de PRs fusionados"
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:251
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:250
|
||||
msgid "GitHub: Total Open Issues"
|
||||
msgstr "GitHub: Total de problemas abiertos"
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:225
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:224
|
||||
msgid "GitHub: Total Stars"
|
||||
msgstr "GitHub: Total de estrellas"
|
||||
|
||||
@ -266,7 +262,7 @@ msgstr "GitHub: Total de estrellas"
|
||||
msgid "Global Salary Bands"
|
||||
msgstr "Bandas salariales globales"
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:261
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:260
|
||||
msgid "Growth"
|
||||
msgstr "Crecimiento"
|
||||
|
||||
@ -290,7 +286,7 @@ msgstr "Pagos integrados con Stripe para que no tengas que preocuparte por cobra
|
||||
msgid "Integrates with all your favourite tools."
|
||||
msgstr "Se integra con todas tus herramientas favoritas."
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:289
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:288
|
||||
msgid "Is there more?"
|
||||
msgstr "¿Hay más?"
|
||||
|
||||
@ -314,11 +310,11 @@ msgstr "Ubicación"
|
||||
msgid "Make it your own through advanced customization and adjustability."
|
||||
msgstr "Hazlo tuyo a través de personalización y ajustabilidad avanzadas."
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:199
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:198
|
||||
msgid "Merged PR's"
|
||||
msgstr "PRs fusionados"
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:234
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:233
|
||||
msgid "Merged PRs"
|
||||
msgstr "PRs fusionados"
|
||||
|
||||
@ -349,8 +345,8 @@ msgstr "No se requiere tarjeta de crédito"
|
||||
msgid "None of these work for you? Try self-hosting!"
|
||||
msgstr "¿Ninguna de estas opciones funciona para ti? ¡Prueba el autoalojamiento!"
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:194
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:252
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:193
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:251
|
||||
msgid "Open Issues"
|
||||
msgstr "Problemas Abiertos"
|
||||
|
||||
@ -358,7 +354,7 @@ msgstr "Problemas Abiertos"
|
||||
msgid "Open Source or Hosted."
|
||||
msgstr "Código Abierto o Alojado."
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:161
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:160
|
||||
#: apps/marketing/src/components/(marketing)/footer.tsx:37
|
||||
#: apps/marketing/src/components/(marketing)/header.tsx:64
|
||||
#: apps/marketing/src/components/(marketing)/mobile-navigation.tsx:40
|
||||
@ -377,18 +373,10 @@ msgstr "Nuestras plantillas personalizadas vienen con reglas inteligentes que pu
|
||||
msgid "Our Enterprise License is great for large organizations looking to switch to Documenso for all their signing needs. It's available for our cloud offering as well as self-hosted setups and offers a wide range of compliance and Adminstration Features."
|
||||
msgstr "Nuestra Licencia Empresarial es excelente para grandes organizaciones que buscan cambiar a Documenso para todas sus necesidades de firma. Está disponible para nuestra oferta en la nube, así como para configuraciones autoalojadas y ofrece una amplia gama de funciones de cumplimiento y administración."
|
||||
|
||||
#: apps/marketing/src/components/(marketing)/enterprise.tsx:20
|
||||
#~ msgid "Our Enterprise License is great large organizations looking to switch to Documenso for all their signing needs. It's availible for our cloud offering as well as self-hosted setups and offer a wide range of compliance and Adminstration Features."
|
||||
#~ msgstr "Our Enterprise License is great large organizations looking to switch to Documenso for all their signing needs. It's availible for our cloud offering as well as self-hosted setups and offer a wide range of compliance and Adminstration Features."
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/pricing/page.tsx:65
|
||||
msgid "Our self-hosted option is great for small teams and individuals who need a simple solution. You can use our docker based setup to get started in minutes. Take control with full customizability and data ownership."
|
||||
msgstr "Nuestra opción de autoalojamiento es excelente para pequeños equipos e individuos que necesitan una solución simple. Puedes usar nuestra configuración basada en docker para empezar en minutos. Toma el control con total personalización y propiedad de los datos."
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/open/data.ts:25
|
||||
#~ msgid "Part-Time"
|
||||
#~ msgstr "Part-Time"
|
||||
|
||||
#: apps/marketing/src/components/(marketing)/pricing-table.tsx:151
|
||||
msgid "Premium Profile Name"
|
||||
msgstr "Nombre de Perfil Premium"
|
||||
@ -430,10 +418,6 @@ msgstr "Salario"
|
||||
msgid "Save $60 or $120"
|
||||
msgstr "Ahorra $60 o $120"
|
||||
|
||||
#: apps/marketing/src/components/(marketing)/i18n-switcher.tsx:47
|
||||
#~ msgid "Search languages..."
|
||||
#~ msgstr "Search languages..."
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/pricing/page.tsx:109
|
||||
msgid "Securely. Our data centers are located in Frankfurt (Germany), giving us the best local privacy laws. We are very aware of the sensitive nature of our data and follow best practices to ensure the security and integrity of the data entrusted to us."
|
||||
msgstr "De manera segura. Nuestros centros de datos están ubicados en Frankfurt (Alemania), dándonos las mejores leyes de privacidad locales. Somos muy conscientes de la naturaleza sensible de nuestros datos y seguimos las mejores prácticas para garantizar la seguridad y la integridad de los datos que se nos confían."
|
||||
@ -482,7 +466,7 @@ msgstr "Inteligente."
|
||||
msgid "Star on GitHub"
|
||||
msgstr "Estrella en GitHub"
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:226
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:225
|
||||
msgid "Stars"
|
||||
msgstr "Estrellas"
|
||||
|
||||
@ -517,7 +501,7 @@ msgstr "Tienda de Plantillas (Pronto)."
|
||||
msgid "That's awesome. You can take a look at the current <0>Issues</0> and join our <1>Discord Community</1> to keep up to date, on what the current priorities are. In any case, we are an open community and welcome all input, technical and non-technical ❤️"
|
||||
msgstr "Eso es increíble. Puedes echar un vistazo a los <0>Problemas</0> actuales y unirte a nuestra <1>Comunidad de Discord</1> para mantenerte al día sobre cuáles son las prioridades actuales. En cualquier caso, somos una comunidad abierta y agradecemos todas las aportaciones, técnicas y no técnicas ❤️"
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:293
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:292
|
||||
msgid "This page is evolving as we learn what makes a great signing company. We'll update it when we have more to share."
|
||||
msgstr "Esta página está evolucionando a medida que aprendemos lo que hace una gran empresa de firmas. La actualizaremos cuando tengamos más para compartir."
|
||||
|
||||
@ -530,8 +514,8 @@ msgstr "Título"
|
||||
msgid "Total Completed Documents"
|
||||
msgstr "Total de Documentos Completados"
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:266
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:267
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:268
|
||||
msgid "Total Customers"
|
||||
msgstr "Total de Clientes"
|
||||
|
||||
@ -618,4 +602,3 @@ msgstr "Puedes autoalojar Documenso de forma gratuita o usar nuestra versión al
|
||||
#: apps/marketing/src/components/(marketing)/carousel.tsx:272
|
||||
msgid "Your browser does not support the video tag."
|
||||
msgstr "Tu navegador no soporta la etiqueta de video."
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -8,7 +8,7 @@ msgstr ""
|
||||
"Language: fr\n"
|
||||
"Project-Id-Version: documenso-app\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"PO-Revision-Date: 2024-10-18 04:04\n"
|
||||
"PO-Revision-Date: 2024-11-01 02:29\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: French\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
|
||||
@ -42,7 +42,7 @@ msgstr "Ajouter un document"
|
||||
msgid "Add More Users for {0}"
|
||||
msgstr "Ajouter plus d'utilisateurs pour {0}"
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:165
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:164
|
||||
msgid "All our metrics, finances, and learnings are public. We believe in transparency and want to share our journey with you. You can read more about why here: <0>Announcing Open Metrics</0>"
|
||||
msgstr "Tous nos indicateurs, finances et apprentissages sont publics. Nous croyons en la transparence et souhaitons partager notre parcours avec vous. Vous pouvez en lire plus sur pourquoi ici : <0>Annonce de Open Metrics</0>"
|
||||
|
||||
@ -90,7 +90,7 @@ msgstr "Changelog"
|
||||
msgid "Choose a template from the community app store. Or submit your own template for others to use."
|
||||
msgstr "Choisissez un modèle dans la boutique d'applications communautaires. Ou soumettez votre propre modèle pour que d'autres puissent l'utiliser."
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:219
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:218
|
||||
msgid "Community"
|
||||
msgstr "Communauté"
|
||||
|
||||
@ -160,10 +160,6 @@ msgstr "Documentation"
|
||||
msgid "Easily embed Documenso into your product. Simply copy and paste our react widget into your application."
|
||||
msgstr "Intégrez facilement Documenso dans votre produit. Il vous suffit de copier et coller notre widget React dans votre application."
|
||||
|
||||
#: apps/marketing/src/components/(marketing)/share-connect-paid-widget-bento.tsx:42
|
||||
#~ msgid "Easy Sharing (Soon)."
|
||||
#~ msgstr "Easy Sharing (Soon)."
|
||||
|
||||
#: apps/marketing/src/components/(marketing)/share-connect-paid-widget-bento.tsx:46
|
||||
msgid "Easy Sharing."
|
||||
msgstr "Partage facile."
|
||||
@ -197,7 +193,7 @@ msgstr "Rapide."
|
||||
msgid "Faster, smarter and more beautiful."
|
||||
msgstr "Plus rapide, plus intelligent et plus beau."
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:210
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:209
|
||||
msgid "Finances"
|
||||
msgstr "Finances"
|
||||
|
||||
@ -250,15 +246,15 @@ msgstr "Commencez aujourd'hui."
|
||||
msgid "Get the latest news from Documenso, including product updates, team announcements and more!"
|
||||
msgstr "Obtenez les dernières nouvelles de Documenso, y compris les mises à jour de produits, les annonces d'équipe et plus encore !"
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:233
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:232
|
||||
msgid "GitHub: Total Merged PRs"
|
||||
msgstr "GitHub : Total des PRs fusionnées"
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:251
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:250
|
||||
msgid "GitHub: Total Open Issues"
|
||||
msgstr "GitHub : Total des problèmes ouverts"
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:225
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:224
|
||||
msgid "GitHub: Total Stars"
|
||||
msgstr "GitHub : Nombre total d'étoiles"
|
||||
|
||||
@ -266,7 +262,7 @@ msgstr "GitHub : Nombre total d'étoiles"
|
||||
msgid "Global Salary Bands"
|
||||
msgstr "Bandes de salaire globales"
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:261
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:260
|
||||
msgid "Growth"
|
||||
msgstr "Croissance"
|
||||
|
||||
@ -290,7 +286,7 @@ msgstr "Paiements intégrés avec Stripe afin que vous n'ayez pas à vous soucie
|
||||
msgid "Integrates with all your favourite tools."
|
||||
msgstr "S'intègre à tous vos outils préférés."
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:289
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:288
|
||||
msgid "Is there more?"
|
||||
msgstr "Y a-t-il plus ?"
|
||||
|
||||
@ -314,11 +310,11 @@ msgstr "Emplacement"
|
||||
msgid "Make it your own through advanced customization and adjustability."
|
||||
msgstr "Faites-en votre propre grâce à une personnalisation avancée et un ajustement."
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:199
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:198
|
||||
msgid "Merged PR's"
|
||||
msgstr "PRs fusionnées"
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:234
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:233
|
||||
msgid "Merged PRs"
|
||||
msgstr "PRs fusionnées"
|
||||
|
||||
@ -349,8 +345,8 @@ msgstr "Aucune carte de crédit requise"
|
||||
msgid "None of these work for you? Try self-hosting!"
|
||||
msgstr "Aucune de ces options ne fonctionne pour vous ? Essayez l'hébergement autonome !"
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:194
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:252
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:193
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:251
|
||||
msgid "Open Issues"
|
||||
msgstr "Problèmes ouverts"
|
||||
|
||||
@ -358,7 +354,7 @@ msgstr "Problèmes ouverts"
|
||||
msgid "Open Source or Hosted."
|
||||
msgstr "Open Source ou hébergé."
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:161
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:160
|
||||
#: apps/marketing/src/components/(marketing)/footer.tsx:37
|
||||
#: apps/marketing/src/components/(marketing)/header.tsx:64
|
||||
#: apps/marketing/src/components/(marketing)/mobile-navigation.tsx:40
|
||||
@ -377,18 +373,10 @@ msgstr "Nos modèles personnalisés sont dotés de règles intelligentes qui peu
|
||||
msgid "Our Enterprise License is great for large organizations looking to switch to Documenso for all their signing needs. It's available for our cloud offering as well as self-hosted setups and offers a wide range of compliance and Adminstration Features."
|
||||
msgstr "Notre licence entreprise est idéale pour les grandes organisations cherchant à passer à Documenso pour tous leurs besoins de signature. Elle est disponible pour notre offre cloud ainsi que pour des configurations auto-hébergées et propose un large éventail de fonctionnalités de conformité et d'administration."
|
||||
|
||||
#: apps/marketing/src/components/(marketing)/enterprise.tsx:20
|
||||
#~ msgid "Our Enterprise License is great large organizations looking to switch to Documenso for all their signing needs. It's availible for our cloud offering as well as self-hosted setups and offer a wide range of compliance and Adminstration Features."
|
||||
#~ msgstr "Our Enterprise License is great large organizations looking to switch to Documenso for all their signing needs. It's availible for our cloud offering as well as self-hosted setups and offer a wide range of compliance and Adminstration Features."
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/pricing/page.tsx:65
|
||||
msgid "Our self-hosted option is great for small teams and individuals who need a simple solution. You can use our docker based setup to get started in minutes. Take control with full customizability and data ownership."
|
||||
msgstr "Notre option auto-hébergée est idéale pour les petites équipes et les individus qui ont besoin d'une solution simple. Vous pouvez utiliser notre configuration basée sur Docker pour commencer en quelques minutes. Prenez le contrôle avec une personnalisation complète et une propriété des données."
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/open/data.ts:25
|
||||
#~ msgid "Part-Time"
|
||||
#~ msgstr "Part-Time"
|
||||
|
||||
#: apps/marketing/src/components/(marketing)/pricing-table.tsx:151
|
||||
msgid "Premium Profile Name"
|
||||
msgstr "Nom de profil premium"
|
||||
@ -430,10 +418,6 @@ msgstr "Salaire"
|
||||
msgid "Save $60 or $120"
|
||||
msgstr "Économisez 60 $ ou 120 $"
|
||||
|
||||
#: apps/marketing/src/components/(marketing)/i18n-switcher.tsx:47
|
||||
#~ msgid "Search languages..."
|
||||
#~ msgstr "Search languages..."
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/pricing/page.tsx:109
|
||||
msgid "Securely. Our data centers are located in Frankfurt (Germany), giving us the best local privacy laws. We are very aware of the sensitive nature of our data and follow best practices to ensure the security and integrity of the data entrusted to us."
|
||||
msgstr "De manière sécurisée. Nos centres de données sont situés à Francfort (Allemagne), ce qui nous permet de bénéficier des meilleures lois locales sur la confidentialité. Nous sommes très conscients de la nature sensible de nos données et suivons les meilleures pratiques pour garantir la sécurité et l'intégrité des données qui nous sont confiées."
|
||||
@ -482,7 +466,7 @@ msgstr "Intelligent."
|
||||
msgid "Star on GitHub"
|
||||
msgstr "Étoile sur GitHub"
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:226
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:225
|
||||
msgid "Stars"
|
||||
msgstr "Étoiles"
|
||||
|
||||
@ -517,7 +501,7 @@ msgstr "Boutique de modèles (Bientôt)."
|
||||
msgid "That's awesome. You can take a look at the current <0>Issues</0> and join our <1>Discord Community</1> to keep up to date, on what the current priorities are. In any case, we are an open community and welcome all input, technical and non-technical ❤️"
|
||||
msgstr "C'est génial. Vous pouvez consulter les <0>Problèmes</0> actuels et rejoindre notre <1>Communauté Discord</1> pour rester à jour sur ce qui est actuellement prioritaire. Dans tous les cas, nous sommes une communauté ouverte et accueillons toutes les contributions, techniques et non techniques ❤️"
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:293
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:292
|
||||
msgid "This page is evolving as we learn what makes a great signing company. We'll update it when we have more to share."
|
||||
msgstr "Cette page évolue à mesure que nous apprenons ce qui fait une grande entreprise de signature. Nous la mettrons à jour lorsque nous aurons plus à partager."
|
||||
|
||||
@ -530,8 +514,8 @@ msgstr "Titre"
|
||||
msgid "Total Completed Documents"
|
||||
msgstr "Documents totalisés complétés"
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:266
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:267
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:268
|
||||
msgid "Total Customers"
|
||||
msgstr "Total des clients"
|
||||
|
||||
@ -618,4 +602,3 @@ msgstr "Vous pouvez auto-héberger Documenso gratuitement ou utiliser notre vers
|
||||
#: apps/marketing/src/components/(marketing)/carousel.tsx:272
|
||||
msgid "Your browser does not support the video tag."
|
||||
msgstr "Votre navigateur ne prend pas en charge la balise vidéo."
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -1,6 +1,6 @@
|
||||
import type { ReadonlyRequestCookies } from 'next/dist/server/web/spec-extension/adapters/request-cookies';
|
||||
|
||||
import type { I18n } from '@lingui/core';
|
||||
import type { I18n, MessageDescriptor } from '@lingui/core';
|
||||
|
||||
import { IS_APP_WEB, IS_APP_WEB_I18N_ENABLED } from '../constants/app';
|
||||
import type { I18nLocaleData, SupportedLanguageCodes } from '../constants/i18n';
|
||||
@ -10,7 +10,17 @@ export async function dynamicActivate(i18nInstance: I18n, locale: string) {
|
||||
const extension = process.env.NODE_ENV === 'development' ? 'po' : 'js';
|
||||
const context = IS_APP_WEB ? 'web' : 'marketing';
|
||||
|
||||
const { messages } = await import(`../translations/${locale}/${context}.${extension}`);
|
||||
let { messages } = await import(`../translations/${locale}/${context}.${extension}`);
|
||||
|
||||
// Dirty way to load common messages for development since it's not compiled.
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
const commonMessages = await import(`../translations/${locale}/common.${extension}`);
|
||||
|
||||
messages = {
|
||||
...messages,
|
||||
...commonMessages.messages,
|
||||
};
|
||||
}
|
||||
|
||||
i18nInstance.loadAndActivate({ locale, messages });
|
||||
}
|
||||
@ -106,3 +116,7 @@ export const extractLocaleData = ({
|
||||
locales,
|
||||
};
|
||||
};
|
||||
|
||||
export const parseMessageDescriptor = (_: I18n['_'], value: string | MessageDescriptor) => {
|
||||
return typeof value === 'string' ? value : _(value);
|
||||
};
|
||||
|
||||
@ -4,7 +4,6 @@ export const isValidRedirectUrl = (value: string) => {
|
||||
try {
|
||||
const url = new URL(value);
|
||||
|
||||
console.log({ protocol: url.protocol });
|
||||
if (!ALLOWED_PROTOCOLS.includes(url.protocol.slice(0, -1).toLowerCase())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
18
packages/lib/utils/remember.ts
Normal file
18
packages/lib/utils/remember.ts
Normal file
@ -0,0 +1,18 @@
|
||||
declare global {
|
||||
// eslint-disable-next-line no-var, @typescript-eslint/no-explicit-any
|
||||
var __documenso_util_remember: Map<string, any>;
|
||||
}
|
||||
|
||||
export function remember<T>(name: string, getValue: () => T): T {
|
||||
const thusly = globalThis;
|
||||
|
||||
if (!thusly.__documenso_util_remember) {
|
||||
thusly.__documenso_util_remember = new Map();
|
||||
}
|
||||
|
||||
if (!thusly.__documenso_util_remember.has(name)) {
|
||||
thusly.__documenso_util_remember.set(name, getValue());
|
||||
}
|
||||
|
||||
return thusly.__documenso_util_remember.get(name);
|
||||
}
|
||||
36
packages/lib/utils/render-email-with-i18n.tsx
Normal file
36
packages/lib/utils/render-email-with-i18n.tsx
Normal file
@ -0,0 +1,36 @@
|
||||
import { I18nProvider } from '@lingui/react';
|
||||
|
||||
import { render } from '@documenso/email/render';
|
||||
|
||||
import { getI18nInstance } from '../client-only/providers/i18n.server';
|
||||
import {
|
||||
APP_I18N_OPTIONS,
|
||||
type SupportedLanguageCodes,
|
||||
isValidLanguageCode,
|
||||
} from '../constants/i18n';
|
||||
|
||||
export const renderEmailWithI18N = async (
|
||||
component: React.ReactElement,
|
||||
options?: {
|
||||
plainText?: boolean;
|
||||
// eslint-disable-next-line @typescript-eslint/ban-types
|
||||
lang?: SupportedLanguageCodes | (string & {});
|
||||
},
|
||||
) => {
|
||||
try {
|
||||
const providedLang = options?.lang;
|
||||
|
||||
const lang = isValidLanguageCode(providedLang) ? providedLang : APP_I18N_OPTIONS.sourceLang;
|
||||
|
||||
const i18n = await getI18nInstance(lang);
|
||||
|
||||
i18n.activate(lang);
|
||||
|
||||
return render(<I18nProvider i18n={i18n}>{component}</I18nProvider>, {
|
||||
plainText: options?.plainText,
|
||||
});
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
throw new Error('Failed to render email');
|
||||
}
|
||||
};
|
||||
@ -0,0 +1,5 @@
|
||||
-- AlterTable
|
||||
ALTER TABLE "DocumentMeta" ADD COLUMN "language" TEXT NOT NULL DEFAULT 'en';
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "TemplateMeta" ADD COLUMN "language" TEXT NOT NULL DEFAULT 'en';
|
||||
@ -370,6 +370,7 @@ model DocumentMeta {
|
||||
redirectUrl String?
|
||||
signingOrder DocumentSigningOrder @default(PARALLEL)
|
||||
typedSignatureEnabled Boolean @default(false)
|
||||
language String @default("en")
|
||||
}
|
||||
|
||||
enum ReadStatus {
|
||||
@ -612,6 +613,7 @@ model TemplateMeta {
|
||||
templateId Int @unique
|
||||
template Template @relation(fields: [templateId], references: [id], onDelete: Cascade)
|
||||
redirectUrl String?
|
||||
language String @default("en")
|
||||
}
|
||||
|
||||
model Template {
|
||||
|
||||
@ -232,6 +232,7 @@ export const documentRouter = router({
|
||||
dateFormat: meta.dateFormat,
|
||||
timezone: meta.timezone,
|
||||
redirectUrl: meta.redirectUrl,
|
||||
language: meta.language,
|
||||
userId: ctx.user.id,
|
||||
requestMetadata,
|
||||
});
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
import { SUPPORTED_LANGUAGE_CODES } from '@documenso/lib/constants/i18n';
|
||||
import {
|
||||
ZDocumentAccessAuthTypesSchema,
|
||||
ZDocumentActionAuthTypesSchema,
|
||||
@ -70,6 +71,7 @@ export const ZSetSettingsForDocumentMutationSchema = z.object({
|
||||
message:
|
||||
'Please enter a valid URL, make sure you include http:// or https:// part of the url.',
|
||||
}),
|
||||
language: z.enum(SUPPORTED_LANGUAGE_CODES).optional(),
|
||||
}),
|
||||
});
|
||||
|
||||
|
||||
@ -3,7 +3,6 @@ import { createElement } from 'react';
|
||||
import { PDFDocument } from 'pdf-lib';
|
||||
|
||||
import { mailer } from '@documenso/email/mailer';
|
||||
import { renderAsync } from '@documenso/email/render';
|
||||
import { DocumentSelfSignedEmailTemplate } from '@documenso/email/templates/document-self-signed';
|
||||
import { NEXT_PUBLIC_WEBAPP_URL } from '@documenso/lib/constants/app';
|
||||
import { FROM_ADDRESS, FROM_NAME, SERVICE_USER_EMAIL } from '@documenso/lib/constants/email';
|
||||
@ -11,6 +10,7 @@ import { insertFieldInPDF } from '@documenso/lib/server-only/pdf/insert-field-in
|
||||
import { alphaid } from '@documenso/lib/universal/id';
|
||||
import { getFile } from '@documenso/lib/universal/upload/get-file';
|
||||
import { putPdfFile } from '@documenso/lib/universal/upload/put-file';
|
||||
import { renderEmailWithI18N } from '@documenso/lib/utils/render-email-with-i18n';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import {
|
||||
DocumentSource,
|
||||
@ -158,8 +158,8 @@ export const singleplayerRouter = router({
|
||||
});
|
||||
|
||||
const [html, text] = await Promise.all([
|
||||
renderAsync(template),
|
||||
renderAsync(template, { plainText: true }),
|
||||
renderEmailWithI18N(template),
|
||||
renderEmailWithI18N(template, { plainText: true }),
|
||||
]);
|
||||
|
||||
// Send email to signer.
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import { TRPCError } from '@trpc/server';
|
||||
|
||||
import { getServerLimits } from '@documenso/ee/server-only/limits/server';
|
||||
import { isValidLanguageCode } from '@documenso/lib/constants/i18n';
|
||||
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
||||
import { sendDocument } from '@documenso/lib/server-only/document/send-document';
|
||||
import { createDocumentFromDirectTemplate } from '@documenso/lib/server-only/template/create-document-from-direct-template';
|
||||
@ -214,7 +215,10 @@ export const templateRouter = router({
|
||||
teamId,
|
||||
templateId,
|
||||
data,
|
||||
meta,
|
||||
meta: {
|
||||
...meta,
|
||||
language: isValidLanguageCode(meta?.language) ? meta?.language : undefined,
|
||||
},
|
||||
requestMetadata,
|
||||
});
|
||||
} catch (err) {
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
import { SUPPORTED_LANGUAGE_CODES } from '@documenso/lib/constants/i18n';
|
||||
import {
|
||||
ZDocumentAccessAuthTypesSchema,
|
||||
ZDocumentActionAuthTypesSchema,
|
||||
@ -87,6 +88,10 @@ export const ZUpdateTemplateSettingsMutationSchema = z.object({
|
||||
.max(MAX_TEMPLATE_PUBLIC_DESCRIPTION_LENGTH)
|
||||
.optional(),
|
||||
type: z.nativeEnum(TemplateType).optional(),
|
||||
language: z
|
||||
.union([z.string(), z.enum(SUPPORTED_LANGUAGE_CODES)])
|
||||
.optional()
|
||||
.default('en'),
|
||||
}),
|
||||
meta: z
|
||||
.object({
|
||||
@ -101,6 +106,7 @@ export const ZUpdateTemplateSettingsMutationSchema = z.object({
|
||||
message:
|
||||
'Please enter a valid URL, make sure you include http:// or https:// part of the url.',
|
||||
}),
|
||||
language: z.enum(SUPPORTED_LANGUAGE_CODES).optional(),
|
||||
})
|
||||
.optional(),
|
||||
});
|
||||
|
||||
@ -139,12 +139,16 @@ export const DocumentShareButton = ({
|
||||
|
||||
<DialogContent position="end">
|
||||
<DialogHeader>
|
||||
<DialogTitle>Share your signing experience!</DialogTitle>
|
||||
<DialogTitle>
|
||||
<Trans>Share your signing experience!</Trans>
|
||||
</DialogTitle>
|
||||
|
||||
<DialogDescription className="mt-4">
|
||||
Rest assured, your document is strictly confidential and will never be shared. Only your
|
||||
signing experience will be highlighted. Share your personalized signing card to showcase
|
||||
your signature!
|
||||
<Trans>
|
||||
Rest assured, your document is strictly confidential and will never be shared. Only
|
||||
your signing experience will be highlighted. Share your personalized signing card to
|
||||
showcase your signature!
|
||||
</Trans>
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
@ -187,7 +191,7 @@ export const DocumentShareButton = ({
|
||||
|
||||
<Button variant="outline" className="flex-1" onClick={onCopyClick}>
|
||||
<Copy className="mr-2 h-4 w-4" />
|
||||
Copy Link
|
||||
<Trans>Copy Link</Trans>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -5,6 +5,7 @@ import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { Caveat } from 'next/font/google';
|
||||
|
||||
import { Trans, msg } from '@lingui/macro';
|
||||
import { useLingui } from '@lingui/react';
|
||||
import { Prisma } from '@prisma/client';
|
||||
import {
|
||||
CalendarDays,
|
||||
@ -34,6 +35,7 @@ import {
|
||||
} from '@documenso/lib/types/field-meta';
|
||||
import { nanoid } from '@documenso/lib/universal/id';
|
||||
import { validateFieldsUninserted } from '@documenso/lib/utils/fields';
|
||||
import { parseMessageDescriptor } from '@documenso/lib/utils/i18n';
|
||||
import {
|
||||
canRecipientBeModified,
|
||||
canRecipientFieldsBeModified,
|
||||
@ -114,6 +116,7 @@ export const AddFieldsFormPartial = ({
|
||||
teamId,
|
||||
}: AddFieldsFormProps) => {
|
||||
const { toast } = useToast();
|
||||
const { _ } = useLingui();
|
||||
|
||||
const [isMissingSignatureDialogVisible, setIsMissingSignatureDialogVisible] = useState(false);
|
||||
|
||||
@ -568,7 +571,10 @@ export const AddFieldsFormPartial = ({
|
||||
{showAdvancedSettings && currentField ? (
|
||||
<FieldAdvancedSettings
|
||||
title={msg`Advanced settings`}
|
||||
description={msg`Configure the ${FRIENDLY_FIELD_TYPE[currentField.type]} field`}
|
||||
description={msg`Configure the ${parseMessageDescriptor(
|
||||
_,
|
||||
FRIENDLY_FIELD_TYPE[currentField.type],
|
||||
)} field`}
|
||||
field={currentField}
|
||||
fields={localFields}
|
||||
onAdvancedSettings={handleAdvancedSettings}
|
||||
@ -603,7 +609,7 @@ export const AddFieldsFormPartial = ({
|
||||
width: fieldBounds.current.width,
|
||||
}}
|
||||
>
|
||||
{FRIENDLY_FIELD_TYPE[selectedField]}
|
||||
{parseMessageDescriptor(_, FRIENDLY_FIELD_TYPE[selectedField])}
|
||||
</div>
|
||||
)}
|
||||
|
||||
@ -684,8 +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">
|
||||
{/* Todo: Translations - Add plural translations. */}
|
||||
{`${RECIPIENT_ROLES_DESCRIPTION_ENG[role].roleName}s`}
|
||||
{_(RECIPIENT_ROLES_DESCRIPTION_ENG[role].roleNamePlural)}
|
||||
</div>
|
||||
|
||||
{roleRecipients.length === 0 && (
|
||||
@ -997,7 +1002,7 @@ export const AddFieldsFormPartial = ({
|
||||
)}
|
||||
>
|
||||
<Disc className="h-4 w-4" />
|
||||
<Trans>Radio</Trans>
|
||||
Radio
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
@ -1023,7 +1028,8 @@ export const AddFieldsFormPartial = ({
|
||||
)}
|
||||
>
|
||||
<CheckSquare className="h-4 w-4" />
|
||||
<Trans>Checkbox</Trans>
|
||||
{/* Not translated on purpose. */}
|
||||
Checkbox
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
@ -8,6 +8,7 @@ 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 { SUPPORTED_LANGUAGES } from '@documenso/lib/constants/i18n';
|
||||
import { DEFAULT_DOCUMENT_TIME_ZONE, TIME_ZONES } from '@documenso/lib/constants/time-zones';
|
||||
import { extractDocumentAuthMethods } from '@documenso/lib/utils/document-auth';
|
||||
import type { TeamMemberRole } from '@documenso/prisma/client';
|
||||
@ -98,6 +99,7 @@ export const AddSettingsFormPartial = ({
|
||||
DATE_FORMATS.find((format) => format.value === document.documentMeta?.dateFormat)
|
||||
?.value ?? DEFAULT_DOCUMENT_DATE_FORMAT,
|
||||
redirectUrl: document.documentMeta?.redirectUrl ?? '',
|
||||
language: document.documentMeta?.language ?? 'en',
|
||||
},
|
||||
},
|
||||
});
|
||||
@ -165,6 +167,46 @@ export const AddSettingsFormPartial = ({
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="meta.language"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel className="inline-flex items-center">
|
||||
<Trans>Language</Trans>
|
||||
<Tooltip>
|
||||
<TooltipTrigger>
|
||||
<InfoIcon className="mx-2 h-4 w-4" />
|
||||
</TooltipTrigger>
|
||||
|
||||
<TooltipContent className="text-foreground max-w-md space-y-2 p-4">
|
||||
Controls the language for the document, including the language to be used
|
||||
for email notifications, and the final certificate that is generated and
|
||||
attached to the document.
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</FormLabel>
|
||||
|
||||
<FormControl>
|
||||
<Select {...field} onValueChange={field.onChange}>
|
||||
<SelectTrigger className="bg-background">
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
|
||||
<SelectContent>
|
||||
{Object.entries(SUPPORTED_LANGUAGES).map(([code, language]) => (
|
||||
<SelectItem key={code} value={code}>
|
||||
{language.full}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="globalAccessAuth"
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
import { DEFAULT_DOCUMENT_DATE_FORMAT } from '@documenso/lib/constants/date-formats';
|
||||
import { SUPPORTED_LANGUAGE_CODES } from '@documenso/lib/constants/i18n';
|
||||
import { DEFAULT_DOCUMENT_TIME_ZONE } from '@documenso/lib/constants/time-zones';
|
||||
import {
|
||||
ZDocumentAccessAuthTypesSchema,
|
||||
@ -39,6 +40,10 @@ export const ZAddSettingsFormSchema = z.object({
|
||||
message:
|
||||
'Please enter a valid URL, make sure you include http:// or https:// part of the url.',
|
||||
}),
|
||||
language: z
|
||||
.union([z.string(), z.enum(SUPPORTED_LANGUAGE_CODES)])
|
||||
.optional()
|
||||
.default('en'),
|
||||
}),
|
||||
});
|
||||
|
||||
|
||||
@ -1,10 +1,11 @@
|
||||
import { msg } from '@lingui/macro';
|
||||
import { z } from 'zod';
|
||||
|
||||
export const ZAddSignatureFormSchema = z.object({
|
||||
email: z
|
||||
.string()
|
||||
.min(1, { message: 'Email is required' })
|
||||
.email({ message: 'Invalid email address' }),
|
||||
.min(1, { message: msg`Email is required`.id })
|
||||
.email({ message: msg`Invalid email address`.id }),
|
||||
name: z.string(),
|
||||
customText: z.string(),
|
||||
number: z.number().optional(),
|
||||
|
||||
@ -504,7 +504,7 @@ export const AddSignersFormPartial = ({
|
||||
<FormControl>
|
||||
<Input
|
||||
type="email"
|
||||
placeholder="Email"
|
||||
placeholder={_(msg`Email`)}
|
||||
{...field}
|
||||
disabled={
|
||||
snapshot.isDragging ||
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import { msg } from '@lingui/macro';
|
||||
import { z } from 'zod';
|
||||
|
||||
import { ZRecipientActionAuthTypesSchema } from '@documenso/lib/types/document-auth';
|
||||
@ -11,7 +12,10 @@ export const ZAddSignersFormSchema = z
|
||||
z.object({
|
||||
formId: z.string().min(1),
|
||||
nativeId: z.number().optional(),
|
||||
email: z.string().email().min(1),
|
||||
email: z
|
||||
.string()
|
||||
.email({ message: msg`Invalid email`.id })
|
||||
.min(1),
|
||||
name: z.string(),
|
||||
role: z.nativeEnum(RecipientRole),
|
||||
signingOrder: z.number().optional(),
|
||||
@ -29,7 +33,7 @@ export const ZAddSignersFormSchema = z
|
||||
return new Set(emails).size === emails.length;
|
||||
},
|
||||
// Dirty hack to handle errors when .root is populated for an array type
|
||||
{ message: 'Signers must have unique emails', path: ['signers__root'] },
|
||||
{ message: msg`Signers must have unique emails`.id, path: ['signers__root'] },
|
||||
);
|
||||
|
||||
export type TAddSignersFormSchema = z.infer<typeof ZAddSignersFormSchema>;
|
||||
|
||||
@ -2,10 +2,12 @@
|
||||
|
||||
import { Caveat } from 'next/font/google';
|
||||
|
||||
import { useLingui } from '@lingui/react';
|
||||
import type { Prisma } from '@prisma/client';
|
||||
import { createPortal } from 'react-dom';
|
||||
|
||||
import { useFieldPageCoords } from '@documenso/lib/client-only/hooks/use-field-page-coords';
|
||||
import { parseMessageDescriptor } from '@documenso/lib/utils/i18n';
|
||||
import { FieldType } from '@documenso/prisma/client';
|
||||
|
||||
import { cn } from '../../lib/utils';
|
||||
@ -25,6 +27,8 @@ export type ShowFieldItemProps = {
|
||||
};
|
||||
|
||||
export const ShowFieldItem = ({ field, recipients }: ShowFieldItemProps) => {
|
||||
const { _ } = useLingui();
|
||||
|
||||
const coords = useFieldPageCoords(field);
|
||||
|
||||
const signerEmail =
|
||||
@ -47,7 +51,7 @@ export const ShowFieldItem = ({ field, recipients }: ShowFieldItemProps) => {
|
||||
field.type === FieldType.SIGNATURE && fontCaveat.className,
|
||||
)}
|
||||
>
|
||||
{FRIENDLY_FIELD_TYPE[field.type]}
|
||||
{parseMessageDescriptor(_, FRIENDLY_FIELD_TYPE[field.type])}
|
||||
|
||||
<p className="text-muted-foreground/50 w-full truncate text-center text-xs">
|
||||
{signerEmail}
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import type { MessageDescriptor } from '@lingui/core';
|
||||
import { msg } from '@lingui/macro';
|
||||
import { z } from 'zod';
|
||||
|
||||
import { ZFieldMetaSchema } from '@documenso/lib/types/field-meta';
|
||||
@ -44,18 +45,18 @@ export const ZDocumentFlowFormSchema = z.object({
|
||||
|
||||
export type TDocumentFlowFormSchema = z.infer<typeof ZDocumentFlowFormSchema>;
|
||||
|
||||
export const FRIENDLY_FIELD_TYPE: Record<FieldType, string> = {
|
||||
[FieldType.SIGNATURE]: 'Signature',
|
||||
[FieldType.FREE_SIGNATURE]: 'Free Signature',
|
||||
[FieldType.INITIALS]: 'Initials',
|
||||
[FieldType.TEXT]: 'Text',
|
||||
[FieldType.DATE]: 'Date',
|
||||
[FieldType.EMAIL]: 'Email',
|
||||
[FieldType.NAME]: 'Name',
|
||||
[FieldType.NUMBER]: 'Number',
|
||||
[FieldType.RADIO]: 'Radio',
|
||||
[FieldType.CHECKBOX]: 'Checkbox',
|
||||
[FieldType.DROPDOWN]: 'Select',
|
||||
export const FRIENDLY_FIELD_TYPE: Record<FieldType, MessageDescriptor | string> = {
|
||||
[FieldType.SIGNATURE]: msg`Signature`,
|
||||
[FieldType.FREE_SIGNATURE]: msg`Free Signature`,
|
||||
[FieldType.INITIALS]: msg`Initials`,
|
||||
[FieldType.TEXT]: msg`Text`,
|
||||
[FieldType.DATE]: msg`Date`,
|
||||
[FieldType.EMAIL]: msg`Email`,
|
||||
[FieldType.NAME]: msg`Name`,
|
||||
[FieldType.NUMBER]: msg`Number`,
|
||||
[FieldType.RADIO]: `Radio`,
|
||||
[FieldType.CHECKBOX]: `Checkbox`,
|
||||
[FieldType.DROPDOWN]: `Select`,
|
||||
};
|
||||
|
||||
export interface DocumentFlowStep {
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import { useLingui } from '@lingui/react';
|
||||
import { AnimatePresence, motion } from 'framer-motion';
|
||||
|
||||
import { cn } from '../../lib/utils';
|
||||
@ -12,6 +13,15 @@ const isErrorWithMessage = (error: unknown): error is { message?: string } => {
|
||||
};
|
||||
|
||||
export const FormErrorMessage = ({ error, className }: FormErrorMessageProps) => {
|
||||
const { i18n } = useLingui();
|
||||
|
||||
let errorMessage = isErrorWithMessage(error) ? error.message : '';
|
||||
|
||||
// Checks to see if there's a translation for the string, since we're passing IDs for Zod errors.
|
||||
if (typeof errorMessage === 'string' && i18n.t(errorMessage)) {
|
||||
errorMessage = i18n.t(errorMessage);
|
||||
}
|
||||
|
||||
return (
|
||||
<AnimatePresence>
|
||||
{isErrorWithMessage(error) && (
|
||||
@ -30,7 +40,7 @@ export const FormErrorMessage = ({ error, className }: FormErrorMessageProps) =>
|
||||
}}
|
||||
className={cn('text-xs text-red-500', className)}
|
||||
>
|
||||
{error.message}
|
||||
{errorMessage}
|
||||
</motion.p>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import * as React from 'react';
|
||||
|
||||
import { useLingui } from '@lingui/react';
|
||||
import type * as LabelPrimitive from '@radix-ui/react-label';
|
||||
import { Slot } from '@radix-ui/react-slot';
|
||||
import { AnimatePresence, motion } from 'framer-motion';
|
||||
@ -136,13 +137,21 @@ const FormMessage = React.forwardRef<
|
||||
HTMLParagraphElement,
|
||||
React.HTMLAttributes<HTMLParagraphElement>
|
||||
>(({ className, children, ...props }, ref) => {
|
||||
const { i18n } = useLingui();
|
||||
|
||||
const { error, formMessageId } = useFormField();
|
||||
const body = error ? String(error?.message) : children;
|
||||
|
||||
let body = error ? String(error?.message) : children;
|
||||
|
||||
if (!body) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Checks to see if there's a translation for the string, since we're passing IDs for Zod errors.
|
||||
if (typeof body === 'string' && i18n.t(body)) {
|
||||
body = i18n.t(body);
|
||||
}
|
||||
|
||||
return (
|
||||
<AnimatePresence>
|
||||
<motion.div
|
||||
|
||||
@ -2,6 +2,7 @@
|
||||
|
||||
import dynamic from 'next/dynamic';
|
||||
|
||||
import { Trans } from '@lingui/macro';
|
||||
import { Loader } from 'lucide-react';
|
||||
|
||||
export const LazyPDFViewer = dynamic(async () => import('./pdf-viewer'), {
|
||||
@ -10,7 +11,9 @@ export const LazyPDFViewer = dynamic(async () => import('./pdf-viewer'), {
|
||||
<div className="dark:bg-background flex h-[80vh] max-h-[60rem] flex-col items-center justify-center bg-white/50">
|
||||
<Loader className="text-documenso h-12 w-12 animate-spin" />
|
||||
|
||||
<p className="text-muted-foreground mt-4">Loading document...</p>
|
||||
<p className="text-muted-foreground mt-4">
|
||||
<Trans>Loading document...</Trans>
|
||||
</p>
|
||||
</div>
|
||||
),
|
||||
});
|
||||
|
||||
@ -2,6 +2,8 @@
|
||||
|
||||
import React, { useEffect, useMemo, useRef, useState } from 'react';
|
||||
|
||||
import { Trans, msg } from '@lingui/macro';
|
||||
import { useLingui } from '@lingui/react';
|
||||
import { Loader } from 'lucide-react';
|
||||
import { type PDFDocumentProxy, PasswordResponses } from 'pdfjs-dist';
|
||||
import { Document as PDFDocument, Page as PDFPage, pdfjs } from 'react-pdf';
|
||||
@ -38,7 +40,9 @@ const PDFLoader = () => (
|
||||
<>
|
||||
<Loader className="text-documenso h-12 w-12 animate-spin" />
|
||||
|
||||
<p className="text-muted-foreground mt-4">Loading document...</p>
|
||||
<p className="text-muted-foreground mt-4">
|
||||
<Trans>Loading document...</Trans>
|
||||
</p>
|
||||
</>
|
||||
);
|
||||
|
||||
@ -61,6 +65,7 @@ export const PDFViewer = ({
|
||||
onPageClick,
|
||||
...props
|
||||
}: PDFViewerProps) => {
|
||||
const { _ } = useLingui();
|
||||
const { toast } = useToast();
|
||||
|
||||
const $el = useRef<HTMLDivElement>(null);
|
||||
@ -158,8 +163,8 @@ export const PDFViewer = ({
|
||||
console.error(err);
|
||||
|
||||
toast({
|
||||
title: 'Error',
|
||||
description: 'An error occurred while loading the document.',
|
||||
title: _(msg`Error`),
|
||||
description: _(msg`An error occurred while loading the document.`),
|
||||
variant: 'destructive',
|
||||
});
|
||||
}
|
||||
@ -211,8 +216,12 @@ export const PDFViewer = ({
|
||||
<div className="dark:bg-background flex h-[80vh] max-h-[60rem] flex-col items-center justify-center bg-white/50">
|
||||
{pdfError ? (
|
||||
<div className="text-muted-foreground text-center">
|
||||
<p>Something went wrong while loading the document.</p>
|
||||
<p className="mt-1 text-sm">Please try again or contact our support.</p>
|
||||
<p>
|
||||
<Trans>Something went wrong while loading the document.</Trans>
|
||||
</p>
|
||||
<p className="mt-1 text-sm">
|
||||
<Trans>Please try again or contact our support.</Trans>
|
||||
</p>
|
||||
</div>
|
||||
) : (
|
||||
<PDFLoader />
|
||||
@ -222,8 +231,12 @@ export const PDFViewer = ({
|
||||
error={
|
||||
<div className="dark:bg-background flex h-[80vh] max-h-[60rem] flex-col items-center justify-center bg-white/50">
|
||||
<div className="text-muted-foreground text-center">
|
||||
<p>Something went wrong while loading the document.</p>
|
||||
<p className="mt-1 text-sm">Please try again or contact our support.</p>
|
||||
<p>
|
||||
<Trans>Something went wrong while loading the document.</Trans>
|
||||
</p>
|
||||
<p className="mt-1 text-sm">
|
||||
<Trans>Please try again or contact our support.</Trans>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
@ -243,7 +256,9 @@ export const PDFViewer = ({
|
||||
/>
|
||||
</div>
|
||||
<p className="text-muted-foreground/80 my-2 text-center text-[11px]">
|
||||
Page {i + 1} of {numPages}
|
||||
<Trans>
|
||||
Page {i + 1} of {numPages}
|
||||
</Trans>
|
||||
</p>
|
||||
</div>
|
||||
))}
|
||||
|
||||
@ -5,6 +5,7 @@ import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { Caveat } from 'next/font/google';
|
||||
|
||||
import { Trans, msg } from '@lingui/macro';
|
||||
import { useLingui } from '@lingui/react';
|
||||
import {
|
||||
CalendarDays,
|
||||
CheckSquare,
|
||||
@ -28,6 +29,7 @@ import {
|
||||
ZFieldMetaSchema,
|
||||
} from '@documenso/lib/types/field-meta';
|
||||
import { nanoid } from '@documenso/lib/universal/id';
|
||||
import { parseMessageDescriptor } from '@documenso/lib/utils/i18n';
|
||||
import type { Field, Recipient } from '@documenso/prisma/client';
|
||||
import { FieldType, RecipientRole } from '@documenso/prisma/client';
|
||||
import { cn } from '@documenso/ui/lib/utils';
|
||||
@ -85,6 +87,8 @@ export const AddTemplateFieldsFormPartial = ({
|
||||
onSubmit,
|
||||
teamId,
|
||||
}: AddTemplateFieldsFormProps) => {
|
||||
const { _ } = useLingui();
|
||||
|
||||
const { isWithinPageBounds, getFieldPosition, getPage } = useDocumentElement();
|
||||
const { currentStep, totalSteps, previousStep } = useStep();
|
||||
const [showAdvancedSettings, setShowAdvancedSettings] = useState(false);
|
||||
@ -400,7 +404,10 @@ export const AddTemplateFieldsFormPartial = ({
|
||||
{showAdvancedSettings && currentField ? (
|
||||
<FieldAdvancedSettings
|
||||
title={msg`Advanced settings`}
|
||||
description={msg`Configure the ${FRIENDLY_FIELD_TYPE[currentField.type]} field`}
|
||||
description={msg`Configure the ${parseMessageDescriptor(
|
||||
_,
|
||||
FRIENDLY_FIELD_TYPE[currentField.type],
|
||||
)} field`}
|
||||
field={currentField}
|
||||
fields={localFields}
|
||||
onAdvancedSettings={handleAdvancedSettings}
|
||||
@ -432,7 +439,7 @@ export const AddTemplateFieldsFormPartial = ({
|
||||
width: fieldBounds.current.width,
|
||||
}}
|
||||
>
|
||||
{FRIENDLY_FIELD_TYPE[selectedField]}
|
||||
{parseMessageDescriptor(_, FRIENDLY_FIELD_TYPE[selectedField])}
|
||||
</div>
|
||||
)}
|
||||
|
||||
@ -501,8 +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">
|
||||
{/* Todo: Translations - Add plural translations. */}
|
||||
{`${RECIPIENT_ROLES_DESCRIPTION_ENG[role].roleName}s`}
|
||||
{_(RECIPIENT_ROLES_DESCRIPTION_ENG[role].roleNamePlural)}
|
||||
</div>
|
||||
|
||||
{roleRecipients.length === 0 && (
|
||||
@ -785,7 +791,7 @@ export const AddTemplateFieldsFormPartial = ({
|
||||
)}
|
||||
>
|
||||
<CheckSquare className="h-4 w-4" />
|
||||
<Trans>Checkbox</Trans>
|
||||
Checkbox
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
@ -4,11 +4,11 @@ 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 { SUPPORTED_LANGUAGES } from '@documenso/lib/constants/i18n';
|
||||
import { DEFAULT_DOCUMENT_TIME_ZONE, TIME_ZONES } from '@documenso/lib/constants/time-zones';
|
||||
import { extractDocumentAuthMethods } from '@documenso/lib/utils/document-auth';
|
||||
import { type Field, type Recipient } from '@documenso/prisma/client';
|
||||
@ -74,8 +74,6 @@ export const AddTemplateSettingsFormPartial = ({
|
||||
template,
|
||||
onSubmit,
|
||||
}: AddTemplateSettingsFormProps) => {
|
||||
const { _ } = useLingui();
|
||||
|
||||
const { documentAuthOption } = extractDocumentAuthMethods({
|
||||
documentAuth: template.authOptions,
|
||||
});
|
||||
@ -93,6 +91,7 @@ export const AddTemplateSettingsFormPartial = ({
|
||||
timezone: template.templateMeta?.timezone ?? DEFAULT_DOCUMENT_TIME_ZONE,
|
||||
dateFormat: template.templateMeta?.dateFormat ?? DEFAULT_DOCUMENT_DATE_FORMAT,
|
||||
redirectUrl: template.templateMeta?.redirectUrl ?? '',
|
||||
language: template.templateMeta?.language ?? 'en',
|
||||
},
|
||||
},
|
||||
});
|
||||
@ -102,7 +101,7 @@ export const AddTemplateSettingsFormPartial = ({
|
||||
// We almost always want to set the timezone to the user's local timezone to avoid confusion
|
||||
// when the document is signed.
|
||||
useEffect(() => {
|
||||
if (!form.formState.touchedFields.meta?.timezone) {
|
||||
if (!form.formState.touchedFields.meta?.timezone && !template.templateMeta?.timezone) {
|
||||
form.setValue('meta.timezone', Intl.DateTimeFormat().resolvedOptions().timeZone);
|
||||
}
|
||||
}, [form, form.setValue, form.formState.touchedFields.meta?.timezone]);
|
||||
@ -142,6 +141,46 @@ export const AddTemplateSettingsFormPartial = ({
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="meta.language"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel className="inline-flex items-center">
|
||||
<Trans>Language</Trans>
|
||||
<Tooltip>
|
||||
<TooltipTrigger>
|
||||
<InfoIcon className="mx-2 h-4 w-4" />
|
||||
</TooltipTrigger>
|
||||
|
||||
<TooltipContent className="text-foreground max-w-md space-y-2 p-4">
|
||||
Controls the language for the document, including the language to be used
|
||||
for email notifications, and the final certificate that is generated and
|
||||
attached to the document.
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</FormLabel>
|
||||
|
||||
<FormControl>
|
||||
<Select {...field} onValueChange={field.onChange}>
|
||||
<SelectTrigger className="bg-background">
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
|
||||
<SelectContent>
|
||||
{Object.entries(SUPPORTED_LANGUAGES).map(([code, language]) => (
|
||||
<SelectItem key={code} value={code}>
|
||||
{language.full}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="globalAccessAuth"
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user