mirror of
https://github.com/documenso/documenso.git
synced 2026-06-22 04:12:06 +10:00
fix: link signing brand logos (#2881)
This commit is contained in:
@@ -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.
|
||||||
+10
-1
@@ -50,6 +50,11 @@ import { useRequiredDocumentSigningAuthContext } from './document-signing-auth-p
|
|||||||
import { DocumentSigningCompleteDialog } from './document-signing-complete-dialog';
|
import { DocumentSigningCompleteDialog } from './document-signing-complete-dialog';
|
||||||
import { DocumentSigningRecipientProvider } from './document-signing-recipient-provider';
|
import { DocumentSigningRecipientProvider } from './document-signing-recipient-provider';
|
||||||
|
|
||||||
|
type DocumentSigningBranding = {
|
||||||
|
brandingEnabled: boolean;
|
||||||
|
brandingLogo: string;
|
||||||
|
};
|
||||||
|
|
||||||
export type DocumentSigningPageViewV1Props = {
|
export type DocumentSigningPageViewV1Props = {
|
||||||
recipient: RecipientWithFields;
|
recipient: RecipientWithFields;
|
||||||
document: DocumentAndSender;
|
document: DocumentAndSender;
|
||||||
@@ -57,6 +62,7 @@ export type DocumentSigningPageViewV1Props = {
|
|||||||
completedFields: CompletedField[];
|
completedFields: CompletedField[];
|
||||||
isRecipientsTurn: boolean;
|
isRecipientsTurn: boolean;
|
||||||
allRecipients?: RecipientWithFields[];
|
allRecipients?: RecipientWithFields[];
|
||||||
|
branding: DocumentSigningBranding;
|
||||||
includeSenderDetails: boolean;
|
includeSenderDetails: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -68,6 +74,7 @@ export const DocumentSigningPageViewV1 = ({
|
|||||||
isRecipientsTurn,
|
isRecipientsTurn,
|
||||||
allRecipients = [],
|
allRecipients = [],
|
||||||
includeSenderDetails,
|
includeSenderDetails,
|
||||||
|
branding,
|
||||||
}: DocumentSigningPageViewV1Props) => {
|
}: DocumentSigningPageViewV1Props) => {
|
||||||
const { documentData, documentMeta } = document;
|
const { documentData, documentMeta } = document;
|
||||||
|
|
||||||
@@ -168,10 +175,12 @@ export const DocumentSigningPageViewV1 = ({
|
|||||||
const pendingFields = fieldsRequiringValidation.filter((field) => !field.inserted);
|
const pendingFields = fieldsRequiringValidation.filter((field) => !field.inserted);
|
||||||
const hasPendingFields = pendingFields.length > 0;
|
const hasPendingFields = pendingFields.length > 0;
|
||||||
|
|
||||||
|
const hasCustomBrandingLogo = branding.brandingEnabled && Boolean(branding.brandingLogo);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DocumentSigningRecipientProvider recipient={recipient} targetSigner={targetSigner}>
|
<DocumentSigningRecipientProvider recipient={recipient} targetSigner={targetSigner}>
|
||||||
<div className="mx-auto w-full max-w-screen-xl sm:px-6">
|
<div className="mx-auto w-full max-w-screen-xl sm:px-6">
|
||||||
{document.team.teamGlobalSettings.brandingEnabled && document.team.teamGlobalSettings.brandingLogo && (
|
{hasCustomBrandingLogo && (
|
||||||
<img
|
<img
|
||||||
src={`/api/branding/logo/team/${document.teamId}`}
|
src={`/api/branding/logo/team/${document.teamId}`}
|
||||||
alt={`${document.team.name}'s Logo`}
|
alt={`${document.team.name}'s Logo`}
|
||||||
|
|||||||
@@ -27,27 +27,25 @@ export const EnvelopeSignerHeader = () => {
|
|||||||
const { envelopeData, envelope, recipientFieldsRemaining, recipient } = useRequiredEnvelopeSigningContext();
|
const { envelopeData, envelope, recipientFieldsRemaining, recipient } = useRequiredEnvelopeSigningContext();
|
||||||
|
|
||||||
const isEmbedSigning = useEmbedSigningContext() !== null;
|
const isEmbedSigning = useEmbedSigningContext() !== null;
|
||||||
|
const hasCustomBrandingLogo = envelopeData.settings.brandingEnabled && Boolean(envelopeData.settings.brandingLogo);
|
||||||
|
|
||||||
return (
|
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">
|
<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 */}
|
{/* Left side - Logo and title */}
|
||||||
<div className="flex min-w-0 flex-1 items-center space-x-2 md:w-auto md:flex-none">
|
<div className="flex min-w-0 flex-1 items-center space-x-2 md:w-auto md:flex-none">
|
||||||
{!isEmbedSigning && (
|
{!isEmbedSigning &&
|
||||||
<Link to="/" className="flex-shrink-0">
|
(hasCustomBrandingLogo ? (
|
||||||
{envelopeData.settings.brandingEnabled && envelopeData.settings.brandingLogo ? (
|
<img
|
||||||
<img
|
src={`/api/branding/logo/team/${envelope.teamId}`}
|
||||||
src={`/api/branding/logo/team/${envelope.teamId}`}
|
alt={`${envelope.team.name}'s Logo`}
|
||||||
alt={`${envelope.team.name}'s Logo`}
|
className="h-6 w-auto flex-shrink-0"
|
||||||
className="h-6 w-auto"
|
/>
|
||||||
/>
|
) : (
|
||||||
) : (
|
<Link to="/" className="flex-shrink-0">
|
||||||
<>
|
<BrandingLogo className="hidden h-6 w-auto md:block" />
|
||||||
<BrandingLogo className="hidden h-6 w-auto md:block" />
|
<BrandingLogoIcon className="h-6 w-auto md:hidden" />
|
||||||
<BrandingLogoIcon className="h-6 w-auto md:hidden" />
|
</Link>
|
||||||
</>
|
))}
|
||||||
)}
|
|
||||||
</Link>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<h1 title={envelope.title} className="min-w-0 truncate font-semibold text-base text-foreground md:hidden">
|
<h1 title={envelope.title} className="min-w-0 truncate font-semibold text-base text-foreground md:hidden">
|
||||||
{envelope.title}
|
{envelope.title}
|
||||||
|
|||||||
@@ -164,6 +164,10 @@ const handleV1Loader = async ({ params, request }: Route.LoaderArgs) => {
|
|||||||
recipientSignature,
|
recipientSignature,
|
||||||
isRecipientsTurn,
|
isRecipientsTurn,
|
||||||
includeSenderDetails: settings.includeSenderDetails,
|
includeSenderDetails: settings.includeSenderDetails,
|
||||||
|
branding: {
|
||||||
|
brandingEnabled: settings.brandingEnabled,
|
||||||
|
brandingLogo: settings.brandingLogo,
|
||||||
|
},
|
||||||
} as const;
|
} as const;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -338,6 +342,7 @@ const SigningPageV1 = ({ data }: { data: Awaited<ReturnType<typeof handleV1Loade
|
|||||||
isRecipientsTurn,
|
isRecipientsTurn,
|
||||||
allRecipients,
|
allRecipients,
|
||||||
includeSenderDetails,
|
includeSenderDetails,
|
||||||
|
branding,
|
||||||
recipientWithFields,
|
recipientWithFields,
|
||||||
} = data;
|
} = data;
|
||||||
|
|
||||||
@@ -410,6 +415,7 @@ const SigningPageV1 = ({ data }: { data: Awaited<ReturnType<typeof handleV1Loade
|
|||||||
isRecipientsTurn={isRecipientsTurn}
|
isRecipientsTurn={isRecipientsTurn}
|
||||||
allRecipients={allRecipients}
|
allRecipients={allRecipients}
|
||||||
includeSenderDetails={includeSenderDetails}
|
includeSenderDetails={includeSenderDetails}
|
||||||
|
branding={branding}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</DocumentSigningAuthProvider>
|
</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 { Link, Section, Text } from '../components';
|
||||||
import { useBranding } from '../providers/branding';
|
import { useBranding } from '../providers/branding';
|
||||||
|
import { getSafeBrandingUrl } from '../utils/branding-url';
|
||||||
|
|
||||||
export type TemplateFooterProps = {
|
export type TemplateFooterProps = {
|
||||||
isDocument?: boolean;
|
isDocument?: boolean;
|
||||||
@@ -11,6 +12,8 @@ export type TemplateFooterProps = {
|
|||||||
export const TemplateFooter = ({ isDocument = true, reportUrl }: TemplateFooterProps) => {
|
export const TemplateFooter = ({ isDocument = true, reportUrl }: TemplateFooterProps) => {
|
||||||
const branding = useBranding();
|
const branding = useBranding();
|
||||||
|
|
||||||
|
const safeBrandingUrl = branding.brandingEnabled ? getSafeBrandingUrl(branding.brandingUrl) : null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Section>
|
<Section>
|
||||||
{reportUrl && (
|
{reportUrl && (
|
||||||
@@ -50,6 +53,14 @@ export const TemplateFooter = ({ isDocument = true, reportUrl }: TemplateFooterP
|
|||||||
</Text>
|
</Text>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{branding.brandingEnabled && safeBrandingUrl && (
|
||||||
|
<Text className="my-8 text-slate-400 text-sm">
|
||||||
|
<Link href={safeBrandingUrl} target="_blank">
|
||||||
|
{safeBrandingUrl}
|
||||||
|
</Link>
|
||||||
|
</Text>
|
||||||
|
)}
|
||||||
|
|
||||||
{!branding.brandingEnabled && (
|
{!branding.brandingEnabled && (
|
||||||
<Text className="my-8 text-slate-400 text-sm">
|
<Text className="my-8 text-slate-400 text-sm">
|
||||||
Documenso, Inc.
|
Documenso, Inc.
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import { msg } from '@lingui/core/macro';
|
import { msg } from '@lingui/core/macro';
|
||||||
import { useLingui } from '@lingui/react';
|
import { useLingui } from '@lingui/react';
|
||||||
|
|
||||||
import { Body, Container, Head, Html, Img, Preview, Section } from '../components';
|
import { Body, Container, Head, Html, Preview, Section } from '../components';
|
||||||
import { useBranding } from '../providers/branding';
|
|
||||||
import { TemplateAccessAuth2FA } from '../template-components/template-access-auth-2fa';
|
import { TemplateAccessAuth2FA } from '../template-components/template-access-auth-2fa';
|
||||||
|
import { TemplateBrandingLogo } from '../template-components/template-branding-logo';
|
||||||
import { TemplateFooter } from '../template-components/template-footer';
|
import { TemplateFooter } from '../template-components/template-footer';
|
||||||
|
|
||||||
export type AccessAuth2FAEmailTemplateProps = {
|
export type AccessAuth2FAEmailTemplateProps = {
|
||||||
@@ -25,14 +25,8 @@ export const AccessAuth2FAEmailTemplate = ({
|
|||||||
}: AccessAuth2FAEmailTemplateProps) => {
|
}: AccessAuth2FAEmailTemplateProps) => {
|
||||||
const { _ } = useLingui();
|
const { _ } = useLingui();
|
||||||
|
|
||||||
const branding = useBranding();
|
|
||||||
|
|
||||||
const previewText = msg`Your verification code is ${code}`;
|
const previewText = msg`Your verification code is ${code}`;
|
||||||
|
|
||||||
const getAssetUrl = (path: string) => {
|
|
||||||
return new URL(path, assetBaseUrl).toString();
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Html>
|
<Html>
|
||||||
<Head />
|
<Head />
|
||||||
@@ -42,11 +36,7 @@ export const AccessAuth2FAEmailTemplate = ({
|
|||||||
<Section>
|
<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">
|
<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>
|
<Section>
|
||||||
{branding.brandingEnabled && branding.brandingLogo ? (
|
<TemplateBrandingLogo assetBaseUrl={assetBaseUrl} className="mb-4 h-6" />
|
||||||
<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" />
|
|
||||||
)}
|
|
||||||
|
|
||||||
<TemplateAccessAuth2FA
|
<TemplateAccessAuth2FA
|
||||||
documentTitle={documentTitle}
|
documentTitle={documentTitle}
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import { msg } from '@lingui/core/macro';
|
import { msg } from '@lingui/core/macro';
|
||||||
import { useLingui } from '@lingui/react';
|
import { useLingui } from '@lingui/react';
|
||||||
|
|
||||||
import { Body, Container, Head, Html, Img, Preview, Section } from '../components';
|
import { Body, Container, Head, Html, Preview, Section } from '../components';
|
||||||
import { useBranding } from '../providers/branding';
|
import { TemplateBrandingLogo } from '../template-components/template-branding-logo';
|
||||||
import type { TemplateConfirmationEmailProps } from '../template-components/template-confirmation-email';
|
import type { TemplateConfirmationEmailProps } from '../template-components/template-confirmation-email';
|
||||||
import { TemplateConfirmationEmail } from '../template-components/template-confirmation-email';
|
import { TemplateConfirmationEmail } from '../template-components/template-confirmation-email';
|
||||||
import { TemplateFooter } from '../template-components/template-footer';
|
import { TemplateFooter } from '../template-components/template-footer';
|
||||||
@@ -12,14 +12,9 @@ export const ConfirmEmailTemplate = ({
|
|||||||
assetBaseUrl = 'http://localhost:3002',
|
assetBaseUrl = 'http://localhost:3002',
|
||||||
}: TemplateConfirmationEmailProps) => {
|
}: TemplateConfirmationEmailProps) => {
|
||||||
const { _ } = useLingui();
|
const { _ } = useLingui();
|
||||||
const branding = useBranding();
|
|
||||||
|
|
||||||
const previewText = msg`Please confirm your email address`;
|
const previewText = msg`Please confirm your email address`;
|
||||||
|
|
||||||
const getAssetUrl = (path: string) => {
|
|
||||||
return new URL(path, assetBaseUrl).toString();
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Html>
|
<Html>
|
||||||
<Head />
|
<Head />
|
||||||
@@ -28,11 +23,7 @@ export const ConfirmEmailTemplate = ({
|
|||||||
<Section>
|
<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">
|
<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>
|
<Section>
|
||||||
{branding.brandingEnabled && branding.brandingLogo ? (
|
<TemplateBrandingLogo assetBaseUrl={assetBaseUrl} className="mb-4 h-6" />
|
||||||
<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} assetBaseUrl={assetBaseUrl} />
|
<TemplateConfirmationEmail confirmationLink={confirmationLink} assetBaseUrl={assetBaseUrl} />
|
||||||
</Section>
|
</Section>
|
||||||
|
|||||||
@@ -3,8 +3,8 @@ import { msg } from '@lingui/core/macro';
|
|||||||
import { useLingui } from '@lingui/react';
|
import { useLingui } from '@lingui/react';
|
||||||
import { Trans } from '@lingui/react/macro';
|
import { Trans } from '@lingui/react/macro';
|
||||||
|
|
||||||
import { Body, Button, Container, Head, Hr, Html, Img, Link, Preview, Section, Text } from '../components';
|
import { Body, Button, Container, Head, Hr, Html, Link, Preview, Section, Text } from '../components';
|
||||||
import { useBranding } from '../providers/branding';
|
import { TemplateBrandingLogo } from '../template-components/template-branding-logo';
|
||||||
import { TemplateFooter } from '../template-components/template-footer';
|
import { TemplateFooter } from '../template-components/template-footer';
|
||||||
import TemplateImage from '../template-components/template-image';
|
import TemplateImage from '../template-components/template-image';
|
||||||
|
|
||||||
@@ -24,7 +24,6 @@ export const ConfirmTeamEmailTemplate = ({
|
|||||||
token = '',
|
token = '',
|
||||||
}: ConfirmTeamEmailProps) => {
|
}: ConfirmTeamEmailProps) => {
|
||||||
const { _ } = useLingui();
|
const { _ } = useLingui();
|
||||||
const branding = useBranding();
|
|
||||||
|
|
||||||
const previewText = msg`Accept team email request for ${teamName} on Documenso`;
|
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">
|
<Body className="mx-auto my-auto font-sans">
|
||||||
<Section className="bg-white">
|
<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">
|
<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 ? (
|
<TemplateBrandingLogo assetBaseUrl={assetBaseUrl} className="mb-4 h-6 p-2" />
|
||||||
<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>
|
<Section>
|
||||||
<TemplateImage className="mx-auto" assetBaseUrl={assetBaseUrl} staticAsset="mail-open.png" />
|
<TemplateImage className="mx-auto" assetBaseUrl={assetBaseUrl} staticAsset="mail-open.png" />
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import { msg } from '@lingui/core/macro';
|
import { msg } from '@lingui/core/macro';
|
||||||
import { useLingui } from '@lingui/react';
|
import { useLingui } from '@lingui/react';
|
||||||
|
|
||||||
import { Body, Container, Head, Hr, Html, Img, Preview, Section } from '../components';
|
import { Body, Container, Head, Hr, Html, Preview, Section } from '../components';
|
||||||
import { useBranding } from '../providers/branding';
|
import { TemplateBrandingLogo } from '../template-components/template-branding-logo';
|
||||||
import type { TemplateDocumentCancelProps } from '../template-components/template-document-cancel';
|
import type { TemplateDocumentCancelProps } from '../template-components/template-document-cancel';
|
||||||
import { TemplateDocumentCancel } from '../template-components/template-document-cancel';
|
import { TemplateDocumentCancel } from '../template-components/template-document-cancel';
|
||||||
import { TemplateFooter } from '../template-components/template-footer';
|
import { TemplateFooter } from '../template-components/template-footer';
|
||||||
@@ -17,14 +17,9 @@ export const DocumentCancelTemplate = ({
|
|||||||
cancellationReason,
|
cancellationReason,
|
||||||
}: DocumentCancelEmailTemplateProps) => {
|
}: DocumentCancelEmailTemplateProps) => {
|
||||||
const { _ } = useLingui();
|
const { _ } = useLingui();
|
||||||
const branding = useBranding();
|
|
||||||
|
|
||||||
const previewText = msg`${inviterName} has cancelled the document ${documentName}, you don't need to sign it anymore.`;
|
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 (
|
return (
|
||||||
<Html>
|
<Html>
|
||||||
<Head />
|
<Head />
|
||||||
@@ -34,11 +29,7 @@ export const DocumentCancelTemplate = ({
|
|||||||
<Section>
|
<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">
|
<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>
|
<Section>
|
||||||
{branding.brandingEnabled && branding.brandingLogo ? (
|
<TemplateBrandingLogo assetBaseUrl={assetBaseUrl} className="mb-4 h-6" />
|
||||||
<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
|
<TemplateDocumentCancel
|
||||||
inviterName={inviterName}
|
inviterName={inviterName}
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import { msg } from '@lingui/core/macro';
|
import { msg } from '@lingui/core/macro';
|
||||||
import { useLingui } from '@lingui/react';
|
import { useLingui } from '@lingui/react';
|
||||||
|
|
||||||
import { Body, Container, Head, Html, Img, Preview, Section } from '../components';
|
import { Body, Container, Head, Html, Preview, Section } from '../components';
|
||||||
import { useBranding } from '../providers/branding';
|
import { TemplateBrandingLogo } from '../template-components/template-branding-logo';
|
||||||
import type { TemplateDocumentCompletedProps } from '../template-components/template-document-completed';
|
import type { TemplateDocumentCompletedProps } from '../template-components/template-document-completed';
|
||||||
import { TemplateDocumentCompleted } from '../template-components/template-document-completed';
|
import { TemplateDocumentCompleted } from '../template-components/template-document-completed';
|
||||||
import { TemplateFooter } from '../template-components/template-footer';
|
import { TemplateFooter } from '../template-components/template-footer';
|
||||||
@@ -20,14 +20,9 @@ export const DocumentCompletedEmailTemplate = ({
|
|||||||
reportUrl,
|
reportUrl,
|
||||||
}: DocumentCompletedEmailTemplateProps) => {
|
}: DocumentCompletedEmailTemplateProps) => {
|
||||||
const { _ } = useLingui();
|
const { _ } = useLingui();
|
||||||
const branding = useBranding();
|
|
||||||
|
|
||||||
const previewText = msg`Completed Document`;
|
const previewText = msg`Completed Document`;
|
||||||
|
|
||||||
const getAssetUrl = (path: string) => {
|
|
||||||
return new URL(path, assetBaseUrl).toString();
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Html>
|
<Html>
|
||||||
<Head />
|
<Head />
|
||||||
@@ -37,11 +32,7 @@ export const DocumentCompletedEmailTemplate = ({
|
|||||||
<Section className="bg-white">
|
<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">
|
<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">
|
<Section className="p-2">
|
||||||
{branding.brandingEnabled && branding.brandingLogo ? (
|
<TemplateBrandingLogo assetBaseUrl={assetBaseUrl} className="mb-4 h-6" />
|
||||||
<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
|
<TemplateDocumentCompleted
|
||||||
downloadLink={downloadLink}
|
downloadLink={downloadLink}
|
||||||
|
|||||||
@@ -4,8 +4,8 @@ import { useLingui } from '@lingui/react';
|
|||||||
import { Trans } from '@lingui/react/macro';
|
import { Trans } from '@lingui/react/macro';
|
||||||
import { RecipientRole } from '@prisma/client';
|
import { RecipientRole } from '@prisma/client';
|
||||||
|
|
||||||
import { Body, Button, Container, Head, Html, Img, Preview, Section, Text } from '../components';
|
import { Body, Button, Container, Head, Html, Preview, Section, Text } from '../components';
|
||||||
import { useBranding } from '../providers/branding';
|
import { TemplateBrandingLogo } from '../template-components/template-branding-logo';
|
||||||
import TemplateDocumentImage from '../template-components/template-document-image';
|
import TemplateDocumentImage from '../template-components/template-document-image';
|
||||||
import { TemplateFooter } from '../template-components/template-footer';
|
import { TemplateFooter } from '../template-components/template-footer';
|
||||||
|
|
||||||
@@ -25,16 +25,11 @@ export const DocumentCreatedFromDirectTemplateEmailTemplate = ({
|
|||||||
assetBaseUrl = 'http://localhost:3002',
|
assetBaseUrl = 'http://localhost:3002',
|
||||||
}: DocumentCompletedEmailTemplateProps) => {
|
}: DocumentCompletedEmailTemplateProps) => {
|
||||||
const { _ } = useLingui();
|
const { _ } = useLingui();
|
||||||
const branding = useBranding();
|
|
||||||
|
|
||||||
const action = _(RECIPIENT_ROLES_DESCRIPTION[recipientRole].actioned).toLowerCase();
|
const action = _(RECIPIENT_ROLES_DESCRIPTION[recipientRole].actioned).toLowerCase();
|
||||||
|
|
||||||
const previewText = msg`Document created from direct template`;
|
const previewText = msg`Document created from direct template`;
|
||||||
|
|
||||||
const getAssetUrl = (path: string) => {
|
|
||||||
return new URL(path, assetBaseUrl).toString();
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Html>
|
<Html>
|
||||||
<Head />
|
<Head />
|
||||||
@@ -44,11 +39,7 @@ export const DocumentCreatedFromDirectTemplateEmailTemplate = ({
|
|||||||
<Section className="bg-white">
|
<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">
|
<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">
|
<Section className="p-2">
|
||||||
{branding.brandingEnabled && branding.brandingLogo ? (
|
<TemplateBrandingLogo assetBaseUrl={assetBaseUrl} className="mb-4 h-6" />
|
||||||
<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} />
|
<TemplateDocumentImage className="mt-6" assetBaseUrl={assetBaseUrl} />
|
||||||
|
|
||||||
|
|||||||
@@ -5,8 +5,8 @@ import { Trans } from '@lingui/react/macro';
|
|||||||
import type { RecipientRole } from '@prisma/client';
|
import type { RecipientRole } from '@prisma/client';
|
||||||
import { OrganisationType } from '@prisma/client';
|
import { OrganisationType } from '@prisma/client';
|
||||||
|
|
||||||
import { Body, Container, Head, Hr, Html, Img, Link, Preview, Section, Text } from '../components';
|
import { Body, Container, Head, Hr, Html, Link, Preview, Section, Text } from '../components';
|
||||||
import { useBranding } from '../providers/branding';
|
import { TemplateBrandingLogo } from '../template-components/template-branding-logo';
|
||||||
import { TemplateCustomMessageBody } from '../template-components/template-custom-message-body';
|
import { TemplateCustomMessageBody } from '../template-components/template-custom-message-body';
|
||||||
import type { TemplateDocumentInviteProps } from '../template-components/template-document-invite';
|
import type { TemplateDocumentInviteProps } from '../template-components/template-document-invite';
|
||||||
import { TemplateDocumentInvite } from '../template-components/template-document-invite';
|
import { TemplateDocumentInvite } from '../template-components/template-document-invite';
|
||||||
@@ -38,7 +38,6 @@ export const DocumentInviteEmailTemplate = ({
|
|||||||
reportUrl,
|
reportUrl,
|
||||||
}: DocumentInviteEmailTemplateProps) => {
|
}: DocumentInviteEmailTemplateProps) => {
|
||||||
const { _ } = useLingui();
|
const { _ } = useLingui();
|
||||||
const branding = useBranding();
|
|
||||||
|
|
||||||
const action = _(RECIPIENT_ROLES_DESCRIPTION[role].actionVerb).toLowerCase();
|
const action = _(RECIPIENT_ROLES_DESCRIPTION[role].actionVerb).toLowerCase();
|
||||||
|
|
||||||
@@ -54,10 +53,6 @@ export const DocumentInviteEmailTemplate = ({
|
|||||||
previewText = msg`Please ${action} your document ${documentName}`;
|
previewText = msg`Please ${action} your document ${documentName}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
const getAssetUrl = (path: string) => {
|
|
||||||
return new URL(path, assetBaseUrl).toString();
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Html>
|
<Html>
|
||||||
<Head />
|
<Head />
|
||||||
@@ -67,11 +62,7 @@ export const DocumentInviteEmailTemplate = ({
|
|||||||
<Section>
|
<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">
|
<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>
|
<Section>
|
||||||
{branding.brandingEnabled && branding.brandingLogo ? (
|
<TemplateBrandingLogo assetBaseUrl={assetBaseUrl} className="mb-4 h-6" />
|
||||||
<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
|
<TemplateDocumentInvite
|
||||||
inviterName={inviterName}
|
inviterName={inviterName}
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import { msg } from '@lingui/core/macro';
|
import { msg } from '@lingui/core/macro';
|
||||||
import { useLingui } from '@lingui/react';
|
import { useLingui } from '@lingui/react';
|
||||||
|
|
||||||
import { Body, Container, Head, Html, Img, Preview, Section } from '../components';
|
import { Body, Container, Head, Html, Preview, Section } from '../components';
|
||||||
import { useBranding } from '../providers/branding';
|
import { TemplateBrandingLogo } from '../template-components/template-branding-logo';
|
||||||
import type { TemplateDocumentPendingProps } from '../template-components/template-document-pending';
|
import type { TemplateDocumentPendingProps } from '../template-components/template-document-pending';
|
||||||
import { TemplateDocumentPending } from '../template-components/template-document-pending';
|
import { TemplateDocumentPending } from '../template-components/template-document-pending';
|
||||||
import { TemplateFooter } from '../template-components/template-footer';
|
import { TemplateFooter } from '../template-components/template-footer';
|
||||||
@@ -14,14 +14,9 @@ export const DocumentPendingEmailTemplate = ({
|
|||||||
assetBaseUrl = 'http://localhost:3002',
|
assetBaseUrl = 'http://localhost:3002',
|
||||||
}: DocumentPendingEmailTemplateProps) => {
|
}: DocumentPendingEmailTemplateProps) => {
|
||||||
const { _ } = useLingui();
|
const { _ } = useLingui();
|
||||||
const branding = useBranding();
|
|
||||||
|
|
||||||
const previewText = msg`Pending Document`;
|
const previewText = msg`Pending Document`;
|
||||||
|
|
||||||
const getAssetUrl = (path: string) => {
|
|
||||||
return new URL(path, assetBaseUrl).toString();
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Html>
|
<Html>
|
||||||
<Head />
|
<Head />
|
||||||
@@ -31,11 +26,7 @@ export const DocumentPendingEmailTemplate = ({
|
|||||||
<Section className="bg-white">
|
<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">
|
<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>
|
<Section>
|
||||||
{branding.brandingEnabled && branding.brandingLogo ? (
|
<TemplateBrandingLogo assetBaseUrl={assetBaseUrl} className="mb-4 h-6" />
|
||||||
<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} />
|
<TemplateDocumentPending documentName={documentName} assetBaseUrl={assetBaseUrl} />
|
||||||
</Section>
|
</Section>
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import { msg } from '@lingui/core/macro';
|
import { msg } from '@lingui/core/macro';
|
||||||
import { useLingui } from '@lingui/react';
|
import { useLingui } from '@lingui/react';
|
||||||
|
|
||||||
import { Body, Container, Head, Html, Img, Preview, Section } from '../components';
|
import { Body, Container, Head, Html, Preview, Section } from '../components';
|
||||||
import { useBranding } from '../providers/branding';
|
import { TemplateBrandingLogo } from '../template-components/template-branding-logo';
|
||||||
import { TemplateDocumentRecipientSigned } from '../template-components/template-document-recipient-signed';
|
import { TemplateDocumentRecipientSigned } from '../template-components/template-document-recipient-signed';
|
||||||
import { TemplateFooter } from '../template-components/template-footer';
|
import { TemplateFooter } from '../template-components/template-footer';
|
||||||
|
|
||||||
@@ -20,16 +20,11 @@ export const DocumentRecipientSignedEmailTemplate = ({
|
|||||||
assetBaseUrl = 'http://localhost:3002',
|
assetBaseUrl = 'http://localhost:3002',
|
||||||
}: DocumentRecipientSignedEmailTemplateProps) => {
|
}: DocumentRecipientSignedEmailTemplateProps) => {
|
||||||
const { _ } = useLingui();
|
const { _ } = useLingui();
|
||||||
const branding = useBranding();
|
|
||||||
|
|
||||||
const recipientReference = recipientName || recipientEmail;
|
const recipientReference = recipientName || recipientEmail;
|
||||||
|
|
||||||
const previewText = msg`${recipientReference} has signed ${documentName}`;
|
const previewText = msg`${recipientReference} has signed ${documentName}`;
|
||||||
|
|
||||||
const getAssetUrl = (path: string) => {
|
|
||||||
return new URL(path, assetBaseUrl).toString();
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Html>
|
<Html>
|
||||||
<Head />
|
<Head />
|
||||||
@@ -39,11 +34,7 @@ export const DocumentRecipientSignedEmailTemplate = ({
|
|||||||
<Section className="bg-white">
|
<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">
|
<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">
|
<Section className="p-2">
|
||||||
{branding.brandingEnabled && branding.brandingLogo ? (
|
<TemplateBrandingLogo assetBaseUrl={assetBaseUrl} className="mb-4 h-6" />
|
||||||
<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" />
|
|
||||||
)}
|
|
||||||
|
|
||||||
<TemplateDocumentRecipientSigned
|
<TemplateDocumentRecipientSigned
|
||||||
documentName={documentName}
|
documentName={documentName}
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import { msg } from '@lingui/core/macro';
|
import { msg } from '@lingui/core/macro';
|
||||||
import { useLingui } from '@lingui/react';
|
import { useLingui } from '@lingui/react';
|
||||||
|
|
||||||
import { Body, Container, Head, Html, Img, Preview, Section } from '../components';
|
import { Body, Container, Head, Html, Preview, Section } from '../components';
|
||||||
import { useBranding } from '../providers/branding';
|
import { TemplateBrandingLogo } from '../template-components/template-branding-logo';
|
||||||
import { TemplateDocumentRejected } from '../template-components/template-document-rejected';
|
import { TemplateDocumentRejected } from '../template-components/template-document-rejected';
|
||||||
import { TemplateFooter } from '../template-components/template-footer';
|
import { TemplateFooter } from '../template-components/template-footer';
|
||||||
|
|
||||||
@@ -22,14 +22,9 @@ export function DocumentRejectedEmail({
|
|||||||
assetBaseUrl = 'http://localhost:3002',
|
assetBaseUrl = 'http://localhost:3002',
|
||||||
}: DocumentRejectedEmailProps) {
|
}: DocumentRejectedEmailProps) {
|
||||||
const { _ } = useLingui();
|
const { _ } = useLingui();
|
||||||
const branding = useBranding();
|
|
||||||
|
|
||||||
const previewText = _(msg`${recipientName} has rejected the document '${documentName}'`);
|
const previewText = _(msg`${recipientName} has rejected the document '${documentName}'`);
|
||||||
|
|
||||||
const getAssetUrl = (path: string) => {
|
|
||||||
return new URL(path, assetBaseUrl).toString();
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Html>
|
<Html>
|
||||||
<Head />
|
<Head />
|
||||||
@@ -39,11 +34,7 @@ export function DocumentRejectedEmail({
|
|||||||
<Section>
|
<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">
|
<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>
|
<Section>
|
||||||
{branding.brandingEnabled && branding.brandingLogo ? (
|
<TemplateBrandingLogo assetBaseUrl={assetBaseUrl} className="mb-4 h-6" />
|
||||||
<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" />
|
|
||||||
)}
|
|
||||||
|
|
||||||
<TemplateDocumentRejected
|
<TemplateDocumentRejected
|
||||||
recipientName={recipientName}
|
recipientName={recipientName}
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import { msg } from '@lingui/core/macro';
|
import { msg } from '@lingui/core/macro';
|
||||||
import { useLingui } from '@lingui/react';
|
import { useLingui } from '@lingui/react';
|
||||||
|
|
||||||
import { Body, Container, Head, Html, Img, Preview, Section } from '../components';
|
import { Body, Container, Head, Html, Preview, Section } from '../components';
|
||||||
import { useBranding } from '../providers/branding';
|
import { TemplateBrandingLogo } from '../template-components/template-branding-logo';
|
||||||
import { TemplateDocumentRejectionConfirmed } from '../template-components/template-document-rejection-confirmed';
|
import { TemplateDocumentRejectionConfirmed } from '../template-components/template-document-rejection-confirmed';
|
||||||
import { TemplateFooter } from '../template-components/template-footer';
|
import { TemplateFooter } from '../template-components/template-footer';
|
||||||
|
|
||||||
@@ -22,14 +22,9 @@ export function DocumentRejectionConfirmedEmail({
|
|||||||
assetBaseUrl = 'http://localhost:3002',
|
assetBaseUrl = 'http://localhost:3002',
|
||||||
}: DocumentRejectionConfirmedEmailProps) {
|
}: DocumentRejectionConfirmedEmailProps) {
|
||||||
const { _ } = useLingui();
|
const { _ } = useLingui();
|
||||||
const branding = useBranding();
|
|
||||||
|
|
||||||
const previewText = _(msg`You have rejected the document '${documentName}'`);
|
const previewText = _(msg`You have rejected the document '${documentName}'`);
|
||||||
|
|
||||||
const getAssetUrl = (path: string) => {
|
|
||||||
return new URL(path, assetBaseUrl).toString();
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Html>
|
<Html>
|
||||||
<Head />
|
<Head />
|
||||||
@@ -39,11 +34,7 @@ export function DocumentRejectionConfirmedEmail({
|
|||||||
<Section>
|
<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">
|
<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>
|
<Section>
|
||||||
{branding.brandingEnabled && branding.brandingLogo ? (
|
<TemplateBrandingLogo assetBaseUrl={assetBaseUrl} className="mb-4 h-6" />
|
||||||
<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" />
|
|
||||||
)}
|
|
||||||
|
|
||||||
<TemplateDocumentRejectionConfirmed
|
<TemplateDocumentRejectionConfirmed
|
||||||
recipientName={recipientName}
|
recipientName={recipientName}
|
||||||
|
|||||||
@@ -3,8 +3,8 @@ import { msg } from '@lingui/core/macro';
|
|||||||
import { useLingui } from '@lingui/react';
|
import { useLingui } from '@lingui/react';
|
||||||
import { RecipientRole } from '@prisma/client';
|
import { RecipientRole } from '@prisma/client';
|
||||||
|
|
||||||
import { Body, Container, Head, Hr, Html, Img, Preview, Section, Text } from '../components';
|
import { Body, Container, Head, Hr, Html, Preview, Section, Text } from '../components';
|
||||||
import { useBranding } from '../providers/branding';
|
import { TemplateBrandingLogo } from '../template-components/template-branding-logo';
|
||||||
import { TemplateCustomMessageBody } from '../template-components/template-custom-message-body';
|
import { TemplateCustomMessageBody } from '../template-components/template-custom-message-body';
|
||||||
import { TemplateDocumentReminder } from '../template-components/template-document-reminder';
|
import { TemplateDocumentReminder } from '../template-components/template-document-reminder';
|
||||||
import { TemplateFooter } from '../template-components/template-footer';
|
import { TemplateFooter } from '../template-components/template-footer';
|
||||||
@@ -29,16 +29,11 @@ export const DocumentReminderEmailTemplate = ({
|
|||||||
reportUrl,
|
reportUrl,
|
||||||
}: DocumentReminderEmailTemplateProps) => {
|
}: DocumentReminderEmailTemplateProps) => {
|
||||||
const { _ } = useLingui();
|
const { _ } = useLingui();
|
||||||
const branding = useBranding();
|
|
||||||
|
|
||||||
const action = _(RECIPIENT_ROLES_DESCRIPTION[role].actionVerb).toLowerCase();
|
const action = _(RECIPIENT_ROLES_DESCRIPTION[role].actionVerb).toLowerCase();
|
||||||
|
|
||||||
const previewText = msg`Reminder to ${action} ${documentName}`;
|
const previewText = msg`Reminder to ${action} ${documentName}`;
|
||||||
|
|
||||||
const getAssetUrl = (path: string) => {
|
|
||||||
return new URL(path, assetBaseUrl).toString();
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Html>
|
<Html>
|
||||||
<Head />
|
<Head />
|
||||||
@@ -48,11 +43,7 @@ export const DocumentReminderEmailTemplate = ({
|
|||||||
<Section>
|
<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">
|
<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>
|
<Section>
|
||||||
{branding.brandingEnabled && branding.brandingLogo ? (
|
<TemplateBrandingLogo assetBaseUrl={assetBaseUrl} className="mb-4 h-6" />
|
||||||
<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" />
|
|
||||||
)}
|
|
||||||
|
|
||||||
<TemplateDocumentReminder
|
<TemplateDocumentReminder
|
||||||
recipientName={recipientName}
|
recipientName={recipientName}
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import { msg } from '@lingui/core/macro';
|
import { msg } from '@lingui/core/macro';
|
||||||
import { useLingui } from '@lingui/react';
|
import { useLingui } from '@lingui/react';
|
||||||
|
|
||||||
import { Body, Container, Head, Html, Img, Preview, Section } from '../components';
|
import { Body, Container, Head, Html, Preview, Section } from '../components';
|
||||||
import { useBranding } from '../providers/branding';
|
import { TemplateBrandingLogo } from '../template-components/template-branding-logo';
|
||||||
import type { TemplateDocumentSelfSignedProps } from '../template-components/template-document-self-signed';
|
import type { TemplateDocumentSelfSignedProps } from '../template-components/template-document-self-signed';
|
||||||
import { TemplateDocumentSelfSigned } from '../template-components/template-document-self-signed';
|
import { TemplateDocumentSelfSigned } from '../template-components/template-document-self-signed';
|
||||||
import { TemplateFooter } from '../template-components/template-footer';
|
import { TemplateFooter } from '../template-components/template-footer';
|
||||||
@@ -14,14 +14,9 @@ export const DocumentSelfSignedEmailTemplate = ({
|
|||||||
assetBaseUrl = 'http://localhost:3002',
|
assetBaseUrl = 'http://localhost:3002',
|
||||||
}: DocumentSelfSignedTemplateProps) => {
|
}: DocumentSelfSignedTemplateProps) => {
|
||||||
const { _ } = useLingui();
|
const { _ } = useLingui();
|
||||||
const branding = useBranding();
|
|
||||||
|
|
||||||
const previewText = msg`Completed Document`;
|
const previewText = msg`Completed Document`;
|
||||||
|
|
||||||
const getAssetUrl = (path: string) => {
|
|
||||||
return new URL(path, assetBaseUrl).toString();
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Html>
|
<Html>
|
||||||
<Head />
|
<Head />
|
||||||
@@ -31,11 +26,7 @@ export const DocumentSelfSignedEmailTemplate = ({
|
|||||||
<Section className="bg-white">
|
<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">
|
<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">
|
<Section className="p-2">
|
||||||
{branding.brandingEnabled && branding.brandingLogo ? (
|
<TemplateBrandingLogo assetBaseUrl={assetBaseUrl} className="mb-4 h-6" />
|
||||||
<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} />
|
<TemplateDocumentSelfSigned documentName={documentName} assetBaseUrl={assetBaseUrl} />
|
||||||
</Section>
|
</Section>
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import { msg } from '@lingui/core/macro';
|
import { msg } from '@lingui/core/macro';
|
||||||
import { useLingui } from '@lingui/react';
|
import { useLingui } from '@lingui/react';
|
||||||
|
|
||||||
import { Body, Container, Head, Hr, Html, Img, Preview, Section } from '../components';
|
import { Body, Container, Head, Hr, Html, Preview, Section } from '../components';
|
||||||
import { useBranding } from '../providers/branding';
|
import { TemplateBrandingLogo } from '../template-components/template-branding-logo';
|
||||||
import {
|
import {
|
||||||
TemplateDocumentDelete,
|
TemplateDocumentDelete,
|
||||||
type TemplateDocumentDeleteProps,
|
type TemplateDocumentDeleteProps,
|
||||||
@@ -17,14 +17,9 @@ export const DocumentSuperDeleteEmailTemplate = ({
|
|||||||
reason = 'Unknown',
|
reason = 'Unknown',
|
||||||
}: DocumentDeleteEmailTemplateProps) => {
|
}: DocumentDeleteEmailTemplateProps) => {
|
||||||
const { _ } = useLingui();
|
const { _ } = useLingui();
|
||||||
const branding = useBranding();
|
|
||||||
|
|
||||||
const previewText = msg`An admin has deleted your document "${documentName}".`;
|
const previewText = msg`An admin has deleted your document "${documentName}".`;
|
||||||
|
|
||||||
const getAssetUrl = (path: string) => {
|
|
||||||
return new URL(path, assetBaseUrl).toString();
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Html>
|
<Html>
|
||||||
<Head />
|
<Head />
|
||||||
@@ -34,11 +29,7 @@ export const DocumentSuperDeleteEmailTemplate = ({
|
|||||||
<Section>
|
<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">
|
<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>
|
<Section>
|
||||||
{branding.brandingEnabled && branding.brandingLogo ? (
|
<TemplateBrandingLogo assetBaseUrl={assetBaseUrl} className="mb-4 h-6" />
|
||||||
<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} documentName={documentName} assetBaseUrl={assetBaseUrl} />
|
<TemplateDocumentDelete reason={reason} documentName={documentName} assetBaseUrl={assetBaseUrl} />
|
||||||
</Section>
|
</Section>
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import { msg } from '@lingui/core/macro';
|
import { msg } from '@lingui/core/macro';
|
||||||
import { useLingui } from '@lingui/react';
|
import { useLingui } from '@lingui/react';
|
||||||
|
|
||||||
import { Body, Container, Head, Html, Img, Preview, Section } from '../components';
|
import { Body, Container, Head, Html, Preview, Section } from '../components';
|
||||||
import { useBranding } from '../providers/branding';
|
import { TemplateBrandingLogo } from '../template-components/template-branding-logo';
|
||||||
import { TemplateFooter } from '../template-components/template-footer';
|
import { TemplateFooter } from '../template-components/template-footer';
|
||||||
import type { TemplateForgotPasswordProps } from '../template-components/template-forgot-password';
|
import type { TemplateForgotPasswordProps } from '../template-components/template-forgot-password';
|
||||||
import { TemplateForgotPassword } 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',
|
assetBaseUrl = 'http://localhost:3002',
|
||||||
}: ForgotPasswordTemplateProps) => {
|
}: ForgotPasswordTemplateProps) => {
|
||||||
const { _ } = useLingui();
|
const { _ } = useLingui();
|
||||||
const branding = useBranding();
|
|
||||||
|
|
||||||
const previewText = msg`Password Reset Requested`;
|
const previewText = msg`Password Reset Requested`;
|
||||||
|
|
||||||
const getAssetUrl = (path: string) => {
|
|
||||||
return new URL(path, assetBaseUrl).toString();
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Html>
|
<Html>
|
||||||
<Head />
|
<Head />
|
||||||
@@ -31,11 +26,7 @@ export const ForgotPasswordTemplate = ({
|
|||||||
<Section>
|
<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">
|
<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>
|
<Section>
|
||||||
{branding.brandingEnabled && branding.brandingLogo ? (
|
<TemplateBrandingLogo assetBaseUrl={assetBaseUrl} className="mb-4 h-6" />
|
||||||
<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} assetBaseUrl={assetBaseUrl} />
|
<TemplateForgotPassword resetPasswordLink={resetPasswordLink} assetBaseUrl={assetBaseUrl} />
|
||||||
</Section>
|
</Section>
|
||||||
|
|||||||
@@ -2,8 +2,8 @@ import { msg } from '@lingui/core/macro';
|
|||||||
import { useLingui } from '@lingui/react';
|
import { useLingui } from '@lingui/react';
|
||||||
import { Trans } from '@lingui/react/macro';
|
import { Trans } from '@lingui/react/macro';
|
||||||
|
|
||||||
import { Body, Button, Container, Head, Hr, Html, Img, Preview, Section, Text } from '../components';
|
import { Body, Button, Container, Head, Hr, Html, Preview, Section, Text } from '../components';
|
||||||
import { useBranding } from '../providers/branding';
|
import { TemplateBrandingLogo } from '../template-components/template-branding-logo';
|
||||||
import { TemplateFooter } from '../template-components/template-footer';
|
import { TemplateFooter } from '../template-components/template-footer';
|
||||||
import TemplateImage from '../template-components/template-image';
|
import TemplateImage from '../template-components/template-image';
|
||||||
|
|
||||||
@@ -21,7 +21,6 @@ export const OrganisationAccountLinkConfirmationTemplate = ({
|
|||||||
assetBaseUrl = 'http://localhost:3002',
|
assetBaseUrl = 'http://localhost:3002',
|
||||||
}: OrganisationAccountLinkConfirmationTemplateProps) => {
|
}: OrganisationAccountLinkConfirmationTemplateProps) => {
|
||||||
const { _ } = useLingui();
|
const { _ } = useLingui();
|
||||||
const branding = useBranding();
|
|
||||||
|
|
||||||
const previewText =
|
const previewText =
|
||||||
type === 'create'
|
type === 'create'
|
||||||
@@ -35,11 +34,7 @@ export const OrganisationAccountLinkConfirmationTemplate = ({
|
|||||||
<Body className="mx-auto my-auto font-sans">
|
<Body className="mx-auto my-auto font-sans">
|
||||||
<Section className="bg-white">
|
<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">
|
<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 ? (
|
<TemplateBrandingLogo assetBaseUrl={assetBaseUrl} className="mb-4 h-6 p-2" />
|
||||||
<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>
|
<Section>
|
||||||
<TemplateImage className="mx-auto h-12 w-12" assetBaseUrl={assetBaseUrl} staticAsset="building-2.png" />
|
<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 { msg } from '@lingui/core/macro';
|
||||||
import { useLingui } from '@lingui/react';
|
import { useLingui } from '@lingui/react';
|
||||||
|
|
||||||
import { Body, Container, Head, Hr, Html, Img, Preview, Section, Text } from '../components';
|
import { Body, Container, Head, Hr, Html, Preview, Section, Text } from '../components';
|
||||||
import { useBranding } from '../providers/branding';
|
import { TemplateBrandingLogo } from '../template-components/template-branding-logo';
|
||||||
import { TemplateFooter } from '../template-components/template-footer';
|
import { TemplateFooter } from '../template-components/template-footer';
|
||||||
import TemplateImage from '../template-components/template-image';
|
import TemplateImage from '../template-components/template-image';
|
||||||
|
|
||||||
@@ -22,7 +22,6 @@ export const OrganisationDeleteEmailTemplate = ({
|
|||||||
deletedByAdmin = false,
|
deletedByAdmin = false,
|
||||||
}: OrganisationDeleteEmailProps) => {
|
}: OrganisationDeleteEmailProps) => {
|
||||||
const { _ } = useLingui();
|
const { _ } = useLingui();
|
||||||
const branding = useBranding();
|
|
||||||
|
|
||||||
const previewText = msg`Your organisation has been deleted`;
|
const previewText = msg`Your organisation has been deleted`;
|
||||||
|
|
||||||
@@ -40,11 +39,7 @@ export const OrganisationDeleteEmailTemplate = ({
|
|||||||
<Body className="mx-auto my-auto font-sans">
|
<Body className="mx-auto my-auto font-sans">
|
||||||
<Section className="bg-white text-slate-500">
|
<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">
|
<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 ? (
|
<TemplateBrandingLogo assetBaseUrl={assetBaseUrl} className="mb-4 h-6 p-2" />
|
||||||
<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>
|
<Section>
|
||||||
<TemplateImage className="mx-auto" assetBaseUrl={assetBaseUrl} staticAsset="delete-team.png" />
|
<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 { useLingui } from '@lingui/react';
|
||||||
import { Trans } from '@lingui/react/macro';
|
import { Trans } from '@lingui/react/macro';
|
||||||
|
|
||||||
import { Body, Button, Container, Head, Hr, Html, Img, Preview, Section, Text } from '../components';
|
import { Body, Button, Container, Head, Hr, Html, Preview, Section, Text } from '../components';
|
||||||
import { useBranding } from '../providers/branding';
|
import { TemplateBrandingLogo } from '../template-components/template-branding-logo';
|
||||||
import { TemplateFooter } from '../template-components/template-footer';
|
import { TemplateFooter } from '../template-components/template-footer';
|
||||||
import TemplateImage from '../template-components/template-image';
|
import TemplateImage from '../template-components/template-image';
|
||||||
|
|
||||||
@@ -23,7 +23,6 @@ export const OrganisationInviteEmailTemplate = ({
|
|||||||
token = '',
|
token = '',
|
||||||
}: OrganisationInviteEmailProps) => {
|
}: OrganisationInviteEmailProps) => {
|
||||||
const { _ } = useLingui();
|
const { _ } = useLingui();
|
||||||
const branding = useBranding();
|
|
||||||
|
|
||||||
const previewText = msg`Accept invitation to join an organisation on Documenso`;
|
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">
|
<Body className="mx-auto my-auto font-sans">
|
||||||
<Section className="bg-white text-slate-500">
|
<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">
|
<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 ? (
|
<TemplateBrandingLogo assetBaseUrl={assetBaseUrl} className="mb-4 h-6 p-2" />
|
||||||
<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>
|
<Section>
|
||||||
<TemplateImage className="mx-auto" assetBaseUrl={assetBaseUrl} staticAsset="add-user.png" />
|
<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 { useLingui } from '@lingui/react';
|
||||||
import { Trans } from '@lingui/react/macro';
|
import { Trans } from '@lingui/react/macro';
|
||||||
|
|
||||||
import { Body, Container, Head, Hr, Html, Img, Preview, Section, Text } from '../components';
|
import { Body, Container, Head, Hr, Html, Preview, Section, Text } from '../components';
|
||||||
import { useBranding } from '../providers/branding';
|
import { TemplateBrandingLogo } from '../template-components/template-branding-logo';
|
||||||
import { TemplateFooter } from '../template-components/template-footer';
|
import { TemplateFooter } from '../template-components/template-footer';
|
||||||
import TemplateImage from '../template-components/template-image';
|
import TemplateImage from '../template-components/template-image';
|
||||||
|
|
||||||
@@ -25,7 +25,6 @@ export const OrganisationJoinEmailTemplate = ({
|
|||||||
organisationUrl = 'demo',
|
organisationUrl = 'demo',
|
||||||
}: OrganisationJoinEmailProps) => {
|
}: OrganisationJoinEmailProps) => {
|
||||||
const { _ } = useLingui();
|
const { _ } = useLingui();
|
||||||
const branding = useBranding();
|
|
||||||
|
|
||||||
const previewText = msg`A member has joined your organisation on Documenso`;
|
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">
|
<Body className="mx-auto my-auto font-sans">
|
||||||
<Section className="bg-white text-slate-500">
|
<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">
|
<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 ? (
|
<TemplateBrandingLogo assetBaseUrl={assetBaseUrl} className="mb-4 h-6 p-2" />
|
||||||
<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>
|
<Section>
|
||||||
<TemplateImage className="mx-auto" assetBaseUrl={assetBaseUrl} staticAsset="add-user.png" />
|
<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 { useLingui } from '@lingui/react';
|
||||||
import { Trans } from '@lingui/react/macro';
|
import { Trans } from '@lingui/react/macro';
|
||||||
|
|
||||||
import { Body, Container, Head, Hr, Html, Img, Preview, Section, Text } from '../components';
|
import { Body, Container, Head, Hr, Html, Preview, Section, Text } from '../components';
|
||||||
import { useBranding } from '../providers/branding';
|
import { TemplateBrandingLogo } from '../template-components/template-branding-logo';
|
||||||
import { TemplateFooter } from '../template-components/template-footer';
|
import { TemplateFooter } from '../template-components/template-footer';
|
||||||
import TemplateImage from '../template-components/template-image';
|
import TemplateImage from '../template-components/template-image';
|
||||||
|
|
||||||
@@ -25,7 +25,6 @@ export const OrganisationLeaveEmailTemplate = ({
|
|||||||
organisationUrl = 'demo',
|
organisationUrl = 'demo',
|
||||||
}: OrganisationLeaveEmailProps) => {
|
}: OrganisationLeaveEmailProps) => {
|
||||||
const { _ } = useLingui();
|
const { _ } = useLingui();
|
||||||
const branding = useBranding();
|
|
||||||
|
|
||||||
const previewText = msg`A member has left your organisation on Documenso`;
|
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">
|
<Body className="mx-auto my-auto font-sans">
|
||||||
<Section className="bg-white text-slate-500">
|
<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">
|
<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 ? (
|
<TemplateBrandingLogo assetBaseUrl={assetBaseUrl} className="mb-4 h-6 p-2" />
|
||||||
<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>
|
<Section>
|
||||||
<TemplateImage className="mx-auto" assetBaseUrl={assetBaseUrl} staticAsset="delete-user.png" />
|
<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 { useLingui } from '@lingui/react';
|
||||||
import { Trans } from '@lingui/react/macro';
|
import { Trans } from '@lingui/react/macro';
|
||||||
import { match } from 'ts-pattern';
|
import { match } from 'ts-pattern';
|
||||||
import { Body, Container, Head, Hr, Html, Img, Preview, Section, Text } from '../components';
|
import { Body, Container, Head, Hr, Html, Preview, Section, Text } from '../components';
|
||||||
import { useBranding } from '../providers/branding';
|
import { TemplateBrandingLogo } from '../template-components/template-branding-logo';
|
||||||
import { TemplateFooter } from '../template-components/template-footer';
|
import { TemplateFooter } from '../template-components/template-footer';
|
||||||
import TemplateImage from '../template-components/template-image';
|
|
||||||
|
|
||||||
export type OrganisationLimitExceededEmailProps = {
|
export type OrganisationLimitExceededEmailProps = {
|
||||||
assetBaseUrl: string;
|
assetBaseUrl: string;
|
||||||
@@ -24,7 +23,6 @@ export const OrganisationLimitExceededEmailTemplate = ({
|
|||||||
period = '2026-05',
|
period = '2026-05',
|
||||||
}: OrganisationLimitExceededEmailProps) => {
|
}: OrganisationLimitExceededEmailProps) => {
|
||||||
const { _ } = useLingui();
|
const { _ } = useLingui();
|
||||||
const branding = useBranding();
|
|
||||||
|
|
||||||
const previewText = msg`Organisation Review Required`;
|
const previewText = msg`Organisation Review Required`;
|
||||||
|
|
||||||
@@ -36,11 +34,7 @@ export const OrganisationLimitExceededEmailTemplate = ({
|
|||||||
<Body className="mx-auto my-auto font-sans">
|
<Body className="mx-auto my-auto font-sans">
|
||||||
<Section className="bg-white text-slate-500">
|
<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">
|
<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 ? (
|
<TemplateBrandingLogo assetBaseUrl={assetBaseUrl} className="mb-4 h-6 p-2" />
|
||||||
<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 className="p-2 text-slate-500">
|
<Section className="p-2 text-slate-500">
|
||||||
<Text className="text-center font-medium text-black text-lg">
|
<Text className="text-center font-medium text-black text-lg">
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import { msg } from '@lingui/core/macro';
|
import { msg } from '@lingui/core/macro';
|
||||||
import { useLingui } from '@lingui/react';
|
import { useLingui } from '@lingui/react';
|
||||||
|
|
||||||
import { Body, Container, Head, Hr, Html, Img, Preview, Section } from '../components';
|
import { Body, Container, Head, Hr, Html, Preview, Section } from '../components';
|
||||||
import { useBranding } from '../providers/branding';
|
import { TemplateBrandingLogo } from '../template-components/template-branding-logo';
|
||||||
import { TemplateFooter } from '../template-components/template-footer';
|
import { TemplateFooter } from '../template-components/template-footer';
|
||||||
import type { TemplateRecipientExpiredProps } from '../template-components/template-recipient-expired';
|
import type { TemplateRecipientExpiredProps } from '../template-components/template-recipient-expired';
|
||||||
import { TemplateRecipientExpired } 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',
|
assetBaseUrl = 'http://localhost:3002',
|
||||||
}: RecipientExpiredEmailTemplateProps) => {
|
}: RecipientExpiredEmailTemplateProps) => {
|
||||||
const { _ } = useLingui();
|
const { _ } = useLingui();
|
||||||
const branding = useBranding();
|
|
||||||
|
|
||||||
const previewText = msg`The signing window for "${recipientName}" on document "${documentName}" has expired.`;
|
const previewText = msg`The signing window for "${recipientName}" on document "${documentName}" has expired.`;
|
||||||
|
|
||||||
const getAssetUrl = (path: string) => {
|
|
||||||
return new URL(path, assetBaseUrl).toString();
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Html>
|
<Html>
|
||||||
<Head />
|
<Head />
|
||||||
@@ -34,11 +29,7 @@ export const RecipientExpiredTemplate = ({
|
|||||||
<Section>
|
<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">
|
<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>
|
<Section>
|
||||||
{branding.brandingEnabled && branding.brandingLogo ? (
|
<TemplateBrandingLogo assetBaseUrl={assetBaseUrl} className="mb-4 h-6" />
|
||||||
<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" />
|
|
||||||
)}
|
|
||||||
|
|
||||||
<TemplateRecipientExpired
|
<TemplateRecipientExpired
|
||||||
documentName={documentName}
|
documentName={documentName}
|
||||||
|
|||||||
@@ -2,8 +2,8 @@ import { msg } from '@lingui/core/macro';
|
|||||||
import { useLingui } from '@lingui/react';
|
import { useLingui } from '@lingui/react';
|
||||||
import { Trans } from '@lingui/react/macro';
|
import { Trans } from '@lingui/react/macro';
|
||||||
|
|
||||||
import { Body, Container, Head, Hr, Html, Img, Preview, Section, Text } from '../components';
|
import { Body, Container, Head, Hr, Html, Preview, Section, Text } from '../components';
|
||||||
import { useBranding } from '../providers/branding';
|
import { TemplateBrandingLogo } from '../template-components/template-branding-logo';
|
||||||
import type { TemplateDocumentCancelProps } from '../template-components/template-document-cancel';
|
import type { TemplateDocumentCancelProps } from '../template-components/template-document-cancel';
|
||||||
import TemplateDocumentImage from '../template-components/template-document-image';
|
import TemplateDocumentImage from '../template-components/template-document-image';
|
||||||
import { TemplateFooter } from '../template-components/template-footer';
|
import { TemplateFooter } from '../template-components/template-footer';
|
||||||
@@ -16,14 +16,9 @@ export const RecipientRemovedFromDocumentTemplate = ({
|
|||||||
assetBaseUrl = 'http://localhost:3002',
|
assetBaseUrl = 'http://localhost:3002',
|
||||||
}: DocumentCancelEmailTemplateProps) => {
|
}: DocumentCancelEmailTemplateProps) => {
|
||||||
const { _ } = useLingui();
|
const { _ } = useLingui();
|
||||||
const branding = useBranding();
|
|
||||||
|
|
||||||
const previewText = msg`${inviterName} has removed you from the document ${documentName}.`;
|
const previewText = msg`${inviterName} has removed you from the document ${documentName}.`;
|
||||||
|
|
||||||
const getAssetUrl = (path: string) => {
|
|
||||||
return new URL(path, assetBaseUrl).toString();
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Html>
|
<Html>
|
||||||
<Head />
|
<Head />
|
||||||
@@ -33,11 +28,7 @@ export const RecipientRemovedFromDocumentTemplate = ({
|
|||||||
<Section>
|
<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">
|
<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>
|
<Section>
|
||||||
{branding.brandingEnabled && branding.brandingLogo ? (
|
<TemplateBrandingLogo assetBaseUrl={assetBaseUrl} className="mb-4 h-6" />
|
||||||
<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} />
|
<TemplateDocumentImage className="mt-6" assetBaseUrl={assetBaseUrl} />
|
||||||
|
|
||||||
|
|||||||
@@ -2,8 +2,8 @@ import { msg } from '@lingui/core/macro';
|
|||||||
import { useLingui } from '@lingui/react';
|
import { useLingui } from '@lingui/react';
|
||||||
import { Trans } from '@lingui/react/macro';
|
import { Trans } from '@lingui/react/macro';
|
||||||
|
|
||||||
import { Body, Container, Head, Hr, Html, Img, Link, Preview, Section, Text } from '../components';
|
import { Body, Container, Head, Hr, Html, Link, Preview, Section, Text } from '../components';
|
||||||
import { useBranding } from '../providers/branding';
|
import { TemplateBrandingLogo } from '../template-components/template-branding-logo';
|
||||||
import { TemplateFooter } from '../template-components/template-footer';
|
import { TemplateFooter } from '../template-components/template-footer';
|
||||||
import type { TemplateResetPasswordProps } from '../template-components/template-reset-password';
|
import type { TemplateResetPasswordProps } from '../template-components/template-reset-password';
|
||||||
import { TemplateResetPassword } 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',
|
assetBaseUrl = 'http://localhost:3002',
|
||||||
}: ResetPasswordTemplateProps) => {
|
}: ResetPasswordTemplateProps) => {
|
||||||
const { _ } = useLingui();
|
const { _ } = useLingui();
|
||||||
const branding = useBranding();
|
|
||||||
|
|
||||||
const previewText = msg`Password Reset Successful`;
|
const previewText = msg`Password Reset Successful`;
|
||||||
|
|
||||||
const getAssetUrl = (path: string) => {
|
|
||||||
return new URL(path, assetBaseUrl).toString();
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Html>
|
<Html>
|
||||||
<Head />
|
<Head />
|
||||||
@@ -33,11 +28,7 @@ export const ResetPasswordTemplate = ({
|
|||||||
<Section>
|
<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">
|
<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>
|
<Section>
|
||||||
{branding.brandingEnabled && branding.brandingLogo ? (
|
<TemplateBrandingLogo assetBaseUrl={assetBaseUrl} className="mb-4 h-6" />
|
||||||
<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} userEmail={userEmail} assetBaseUrl={assetBaseUrl} />
|
<TemplateResetPassword userName={userName} userEmail={userEmail} assetBaseUrl={assetBaseUrl} />
|
||||||
</Section>
|
</Section>
|
||||||
|
|||||||
@@ -2,8 +2,8 @@ import { formatTeamUrl } from '@documenso/lib/utils/teams';
|
|||||||
import { msg } from '@lingui/core/macro';
|
import { msg } from '@lingui/core/macro';
|
||||||
import { useLingui } from '@lingui/react';
|
import { useLingui } from '@lingui/react';
|
||||||
|
|
||||||
import { Body, Container, Head, Hr, Html, Img, Preview, Section, Text } from '../components';
|
import { Body, Container, Head, Hr, Html, Preview, Section, Text } from '../components';
|
||||||
import { useBranding } from '../providers/branding';
|
import { TemplateBrandingLogo } from '../template-components/template-branding-logo';
|
||||||
import { TemplateFooter } from '../template-components/template-footer';
|
import { TemplateFooter } from '../template-components/template-footer';
|
||||||
import TemplateImage from '../template-components/template-image';
|
import TemplateImage from '../template-components/template-image';
|
||||||
|
|
||||||
@@ -19,7 +19,6 @@ export const TeamDeleteEmailTemplate = ({
|
|||||||
teamUrl = 'demo',
|
teamUrl = 'demo',
|
||||||
}: TeamDeleteEmailProps) => {
|
}: TeamDeleteEmailProps) => {
|
||||||
const { _ } = useLingui();
|
const { _ } = useLingui();
|
||||||
const branding = useBranding();
|
|
||||||
|
|
||||||
const previewText = msg`A team you were a part of has been deleted`;
|
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">
|
<Body className="mx-auto my-auto font-sans">
|
||||||
<Section className="bg-white text-slate-500">
|
<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">
|
<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 ? (
|
<TemplateBrandingLogo assetBaseUrl={assetBaseUrl} className="mb-4 h-6 p-2" />
|
||||||
<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>
|
<Section>
|
||||||
<TemplateImage className="mx-auto" assetBaseUrl={assetBaseUrl} staticAsset="delete-team.png" />
|
<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 { useLingui } from '@lingui/react';
|
||||||
import { Trans } from '@lingui/react/macro';
|
import { Trans } from '@lingui/react/macro';
|
||||||
|
|
||||||
import { Body, Container, Head, Hr, Html, Img, Preview, Section, Text } from '../components';
|
import { Body, Container, Head, Hr, Html, Preview, Section, Text } from '../components';
|
||||||
import { useBranding } from '../providers/branding';
|
import { TemplateBrandingLogo } from '../template-components/template-branding-logo';
|
||||||
import { TemplateFooter } from '../template-components/template-footer';
|
import { TemplateFooter } from '../template-components/template-footer';
|
||||||
import TemplateImage from '../template-components/template-image';
|
import TemplateImage from '../template-components/template-image';
|
||||||
|
|
||||||
@@ -24,7 +24,6 @@ export const TeamEmailRemovedTemplate = ({
|
|||||||
teamUrl = 'demo',
|
teamUrl = 'demo',
|
||||||
}: TeamEmailRemovedTemplateProps) => {
|
}: TeamEmailRemovedTemplateProps) => {
|
||||||
const { _ } = useLingui();
|
const { _ } = useLingui();
|
||||||
const branding = useBranding();
|
|
||||||
|
|
||||||
const previewText = msg`Team email removed for ${teamName} on Documenso`;
|
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">
|
<Body className="mx-auto my-auto font-sans">
|
||||||
<Section className="bg-white text-slate-500">
|
<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">
|
<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 ? (
|
<TemplateBrandingLogo assetBaseUrl={assetBaseUrl} className="mb-4 h-6 p-2" />
|
||||||
<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>
|
<Section>
|
||||||
<TemplateImage className="mx-auto" assetBaseUrl={assetBaseUrl} staticAsset="mail-open-alert.png" />
|
<TemplateImage className="mx-auto" assetBaseUrl={assetBaseUrl} staticAsset="mail-open-alert.png" />
|
||||||
|
|||||||
@@ -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;
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user