Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
638e92d
feat: add team document analytics dashboard (#3355)
ephraimduncan Sep 23, 2026
39ae854
fix: add date-fns to packages/ui (#3388)
Mythie Sep 24, 2026
a1d4bec
fix: accept owner-password protected pdfs (#3396)
Mythie Sep 26, 2026
586b1f5
v2.19.0
Mythie Sep 29, 2026
5a123be
fix: embed signing completion and reload states (#3409)
Mythie Sep 29, 2026
be94bdd
fix: use legacy pdfjs build for older devices (#3410)
Mythie Sep 29, 2026
573c928
feat: add recipient grouping (#3319)
dguyen Sep 29, 2026
b75a66d
fix(i18n): add missing "zu" in German invite/reminder strings (#3331)
rzaman8677 Sep 29, 2026
22c697f
fix: show validation errors in branding preferences form (#3381)
catalinpit Sep 30, 2026
b8b98f7
fix(ui): discard cancelled signature pad dialog drafts (#3408)
catalinpit Sep 30, 2026
f4105e4
fix: prevent recipients who already signed from rejecting documents (…
catalinpit Sep 30, 2026
15e86bb
docs: add data-readonly and correct field color example (#3413)
ephraimduncan Oct 1, 2026
af1e597
fix(i18n): add support for plural in app-command-menu.tsx (#3364)
mKoonrad Oct 1, 2026
0f29ba6
feat(admin): show team count in organisation stats (#3405)
ephraimduncan Oct 1, 2026
d448e3f
fix: send notification on limits change (#3414)
Mythie Oct 1, 2026
7d947de
fix: use the database url when the direct url is not set (#3404)
ephraimduncan Oct 1, 2026
8a41a3b
feat(web): add configurable legal, data protection, and imprint links…
AyushDubey23 Oct 1, 2026
75f7b98
Merge remote-tracking branch 'upstream/main' into probe/sync
JOY Oct 1, 2026
efafea7
chore(sync): format app-command-menu to satisfy biome
JOY Oct 1, 2026
ce4d1ab
fix(sync): restore the oidcProviderLabel form prop dropped by the merge
JOY Oct 1, 2026
f3ee711
docs(env): document the new legal link envs from upstream #3131
JOY Oct 1, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,11 @@ NEXT_PRIVATE_SMTP_FROM_NAME="Crove Sign"
NEXT_PRIVATE_SMTP_FROM_ADDRESS="noreply@sign.crove.com"
# OPTIONAL: Support contact shown in transactional emails (password reset "contact us").
NEXT_PUBLIC_SUPPORT_EMAIL="help@dos.ai"
# OPTIONAL: Legal links shown on signup and recipient signing pages (self-host default hides them when unset).
NEXT_PUBLIC_TERMS_OF_SERVICE_URL="https://sign.crove.com/terms"
NEXT_PUBLIC_PRIVACY_POLICY_URL="https://sign.crove.com/privacy"
# OPTIONAL: Imprint link (shown alongside the legal links when set).
NEXT_PUBLIC_IMPRINT_URL=
# OPTIONAL: Defines the service for nodemailer
NEXT_PRIVATE_SMTP_SERVICE=
# OPTIONAL: The API key to use for Resend.com
Expand Down
36 changes: 21 additions & 15 deletions apps/docs/content/docs/developers/embedding/css-variables.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -120,20 +120,25 @@ Specific parts of the embed can be targeted with CSS classes for granular stylin

### Component Classes

| Class | Description |
| --------------------------------- | --------------------------------------------- |
| `.embed--Root` | Main container for the embedded experience |
| `.embed--DocumentContainer` | Container for the document and signing widget |
| `.embed--DocumentViewer` | Container for the document viewer |
| `.embed--DocumentWidget` | The signing widget container |
| `.embed--DocumentWidgetContainer` | Outer container for the signing widget |
| `.embed--DocumentWidgetHeader` | Header section of the signing widget |
| `.embed--DocumentWidgetContent` | Main content area of the signing widget |
| `.embed--DocumentWidgetForm` | Form section within the signing widget |
| `.embed--DocumentWidgetFooter` | Footer section of the signing widget |
| `.embed--WaitingForTurn` | Waiting screen when it is not the user's turn |
| `.embed--DocumentCompleted` | Completion screen after signing |
| `.field--FieldRootContainer` | Base container for document fields |
| Class | Description |
| ---------------------------------------- | --------------------------------------------- |
| `.embed--Root` | Main container for the embedded experience |
| `.embed--DocumentContainer` | Container for the document and signing widget |
| `.embed--DocumentViewer` | Container for the document viewer |
| `.embed--DocumentWidget` | The signing widget container |
| `.embed--DocumentWidgetContainer` | Outer container for the signing widget |
| `.embed--DocumentWidgetHeader` | Header section of the signing widget |
| `.embed--DocumentWidgetContent` | Main content area of the signing widget |
| `.embed--DocumentWidgetForm` | Form section within the signing widget |
| `.embed--DocumentWidgetFooter` | Footer section of the signing widget |
| `.embed--WaitingForTurn` | Waiting screen when it is not the user's turn |
| `.embed--DocumentCompleted` | Completion screen after signing |
| `.embed--DocumentCompletedCard` | Signature card on the completion screen |
| `.embed--DocumentCompletedTitle` | Title on the completion screen |
| `.embed--DocumentCompletedStatus` | Status line on the completion screen |
| `.embed--DocumentCompletedDescription` | Description text on the completion screen |
| `.embed--DocumentRejected` | Rejection screen after rejecting the document |
| `.field--FieldRootContainer` | Base container for document fields |

### Field Data Attributes

Expand All @@ -144,6 +149,7 @@ Fields expose data attributes for state-based styling:
| `[data-field-type]` | `SIGNATURE`, `TEXT`, `CHECKBOX`, `RADIO`, etc. | The type of field |
| `[data-inserted]` | `true`, `false` | Whether the field has been filled |
| `[data-validate]` | `true`, `false` | Whether the field is being validated |
| `[data-readonly]` | `true`, `false` | Whether the field is read-only |

### Example

Expand All @@ -155,7 +161,7 @@ Fields expose data attributes for state-based styling:

/* Style filled fields */
.field--FieldRootContainer[data-inserted='true'] {
background-color: var(--primary);
background-color: hsl(var(--primary));
opacity: 0.2;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -191,9 +191,6 @@ For manual deployments or troubleshooting:
```bash
# Apply pending migrations
npm run prisma:migrate-deploy

# Or using npx directly
npx prisma migrate deploy
```

<Callout type="info">
Expand Down
3 changes: 2 additions & 1 deletion apps/docs/content/docs/self-hosting/maintenance/upgrades.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -426,8 +426,9 @@ docker pull documenso/documenso:<version>
# Run migrations only
docker run --rm \
-e NEXT_PRIVATE_DATABASE_URL="postgresql://user:password@host:5432/documenso" \
-e NEXT_PRIVATE_DIRECT_DATABASE_URL="postgresql://user:password@host:5432/documenso" \
documenso/documenso:<version> \
npx prisma migrate deploy
npx prisma migrate deploy --schema ../../packages/prisma/schema.prisma
```

<Callout type="warn">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ export const EnvelopeItemEditDialog = ({

toast({
title: t`Failed to read file`,
description: t`The file is not a valid PDF.`,
description: t`The file is not a valid PDF or is password protected.`,
variant: 'destructive',
});
}
Expand Down
26 changes: 16 additions & 10 deletions apps/remix/app/components/embed/embed-document-completed.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import signingCelebration from '@documenso/assets/images/signing-celebration.png
import { SigningCard3D } from '@documenso/ui/components/signing-card';
import { Trans } from '@lingui/react/macro';
import type { Signature } from '@prisma/client';
import { CheckCircle2Icon } from 'lucide-react';

export type EmbedDocumentCompletedPageProps = {
name?: string;
Expand All @@ -10,12 +11,8 @@ export type EmbedDocumentCompletedPageProps = {

export const EmbedDocumentCompleted = ({ name, signature }: EmbedDocumentCompletedPageProps) => {
return (
<div className="embed--DocumentCompleted relative mx-auto flex min-h-[100dvh] max-w-screen-lg flex-col items-center justify-center p-6">
<h3 className="font-semibold text-2xl text-foreground">
<Trans>Document Completed!</Trans>
</h3>

<div className="mt-8 w-full max-w-md">
<div className="embed--DocumentCompleted relative mx-auto flex min-h-[100dvh] max-w-screen-lg flex-col items-center justify-center overflow-hidden p-6">
<div className="embed--DocumentCompletedCard w-full max-w-sm md:max-w-md">
<SigningCard3D
className="mx-auto w-full"
name={name || 'Crove Sign'}
Expand All @@ -24,10 +21,19 @@ export const EmbedDocumentCompleted = ({ name, signature }: EmbedDocumentComplet
/>
</div>

<p className="mt-8 max-w-[50ch] text-center text-muted-foreground text-sm">
<Trans>
The document is now completed, please follow any instructions provided within the parent application.
</Trans>
<h2 className="embed--DocumentCompletedTitle mt-8 max-w-[35ch] text-center font-semibold text-2xl text-foreground leading-normal md:text-3xl">
<Trans>Document Completed</Trans>
</h2>

<div className="embed--DocumentCompletedStatus mt-4 flex items-center text-center text-documenso-700">
<CheckCircle2Icon className="mr-2 h-5 w-5" />
<span className="text-sm">
<Trans>No further action is required</Trans>
</span>
</div>

<p className="embed--DocumentCompletedDescription mt-2.5 max-w-[50ch] text-center font-medium text-muted-foreground/60 text-sm md:text-base">
<Trans>Please follow any instructions provided within the parent application.</Trans>
</p>
</div>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ export type EmbedSignDocumentV1ClientPageProps = {
completedFields: DocumentField[];
metadata?: DocumentMeta | null;
isCompleted?: boolean;
isRejected?: boolean;
hidePoweredBy?: boolean;
allowWhitelabelling?: boolean;
allRecipients?: RecipientWithFields[];
Expand All @@ -70,6 +71,7 @@ export const EmbedSignDocumentV1ClientPage = ({
completedFields,
metadata,
isCompleted,
isRejected,
hidePoweredBy = false,
allowWhitelabelling = false,
allRecipients = [],
Expand All @@ -83,7 +85,9 @@ export const EmbedSignDocumentV1ClientPage = ({
const [hasFinishedInit, setHasFinishedInit] = useState(false);
const [hasDocumentLoaded, setHasDocumentLoaded] = useState(false);
const [hasCompletedDocument, setHasCompletedDocument] = useState(isCompleted);
const [hasRejectedDocument, setHasRejectedDocument] = useState(recipient.signingStatus === SigningStatus.REJECTED);
const [hasRejectedDocument, setHasRejectedDocument] = useState(
isRejected ?? recipient.signingStatus === SigningStatus.REJECTED,
);
const [selectedSignerId, setSelectedSignerId] = useState<number | null>(
allRecipients.length > 0 ? allRecipients[0].id : null,
);
Expand Down Expand Up @@ -263,6 +267,44 @@ export const EmbedSignDocumentV1ClientPage = ({
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);

useEffect(() => {
if (!window.parent) {
return;
}

if (hasRejectedDocument) {
window.parent.postMessage(
{
action: 'document-rejected',
data: {
token,
documentId,
recipientId: recipient.id,
},
},
'*',
);

return;
}

if (hasCompletedDocument) {
window.parent.postMessage(
{
action: 'document-completed',
data: {
token,
documentId,
recipientId: recipient.id,
},
},
'*',
);
}

// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
Comment on lines +270 to +306

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

security-high high

Security & Code Quality Issues\n\n1. Sensitive Data Exposure (High Severity): Sending the sensitive signing token via postMessage with a wildcard target origin ('*') allows any parent window (including malicious or hijacked frames) to intercept the token. Consider restricting the target origin to a trusted origin (e.g., a configured parent origin) instead of using * when transmitting sensitive credentials.\n\n2. React Hook Anti-Pattern (Medium Severity): This useEffect uses an empty dependency array [] but references state variables hasRejectedDocument and hasCompletedDocument. This creates a stale closure and requires disabling the react-hooks/exhaustive-deps lint rule. Since this effect is intended to notify the parent window on mount if the document is already completed or rejected, you should use the initial props isRejected and isCompleted directly and include them in the dependency array.

  useEffect(() => {\n    if (!window.parent) {\n      return;\n    }\n\n    if (isRejected) {\n      window.parent.postMessage(\n        {\n          action: 'document-rejected',\n          data: {\n            token,\n            documentId,\n            recipientId: recipient.id,\n          },\n        },\n        '*',\n      );\n\n      return;\n    }\n\n    if (isCompleted) {\n      window.parent.postMessage(\n        {\n          action: 'document-completed',\n          data: {\n            token,\n            documentId,\n            recipientId: recipient.id,\n          },\n        },\n        '*',\n      );\n    }\n  }, [isRejected, isCompleted, token, documentId, recipient.id]);


useEffect(() => {
if (hasFinishedInit && hasDocumentLoaded && window.parent) {
window.parent.postMessage(
Expand Down
21 changes: 16 additions & 5 deletions apps/remix/app/components/embed/embed-document-signing-page-v2.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,12 +40,18 @@ export const EmbedSignDocumentV2ClientPage = ({
const [isNameLocked, setIsNameLocked] = useState(false);
const [isEmailLocked, setIsEmailLocked] = useState(envelope.type === EnvelopeType.DOCUMENT && !!email);

// The signing provider's envelope data isn't refreshed on revalidation.
const [hasCompletedDocument, setHasCompletedDocument] = useState(isCompleted);
const [hasRejectedDocument, setHasRejectedDocument] = useState(isRejected);

const onDocumentCompleted = (data: {
token: string;
documentId: number;
envelopeId: string;
recipientId: number;
}) => {
setHasCompletedDocument(true);

if (window.parent) {
window.parent.postMessage(
{
Expand Down Expand Up @@ -112,6 +118,8 @@ export const EmbedSignDocumentV2ClientPage = ({
recipientId: number;
reason?: string;
}) => {
setHasRejectedDocument(true);

if (window.parent) {
window.parent.postMessage(
{
Expand Down Expand Up @@ -219,23 +227,26 @@ export const EmbedSignDocumentV2ClientPage = ({
}
}, [isRejected, envelope.id, recipient.id, recipient.token]);

if (isRejected) {
if (hasRejectedDocument) {
return <EmbedDocumentRejected />;
}

if (isCompleted) {
if (hasCompletedDocument) {
const completedSignature =
recipient.fields.find((field) => field.signature)?.signature ?? recipientSignature ?? null;

return (
<EmbedDocumentCompleted
name={fullName}
signature={
recipientSignature
completedSignature
? {
id: 1,
fieldId: 1,
recipientId: recipient.id,
created: new Date(),
signatureImageAsBase64: recipientSignature.signatureImageAsBase64,
typedSignature: recipientSignature.typedSignature,
signatureImageAsBase64: completedSignature.signatureImageAsBase64,
typedSignature: completedSignature.typedSignature,
}
: undefined
}
Expand Down
34 changes: 28 additions & 6 deletions apps/remix/app/components/forms/branding-preferences-form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,20 @@ import { cn } from '@documenso/ui/lib/utils';
import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from '@documenso/ui/primitives/accordion';
import { Button } from '@documenso/ui/primitives/button';
import { ColorPicker } from '@documenso/ui/primitives/color-picker';
import { Form, FormControl, FormDescription, FormField, FormItem, FormLabel } from '@documenso/ui/primitives/form/form';
import {
Form,
FormControl,
FormDescription,
FormField,
FormItem,
FormLabel,
FormMessage,
} from '@documenso/ui/primitives/form/form';
import { Input } from '@documenso/ui/primitives/input';
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@documenso/ui/primitives/select';
import { Textarea } from '@documenso/ui/primitives/textarea';
import { zodResolver } from '@hookform/resolvers/zod';
import { msg } from '@lingui/core/macro';
import { Trans, useLingui } from '@lingui/react/macro';
import type { TeamGlobalSettings } from '@prisma/client';
import { Loader } from 'lucide-react';
Expand All @@ -37,14 +46,17 @@ const ZBrandingPreferencesFormSchema = z.object({
.instanceof(File)
.refine(
(file) => file.size <= BRANDING_LOGO_MAX_SIZE_BYTES,
`File size must be less than ${BRANDING_LOGO_MAX_SIZE_MB}MB`,
msg`File size must be less than ${BRANDING_LOGO_MAX_SIZE_MB}MB`,
)
.refine(
(file) => BRANDING_LOGO_ALLOWED_TYPES.includes(file.type),
msg`Only .jpg, .png, and .webp files are accepted`,
)
.refine((file) => BRANDING_LOGO_ALLOWED_TYPES.includes(file.type), 'Only .jpg, .png, and .webp files are accepted')
.nullish(),
brandingUrl: z.string().url().optional().or(z.literal('')),
brandingCompanyDetails: z.string().max(500).optional(),
brandingUrl: z.string().url(msg`Please enter a valid URL`).optional().or(z.literal('')),
brandingCompanyDetails: z.string().max(500, msg`Brand details must be less than 500 characters`).optional(),
brandingColors: ZCssVarsSchema.default({}),
brandingCss: z.string().max(10_000).default(''),
brandingCss: z.string().max(10_000, msg`Custom CSS must be less than 10,000 characters`).default(''),
});

export type TBrandingPreferencesFormSchema = z.infer<typeof ZBrandingPreferencesFormSchema>;
Expand Down Expand Up @@ -349,6 +361,8 @@ export function BrandingPreferencesForm({
</span>
)}
</FormDescription>

<FormMessage />
</div>
</InheritableField>
)}
Expand Down Expand Up @@ -379,6 +393,8 @@ export function BrandingPreferencesForm({
</span>
)}
</FormDescription>

<FormMessage />
</InheritableField>
)}
/>
Expand Down Expand Up @@ -413,6 +429,8 @@ export function BrandingPreferencesForm({
</span>
)}
</FormDescription>

<FormMessage />
</InheritableField>
)}
/>
Expand Down Expand Up @@ -597,6 +615,8 @@ export function BrandingPreferencesForm({
<FormDescription>
<Trans>Border radius size in REM units (e.g. 0.5rem).</Trans>
</FormDescription>

<FormMessage />
</FormItem>
)}
/>
Expand Down Expand Up @@ -634,6 +654,8 @@ export function BrandingPreferencesForm({
shown after you save.
</Trans>
</FormDescription>

<FormMessage />
</FormItem>
)}
/>
Expand Down
Loading
Loading