mirror of
https://github.com/documenso/documenso.git
synced 2025-11-16 01:32:06 +10:00
feat: add global settings for teams (#1391)
## Description This PR introduces global settings for teams. At the moment, it allows team admins to configure the following: * The default visibility of the documents uploaded to the team account * Whether to include the document owner (sender) details when sending emails to the recipients. ### Include Sender Details If the Sender Details setting is enabled, the emails sent by the team will include the sender's name: > "Example User" on behalf of "Example Team" has invited you to sign "document.pdf" Otherwise, the email will say: > "Example Team" has invited you to sign "document.pdf" ### Default Document Visibility This new option allows users to set the default visibility for the documents uploaded to the team account. It can have the following values: * Everyone * Manager and above * Admins only If the default document visibility isn't set, the document will be set to the role of the user who created the document: * If a user with the "User" role creates a document, the document's visibility is set to "Everyone". * Manager role -> "Manager and above" * Admin role -> "Admins only" Otherwise, if there is a default document visibility value, it uses that value. #### Gotcha To avoid issues, the `document owner` and the `recipient` can access the document irrespective of their role. For example: * If a team member with the role "Member" uploads a document and the default document visibility is "Admins", only the document owner and admins can access the document. * Similar to the other scenarios. * If an admin uploads a document and the default document visibility is "Admins", the recipient can access the document. * The admins have access to all the documents. * Managers have access to documents with the visibility set to "Everyone" and "Manager and above" * Members have access only to the documents with the visibility set to "Everyone". ## Testing Performed Tested it locally.
This commit is contained in:
44
packages/email/providers/branding.tsx
Normal file
44
packages/email/providers/branding.tsx
Normal file
@ -0,0 +1,44 @@
|
||||
'use client';
|
||||
|
||||
import { createContext, useContext } from 'react';
|
||||
|
||||
type BrandingContextValue = {
|
||||
brandingEnabled: boolean;
|
||||
brandingUrl: string;
|
||||
brandingLogo: string;
|
||||
brandingCompanyDetails: string;
|
||||
brandingHidePoweredBy: boolean;
|
||||
};
|
||||
|
||||
const BrandingContext = createContext<BrandingContextValue | undefined>(undefined);
|
||||
|
||||
const defaultBrandingContextValue: BrandingContextValue = {
|
||||
brandingEnabled: false,
|
||||
brandingUrl: '',
|
||||
brandingLogo: '',
|
||||
brandingCompanyDetails: '',
|
||||
brandingHidePoweredBy: false,
|
||||
};
|
||||
|
||||
export const BrandingProvider = (props: {
|
||||
branding?: BrandingContextValue;
|
||||
children: React.ReactNode;
|
||||
}) => {
|
||||
return (
|
||||
<BrandingContext.Provider value={props.branding ?? defaultBrandingContextValue}>
|
||||
{props.children}
|
||||
</BrandingContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export const useBranding = () => {
|
||||
const ctx = useContext(BrandingContext);
|
||||
|
||||
if (!ctx) {
|
||||
throw new Error('Branding context not found');
|
||||
}
|
||||
|
||||
return ctx;
|
||||
};
|
||||
|
||||
export type BrandingSettings = BrandingContextValue;
|
||||
@ -1,11 +1,18 @@
|
||||
import * as reactEmail from '@react-email/render';
|
||||
import * as ReactEmail from '@react-email/render';
|
||||
|
||||
import config from '@documenso/tailwind-config';
|
||||
|
||||
import { Tailwind } from './components';
|
||||
import { BrandingProvider, type BrandingSettings } from './providers/branding';
|
||||
|
||||
export const render: typeof reactEmail.render = (element, options) => {
|
||||
return reactEmail.render(
|
||||
export type RenderOptions = ReactEmail.Options & {
|
||||
branding?: BrandingSettings;
|
||||
};
|
||||
|
||||
export const render = (element: React.ReactNode, options?: RenderOptions) => {
|
||||
const { branding, ...otherOptions } = options ?? {};
|
||||
|
||||
return ReactEmail.render(
|
||||
<Tailwind
|
||||
config={{
|
||||
theme: {
|
||||
@ -15,14 +22,16 @@ export const render: typeof reactEmail.render = (element, options) => {
|
||||
},
|
||||
}}
|
||||
>
|
||||
{element}
|
||||
<BrandingProvider branding={branding}>{element}</BrandingProvider>
|
||||
</Tailwind>,
|
||||
options,
|
||||
otherOptions,
|
||||
);
|
||||
};
|
||||
|
||||
export const renderAsync: typeof reactEmail.renderAsync = async (element, options) => {
|
||||
return reactEmail.renderAsync(
|
||||
export const renderAsync = async (element: React.ReactNode, options?: RenderOptions) => {
|
||||
const { branding, ...otherOptions } = options ?? {};
|
||||
|
||||
return await ReactEmail.renderAsync(
|
||||
<Tailwind
|
||||
config={{
|
||||
theme: {
|
||||
@ -32,8 +41,8 @@ export const renderAsync: typeof reactEmail.renderAsync = async (element, option
|
||||
},
|
||||
}}
|
||||
>
|
||||
{element}
|
||||
<BrandingProvider branding={branding}>{element}</BrandingProvider>
|
||||
</Tailwind>,
|
||||
options,
|
||||
otherOptions,
|
||||
);
|
||||
};
|
||||
|
||||
@ -46,13 +46,6 @@ export const TemplateDocumentCompleted = ({
|
||||
</Text>
|
||||
|
||||
<Section className="mb-6 mt-8 text-center">
|
||||
{/* <Button
|
||||
className="mr-4 inline-flex items-center justify-center rounded-lg border border-solid border-slate-200 px-4 py-2 text-center text-sm font-medium text-black no-underline"
|
||||
href={reviewLink}
|
||||
>
|
||||
<Img src={getAssetUrl('/static/review.png')} className="-mb-1 mr-2 inline h-5 w-5" />
|
||||
Review
|
||||
</Button> */}
|
||||
<Button
|
||||
className="rounded-lg border border-solid border-slate-200 px-4 py-2 text-center text-sm font-medium text-black no-underline"
|
||||
href={downloadLink}
|
||||
|
||||
@ -18,6 +18,7 @@ export interface TemplateDocumentInviteProps {
|
||||
selfSigner: boolean;
|
||||
isTeamInvite: boolean;
|
||||
teamName?: string;
|
||||
includeSenderDetails?: boolean;
|
||||
}
|
||||
|
||||
export const TemplateDocumentInvite = ({
|
||||
@ -29,6 +30,7 @@ export const TemplateDocumentInvite = ({
|
||||
selfSigner,
|
||||
isTeamInvite,
|
||||
teamName,
|
||||
includeSenderDetails,
|
||||
}: TemplateDocumentInviteProps) => {
|
||||
const { _ } = useLingui();
|
||||
|
||||
@ -46,10 +48,19 @@ export const TemplateDocumentInvite = ({
|
||||
<br />"{documentName}"
|
||||
</Trans>
|
||||
) : isTeamInvite ? (
|
||||
<Trans>
|
||||
{inviterName} on behalf of {teamName} has invited you to {_(actionVerb).toLowerCase()}
|
||||
<>
|
||||
{includeSenderDetails ? (
|
||||
<Trans>
|
||||
{inviterName} on behalf of {teamName} has invited you to{' '}
|
||||
{_(actionVerb).toLowerCase()}
|
||||
</Trans>
|
||||
) : (
|
||||
<Trans>
|
||||
{teamName} has invited you to {_(actionVerb).toLowerCase()}
|
||||
</Trans>
|
||||
)}
|
||||
<br />"{documentName}"
|
||||
</Trans>
|
||||
</>
|
||||
) : (
|
||||
<Trans>
|
||||
{inviterName} has invited you to {_(actionVerb).toLowerCase()}
|
||||
|
||||
@ -1,15 +1,18 @@
|
||||
import { Trans } from '@lingui/macro';
|
||||
|
||||
import { Link, Section, Text } from '../components';
|
||||
import { useBranding } from '../providers/branding';
|
||||
|
||||
export type TemplateFooterProps = {
|
||||
isDocument?: boolean;
|
||||
};
|
||||
|
||||
export const TemplateFooter = ({ isDocument = true }: TemplateFooterProps) => {
|
||||
const branding = useBranding();
|
||||
|
||||
return (
|
||||
<Section>
|
||||
{isDocument && (
|
||||
{isDocument && !branding.brandingHidePoweredBy && (
|
||||
<Text className="my-4 text-base text-slate-400">
|
||||
<Trans>
|
||||
This document was sent using{' '}
|
||||
@ -20,11 +23,24 @@ export const TemplateFooter = ({ isDocument = true }: TemplateFooterProps) => {
|
||||
</Text>
|
||||
)}
|
||||
|
||||
<Text className="my-8 text-sm text-slate-400">
|
||||
Documenso, Inc.
|
||||
<br />
|
||||
2261 Market Street, #5211, San Francisco, CA 94114, USA
|
||||
</Text>
|
||||
{branding.brandingCompanyDetails ? (
|
||||
<Text className="my-8 text-sm text-slate-400">
|
||||
{branding.brandingCompanyDetails.split('\n').map((line, idx) => {
|
||||
return (
|
||||
<>
|
||||
{idx > 0 && <br />}
|
||||
{line}
|
||||
</>
|
||||
);
|
||||
})}
|
||||
</Text>
|
||||
) : (
|
||||
<Text className="my-8 text-sm text-slate-400">
|
||||
Documenso, Inc.
|
||||
<br />
|
||||
2261 Market Street, #5211, San Francisco, CA 94114, USA
|
||||
</Text>
|
||||
)}
|
||||
</Section>
|
||||
);
|
||||
};
|
||||
|
||||
@ -2,6 +2,7 @@ import { msg } from '@lingui/macro';
|
||||
import { useLingui } from '@lingui/react';
|
||||
|
||||
import { Body, Container, Head, Html, Img, Preview, Section } from '../components';
|
||||
import { useBranding } from '../providers/branding';
|
||||
import type { TemplateConfirmationEmailProps } from '../template-components/template-confirmation-email';
|
||||
import { TemplateConfirmationEmail } from '../template-components/template-confirmation-email';
|
||||
import { TemplateFooter } from '../template-components/template-footer';
|
||||
@ -11,6 +12,7 @@ export const ConfirmEmailTemplate = ({
|
||||
assetBaseUrl = 'http://localhost:3002',
|
||||
}: TemplateConfirmationEmailProps) => {
|
||||
const { _ } = useLingui();
|
||||
const branding = useBranding();
|
||||
|
||||
const previewText = msg`Please confirm your email address`;
|
||||
|
||||
@ -26,11 +28,15 @@ export const ConfirmEmailTemplate = ({
|
||||
<Section>
|
||||
<Container className="mx-auto mb-2 mt-8 max-w-xl rounded-lg border border-solid border-slate-200 p-4 backdrop-blur-sm">
|
||||
<Section>
|
||||
<Img
|
||||
src={getAssetUrl('/static/logo.png')}
|
||||
alt="Documenso Logo"
|
||||
className="mb-4 h-6"
|
||||
/>
|
||||
{branding.brandingEnabled && branding.brandingLogo ? (
|
||||
<Img src={branding.brandingLogo} alt="Branding Logo" className="mb-4 h-6" />
|
||||
) : (
|
||||
<Img
|
||||
src={getAssetUrl('/static/logo.png')}
|
||||
alt="Documenso Logo"
|
||||
className="mb-4 h-6"
|
||||
/>
|
||||
)}
|
||||
|
||||
<TemplateConfirmationEmail
|
||||
confirmationLink={confirmationLink}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { msg } from '@lingui/macro';
|
||||
import { Trans, msg } from '@lingui/macro';
|
||||
import { useLingui } from '@lingui/react';
|
||||
|
||||
import { formatTeamUrl } from '@documenso/lib/utils/teams';
|
||||
@ -10,11 +10,13 @@ import {
|
||||
Head,
|
||||
Hr,
|
||||
Html,
|
||||
Img,
|
||||
Link,
|
||||
Preview,
|
||||
Section,
|
||||
Text,
|
||||
} from '../components';
|
||||
import { useBranding } from '../providers/branding';
|
||||
import { TemplateFooter } from '../template-components/template-footer';
|
||||
import TemplateImage from '../template-components/template-image';
|
||||
|
||||
@ -34,6 +36,7 @@ export const ConfirmTeamEmailTemplate = ({
|
||||
token = '',
|
||||
}: ConfirmTeamEmailProps) => {
|
||||
const { _ } = useLingui();
|
||||
const branding = useBranding();
|
||||
|
||||
const previewText = msg`Accept team email request for ${teamName} on Documenso`;
|
||||
|
||||
@ -45,11 +48,15 @@ export const ConfirmTeamEmailTemplate = ({
|
||||
<Body className="mx-auto my-auto font-sans">
|
||||
<Section className="bg-white">
|
||||
<Container className="mx-auto mb-2 mt-8 max-w-xl rounded-lg border border-solid border-slate-200 px-2 pt-2 backdrop-blur-sm">
|
||||
<TemplateImage
|
||||
assetBaseUrl={assetBaseUrl}
|
||||
className="mb-4 h-6 p-2"
|
||||
staticAsset="logo.png"
|
||||
/>
|
||||
{branding.brandingEnabled && branding.brandingLogo ? (
|
||||
<Img src={branding.brandingLogo} alt="Branding Logo" className="mb-4 h-6 p-2" />
|
||||
) : (
|
||||
<TemplateImage
|
||||
assetBaseUrl={assetBaseUrl}
|
||||
className="mb-4 h-6 p-2"
|
||||
staticAsset="logo.png"
|
||||
/>
|
||||
)}
|
||||
|
||||
<Section>
|
||||
<TemplateImage
|
||||
@ -61,12 +68,14 @@ export const ConfirmTeamEmailTemplate = ({
|
||||
|
||||
<Section className="p-2 text-slate-500">
|
||||
<Text className="text-center text-lg font-medium text-black">
|
||||
Verify your team email address
|
||||
<Trans>Verify your team email address</Trans>
|
||||
</Text>
|
||||
|
||||
<Text className="text-center text-base">
|
||||
<span className="font-bold">{teamName}</span> has requested to use your email
|
||||
address for their team on Documenso.
|
||||
<Trans>
|
||||
<span className="font-bold">{teamName}</span> has requested to use your email
|
||||
address for their team on Documenso.
|
||||
</Trans>
|
||||
</Text>
|
||||
|
||||
<div className="mx-auto mt-6 w-fit rounded-lg bg-gray-50 px-4 py-2 text-base font-medium text-slate-600">
|
||||
@ -75,25 +84,29 @@ export const ConfirmTeamEmailTemplate = ({
|
||||
|
||||
<Section className="mt-6">
|
||||
<Text className="my-0 text-sm">
|
||||
By accepting this request, you will be granting <strong>{teamName}</strong> access
|
||||
to:
|
||||
<Trans>
|
||||
By accepting this request, you will be granting <strong>{teamName}</strong>{' '}
|
||||
access to:
|
||||
</Trans>
|
||||
</Text>
|
||||
|
||||
<ul className="mb-0 mt-2">
|
||||
<li className="text-sm">
|
||||
View all documents sent to and from this email address
|
||||
<Trans>View all documents sent to and from this email address</Trans>
|
||||
</li>
|
||||
<li className="mt-1 text-sm">
|
||||
Allow document recipients to reply directly to this email address
|
||||
<Trans>Allow document recipients to reply directly to this email address</Trans>
|
||||
</li>
|
||||
<li className="mt-1 text-sm">
|
||||
Send documents on behalf of the team using the email address
|
||||
<Trans>Send documents on behalf of the team using the email address</Trans>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<Text className="mt-2 text-sm">
|
||||
You can revoke access at any time in your team settings on Documenso{' '}
|
||||
<Link href={`${baseUrl}/settings/teams`}>here.</Link>
|
||||
<Trans>
|
||||
You can revoke access at any time in your team settings on Documenso{' '}
|
||||
<Link href={`${baseUrl}/settings/teams`}>here.</Link>
|
||||
</Trans>
|
||||
</Text>
|
||||
</Section>
|
||||
|
||||
@ -102,12 +115,14 @@ export const ConfirmTeamEmailTemplate = ({
|
||||
className="bg-documenso-500 inline-flex items-center justify-center rounded-lg px-6 py-3 text-center text-sm font-medium text-black no-underline"
|
||||
href={`${baseUrl}/team/verify/email/${token}`}
|
||||
>
|
||||
Accept
|
||||
<Trans>Accept</Trans>
|
||||
</Button>
|
||||
</Section>
|
||||
</Section>
|
||||
|
||||
<Text className="text-center text-xs text-slate-500">Link expires in 1 hour.</Text>
|
||||
<Text className="text-center text-xs text-slate-500">
|
||||
<Trans>Link expires in 1 hour.</Trans>
|
||||
</Text>
|
||||
</Container>
|
||||
|
||||
<Hr className="mx-auto mt-12 max-w-xl" />
|
||||
|
||||
@ -2,6 +2,7 @@ import { msg } from '@lingui/macro';
|
||||
import { useLingui } from '@lingui/react';
|
||||
|
||||
import { Body, Container, Head, Hr, Html, Img, Preview, Section } from '../components';
|
||||
import { useBranding } from '../providers/branding';
|
||||
import type { TemplateDocumentCancelProps } from '../template-components/template-document-cancel';
|
||||
import { TemplateDocumentCancel } from '../template-components/template-document-cancel';
|
||||
import { TemplateFooter } from '../template-components/template-footer';
|
||||
@ -15,6 +16,7 @@ export const DocumentCancelTemplate = ({
|
||||
assetBaseUrl = 'http://localhost:3002',
|
||||
}: DocumentCancelEmailTemplateProps) => {
|
||||
const { _ } = useLingui();
|
||||
const branding = useBranding();
|
||||
|
||||
const previewText = msg`${inviterName} has cancelled the document ${documentName}, you don't need to sign it anymore.`;
|
||||
|
||||
@ -31,11 +33,15 @@ export const DocumentCancelTemplate = ({
|
||||
<Section>
|
||||
<Container className="mx-auto mb-2 mt-8 max-w-xl rounded-lg border border-solid border-slate-200 p-4 backdrop-blur-sm">
|
||||
<Section>
|
||||
<Img
|
||||
src={getAssetUrl('/static/logo.png')}
|
||||
alt="Documenso Logo"
|
||||
className="mb-4 h-6"
|
||||
/>
|
||||
{branding.brandingEnabled && branding.brandingLogo ? (
|
||||
<Img src={branding.brandingLogo} alt="Branding Logo" className="mb-4 h-6" />
|
||||
) : (
|
||||
<Img
|
||||
src={getAssetUrl('/static/logo.png')}
|
||||
alt="Documenso Logo"
|
||||
className="mb-4 h-6"
|
||||
/>
|
||||
)}
|
||||
|
||||
<TemplateDocumentCancel
|
||||
inviterName={inviterName}
|
||||
|
||||
@ -2,6 +2,7 @@ import { msg } from '@lingui/macro';
|
||||
import { useLingui } from '@lingui/react';
|
||||
|
||||
import { Body, Container, Head, Html, Img, Preview, Section } from '../components';
|
||||
import { useBranding } from '../providers/branding';
|
||||
import type { TemplateDocumentCompletedProps } from '../template-components/template-document-completed';
|
||||
import { TemplateDocumentCompleted } from '../template-components/template-document-completed';
|
||||
import { TemplateFooter } from '../template-components/template-footer';
|
||||
@ -17,6 +18,7 @@ export const DocumentCompletedEmailTemplate = ({
|
||||
customBody,
|
||||
}: DocumentCompletedEmailTemplateProps) => {
|
||||
const { _ } = useLingui();
|
||||
const branding = useBranding();
|
||||
|
||||
const previewText = msg`Completed Document`;
|
||||
|
||||
@ -33,11 +35,15 @@ export const DocumentCompletedEmailTemplate = ({
|
||||
<Section className="bg-white">
|
||||
<Container className="mx-auto mb-2 mt-8 max-w-xl rounded-lg border border-solid border-slate-200 p-2 backdrop-blur-sm">
|
||||
<Section className="p-2">
|
||||
<Img
|
||||
src={getAssetUrl('/static/logo.png')}
|
||||
alt="Documenso Logo"
|
||||
className="mb-4 h-6"
|
||||
/>
|
||||
{branding.brandingEnabled && branding.brandingLogo ? (
|
||||
<Img src={branding.brandingLogo} alt="Branding Logo" className="mb-4 h-6" />
|
||||
) : (
|
||||
<Img
|
||||
src={getAssetUrl('/static/logo.png')}
|
||||
alt="Documenso Logo"
|
||||
className="mb-4 h-6"
|
||||
/>
|
||||
)}
|
||||
|
||||
<TemplateDocumentCompleted
|
||||
downloadLink={downloadLink}
|
||||
|
||||
@ -4,6 +4,7 @@ import { useLingui } from '@lingui/react';
|
||||
import { RECIPIENT_ROLES_DESCRIPTION } from '@documenso/lib/constants/recipient-roles';
|
||||
|
||||
import { Body, Button, Container, Head, Html, Img, Preview, Section, Text } from '../components';
|
||||
import { useBranding } from '../providers/branding';
|
||||
import TemplateDocumentImage from '../template-components/template-document-image';
|
||||
import { TemplateFooter } from '../template-components/template-footer';
|
||||
import { RecipientRole } from '.prisma/client';
|
||||
@ -24,6 +25,7 @@ export const DocumentCreatedFromDirectTemplateEmailTemplate = ({
|
||||
assetBaseUrl = 'http://localhost:3002',
|
||||
}: DocumentCompletedEmailTemplateProps) => {
|
||||
const { _ } = useLingui();
|
||||
const branding = useBranding();
|
||||
|
||||
const action = _(RECIPIENT_ROLES_DESCRIPTION[recipientRole].actioned).toLowerCase();
|
||||
|
||||
@ -42,11 +44,15 @@ export const DocumentCreatedFromDirectTemplateEmailTemplate = ({
|
||||
<Section className="bg-white">
|
||||
<Container className="mx-auto mb-2 mt-8 max-w-xl rounded-lg border border-solid border-slate-200 p-2 backdrop-blur-sm">
|
||||
<Section className="p-2">
|
||||
<Img
|
||||
src={getAssetUrl('/static/logo.png')}
|
||||
alt="Documenso Logo"
|
||||
className="mb-4 h-6"
|
||||
/>
|
||||
{branding.brandingEnabled && branding.brandingLogo ? (
|
||||
<Img src={branding.brandingLogo} alt="Branding Logo" className="mb-4 h-6" />
|
||||
) : (
|
||||
<Img
|
||||
src={getAssetUrl('/static/logo.png')}
|
||||
alt="Documenso Logo"
|
||||
className="mb-4 h-6"
|
||||
/>
|
||||
)}
|
||||
|
||||
<TemplateDocumentImage className="mt-6" assetBaseUrl={assetBaseUrl} />
|
||||
|
||||
|
||||
@ -5,6 +5,7 @@ import { RECIPIENT_ROLES_DESCRIPTION } from '@documenso/lib/constants/recipient-
|
||||
import type { RecipientRole } from '@documenso/prisma/client';
|
||||
|
||||
import { Body, Container, Head, Hr, Html, Img, Link, Preview, Section, Text } from '../components';
|
||||
import { useBranding } from '../providers/branding';
|
||||
import type { TemplateDocumentInviteProps } from '../template-components/template-document-invite';
|
||||
import { TemplateDocumentInvite } from '../template-components/template-document-invite';
|
||||
import { TemplateFooter } from '../template-components/template-footer';
|
||||
@ -16,6 +17,7 @@ export type DocumentInviteEmailTemplateProps = Partial<TemplateDocumentInvitePro
|
||||
isTeamInvite?: boolean;
|
||||
teamName?: string;
|
||||
teamEmail?: string;
|
||||
includeSenderDetails?: boolean;
|
||||
};
|
||||
|
||||
export const DocumentInviteEmailTemplate = ({
|
||||
@ -29,16 +31,24 @@ export const DocumentInviteEmailTemplate = ({
|
||||
selfSigner = false,
|
||||
isTeamInvite = false,
|
||||
teamName,
|
||||
includeSenderDetails,
|
||||
}: DocumentInviteEmailTemplateProps) => {
|
||||
const { _ } = useLingui();
|
||||
const branding = useBranding();
|
||||
|
||||
const action = _(RECIPIENT_ROLES_DESCRIPTION[role].actionVerb).toLowerCase();
|
||||
|
||||
const previewText = selfSigner
|
||||
? msg`Please ${action} your document ${documentName}`
|
||||
: isTeamInvite
|
||||
? msg`${inviterName} on behalf of ${teamName} has invited you to ${action} ${documentName}`
|
||||
: msg`${inviterName} has invited you to ${action} ${documentName}`;
|
||||
let previewText = msg`${inviterName} has invited you to ${action} ${documentName}`;
|
||||
|
||||
if (isTeamInvite) {
|
||||
previewText = includeSenderDetails
|
||||
? msg`${inviterName} on behalf of ${teamName} has invited you to ${action} ${documentName}`
|
||||
: msg`${teamName} has invited you to ${action} ${documentName}`;
|
||||
}
|
||||
|
||||
if (selfSigner) {
|
||||
previewText = msg`Please ${action} your document ${documentName}`;
|
||||
}
|
||||
|
||||
const getAssetUrl = (path: string) => {
|
||||
return new URL(path, assetBaseUrl).toString();
|
||||
@ -53,11 +63,15 @@ export const DocumentInviteEmailTemplate = ({
|
||||
<Section>
|
||||
<Container className="mx-auto mb-2 mt-8 max-w-xl rounded-lg border border-solid border-slate-200 p-4 backdrop-blur-sm">
|
||||
<Section>
|
||||
<Img
|
||||
src={getAssetUrl('/static/logo.png')}
|
||||
alt="Documenso Logo"
|
||||
className="mb-4 h-6"
|
||||
/>
|
||||
{branding.brandingEnabled && branding.brandingLogo ? (
|
||||
<Img src={branding.brandingLogo} alt="Branding Logo" className="mb-4 h-6" />
|
||||
) : (
|
||||
<Img
|
||||
src={getAssetUrl('/static/logo.png')}
|
||||
alt="Documenso Logo"
|
||||
className="mb-4 h-6"
|
||||
/>
|
||||
)}
|
||||
|
||||
<TemplateDocumentInvite
|
||||
inviterName={inviterName}
|
||||
@ -69,6 +83,7 @@ export const DocumentInviteEmailTemplate = ({
|
||||
selfSigner={selfSigner}
|
||||
isTeamInvite={isTeamInvite}
|
||||
teamName={teamName}
|
||||
includeSenderDetails={includeSenderDetails}
|
||||
/>
|
||||
</Section>
|
||||
</Container>
|
||||
@ -89,7 +104,7 @@ export const DocumentInviteEmailTemplate = ({
|
||||
<pre className="font-sans text-base text-slate-400">{customBody}</pre>
|
||||
) : (
|
||||
<Trans>
|
||||
{inviterName} has invited you to {action} the document {documentName}.
|
||||
{inviterName} has invited you to {action} the document "{documentName}".
|
||||
</Trans>
|
||||
)}
|
||||
</Text>
|
||||
|
||||
@ -2,6 +2,7 @@ import { msg } from '@lingui/macro';
|
||||
import { useLingui } from '@lingui/react';
|
||||
|
||||
import { Body, Container, Head, Html, Img, Preview, Section } from '../components';
|
||||
import { useBranding } from '../providers/branding';
|
||||
import type { TemplateDocumentPendingProps } from '../template-components/template-document-pending';
|
||||
import { TemplateDocumentPending } from '../template-components/template-document-pending';
|
||||
import { TemplateFooter } from '../template-components/template-footer';
|
||||
@ -13,6 +14,7 @@ export const DocumentPendingEmailTemplate = ({
|
||||
assetBaseUrl = 'http://localhost:3002',
|
||||
}: DocumentPendingEmailTemplateProps) => {
|
||||
const { _ } = useLingui();
|
||||
const branding = useBranding();
|
||||
|
||||
const previewText = msg`Pending Document`;
|
||||
|
||||
@ -29,11 +31,15 @@ export const DocumentPendingEmailTemplate = ({
|
||||
<Section className="bg-white">
|
||||
<Container className="mx-auto mb-2 mt-8 max-w-xl rounded-lg border border-solid border-slate-200 p-4 backdrop-blur-sm">
|
||||
<Section>
|
||||
<Img
|
||||
src={getAssetUrl('/static/logo.png')}
|
||||
alt="Documenso Logo"
|
||||
className="mb-4 h-6"
|
||||
/>
|
||||
{branding.brandingEnabled && branding.brandingLogo ? (
|
||||
<Img src={branding.brandingLogo} alt="Branding Logo" className="mb-4 h-6" />
|
||||
) : (
|
||||
<Img
|
||||
src={getAssetUrl('/static/logo.png')}
|
||||
alt="Documenso Logo"
|
||||
className="mb-4 h-6"
|
||||
/>
|
||||
)}
|
||||
|
||||
<TemplateDocumentPending documentName={documentName} assetBaseUrl={assetBaseUrl} />
|
||||
</Section>
|
||||
|
||||
@ -2,6 +2,7 @@ import { msg } from '@lingui/macro';
|
||||
import { useLingui } from '@lingui/react';
|
||||
|
||||
import { Body, Container, Head, Html, Img, Preview, Section } from '../components';
|
||||
import { useBranding } from '../providers/branding';
|
||||
import type { TemplateDocumentSelfSignedProps } from '../template-components/template-document-self-signed';
|
||||
import { TemplateDocumentSelfSigned } from '../template-components/template-document-self-signed';
|
||||
import { TemplateFooter } from '../template-components/template-footer';
|
||||
@ -13,6 +14,7 @@ export const DocumentSelfSignedEmailTemplate = ({
|
||||
assetBaseUrl = 'http://localhost:3002',
|
||||
}: DocumentSelfSignedTemplateProps) => {
|
||||
const { _ } = useLingui();
|
||||
const branding = useBranding();
|
||||
|
||||
const previewText = msg`Completed Document`;
|
||||
|
||||
@ -29,11 +31,15 @@ export const DocumentSelfSignedEmailTemplate = ({
|
||||
<Section className="bg-white">
|
||||
<Container className="mx-auto mb-2 mt-8 max-w-xl rounded-lg border border-solid border-slate-200 p-2 backdrop-blur-sm">
|
||||
<Section className="p-2">
|
||||
<Img
|
||||
src={getAssetUrl('/static/logo.png')}
|
||||
alt="Documenso Logo"
|
||||
className="mb-4 h-6"
|
||||
/>
|
||||
{branding.brandingEnabled && branding.brandingLogo ? (
|
||||
<Img src={branding.brandingLogo} alt="Branding Logo" className="mb-4 h-6" />
|
||||
) : (
|
||||
<Img
|
||||
src={getAssetUrl('/static/logo.png')}
|
||||
alt="Documenso Logo"
|
||||
className="mb-4 h-6"
|
||||
/>
|
||||
)}
|
||||
|
||||
<TemplateDocumentSelfSigned documentName={documentName} assetBaseUrl={assetBaseUrl} />
|
||||
</Section>
|
||||
|
||||
@ -2,6 +2,7 @@ import { msg } from '@lingui/macro';
|
||||
import { useLingui } from '@lingui/react';
|
||||
|
||||
import { Body, Container, Head, Hr, Html, Img, Preview, Section } from '../components';
|
||||
import { useBranding } from '../providers/branding';
|
||||
import {
|
||||
TemplateDocumentDelete,
|
||||
type TemplateDocumentDeleteProps,
|
||||
@ -16,6 +17,7 @@ export const DocumentSuperDeleteEmailTemplate = ({
|
||||
reason = 'Unknown',
|
||||
}: DocumentDeleteEmailTemplateProps) => {
|
||||
const { _ } = useLingui();
|
||||
const branding = useBranding();
|
||||
|
||||
const previewText = msg`An admin has deleted your document "${documentName}".`;
|
||||
|
||||
@ -32,11 +34,15 @@ export const DocumentSuperDeleteEmailTemplate = ({
|
||||
<Section>
|
||||
<Container className="mx-auto mb-2 mt-8 max-w-xl rounded-lg border border-solid border-slate-200 p-4 backdrop-blur-sm">
|
||||
<Section>
|
||||
<Img
|
||||
src={getAssetUrl('/static/logo.png')}
|
||||
alt="Documenso Logo"
|
||||
className="mb-4 h-6"
|
||||
/>
|
||||
{branding.brandingEnabled && branding.brandingLogo ? (
|
||||
<Img src={branding.brandingLogo} alt="Branding Logo" className="mb-4 h-6" />
|
||||
) : (
|
||||
<Img
|
||||
src={getAssetUrl('/static/logo.png')}
|
||||
alt="Documenso Logo"
|
||||
className="mb-4 h-6"
|
||||
/>
|
||||
)}
|
||||
|
||||
<TemplateDocumentDelete
|
||||
reason={reason}
|
||||
|
||||
@ -2,6 +2,7 @@ import { msg } from '@lingui/macro';
|
||||
import { useLingui } from '@lingui/react';
|
||||
|
||||
import { Body, Container, Head, Html, Img, Preview, Section } from '../components';
|
||||
import { useBranding } from '../providers/branding';
|
||||
import { TemplateFooter } from '../template-components/template-footer';
|
||||
import type { TemplateForgotPasswordProps } from '../template-components/template-forgot-password';
|
||||
import { TemplateForgotPassword } from '../template-components/template-forgot-password';
|
||||
@ -13,6 +14,7 @@ export const ForgotPasswordTemplate = ({
|
||||
assetBaseUrl = 'http://localhost:3002',
|
||||
}: ForgotPasswordTemplateProps) => {
|
||||
const { _ } = useLingui();
|
||||
const branding = useBranding();
|
||||
|
||||
const previewText = msg`Password Reset Requested`;
|
||||
|
||||
@ -29,11 +31,15 @@ export const ForgotPasswordTemplate = ({
|
||||
<Section>
|
||||
<Container className="mx-auto mb-2 mt-8 max-w-xl rounded-lg border border-solid border-slate-200 p-4 backdrop-blur-sm">
|
||||
<Section>
|
||||
<Img
|
||||
src={getAssetUrl('/static/logo.png')}
|
||||
alt="Documenso Logo"
|
||||
className="mb-4 h-6"
|
||||
/>
|
||||
{branding.brandingEnabled && branding.brandingLogo ? (
|
||||
<Img src={branding.brandingLogo} alt="Branding Logo" className="mb-4 h-6" />
|
||||
) : (
|
||||
<Img
|
||||
src={getAssetUrl('/static/logo.png')}
|
||||
alt="Documenso Logo"
|
||||
className="mb-4 h-6"
|
||||
/>
|
||||
)}
|
||||
|
||||
<TemplateForgotPassword
|
||||
resetPasswordLink={resetPasswordLink}
|
||||
|
||||
@ -2,6 +2,7 @@ import { Trans, msg } from '@lingui/macro';
|
||||
import { useLingui } from '@lingui/react';
|
||||
|
||||
import { Body, Container, Head, Hr, Html, Img, Preview, Section, Text } from '../components';
|
||||
import { useBranding } from '../providers/branding';
|
||||
import type { TemplateDocumentCancelProps } from '../template-components/template-document-cancel';
|
||||
import TemplateDocumentImage from '../template-components/template-document-image';
|
||||
import { TemplateFooter } from '../template-components/template-footer';
|
||||
@ -14,6 +15,7 @@ export const RecipientRemovedFromDocumentTemplate = ({
|
||||
assetBaseUrl = 'http://localhost:3002',
|
||||
}: DocumentCancelEmailTemplateProps) => {
|
||||
const { _ } = useLingui();
|
||||
const branding = useBranding();
|
||||
|
||||
const previewText = msg`${inviterName} has removed you from the document ${documentName}.`;
|
||||
|
||||
@ -30,11 +32,15 @@ export const RecipientRemovedFromDocumentTemplate = ({
|
||||
<Section>
|
||||
<Container className="mx-auto mb-2 mt-8 max-w-xl rounded-lg border border-solid border-slate-200 p-4 backdrop-blur-sm">
|
||||
<Section>
|
||||
<Img
|
||||
src={getAssetUrl('/static/logo.png')}
|
||||
alt="Documenso Logo"
|
||||
className="mb-4 h-6"
|
||||
/>
|
||||
{branding.brandingEnabled && branding.brandingLogo ? (
|
||||
<Img src={branding.brandingLogo} alt="Branding Logo" className="mb-4 h-6" />
|
||||
) : (
|
||||
<Img
|
||||
src={getAssetUrl('/static/logo.png')}
|
||||
alt="Documenso Logo"
|
||||
className="mb-4 h-6"
|
||||
/>
|
||||
)}
|
||||
|
||||
<TemplateDocumentImage className="mt-6" assetBaseUrl={assetBaseUrl} />
|
||||
|
||||
|
||||
@ -2,6 +2,7 @@ import { Trans, msg } from '@lingui/macro';
|
||||
import { useLingui } from '@lingui/react';
|
||||
|
||||
import { Body, Container, Head, Hr, Html, Img, Link, Preview, Section, Text } from '../components';
|
||||
import { useBranding } from '../providers/branding';
|
||||
import { TemplateFooter } from '../template-components/template-footer';
|
||||
import type { TemplateResetPasswordProps } from '../template-components/template-reset-password';
|
||||
import { TemplateResetPassword } from '../template-components/template-reset-password';
|
||||
@ -14,6 +15,7 @@ export const ResetPasswordTemplate = ({
|
||||
assetBaseUrl = 'http://localhost:3002',
|
||||
}: ResetPasswordTemplateProps) => {
|
||||
const { _ } = useLingui();
|
||||
const branding = useBranding();
|
||||
|
||||
const previewText = msg`Password Reset Successful`;
|
||||
|
||||
@ -30,11 +32,15 @@ export const ResetPasswordTemplate = ({
|
||||
<Section>
|
||||
<Container className="mx-auto mb-2 mt-8 max-w-xl rounded-lg border border-solid border-slate-200 p-4 backdrop-blur-sm">
|
||||
<Section>
|
||||
<Img
|
||||
src={getAssetUrl('/static/logo.png')}
|
||||
alt="Documenso Logo"
|
||||
className="mb-4 h-6"
|
||||
/>
|
||||
{branding.brandingEnabled && branding.brandingLogo ? (
|
||||
<Img src={branding.brandingLogo} alt="Branding Logo" className="mb-4 h-6" />
|
||||
) : (
|
||||
<Img
|
||||
src={getAssetUrl('/static/logo.png')}
|
||||
alt="Documenso Logo"
|
||||
className="mb-4 h-6"
|
||||
/>
|
||||
)}
|
||||
|
||||
<TemplateResetPassword
|
||||
userName={userName}
|
||||
|
||||
@ -3,7 +3,8 @@ import { useLingui } from '@lingui/react';
|
||||
|
||||
import { formatTeamUrl } from '@documenso/lib/utils/teams';
|
||||
|
||||
import { Body, Container, Head, Hr, Html, Preview, Section, Text } from '../components';
|
||||
import { Body, Container, Head, Hr, Html, Img, Preview, Section, Text } from '../components';
|
||||
import { useBranding } from '../providers/branding';
|
||||
import { TemplateFooter } from '../template-components/template-footer';
|
||||
import TemplateImage from '../template-components/template-image';
|
||||
|
||||
@ -21,6 +22,7 @@ export const TeamDeleteEmailTemplate = ({
|
||||
isOwner = false,
|
||||
}: TeamDeleteEmailProps) => {
|
||||
const { _ } = useLingui();
|
||||
const branding = useBranding();
|
||||
|
||||
const previewText = isOwner
|
||||
? msg`Your team has been deleted`
|
||||
@ -42,11 +44,15 @@ export const TeamDeleteEmailTemplate = ({
|
||||
<Body className="mx-auto my-auto font-sans">
|
||||
<Section className="bg-white text-slate-500">
|
||||
<Container className="mx-auto mb-2 mt-8 max-w-xl rounded-lg border border-solid border-slate-200 p-2 backdrop-blur-sm">
|
||||
<TemplateImage
|
||||
assetBaseUrl={assetBaseUrl}
|
||||
className="mb-4 h-6 p-2"
|
||||
staticAsset="logo.png"
|
||||
/>
|
||||
{branding.brandingEnabled && branding.brandingLogo ? (
|
||||
<Img src={branding.brandingLogo} alt="Branding Logo" className="mb-4 h-6 p-2" />
|
||||
) : (
|
||||
<TemplateImage
|
||||
assetBaseUrl={assetBaseUrl}
|
||||
className="mb-4 h-6 p-2"
|
||||
staticAsset="logo.png"
|
||||
/>
|
||||
)}
|
||||
|
||||
<Section>
|
||||
<TemplateImage
|
||||
|
||||
@ -3,7 +3,8 @@ import { useLingui } from '@lingui/react';
|
||||
|
||||
import { formatTeamUrl } from '@documenso/lib/utils/teams';
|
||||
|
||||
import { Body, Container, Head, Hr, Html, Preview, Section, Text } from '../components';
|
||||
import { Body, Container, Head, Hr, Html, Img, Preview, Section, Text } from '../components';
|
||||
import { useBranding } from '../providers/branding';
|
||||
import { TemplateFooter } from '../template-components/template-footer';
|
||||
import TemplateImage from '../template-components/template-image';
|
||||
|
||||
@ -23,6 +24,7 @@ export const TeamEmailRemovedTemplate = ({
|
||||
teamUrl = 'demo',
|
||||
}: TeamEmailRemovedTemplateProps) => {
|
||||
const { _ } = useLingui();
|
||||
const branding = useBranding();
|
||||
|
||||
const previewText = msg`Team email removed for ${teamName} on Documenso`;
|
||||
|
||||
@ -34,11 +36,15 @@ export const TeamEmailRemovedTemplate = ({
|
||||
<Body className="mx-auto my-auto font-sans">
|
||||
<Section className="bg-white text-slate-500">
|
||||
<Container className="mx-auto mb-2 mt-8 max-w-xl rounded-lg border border-solid border-slate-200 px-2 pt-2 backdrop-blur-sm">
|
||||
<TemplateImage
|
||||
assetBaseUrl={assetBaseUrl}
|
||||
className="mb-4 h-6 p-2"
|
||||
staticAsset="logo.png"
|
||||
/>
|
||||
{branding.brandingEnabled && branding.brandingLogo ? (
|
||||
<Img src={branding.brandingLogo} alt="Branding Logo" className="mb-4 h-6 p-2" />
|
||||
) : (
|
||||
<TemplateImage
|
||||
assetBaseUrl={assetBaseUrl}
|
||||
className="mb-4 h-6 p-2"
|
||||
staticAsset="logo.png"
|
||||
/>
|
||||
)}
|
||||
|
||||
<Section>
|
||||
<TemplateImage
|
||||
|
||||
@ -3,7 +3,19 @@ import { useLingui } from '@lingui/react';
|
||||
|
||||
import { formatTeamUrl } from '@documenso/lib/utils/teams';
|
||||
|
||||
import { Body, Button, Container, Head, Hr, Html, Preview, Section, Text } from '../components';
|
||||
import {
|
||||
Body,
|
||||
Button,
|
||||
Container,
|
||||
Head,
|
||||
Hr,
|
||||
Html,
|
||||
Img,
|
||||
Preview,
|
||||
Section,
|
||||
Text,
|
||||
} from '../components';
|
||||
import { useBranding } from '../providers/branding';
|
||||
import { TemplateFooter } from '../template-components/template-footer';
|
||||
import TemplateImage from '../template-components/template-image';
|
||||
|
||||
@ -25,6 +37,7 @@ export const TeamInviteEmailTemplate = ({
|
||||
token = '',
|
||||
}: TeamInviteEmailProps) => {
|
||||
const { _ } = useLingui();
|
||||
const branding = useBranding();
|
||||
|
||||
const previewText = msg`Accept invitation to join a team on Documenso`;
|
||||
|
||||
@ -36,11 +49,15 @@ export const TeamInviteEmailTemplate = ({
|
||||
<Body className="mx-auto my-auto font-sans">
|
||||
<Section className="bg-white text-slate-500">
|
||||
<Container className="mx-auto mb-2 mt-8 max-w-xl rounded-lg border border-solid border-slate-200 p-2 backdrop-blur-sm">
|
||||
<TemplateImage
|
||||
assetBaseUrl={assetBaseUrl}
|
||||
className="mb-4 h-6 p-2"
|
||||
staticAsset="logo.png"
|
||||
/>
|
||||
{branding.brandingEnabled && branding.brandingLogo ? (
|
||||
<Img src={branding.brandingLogo} alt="Branding Logo" className="mb-4 h-6 p-2" />
|
||||
) : (
|
||||
<TemplateImage
|
||||
assetBaseUrl={assetBaseUrl}
|
||||
className="mb-4 h-6 p-2"
|
||||
staticAsset="logo.png"
|
||||
/>
|
||||
)}
|
||||
|
||||
<Section>
|
||||
<TemplateImage
|
||||
|
||||
@ -3,7 +3,8 @@ import { useLingui } from '@lingui/react';
|
||||
|
||||
import { formatTeamUrl } from '@documenso/lib/utils/teams';
|
||||
|
||||
import { Body, Container, Head, Hr, Html, Preview, Section, Text } from '../components';
|
||||
import { Body, Container, Head, Hr, Html, Img, Preview, Section, Text } from '../components';
|
||||
import { useBranding } from '../providers/branding';
|
||||
import { TemplateFooter } from '../template-components/template-footer';
|
||||
import TemplateImage from '../template-components/template-image';
|
||||
|
||||
@ -25,6 +26,7 @@ export const TeamJoinEmailTemplate = ({
|
||||
teamUrl = 'demo',
|
||||
}: TeamJoinEmailProps) => {
|
||||
const { _ } = useLingui();
|
||||
const branding = useBranding();
|
||||
|
||||
const previewText = msg`A team member has joined a team on Documenso`;
|
||||
|
||||
@ -36,11 +38,15 @@ export const TeamJoinEmailTemplate = ({
|
||||
<Body className="mx-auto my-auto font-sans">
|
||||
<Section className="bg-white text-slate-500">
|
||||
<Container className="mx-auto mb-2 mt-8 max-w-xl rounded-lg border border-solid border-slate-200 p-2 backdrop-blur-sm">
|
||||
<TemplateImage
|
||||
assetBaseUrl={assetBaseUrl}
|
||||
className="mb-4 h-6 p-2"
|
||||
staticAsset="logo.png"
|
||||
/>
|
||||
{branding.brandingEnabled && branding.brandingLogo ? (
|
||||
<Img src={branding.brandingLogo} alt="Branding Logo" className="mb-4 h-6 p-2" />
|
||||
) : (
|
||||
<TemplateImage
|
||||
assetBaseUrl={assetBaseUrl}
|
||||
className="mb-4 h-6 p-2"
|
||||
staticAsset="logo.png"
|
||||
/>
|
||||
)}
|
||||
|
||||
<Section>
|
||||
<TemplateImage
|
||||
|
||||
@ -3,7 +3,8 @@ import { useLingui } from '@lingui/react';
|
||||
|
||||
import { formatTeamUrl } from '@documenso/lib/utils/teams';
|
||||
|
||||
import { Body, Container, Head, Hr, Html, Preview, Section, Text } from '../components';
|
||||
import { Body, Container, Head, Hr, Html, Img, Preview, Section, Text } from '../components';
|
||||
import { useBranding } from '../providers/branding';
|
||||
import { TemplateFooter } from '../template-components/template-footer';
|
||||
import TemplateImage from '../template-components/template-image';
|
||||
|
||||
@ -25,6 +26,7 @@ export const TeamLeaveEmailTemplate = ({
|
||||
teamUrl = 'demo',
|
||||
}: TeamLeaveEmailProps) => {
|
||||
const { _ } = useLingui();
|
||||
const branding = useBranding();
|
||||
|
||||
const previewText = msg`A team member has left a team on Documenso`;
|
||||
|
||||
@ -36,11 +38,15 @@ export const TeamLeaveEmailTemplate = ({
|
||||
<Body className="mx-auto my-auto font-sans">
|
||||
<Section className="bg-white text-slate-500">
|
||||
<Container className="mx-auto mb-2 mt-8 max-w-xl rounded-lg border border-solid border-slate-200 p-2 backdrop-blur-sm">
|
||||
<TemplateImage
|
||||
assetBaseUrl={assetBaseUrl}
|
||||
className="mb-4 h-6 p-2"
|
||||
staticAsset="logo.png"
|
||||
/>
|
||||
{branding.brandingEnabled && branding.brandingLogo ? (
|
||||
<Img src={branding.brandingLogo} alt="Branding Logo" className="mb-4 h-6 p-2" />
|
||||
) : (
|
||||
<TemplateImage
|
||||
assetBaseUrl={assetBaseUrl}
|
||||
className="mb-4 h-6 p-2"
|
||||
staticAsset="logo.png"
|
||||
/>
|
||||
)}
|
||||
|
||||
<Section>
|
||||
<TemplateImage
|
||||
|
||||
Reference in New Issue
Block a user