Save
diff --git a/apps/web/src/app/embed/base-schema.ts b/apps/web/src/app/embed/base-schema.ts
index 542e70724..003553301 100644
--- a/apps/web/src/app/embed/base-schema.ts
+++ b/apps/web/src/app/embed/base-schema.ts
@@ -1,8 +1,12 @@
import { z } from 'zod';
+import { ZCssVarsSchema } from './css-vars';
+
export const ZBaseEmbedDataSchema = z.object({
+ darkModeDisabled: z.boolean().optional().default(false),
css: z
.string()
.optional()
.transform((value) => value || undefined),
+ cssVars: ZCssVarsSchema.optional().default({}),
});
diff --git a/apps/web/src/app/embed/completed.tsx b/apps/web/src/app/embed/completed.tsx
index 50b74513a..1cfc07d3b 100644
--- a/apps/web/src/app/embed/completed.tsx
+++ b/apps/web/src/app/embed/completed.tsx
@@ -10,6 +10,7 @@ export type EmbedDocumentCompletedPageProps = {
};
export const EmbedDocumentCompleted = ({ name, signature }: EmbedDocumentCompletedPageProps) => {
+ console.log({ signature });
return (
diff --git a/apps/web/src/app/embed/css-vars.ts b/apps/web/src/app/embed/css-vars.ts
new file mode 100644
index 000000000..711a3e51f
--- /dev/null
+++ b/apps/web/src/app/embed/css-vars.ts
@@ -0,0 +1,59 @@
+import { colord } from 'colord';
+import { toSnakeCase } from 'remeda';
+import { z } from 'zod';
+
+export const ZCssVarsSchema = z
+ .object({
+ background: z.string().optional().describe('Base background color'),
+ foreground: z.string().optional().describe('Base text color'),
+ muted: z.string().optional().describe('Muted/subtle background color'),
+ mutedForeground: z.string().optional().describe('Muted/subtle text color'),
+ popover: z.string().optional().describe('Popover/dropdown background color'),
+ popoverForeground: z.string().optional().describe('Popover/dropdown text color'),
+ card: z.string().optional().describe('Card background color'),
+ cardBorder: z.string().optional().describe('Card border color'),
+ cardBorderTint: z.string().optional().describe('Card border tint/highlight color'),
+ cardForeground: z.string().optional().describe('Card text color'),
+ fieldCard: z.string().optional().describe('Field card background color'),
+ fieldCardBorder: z.string().optional().describe('Field card border color'),
+ fieldCardForeground: z.string().optional().describe('Field card text color'),
+ widget: z.string().optional().describe('Widget background color'),
+ widgetForeground: z.string().optional().describe('Widget text color'),
+ border: z.string().optional().describe('Default border color'),
+ input: z.string().optional().describe('Input field border color'),
+ primary: z.string().optional().describe('Primary action/button color'),
+ primaryForeground: z.string().optional().describe('Primary action/button text color'),
+ secondary: z.string().optional().describe('Secondary action/button color'),
+ secondaryForeground: z.string().optional().describe('Secondary action/button text color'),
+ accent: z.string().optional().describe('Accent/highlight color'),
+ accentForeground: z.string().optional().describe('Accent/highlight text color'),
+ destructive: z.string().optional().describe('Destructive/danger action color'),
+ destructiveForeground: z.string().optional().describe('Destructive/danger text color'),
+ ring: z.string().optional().describe('Focus ring color'),
+ radius: z.string().optional().describe('Border radius size in REM units'),
+ warning: z.string().optional().describe('Warning/alert color'),
+ })
+ .describe('Custom CSS variables for theming');
+
+export type TCssVarsSchema = z.infer;
+
+export const toNativeCssVars = (vars: TCssVarsSchema) => {
+ const cssVars: Record = {};
+
+ const { radius, ...colorVars } = vars;
+
+ for (const [key, value] of Object.entries(colorVars)) {
+ if (value) {
+ const color = colord(value);
+ const { h, s, l } = color.toHsl();
+
+ cssVars[`--${toSnakeCase(key)}`] = `${h} ${s} ${l}`;
+ }
+ }
+
+ if (radius) {
+ cssVars[`--radius`] = `${radius}`;
+ }
+
+ return cssVars;
+};
diff --git a/apps/web/src/app/embed/direct/[[...url]]/client.tsx b/apps/web/src/app/embed/direct/[[...url]]/client.tsx
index 0f71c0e89..8e8545310 100644
--- a/apps/web/src/app/embed/direct/[[...url]]/client.tsx
+++ b/apps/web/src/app/embed/direct/[[...url]]/client.tsx
@@ -1,6 +1,6 @@
'use client';
-import { useEffect, useState } from 'react';
+import { useEffect, useLayoutEffect, useState } from 'react';
import { useSearchParams } from 'next/navigation';
@@ -14,7 +14,7 @@ import { DEFAULT_DOCUMENT_DATE_FORMAT } from '@documenso/lib/constants/date-form
import { PDF_VIEWER_PAGE_SELECTOR } from '@documenso/lib/constants/pdf-viewer';
import { DEFAULT_DOCUMENT_TIME_ZONE } from '@documenso/lib/constants/time-zones';
import { validateFieldsInserted } from '@documenso/lib/utils/fields';
-import type { DocumentMeta, Recipient, TemplateMeta } from '@documenso/prisma/client';
+import type { DocumentMeta, Recipient, Signature, TemplateMeta } from '@documenso/prisma/client';
import { type DocumentData, type Field, FieldType } from '@documenso/prisma/client';
import { trpc } from '@documenso/trpc/react';
import type {
@@ -38,6 +38,7 @@ import { Logo } from '~/components/branding/logo';
import { EmbedClientLoading } from '../../client-loading';
import { EmbedDocumentCompleted } from '../../completed';
import { EmbedDocumentFields } from '../../document-fields';
+import { injectCss } from '../../util';
import { ZDirectTemplateEmbedDataSchema } from './schema';
export type EmbedDirectTemplateClientPageProps = {
@@ -47,6 +48,8 @@ export type EmbedDirectTemplateClientPageProps = {
recipient: Recipient;
fields: Field[];
metadata?: DocumentMeta | TemplateMeta | null;
+ hidePoweredBy?: boolean;
+ isPlatformOrEnterprise?: boolean;
};
export const EmbedDirectTemplateClientPage = ({
@@ -56,6 +59,8 @@ export const EmbedDirectTemplateClientPage = ({
recipient,
fields,
metadata,
+ hidePoweredBy = false,
+ isPlatformOrEnterprise = false,
}: EmbedDirectTemplateClientPageProps) => {
const { _ } = useLingui();
const { toast } = useToast();
@@ -108,9 +113,9 @@ export const EmbedDirectTemplateClientPage = ({
created: new Date(),
recipientId: 1,
fieldId: 1,
- signatureImageAsBase64: payload.value,
- typedSignature: null,
- };
+ signatureImageAsBase64: payload.value.startsWith('data:') ? payload.value : null,
+ typedSignature: payload.value.startsWith('data:') ? null : payload.value,
+ } satisfies Signature;
}
if (field.type === FieldType.DATE) {
@@ -249,7 +254,7 @@ export const EmbedDirectTemplateClientPage = ({
}
};
- useEffect(() => {
+ useLayoutEffect(() => {
const hash = window.location.hash.slice(1);
try {
@@ -264,6 +269,17 @@ export const EmbedDirectTemplateClientPage = ({
setFullName(data.name);
setIsNameLocked(!!data.lockName);
}
+
+ if (data.darkModeDisabled) {
+ document.documentElement.classList.add('dark-mode-disabled');
+ }
+
+ if (isPlatformOrEnterprise) {
+ injectCss({
+ css: data.css,
+ cssVars: data.cssVars,
+ });
+ }
} catch (err) {
console.error(err);
}
@@ -296,8 +312,8 @@ export const EmbedDirectTemplateClientPage = ({
fieldId: 1,
recipientId: 1,
created: new Date(),
- typedSignature: null,
- signatureImageAsBase64: signature,
+ signatureImageAsBase64: signature?.startsWith('data:') ? signature : null,
+ typedSignature: signature?.startsWith('data:') ? null : signature,
}}
/>
);
@@ -452,10 +468,12 @@ export const EmbedDirectTemplateClientPage = ({
/>
-
- Powered by
-
-
+ {!hidePoweredBy && (
+
+ Powered by
+
+
+ )}