fix: link signing brand logos (#2881)

This commit is contained in:
Ephraim Duncan
2026-06-09 05:51:25 +00:00
committed by GitHub
parent 3e47f1913c
commit 3c0345f755
34 changed files with 449 additions and 295 deletions
@@ -0,0 +1,122 @@
---
date: 2026-05-28
title: Custom Brand Logo Url
---
# Problem
`brandingUrl` (the configured "Brand Website") is persisted and editable in branding
settings, but historically it was never consumed anywhere. It flowed into the database,
the settings form, and the admin read-only view, but never affected any rendered output.
We want `brandingUrl` to actually do something, with deliberately different behavior per
surface.
# Relationship we're going for
`brandingUrl` is an **email-only** linking concept. It is intentionally **not** used on
in-app signing surfaces.
| Surface | Custom branding logo configured | `brandingUrl` behavior |
| --- | --- | --- |
| Transactional emails (logo) | Logo shown | Logo links to `brandingUrl` when it is a safe http(s) URL; otherwise plain image |
| Transactional emails (footer) | n/a | `brandingUrl` rendered as a link in the footer when it is a safe http(s) URL |
| Signing pages (V1 + V2, normal + direct-template) | Logo shown | Ignored — logo is a plain image with no link |
| Signing pages (no custom logo) | Documenso fallback shown | Fallback keeps its internal `/` link |
| Embedded signing | Logo shown | Ignored (logo not linked) |
| Embedded authoring/editor | Logo shown | Ignored |
| Settings / admin branding previews | n/a | Unchanged (display only) |
Rationale:
- On signing pages the recipient is mid-task; sending them off to an external marketing
site via the logo is undesirable, so the custom logo is a plain image there.
- In emails the logo and a footer link to the brand's own site are a normal, expected
pattern and reinforce that the email is legitimately from that brand.
# Decisions
## Scope
- Use `brandingUrl` only in transactional email rendering:
- The shared email logo component links the custom branding logo to `brandingUrl`.
- The shared email footer renders `brandingUrl` as a link.
- On signing surfaces, render a configured custom branding logo as a plain image with no
link wrapper. Leave the Documenso fallback logo's internal `/` link untouched.
- Do not change embedded signing, embedded authoring/editor, or settings/admin previews.
- No Prisma schema or database migration. `brandingUrl` already exists and is editable.
## URL safety
Rendering must be defensive because old/imported data can bypass the branding form's URL
validation. Only treat the stored value as a usable Brand Website when it parses as an
absolute `http:` or `https:` URL.
- Empty, missing, invalid, relative, or non-http(s) values are treated as "no Brand
Website" and produce a plain logo / no footer link.
- Do not mutate stored settings or run a cleanup migration.
- Factored into a single shared helper so both email logo and footer apply identical rules:
- `packages/email/utils/branding-url.ts` -> `getSafeBrandingUrl(value): string | null`.
## Email rendering
- New shared component `packages/email/template-components/template-branding-logo.tsx`
(`TemplateBrandingLogo`) renders either:
- the custom branding logo, wrapped in a `Link` to the safe `brandingUrl` with
`target="_blank"` when one exists, or a plain `Img` when not; or
- the Documenso fallback logo (`/static/logo.png`) when custom branding is disabled or
no logo is set.
- This component replaced the duplicated `brandingEnabled && brandingLogo ? <Img/> : <fallback/>`
ternary that was copy-pasted across all transactional email templates.
- `packages/email/template-components/template-footer.tsx` renders `brandingUrl` as a
footer link (via `getSafeBrandingUrl`) when branding is enabled and the URL is safe.
The branding context already exposes `brandingUrl` (`packages/email/providers/branding.tsx`),
populated by `teamGlobalSettingsToBranding` / `organisationGlobalSettingsToBranding`
(which spread `...settings`), so no additional plumbing into the email branding context was
required.
## Signing rendering
- `apps/remix/app/components/general/document-signing/document-signing-page-view-v1.tsx`:
custom logo renders as a bare `<img>`. `brandingUrl` is not read; the local branding type
and loader payload no longer carry it.
- `apps/remix/app/components/general/envelope-signing/envelope-signer-header.tsx` (V2,
shared by normal and direct-template signing): custom logo renders as a bare `<img>`; the
Documenso fallback keeps its `<Link to="/">`.
- `apps/remix/app/routes/_recipient+/sign.$token+/_index.tsx`: V1 loader branding payload no
longer includes `brandingUrl`.
- `packages/lib/server-only/envelope/get-envelope-for-recipient-signing.ts` and
`get-envelope-for-direct-template-signing.ts`: `brandingUrl` removed from the V2
`EnvelopeForSigningResponse.settings` schema/payload since it is not consumed there.
# History
An earlier iteration of this plan wired `brandingUrl` into the in-app signing pages so a
custom logo linked to the Brand Website (external `<a target="_blank">`, internal `/`
fallback otherwise) and added `brandingUrl` to the V1/V2 signing payloads. That direction
was reversed: signing-page logos are now plain images and `brandingUrl` is email-only. The
signing payload additions were removed.
# Test coverage
`packages/app-tests/e2e/signing-branding.spec.ts`:
- V1 normal `/sign/:token`: custom logo is a plain image, not inside a link, and no
`brandingUrl` link is present.
- V2 normal `/sign/:token` and V2 direct-template: same plain-image assertions.
- V2 with no custom logo: Documenso fallback still links to `/`.
- Embedded signing: no custom-logo Brand Website link is rendered.
# Acceptance criteria
- A custom branding logo on any signing surface (V1, V2 normal, V2 direct-template, embedded)
renders as a plain image with no link, and `brandingUrl` is never rendered as a link there.
- Documenso fallback logos continue linking to `/`.
- In transactional emails, when a custom logo and a safe `brandingUrl` are configured, the
email logo links to `brandingUrl` (new tab) and the footer shows the Brand Website link.
- In transactional emails, when `brandingUrl` is empty/invalid/relative/non-http(s), the logo
is a plain image and no footer Brand Website link is shown.
- URL safety is enforced through the single shared `getSafeBrandingUrl` helper.
- Settings/admin branding previews are unchanged.
- No schema or migration changes.
@@ -50,6 +50,11 @@ import { useRequiredDocumentSigningAuthContext } from './document-signing-auth-p
import { DocumentSigningCompleteDialog } from './document-signing-complete-dialog';
import { DocumentSigningRecipientProvider } from './document-signing-recipient-provider';
type DocumentSigningBranding = {
brandingEnabled: boolean;
brandingLogo: string;
};
export type DocumentSigningPageViewV1Props = {
recipient: RecipientWithFields;
document: DocumentAndSender;
@@ -57,6 +62,7 @@ export type DocumentSigningPageViewV1Props = {
completedFields: CompletedField[];
isRecipientsTurn: boolean;
allRecipients?: RecipientWithFields[];
branding: DocumentSigningBranding;
includeSenderDetails: boolean;
};
@@ -68,6 +74,7 @@ export const DocumentSigningPageViewV1 = ({
isRecipientsTurn,
allRecipients = [],
includeSenderDetails,
branding,
}: DocumentSigningPageViewV1Props) => {
const { documentData, documentMeta } = document;
@@ -168,10 +175,12 @@ export const DocumentSigningPageViewV1 = ({
const pendingFields = fieldsRequiringValidation.filter((field) => !field.inserted);
const hasPendingFields = pendingFields.length > 0;
const hasCustomBrandingLogo = branding.brandingEnabled && Boolean(branding.brandingLogo);
return (
<DocumentSigningRecipientProvider recipient={recipient} targetSigner={targetSigner}>
<div className="mx-auto w-full max-w-screen-xl sm:px-6">
{document.team.teamGlobalSettings.brandingEnabled && document.team.teamGlobalSettings.brandingLogo && (
{hasCustomBrandingLogo && (
<img
src={`/api/branding/logo/team/${document.teamId}`}
alt={`${document.team.name}'s Logo`}
@@ -27,27 +27,25 @@ export const EnvelopeSignerHeader = () => {
const { envelopeData, envelope, recipientFieldsRemaining, recipient } = useRequiredEnvelopeSigningContext();
const isEmbedSigning = useEmbedSigningContext() !== null;
const hasCustomBrandingLogo = envelopeData.settings.brandingEnabled && Boolean(envelopeData.settings.brandingLogo);
return (
<nav className="embed--DocumentWidgetHeader flex max-w-screen flex-row justify-between border-border border-b bg-background px-4 py-3 md:px-6">
{/* Left side - Logo and title */}
<div className="flex min-w-0 flex-1 items-center space-x-2 md:w-auto md:flex-none">
{!isEmbedSigning && (
<Link to="/" className="flex-shrink-0">
{envelopeData.settings.brandingEnabled && envelopeData.settings.brandingLogo ? (
{!isEmbedSigning &&
(hasCustomBrandingLogo ? (
<img
src={`/api/branding/logo/team/${envelope.teamId}`}
alt={`${envelope.team.name}'s Logo`}
className="h-6 w-auto"
className="h-6 w-auto flex-shrink-0"
/>
) : (
<>
<Link to="/" className="flex-shrink-0">
<BrandingLogo className="hidden h-6 w-auto md:block" />
<BrandingLogoIcon className="h-6 w-auto md:hidden" />
</>
)}
</Link>
)}
))}
<h1 title={envelope.title} className="min-w-0 truncate font-semibold text-base text-foreground md:hidden">
{envelope.title}
@@ -164,6 +164,10 @@ const handleV1Loader = async ({ params, request }: Route.LoaderArgs) => {
recipientSignature,
isRecipientsTurn,
includeSenderDetails: settings.includeSenderDetails,
branding: {
brandingEnabled: settings.brandingEnabled,
brandingLogo: settings.brandingLogo,
},
} as const;
};
@@ -338,6 +342,7 @@ const SigningPageV1 = ({ data }: { data: Awaited<ReturnType<typeof handleV1Loade
isRecipientsTurn,
allRecipients,
includeSenderDetails,
branding,
recipientWithFields,
} = data;
@@ -410,6 +415,7 @@ const SigningPageV1 = ({ data }: { data: Awaited<ReturnType<typeof handleV1Loade
isRecipientsTurn={isRecipientsTurn}
allRecipients={allRecipients}
includeSenderDetails={includeSenderDetails}
branding={branding}
/>
</div>
</DocumentSigningAuthProvider>
@@ -0,0 +1,144 @@
import fs from 'node:fs/promises';
import path from 'node:path';
import { formatDirectTemplatePath } from '@documenso/lib/utils/templates';
import { prisma } from '@documenso/prisma';
import { seedPendingDocumentWithFullFields } from '@documenso/prisma/seed/documents';
import { seedDirectTemplate } from '@documenso/prisma/seed/templates';
import { seedUser } from '@documenso/prisma/seed/users';
import { expect, type Page, test } from '@playwright/test';
import { DocumentDataType, FieldType } from '@prisma/client';
const BRANDING_URL = 'https://brand.example/signing?source=documenso';
const PDF_PAGE_SELECTOR = 'img[data-page-number]';
const readBrandingLogo = async () => {
const logo = await fs.readFile(path.join(__dirname, '../../assets/logo.png'));
return JSON.stringify({
type: DocumentDataType.BYTES_64,
data: logo.toString('base64'),
});
};
const enableOrganisationBranding = async ({
organisationGlobalSettingsId,
brandingUrl = BRANDING_URL,
}: {
organisationGlobalSettingsId: string;
brandingUrl?: string;
}) => {
await prisma.organisationGlobalSettings.update({
where: { id: organisationGlobalSettingsId },
data: {
brandingEnabled: true,
brandingLogo: await readBrandingLogo(),
brandingUrl,
},
});
};
/**
* On signing surfaces the custom branding logo must render as a plain image.
* It must not be wrapped in any link, and the Brand Website must never appear
* as a link on these pages.
*/
const expectPlainBrandingLogo = async (page: Page, logoName: string) => {
const logo = page.getByRole('img', { name: logoName });
await expect(logo).toBeVisible();
// The custom logo must not be wrapped in a link.
await expect(page.getByRole('link', { name: logoName })).toHaveCount(0);
// The Brand Website must never be rendered as a link on signing pages.
await expect(page.locator(`a[href="${BRANDING_URL}"]`)).toHaveCount(0);
};
test('[SIGNING_BRANDING]: V1 normal signing renders custom logo as a plain image', async ({ page }) => {
const { user, team, organisation } = await seedUser();
await enableOrganisationBranding({
organisationGlobalSettingsId: organisation.organisationGlobalSettingsId,
});
const { recipients } = await seedPendingDocumentWithFullFields({
owner: user,
teamId: team.id,
recipients: ['v1-branding-signer@test.documenso.com'],
fields: [FieldType.SIGNATURE],
});
await page.goto(`/sign/${recipients[0].token}`);
await expectPlainBrandingLogo(page, `${team.name}'s Logo`);
});
test('[SIGNING_BRANDING]: V2 signing renders custom logo as a plain image', async ({ page }) => {
const { user, team, organisation } = await seedUser();
await enableOrganisationBranding({
organisationGlobalSettingsId: organisation.organisationGlobalSettingsId,
});
const { recipients } = await seedPendingDocumentWithFullFields({
owner: user,
teamId: team.id,
recipients: ['v2-branding-signer@test.documenso.com'],
fields: [FieldType.SIGNATURE],
updateDocumentOptions: { internalVersion: 2 },
});
const directTemplate = await seedDirectTemplate({
title: 'V2 Branding Direct Template',
userId: user.id,
teamId: team.id,
internalVersion: 2,
});
await page.goto(`/sign/${recipients[0].token}`);
await expectPlainBrandingLogo(page, `${team.name}'s Logo`);
await page.goto(formatDirectTemplatePath(directTemplate.directLink?.token || ''));
await expectPlainBrandingLogo(page, `${team.name}'s Logo`);
});
test('[SIGNING_BRANDING]: V2 signing keeps internal link for the Documenso fallback logo', async ({ page }) => {
const { user, team } = await seedUser();
const { recipients } = await seedPendingDocumentWithFullFields({
owner: user,
teamId: team.id,
recipients: ['v2-fallback-signer@test.documenso.com'],
fields: [FieldType.SIGNATURE],
updateDocumentOptions: { internalVersion: 2 },
});
await page.goto(`/sign/${recipients[0].token}`);
const fallbackLogoLink = page.locator('a[href="/"]').first();
await expect(fallbackLogoLink).toBeVisible();
});
test('[SIGNING_BRANDING]: embedded signing does not render custom logo Brand Website links', async ({ page }) => {
const { user, team, organisation } = await seedUser();
await enableOrganisationBranding({
organisationGlobalSettingsId: organisation.organisationGlobalSettingsId,
});
const { recipients } = await seedPendingDocumentWithFullFields({
owner: user,
teamId: team.id,
recipients: ['embed-branding-signer@test.documenso.com'],
fields: [FieldType.SIGNATURE],
updateDocumentOptions: { internalVersion: 2 },
});
await page.goto(`/embed/sign/${recipients[0].token}`);
await expect(page.locator(PDF_PAGE_SELECTOR).first()).toBeVisible({ timeout: 30_000 });
await expect(page.locator(`a[href="${BRANDING_URL}"]`)).toHaveCount(0);
await expect(page.getByRole('link', { name: `${team.name}'s Logo` })).toHaveCount(0);
});
@@ -0,0 +1,43 @@
import { Img, Link } from '../components';
import { useBranding } from '../providers/branding';
import { getSafeBrandingUrl } from '../utils/branding-url';
export type TemplateBrandingLogoProps = {
assetBaseUrl: string;
className?: string;
};
/**
* Renders the email logo.
*
* - When custom branding is enabled with a logo, the branding logo is shown.
* If a safe (http/https) Brand Website is configured, the logo links to it.
* - Otherwise the Documenso logo is shown.
*/
export const TemplateBrandingLogo = ({ assetBaseUrl, className = 'mb-4 h-6' }: TemplateBrandingLogoProps) => {
const branding = useBranding();
const hasCustomBrandingLogo = branding.brandingEnabled && Boolean(branding.brandingLogo);
if (!hasCustomBrandingLogo) {
const documensoLogoUrl = new URL('/static/logo.png', assetBaseUrl).toString();
return <Img src={documensoLogoUrl} alt="Documenso Logo" className={className} />;
}
const brandingLogo = <Img src={branding.brandingLogo} alt="Branding Logo" className={className} />;
const safeBrandingUrl = getSafeBrandingUrl(branding.brandingUrl);
if (!safeBrandingUrl) {
return brandingLogo;
}
return (
<Link href={safeBrandingUrl} target="_blank">
{brandingLogo}
</Link>
);
};
export default TemplateBrandingLogo;
@@ -2,6 +2,7 @@ import { Trans } from '@lingui/react/macro';
import { Link, Section, Text } from '../components';
import { useBranding } from '../providers/branding';
import { getSafeBrandingUrl } from '../utils/branding-url';
export type TemplateFooterProps = {
isDocument?: boolean;
@@ -11,6 +12,8 @@ export type TemplateFooterProps = {
export const TemplateFooter = ({ isDocument = true, reportUrl }: TemplateFooterProps) => {
const branding = useBranding();
const safeBrandingUrl = branding.brandingEnabled ? getSafeBrandingUrl(branding.brandingUrl) : null;
return (
<Section>
{reportUrl && (
@@ -50,6 +53,14 @@ export const TemplateFooter = ({ isDocument = true, reportUrl }: TemplateFooterP
</Text>
)}
{branding.brandingEnabled && safeBrandingUrl && (
<Text className="my-8 text-slate-400 text-sm">
<Link href={safeBrandingUrl} target="_blank">
{safeBrandingUrl}
</Link>
</Text>
)}
{!branding.brandingEnabled && (
<Text className="my-8 text-slate-400 text-sm">
Documenso, Inc.
+3 -13
View File
@@ -1,9 +1,9 @@
import { msg } from '@lingui/core/macro';
import { useLingui } from '@lingui/react';
import { Body, Container, Head, Html, Img, Preview, Section } from '../components';
import { useBranding } from '../providers/branding';
import { Body, Container, Head, Html, Preview, Section } from '../components';
import { TemplateAccessAuth2FA } from '../template-components/template-access-auth-2fa';
import { TemplateBrandingLogo } from '../template-components/template-branding-logo';
import { TemplateFooter } from '../template-components/template-footer';
export type AccessAuth2FAEmailTemplateProps = {
@@ -25,14 +25,8 @@ export const AccessAuth2FAEmailTemplate = ({
}: AccessAuth2FAEmailTemplateProps) => {
const { _ } = useLingui();
const branding = useBranding();
const previewText = msg`Your verification code is ${code}`;
const getAssetUrl = (path: string) => {
return new URL(path, assetBaseUrl).toString();
};
return (
<Html>
<Head />
@@ -42,11 +36,7 @@ export const AccessAuth2FAEmailTemplate = ({
<Section>
<Container className="mx-auto mt-8 mb-2 max-w-xl rounded-lg border border-slate-200 border-solid p-4 backdrop-blur-sm">
<Section>
{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" />
)}
<TemplateBrandingLogo assetBaseUrl={assetBaseUrl} className="mb-4 h-6" />
<TemplateAccessAuth2FA
documentTitle={documentTitle}
+3 -12
View File
@@ -1,8 +1,8 @@
import { msg } from '@lingui/core/macro';
import { useLingui } from '@lingui/react';
import { Body, Container, Head, Html, Img, Preview, Section } from '../components';
import { useBranding } from '../providers/branding';
import { Body, Container, Head, Html, Preview, Section } from '../components';
import { TemplateBrandingLogo } from '../template-components/template-branding-logo';
import type { TemplateConfirmationEmailProps } from '../template-components/template-confirmation-email';
import { TemplateConfirmationEmail } from '../template-components/template-confirmation-email';
import { TemplateFooter } from '../template-components/template-footer';
@@ -12,14 +12,9 @@ export const ConfirmEmailTemplate = ({
assetBaseUrl = 'http://localhost:3002',
}: TemplateConfirmationEmailProps) => {
const { _ } = useLingui();
const branding = useBranding();
const previewText = msg`Please confirm your email address`;
const getAssetUrl = (path: string) => {
return new URL(path, assetBaseUrl).toString();
};
return (
<Html>
<Head />
@@ -28,11 +23,7 @@ export const ConfirmEmailTemplate = ({
<Section>
<Container className="mx-auto mt-8 mb-2 max-w-xl rounded-lg border border-slate-200 border-solid p-4 backdrop-blur-sm">
<Section>
{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" />
)}
<TemplateBrandingLogo assetBaseUrl={assetBaseUrl} className="mb-4 h-6" />
<TemplateConfirmationEmail confirmationLink={confirmationLink} assetBaseUrl={assetBaseUrl} />
</Section>
@@ -3,8 +3,8 @@ import { msg } from '@lingui/core/macro';
import { useLingui } from '@lingui/react';
import { Trans } from '@lingui/react/macro';
import { Body, Button, Container, Head, Hr, Html, Img, Link, Preview, Section, Text } from '../components';
import { useBranding } from '../providers/branding';
import { Body, Button, Container, Head, Hr, Html, Link, Preview, Section, Text } from '../components';
import { TemplateBrandingLogo } from '../template-components/template-branding-logo';
import { TemplateFooter } from '../template-components/template-footer';
import TemplateImage from '../template-components/template-image';
@@ -24,7 +24,6 @@ export const ConfirmTeamEmailTemplate = ({
token = '',
}: ConfirmTeamEmailProps) => {
const { _ } = useLingui();
const branding = useBranding();
const previewText = msg`Accept team email request for ${teamName} on Documenso`;
@@ -36,11 +35,7 @@ export const ConfirmTeamEmailTemplate = ({
<Body className="mx-auto my-auto font-sans">
<Section className="bg-white">
<Container className="mx-auto mt-8 mb-2 max-w-xl rounded-lg border border-slate-200 border-solid px-2 pt-2 backdrop-blur-sm">
{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" />
)}
<TemplateBrandingLogo assetBaseUrl={assetBaseUrl} className="mb-4 h-6 p-2" />
<Section>
<TemplateImage className="mx-auto" assetBaseUrl={assetBaseUrl} staticAsset="mail-open.png" />
+3 -12
View File
@@ -1,8 +1,8 @@
import { msg } from '@lingui/core/macro';
import { useLingui } from '@lingui/react';
import { Body, Container, Head, Hr, Html, Img, Preview, Section } from '../components';
import { useBranding } from '../providers/branding';
import { Body, Container, Head, Hr, Html, Preview, Section } from '../components';
import { TemplateBrandingLogo } from '../template-components/template-branding-logo';
import type { TemplateDocumentCancelProps } from '../template-components/template-document-cancel';
import { TemplateDocumentCancel } from '../template-components/template-document-cancel';
import { TemplateFooter } from '../template-components/template-footer';
@@ -17,14 +17,9 @@ export const DocumentCancelTemplate = ({
cancellationReason,
}: DocumentCancelEmailTemplateProps) => {
const { _ } = useLingui();
const branding = useBranding();
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();
};
return (
<Html>
<Head />
@@ -34,11 +29,7 @@ export const DocumentCancelTemplate = ({
<Section>
<Container className="mx-auto mt-8 mb-2 max-w-xl rounded-lg border border-slate-200 border-solid p-4 backdrop-blur-sm">
<Section>
{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" />
)}
<TemplateBrandingLogo assetBaseUrl={assetBaseUrl} className="mb-4 h-6" />
<TemplateDocumentCancel
inviterName={inviterName}
@@ -1,8 +1,8 @@
import { msg } from '@lingui/core/macro';
import { useLingui } from '@lingui/react';
import { Body, Container, Head, Html, Img, Preview, Section } from '../components';
import { useBranding } from '../providers/branding';
import { Body, Container, Head, Html, Preview, Section } from '../components';
import { TemplateBrandingLogo } from '../template-components/template-branding-logo';
import type { TemplateDocumentCompletedProps } from '../template-components/template-document-completed';
import { TemplateDocumentCompleted } from '../template-components/template-document-completed';
import { TemplateFooter } from '../template-components/template-footer';
@@ -20,14 +20,9 @@ export const DocumentCompletedEmailTemplate = ({
reportUrl,
}: DocumentCompletedEmailTemplateProps) => {
const { _ } = useLingui();
const branding = useBranding();
const previewText = msg`Completed Document`;
const getAssetUrl = (path: string) => {
return new URL(path, assetBaseUrl).toString();
};
return (
<Html>
<Head />
@@ -37,11 +32,7 @@ export const DocumentCompletedEmailTemplate = ({
<Section className="bg-white">
<Container className="mx-auto mt-8 mb-2 max-w-xl rounded-lg border border-slate-200 border-solid p-2 backdrop-blur-sm">
<Section className="p-2">
{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" />
)}
<TemplateBrandingLogo assetBaseUrl={assetBaseUrl} className="mb-4 h-6" />
<TemplateDocumentCompleted
downloadLink={downloadLink}
@@ -4,8 +4,8 @@ import { useLingui } from '@lingui/react';
import { Trans } from '@lingui/react/macro';
import { RecipientRole } from '@prisma/client';
import { Body, Button, Container, Head, Html, Img, Preview, Section, Text } from '../components';
import { useBranding } from '../providers/branding';
import { Body, Button, Container, Head, Html, Preview, Section, Text } from '../components';
import { TemplateBrandingLogo } from '../template-components/template-branding-logo';
import TemplateDocumentImage from '../template-components/template-document-image';
import { TemplateFooter } from '../template-components/template-footer';
@@ -25,16 +25,11 @@ export const DocumentCreatedFromDirectTemplateEmailTemplate = ({
assetBaseUrl = 'http://localhost:3002',
}: DocumentCompletedEmailTemplateProps) => {
const { _ } = useLingui();
const branding = useBranding();
const action = _(RECIPIENT_ROLES_DESCRIPTION[recipientRole].actioned).toLowerCase();
const previewText = msg`Document created from direct template`;
const getAssetUrl = (path: string) => {
return new URL(path, assetBaseUrl).toString();
};
return (
<Html>
<Head />
@@ -44,11 +39,7 @@ export const DocumentCreatedFromDirectTemplateEmailTemplate = ({
<Section className="bg-white">
<Container className="mx-auto mt-8 mb-2 max-w-xl rounded-lg border border-slate-200 border-solid p-2 backdrop-blur-sm">
<Section className="p-2">
{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" />
)}
<TemplateBrandingLogo assetBaseUrl={assetBaseUrl} className="mb-4 h-6" />
<TemplateDocumentImage className="mt-6" assetBaseUrl={assetBaseUrl} />
+3 -12
View File
@@ -5,8 +5,8 @@ import { Trans } from '@lingui/react/macro';
import type { RecipientRole } from '@prisma/client';
import { OrganisationType } from '@prisma/client';
import { Body, Container, Head, Hr, Html, Img, Link, Preview, Section, Text } from '../components';
import { useBranding } from '../providers/branding';
import { Body, Container, Head, Hr, Html, Link, Preview, Section, Text } from '../components';
import { TemplateBrandingLogo } from '../template-components/template-branding-logo';
import { TemplateCustomMessageBody } from '../template-components/template-custom-message-body';
import type { TemplateDocumentInviteProps } from '../template-components/template-document-invite';
import { TemplateDocumentInvite } from '../template-components/template-document-invite';
@@ -38,7 +38,6 @@ export const DocumentInviteEmailTemplate = ({
reportUrl,
}: DocumentInviteEmailTemplateProps) => {
const { _ } = useLingui();
const branding = useBranding();
const action = _(RECIPIENT_ROLES_DESCRIPTION[role].actionVerb).toLowerCase();
@@ -54,10 +53,6 @@ export const DocumentInviteEmailTemplate = ({
previewText = msg`Please ${action} your document ${documentName}`;
}
const getAssetUrl = (path: string) => {
return new URL(path, assetBaseUrl).toString();
};
return (
<Html>
<Head />
@@ -67,11 +62,7 @@ export const DocumentInviteEmailTemplate = ({
<Section>
<Container className="mx-auto mt-8 mb-2 max-w-xl rounded-lg border border-slate-200 border-solid p-4 backdrop-blur-sm">
<Section>
{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" />
)}
<TemplateBrandingLogo assetBaseUrl={assetBaseUrl} className="mb-4 h-6" />
<TemplateDocumentInvite
inviterName={inviterName}
+3 -12
View File
@@ -1,8 +1,8 @@
import { msg } from '@lingui/core/macro';
import { useLingui } from '@lingui/react';
import { Body, Container, Head, Html, Img, Preview, Section } from '../components';
import { useBranding } from '../providers/branding';
import { Body, Container, Head, Html, Preview, Section } from '../components';
import { TemplateBrandingLogo } from '../template-components/template-branding-logo';
import type { TemplateDocumentPendingProps } from '../template-components/template-document-pending';
import { TemplateDocumentPending } from '../template-components/template-document-pending';
import { TemplateFooter } from '../template-components/template-footer';
@@ -14,14 +14,9 @@ export const DocumentPendingEmailTemplate = ({
assetBaseUrl = 'http://localhost:3002',
}: DocumentPendingEmailTemplateProps) => {
const { _ } = useLingui();
const branding = useBranding();
const previewText = msg`Pending Document`;
const getAssetUrl = (path: string) => {
return new URL(path, assetBaseUrl).toString();
};
return (
<Html>
<Head />
@@ -31,11 +26,7 @@ export const DocumentPendingEmailTemplate = ({
<Section className="bg-white">
<Container className="mx-auto mt-8 mb-2 max-w-xl rounded-lg border border-slate-200 border-solid p-4 backdrop-blur-sm">
<Section>
{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" />
)}
<TemplateBrandingLogo assetBaseUrl={assetBaseUrl} className="mb-4 h-6" />
<TemplateDocumentPending documentName={documentName} assetBaseUrl={assetBaseUrl} />
</Section>
@@ -1,8 +1,8 @@
import { msg } from '@lingui/core/macro';
import { useLingui } from '@lingui/react';
import { Body, Container, Head, Html, Img, Preview, Section } from '../components';
import { useBranding } from '../providers/branding';
import { Body, Container, Head, Html, Preview, Section } from '../components';
import { TemplateBrandingLogo } from '../template-components/template-branding-logo';
import { TemplateDocumentRecipientSigned } from '../template-components/template-document-recipient-signed';
import { TemplateFooter } from '../template-components/template-footer';
@@ -20,16 +20,11 @@ export const DocumentRecipientSignedEmailTemplate = ({
assetBaseUrl = 'http://localhost:3002',
}: DocumentRecipientSignedEmailTemplateProps) => {
const { _ } = useLingui();
const branding = useBranding();
const recipientReference = recipientName || recipientEmail;
const previewText = msg`${recipientReference} has signed ${documentName}`;
const getAssetUrl = (path: string) => {
return new URL(path, assetBaseUrl).toString();
};
return (
<Html>
<Head />
@@ -39,11 +34,7 @@ export const DocumentRecipientSignedEmailTemplate = ({
<Section className="bg-white">
<Container className="mx-auto mt-8 mb-2 max-w-xl rounded-lg border border-slate-200 border-solid p-2 backdrop-blur-sm">
<Section className="p-2">
{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" />
)}
<TemplateBrandingLogo assetBaseUrl={assetBaseUrl} className="mb-4 h-6" />
<TemplateDocumentRecipientSigned
documentName={documentName}
+3 -12
View File
@@ -1,8 +1,8 @@
import { msg } from '@lingui/core/macro';
import { useLingui } from '@lingui/react';
import { Body, Container, Head, Html, Img, Preview, Section } from '../components';
import { useBranding } from '../providers/branding';
import { Body, Container, Head, Html, Preview, Section } from '../components';
import { TemplateBrandingLogo } from '../template-components/template-branding-logo';
import { TemplateDocumentRejected } from '../template-components/template-document-rejected';
import { TemplateFooter } from '../template-components/template-footer';
@@ -22,14 +22,9 @@ export function DocumentRejectedEmail({
assetBaseUrl = 'http://localhost:3002',
}: DocumentRejectedEmailProps) {
const { _ } = useLingui();
const branding = useBranding();
const previewText = _(msg`${recipientName} has rejected the document '${documentName}'`);
const getAssetUrl = (path: string) => {
return new URL(path, assetBaseUrl).toString();
};
return (
<Html>
<Head />
@@ -39,11 +34,7 @@ export function DocumentRejectedEmail({
<Section>
<Container className="mx-auto mt-8 mb-2 max-w-xl rounded-lg border border-slate-200 border-solid p-4 backdrop-blur-sm">
<Section>
{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" />
)}
<TemplateBrandingLogo assetBaseUrl={assetBaseUrl} className="mb-4 h-6" />
<TemplateDocumentRejected
recipientName={recipientName}
@@ -1,8 +1,8 @@
import { msg } from '@lingui/core/macro';
import { useLingui } from '@lingui/react';
import { Body, Container, Head, Html, Img, Preview, Section } from '../components';
import { useBranding } from '../providers/branding';
import { Body, Container, Head, Html, Preview, Section } from '../components';
import { TemplateBrandingLogo } from '../template-components/template-branding-logo';
import { TemplateDocumentRejectionConfirmed } from '../template-components/template-document-rejection-confirmed';
import { TemplateFooter } from '../template-components/template-footer';
@@ -22,14 +22,9 @@ export function DocumentRejectionConfirmedEmail({
assetBaseUrl = 'http://localhost:3002',
}: DocumentRejectionConfirmedEmailProps) {
const { _ } = useLingui();
const branding = useBranding();
const previewText = _(msg`You have rejected the document '${documentName}'`);
const getAssetUrl = (path: string) => {
return new URL(path, assetBaseUrl).toString();
};
return (
<Html>
<Head />
@@ -39,11 +34,7 @@ export function DocumentRejectionConfirmedEmail({
<Section>
<Container className="mx-auto mt-8 mb-2 max-w-xl rounded-lg border border-slate-200 border-solid p-4 backdrop-blur-sm">
<Section>
{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" />
)}
<TemplateBrandingLogo assetBaseUrl={assetBaseUrl} className="mb-4 h-6" />
<TemplateDocumentRejectionConfirmed
recipientName={recipientName}
+3 -12
View File
@@ -3,8 +3,8 @@ import { msg } from '@lingui/core/macro';
import { useLingui } from '@lingui/react';
import { RecipientRole } from '@prisma/client';
import { Body, Container, Head, Hr, Html, Img, Preview, Section, Text } from '../components';
import { useBranding } from '../providers/branding';
import { Body, Container, Head, Hr, Html, Preview, Section, Text } from '../components';
import { TemplateBrandingLogo } from '../template-components/template-branding-logo';
import { TemplateCustomMessageBody } from '../template-components/template-custom-message-body';
import { TemplateDocumentReminder } from '../template-components/template-document-reminder';
import { TemplateFooter } from '../template-components/template-footer';
@@ -29,16 +29,11 @@ export const DocumentReminderEmailTemplate = ({
reportUrl,
}: DocumentReminderEmailTemplateProps) => {
const { _ } = useLingui();
const branding = useBranding();
const action = _(RECIPIENT_ROLES_DESCRIPTION[role].actionVerb).toLowerCase();
const previewText = msg`Reminder to ${action} ${documentName}`;
const getAssetUrl = (path: string) => {
return new URL(path, assetBaseUrl).toString();
};
return (
<Html>
<Head />
@@ -48,11 +43,7 @@ export const DocumentReminderEmailTemplate = ({
<Section>
<Container className="mx-auto mt-8 mb-2 max-w-xl rounded-lg border border-slate-200 border-solid p-4 backdrop-blur-sm">
<Section>
{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" />
)}
<TemplateBrandingLogo assetBaseUrl={assetBaseUrl} className="mb-4 h-6" />
<TemplateDocumentReminder
recipientName={recipientName}
@@ -1,8 +1,8 @@
import { msg } from '@lingui/core/macro';
import { useLingui } from '@lingui/react';
import { Body, Container, Head, Html, Img, Preview, Section } from '../components';
import { useBranding } from '../providers/branding';
import { Body, Container, Head, Html, Preview, Section } from '../components';
import { TemplateBrandingLogo } from '../template-components/template-branding-logo';
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';
@@ -14,14 +14,9 @@ export const DocumentSelfSignedEmailTemplate = ({
assetBaseUrl = 'http://localhost:3002',
}: DocumentSelfSignedTemplateProps) => {
const { _ } = useLingui();
const branding = useBranding();
const previewText = msg`Completed Document`;
const getAssetUrl = (path: string) => {
return new URL(path, assetBaseUrl).toString();
};
return (
<Html>
<Head />
@@ -31,11 +26,7 @@ export const DocumentSelfSignedEmailTemplate = ({
<Section className="bg-white">
<Container className="mx-auto mt-8 mb-2 max-w-xl rounded-lg border border-slate-200 border-solid p-2 backdrop-blur-sm">
<Section className="p-2">
{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" />
)}
<TemplateBrandingLogo assetBaseUrl={assetBaseUrl} className="mb-4 h-6" />
<TemplateDocumentSelfSigned documentName={documentName} assetBaseUrl={assetBaseUrl} />
</Section>
@@ -1,8 +1,8 @@
import { msg } from '@lingui/core/macro';
import { useLingui } from '@lingui/react';
import { Body, Container, Head, Hr, Html, Img, Preview, Section } from '../components';
import { useBranding } from '../providers/branding';
import { Body, Container, Head, Hr, Html, Preview, Section } from '../components';
import { TemplateBrandingLogo } from '../template-components/template-branding-logo';
import {
TemplateDocumentDelete,
type TemplateDocumentDeleteProps,
@@ -17,14 +17,9 @@ export const DocumentSuperDeleteEmailTemplate = ({
reason = 'Unknown',
}: DocumentDeleteEmailTemplateProps) => {
const { _ } = useLingui();
const branding = useBranding();
const previewText = msg`An admin has deleted your document "${documentName}".`;
const getAssetUrl = (path: string) => {
return new URL(path, assetBaseUrl).toString();
};
return (
<Html>
<Head />
@@ -34,11 +29,7 @@ export const DocumentSuperDeleteEmailTemplate = ({
<Section>
<Container className="mx-auto mt-8 mb-2 max-w-xl rounded-lg border border-slate-200 border-solid p-4 backdrop-blur-sm">
<Section>
{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" />
)}
<TemplateBrandingLogo assetBaseUrl={assetBaseUrl} className="mb-4 h-6" />
<TemplateDocumentDelete reason={reason} documentName={documentName} assetBaseUrl={assetBaseUrl} />
</Section>
+3 -12
View File
@@ -1,8 +1,8 @@
import { msg } from '@lingui/core/macro';
import { useLingui } from '@lingui/react';
import { Body, Container, Head, Html, Img, Preview, Section } from '../components';
import { useBranding } from '../providers/branding';
import { Body, Container, Head, Html, Preview, Section } from '../components';
import { TemplateBrandingLogo } from '../template-components/template-branding-logo';
import { TemplateFooter } from '../template-components/template-footer';
import type { TemplateForgotPasswordProps } from '../template-components/template-forgot-password';
import { TemplateForgotPassword } from '../template-components/template-forgot-password';
@@ -14,14 +14,9 @@ export const ForgotPasswordTemplate = ({
assetBaseUrl = 'http://localhost:3002',
}: ForgotPasswordTemplateProps) => {
const { _ } = useLingui();
const branding = useBranding();
const previewText = msg`Password Reset Requested`;
const getAssetUrl = (path: string) => {
return new URL(path, assetBaseUrl).toString();
};
return (
<Html>
<Head />
@@ -31,11 +26,7 @@ export const ForgotPasswordTemplate = ({
<Section>
<Container className="mx-auto mt-8 mb-2 max-w-xl rounded-lg border border-slate-200 border-solid p-4 backdrop-blur-sm">
<Section>
{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" />
)}
<TemplateBrandingLogo assetBaseUrl={assetBaseUrl} className="mb-4 h-6" />
<TemplateForgotPassword resetPasswordLink={resetPasswordLink} assetBaseUrl={assetBaseUrl} />
</Section>
@@ -2,8 +2,8 @@ import { msg } from '@lingui/core/macro';
import { useLingui } from '@lingui/react';
import { Trans } from '@lingui/react/macro';
import { Body, Button, Container, Head, Hr, Html, Img, Preview, Section, Text } from '../components';
import { useBranding } from '../providers/branding';
import { Body, Button, Container, Head, Hr, Html, Preview, Section, Text } from '../components';
import { TemplateBrandingLogo } from '../template-components/template-branding-logo';
import { TemplateFooter } from '../template-components/template-footer';
import TemplateImage from '../template-components/template-image';
@@ -21,7 +21,6 @@ export const OrganisationAccountLinkConfirmationTemplate = ({
assetBaseUrl = 'http://localhost:3002',
}: OrganisationAccountLinkConfirmationTemplateProps) => {
const { _ } = useLingui();
const branding = useBranding();
const previewText =
type === 'create'
@@ -35,11 +34,7 @@ export const OrganisationAccountLinkConfirmationTemplate = ({
<Body className="mx-auto my-auto font-sans">
<Section className="bg-white">
<Container className="mx-auto mt-8 mb-2 max-w-xl rounded-lg border border-slate-200 border-solid px-2 pt-2 backdrop-blur-sm">
{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" />
)}
<TemplateBrandingLogo assetBaseUrl={assetBaseUrl} className="mb-4 h-6 p-2" />
<Section>
<TemplateImage className="mx-auto h-12 w-12" assetBaseUrl={assetBaseUrl} staticAsset="building-2.png" />
@@ -1,8 +1,8 @@
import { msg } from '@lingui/core/macro';
import { useLingui } from '@lingui/react';
import { Body, Container, Head, Hr, Html, Img, Preview, Section, Text } from '../components';
import { useBranding } from '../providers/branding';
import { Body, Container, Head, Hr, Html, Preview, Section, Text } from '../components';
import { TemplateBrandingLogo } from '../template-components/template-branding-logo';
import { TemplateFooter } from '../template-components/template-footer';
import TemplateImage from '../template-components/template-image';
@@ -22,7 +22,6 @@ export const OrganisationDeleteEmailTemplate = ({
deletedByAdmin = false,
}: OrganisationDeleteEmailProps) => {
const { _ } = useLingui();
const branding = useBranding();
const previewText = msg`Your organisation has been deleted`;
@@ -40,11 +39,7 @@ export const OrganisationDeleteEmailTemplate = ({
<Body className="mx-auto my-auto font-sans">
<Section className="bg-white text-slate-500">
<Container className="mx-auto mt-8 mb-2 max-w-xl rounded-lg border border-slate-200 border-solid p-2 backdrop-blur-sm">
{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" />
)}
<TemplateBrandingLogo assetBaseUrl={assetBaseUrl} className="mb-4 h-6 p-2" />
<Section>
<TemplateImage className="mx-auto" assetBaseUrl={assetBaseUrl} staticAsset="delete-team.png" />
@@ -2,8 +2,8 @@ import { msg } from '@lingui/core/macro';
import { useLingui } from '@lingui/react';
import { Trans } from '@lingui/react/macro';
import { Body, Button, Container, Head, Hr, Html, Img, Preview, Section, Text } from '../components';
import { useBranding } from '../providers/branding';
import { Body, Button, Container, Head, Hr, Html, Preview, Section, Text } from '../components';
import { TemplateBrandingLogo } from '../template-components/template-branding-logo';
import { TemplateFooter } from '../template-components/template-footer';
import TemplateImage from '../template-components/template-image';
@@ -23,7 +23,6 @@ export const OrganisationInviteEmailTemplate = ({
token = '',
}: OrganisationInviteEmailProps) => {
const { _ } = useLingui();
const branding = useBranding();
const previewText = msg`Accept invitation to join an organisation on Documenso`;
@@ -35,11 +34,7 @@ export const OrganisationInviteEmailTemplate = ({
<Body className="mx-auto my-auto font-sans">
<Section className="bg-white text-slate-500">
<Container className="mx-auto mt-8 mb-2 max-w-xl rounded-lg border border-slate-200 border-solid p-2 backdrop-blur-sm">
{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" />
)}
<TemplateBrandingLogo assetBaseUrl={assetBaseUrl} className="mb-4 h-6 p-2" />
<Section>
<TemplateImage className="mx-auto" assetBaseUrl={assetBaseUrl} staticAsset="add-user.png" />
@@ -2,8 +2,8 @@ import { msg } from '@lingui/core/macro';
import { useLingui } from '@lingui/react';
import { Trans } from '@lingui/react/macro';
import { Body, Container, Head, Hr, Html, Img, Preview, Section, Text } from '../components';
import { useBranding } from '../providers/branding';
import { Body, Container, Head, Hr, Html, Preview, Section, Text } from '../components';
import { TemplateBrandingLogo } from '../template-components/template-branding-logo';
import { TemplateFooter } from '../template-components/template-footer';
import TemplateImage from '../template-components/template-image';
@@ -25,7 +25,6 @@ export const OrganisationJoinEmailTemplate = ({
organisationUrl = 'demo',
}: OrganisationJoinEmailProps) => {
const { _ } = useLingui();
const branding = useBranding();
const previewText = msg`A member has joined your organisation on Documenso`;
@@ -37,11 +36,7 @@ export const OrganisationJoinEmailTemplate = ({
<Body className="mx-auto my-auto font-sans">
<Section className="bg-white text-slate-500">
<Container className="mx-auto mt-8 mb-2 max-w-xl rounded-lg border border-slate-200 border-solid p-2 backdrop-blur-sm">
{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" />
)}
<TemplateBrandingLogo assetBaseUrl={assetBaseUrl} className="mb-4 h-6 p-2" />
<Section>
<TemplateImage className="mx-auto" assetBaseUrl={assetBaseUrl} staticAsset="add-user.png" />
@@ -2,8 +2,8 @@ import { msg } from '@lingui/core/macro';
import { useLingui } from '@lingui/react';
import { Trans } from '@lingui/react/macro';
import { Body, Container, Head, Hr, Html, Img, Preview, Section, Text } from '../components';
import { useBranding } from '../providers/branding';
import { Body, Container, Head, Hr, Html, Preview, Section, Text } from '../components';
import { TemplateBrandingLogo } from '../template-components/template-branding-logo';
import { TemplateFooter } from '../template-components/template-footer';
import TemplateImage from '../template-components/template-image';
@@ -25,7 +25,6 @@ export const OrganisationLeaveEmailTemplate = ({
organisationUrl = 'demo',
}: OrganisationLeaveEmailProps) => {
const { _ } = useLingui();
const branding = useBranding();
const previewText = msg`A member has left your organisation on Documenso`;
@@ -37,11 +36,7 @@ export const OrganisationLeaveEmailTemplate = ({
<Body className="mx-auto my-auto font-sans">
<Section className="bg-white text-slate-500">
<Container className="mx-auto mt-8 mb-2 max-w-xl rounded-lg border border-slate-200 border-solid p-2 backdrop-blur-sm">
{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" />
)}
<TemplateBrandingLogo assetBaseUrl={assetBaseUrl} className="mb-4 h-6 p-2" />
<Section>
<TemplateImage className="mx-auto" assetBaseUrl={assetBaseUrl} staticAsset="delete-user.png" />
@@ -3,10 +3,9 @@ import { msg } from '@lingui/core/macro';
import { useLingui } from '@lingui/react';
import { Trans } from '@lingui/react/macro';
import { match } from 'ts-pattern';
import { Body, Container, Head, Hr, Html, Img, Preview, Section, Text } from '../components';
import { useBranding } from '../providers/branding';
import { Body, Container, Head, Hr, Html, Preview, Section, Text } from '../components';
import { TemplateBrandingLogo } from '../template-components/template-branding-logo';
import { TemplateFooter } from '../template-components/template-footer';
import TemplateImage from '../template-components/template-image';
export type OrganisationLimitExceededEmailProps = {
assetBaseUrl: string;
@@ -24,7 +23,6 @@ export const OrganisationLimitExceededEmailTemplate = ({
period = '2026-05',
}: OrganisationLimitExceededEmailProps) => {
const { _ } = useLingui();
const branding = useBranding();
const previewText = msg`Organisation Review Required`;
@@ -36,11 +34,7 @@ export const OrganisationLimitExceededEmailTemplate = ({
<Body className="mx-auto my-auto font-sans">
<Section className="bg-white text-slate-500">
<Container className="mx-auto mt-8 mb-2 max-w-xl rounded-lg border border-slate-200 border-solid p-2 backdrop-blur-sm">
{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" />
)}
<TemplateBrandingLogo assetBaseUrl={assetBaseUrl} className="mb-4 h-6 p-2" />
<Section className="p-2 text-slate-500">
<Text className="text-center font-medium text-black text-lg">
+3 -12
View File
@@ -1,8 +1,8 @@
import { msg } from '@lingui/core/macro';
import { useLingui } from '@lingui/react';
import { Body, Container, Head, Hr, Html, Img, Preview, Section } from '../components';
import { useBranding } from '../providers/branding';
import { Body, Container, Head, Hr, Html, Preview, Section } from '../components';
import { TemplateBrandingLogo } from '../template-components/template-branding-logo';
import { TemplateFooter } from '../template-components/template-footer';
import type { TemplateRecipientExpiredProps } from '../template-components/template-recipient-expired';
import { TemplateRecipientExpired } from '../template-components/template-recipient-expired';
@@ -17,14 +17,9 @@ export const RecipientExpiredTemplate = ({
assetBaseUrl = 'http://localhost:3002',
}: RecipientExpiredEmailTemplateProps) => {
const { _ } = useLingui();
const branding = useBranding();
const previewText = msg`The signing window for "${recipientName}" on document "${documentName}" has expired.`;
const getAssetUrl = (path: string) => {
return new URL(path, assetBaseUrl).toString();
};
return (
<Html>
<Head />
@@ -34,11 +29,7 @@ export const RecipientExpiredTemplate = ({
<Section>
<Container className="mx-auto mt-8 mb-2 max-w-xl rounded-lg border border-slate-200 border-solid p-4 backdrop-blur-sm">
<Section>
{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" />
)}
<TemplateBrandingLogo assetBaseUrl={assetBaseUrl} className="mb-4 h-6" />
<TemplateRecipientExpired
documentName={documentName}
@@ -2,8 +2,8 @@ import { msg } from '@lingui/core/macro';
import { useLingui } from '@lingui/react';
import { Trans } from '@lingui/react/macro';
import { Body, Container, Head, Hr, Html, Img, Preview, Section, Text } from '../components';
import { useBranding } from '../providers/branding';
import { Body, Container, Head, Hr, Html, Preview, Section, Text } from '../components';
import { TemplateBrandingLogo } from '../template-components/template-branding-logo';
import type { TemplateDocumentCancelProps } from '../template-components/template-document-cancel';
import TemplateDocumentImage from '../template-components/template-document-image';
import { TemplateFooter } from '../template-components/template-footer';
@@ -16,14 +16,9 @@ export const RecipientRemovedFromDocumentTemplate = ({
assetBaseUrl = 'http://localhost:3002',
}: DocumentCancelEmailTemplateProps) => {
const { _ } = useLingui();
const branding = useBranding();
const previewText = msg`${inviterName} has removed you from the document ${documentName}.`;
const getAssetUrl = (path: string) => {
return new URL(path, assetBaseUrl).toString();
};
return (
<Html>
<Head />
@@ -33,11 +28,7 @@ export const RecipientRemovedFromDocumentTemplate = ({
<Section>
<Container className="mx-auto mt-8 mb-2 max-w-xl rounded-lg border border-slate-200 border-solid p-4 backdrop-blur-sm">
<Section>
{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" />
)}
<TemplateBrandingLogo assetBaseUrl={assetBaseUrl} className="mb-4 h-6" />
<TemplateDocumentImage className="mt-6" assetBaseUrl={assetBaseUrl} />
+3 -12
View File
@@ -2,8 +2,8 @@ import { msg } from '@lingui/core/macro';
import { useLingui } from '@lingui/react';
import { Trans } from '@lingui/react/macro';
import { Body, Container, Head, Hr, Html, Img, Link, Preview, Section, Text } from '../components';
import { useBranding } from '../providers/branding';
import { Body, Container, Head, Hr, Html, Link, Preview, Section, Text } from '../components';
import { TemplateBrandingLogo } from '../template-components/template-branding-logo';
import { TemplateFooter } from '../template-components/template-footer';
import type { TemplateResetPasswordProps } from '../template-components/template-reset-password';
import { TemplateResetPassword } from '../template-components/template-reset-password';
@@ -16,14 +16,9 @@ export const ResetPasswordTemplate = ({
assetBaseUrl = 'http://localhost:3002',
}: ResetPasswordTemplateProps) => {
const { _ } = useLingui();
const branding = useBranding();
const previewText = msg`Password Reset Successful`;
const getAssetUrl = (path: string) => {
return new URL(path, assetBaseUrl).toString();
};
return (
<Html>
<Head />
@@ -33,11 +28,7 @@ export const ResetPasswordTemplate = ({
<Section>
<Container className="mx-auto mt-8 mb-2 max-w-xl rounded-lg border border-slate-200 border-solid p-4 backdrop-blur-sm">
<Section>
{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" />
)}
<TemplateBrandingLogo assetBaseUrl={assetBaseUrl} className="mb-4 h-6" />
<TemplateResetPassword userName={userName} userEmail={userEmail} assetBaseUrl={assetBaseUrl} />
</Section>
+3 -8
View File
@@ -2,8 +2,8 @@ import { formatTeamUrl } from '@documenso/lib/utils/teams';
import { msg } from '@lingui/core/macro';
import { useLingui } from '@lingui/react';
import { Body, Container, Head, Hr, Html, Img, Preview, Section, Text } from '../components';
import { useBranding } from '../providers/branding';
import { Body, Container, Head, Hr, Html, Preview, Section, Text } from '../components';
import { TemplateBrandingLogo } from '../template-components/template-branding-logo';
import { TemplateFooter } from '../template-components/template-footer';
import TemplateImage from '../template-components/template-image';
@@ -19,7 +19,6 @@ export const TeamDeleteEmailTemplate = ({
teamUrl = 'demo',
}: TeamDeleteEmailProps) => {
const { _ } = useLingui();
const branding = useBranding();
const previewText = msg`A team you were a part of has been deleted`;
@@ -35,11 +34,7 @@ export const TeamDeleteEmailTemplate = ({
<Body className="mx-auto my-auto font-sans">
<Section className="bg-white text-slate-500">
<Container className="mx-auto mt-8 mb-2 max-w-xl rounded-lg border border-slate-200 border-solid p-2 backdrop-blur-sm">
{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" />
)}
<TemplateBrandingLogo assetBaseUrl={assetBaseUrl} className="mb-4 h-6 p-2" />
<Section>
<TemplateImage className="mx-auto" assetBaseUrl={assetBaseUrl} staticAsset="delete-team.png" />
@@ -3,8 +3,8 @@ import { msg } from '@lingui/core/macro';
import { useLingui } from '@lingui/react';
import { Trans } from '@lingui/react/macro';
import { Body, Container, Head, Hr, Html, Img, Preview, Section, Text } from '../components';
import { useBranding } from '../providers/branding';
import { Body, Container, Head, Hr, Html, Preview, Section, Text } from '../components';
import { TemplateBrandingLogo } from '../template-components/template-branding-logo';
import { TemplateFooter } from '../template-components/template-footer';
import TemplateImage from '../template-components/template-image';
@@ -24,7 +24,6 @@ export const TeamEmailRemovedTemplate = ({
teamUrl = 'demo',
}: TeamEmailRemovedTemplateProps) => {
const { _ } = useLingui();
const branding = useBranding();
const previewText = msg`Team email removed for ${teamName} on Documenso`;
@@ -36,11 +35,7 @@ export const TeamEmailRemovedTemplate = ({
<Body className="mx-auto my-auto font-sans">
<Section className="bg-white text-slate-500">
<Container className="mx-auto mt-8 mb-2 max-w-xl rounded-lg border border-slate-200 border-solid px-2 pt-2 backdrop-blur-sm">
{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" />
)}
<TemplateBrandingLogo assetBaseUrl={assetBaseUrl} className="mb-4 h-6 p-2" />
<Section>
<TemplateImage className="mx-auto" assetBaseUrl={assetBaseUrl} staticAsset="mail-open-alert.png" />
+21
View File
@@ -0,0 +1,21 @@
/**
* Returns the normalized Brand Website URL only when it is a safe absolute
* http(s) URL.
*
* Rendering must be defensive because old/imported data can bypass the branding
* form validation. Empty, missing, invalid, relative, or non-http(s) values are
* treated as no Brand Website.
*/
export const getSafeBrandingUrl = (brandingUrl: string | null | undefined): string | null => {
if (!brandingUrl) {
return null;
}
const parsed = URL.parse(brandingUrl);
if (parsed?.protocol !== 'http:' && parsed?.protocol !== 'https:') {
return null;
}
return parsed.href;
};