Compare commits

..
Author SHA1 Message Date
ephraimduncan 02166af7eb fix: remove the direct url fallback from the migrate script
The fallback uses POSIX shell syntax, but npm on Windows runs scripts in cmd.exe. docker/start.sh keeps the fallback for Docker, and the manual guide sets the two variables.
2026-09-28 10:39:50 +00:00
ephraimduncan a3fbd791de docs: remove the npx migration command that cannot find the schema
From the repository root, `npx prisma migrate deploy` stops with "Could
not find Prisma Schema". It also does not use the fallback for
`NEXT_PRIVATE_DIRECT_DATABASE_URL`. The `npm run prisma:migrate-deploy`
command above it does the same work and uses the fallback.
2026-09-28 10:23:30 +00:00
ephraimduncan 5874b8f8c4 fix: set the direct url for manual migrations
`npm run prisma:migrate-deploy` stopped with error P1012 when
`NEXT_PRIVATE_DIRECT_DATABASE_URL` was not set. If the direct URL is
empty, the script uses `NEXT_PRIVATE_DATABASE_URL`, the same as the
Docker start script.

The manual migration command in the upgrade guide does not use the start
script, and Prisma cannot find the schema at the default path in the
image. The command sets the two variables and gives the path to the
schema.
2026-09-28 10:17:35 +00:00
ephraimduncan c66270ad9d fix: use the database url when the direct url is not set
The Docker start script runs Prisma migrations. The Prisma schema reads
`NEXT_PRIVATE_DIRECT_DATABASE_URL`, and the migrations stopped with error
P1012 when the variable was not set.

If the variable is empty, the start script sets it to
`NEXT_PRIVATE_DATABASE_URL`. This agrees with the documentation and with
docker/production/compose.yml.
2026-09-28 09:38:46 +00:00
12 changed files with 46 additions and 118 deletions
@@ -120,25 +120,20 @@ 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 |
| `.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 |
| 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 |
### Field Data Attributes
@@ -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">
@@ -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">
@@ -2,7 +2,6 @@ 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;
@@ -11,8 +10,12 @@ 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 overflow-hidden p-6">
<div className="embed--DocumentCompletedCard w-full max-w-sm md:max-w-md">
<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">
<SigningCard3D
className="mx-auto w-full"
name={name || 'Documenso'}
@@ -21,19 +24,10 @@ export const EmbedDocumentCompleted = ({ name, signature }: EmbedDocumentComplet
/>
</div>
<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 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>
</p>
</div>
);
@@ -55,7 +55,6 @@ export type EmbedSignDocumentV1ClientPageProps = {
completedFields: DocumentField[];
metadata?: DocumentMeta | null;
isCompleted?: boolean;
isRejected?: boolean;
hidePoweredBy?: boolean;
allowWhitelabelling?: boolean;
allRecipients?: RecipientWithFields[];
@@ -71,7 +70,6 @@ export const EmbedSignDocumentV1ClientPage = ({
completedFields,
metadata,
isCompleted,
isRejected,
hidePoweredBy = false,
allowWhitelabelling = false,
allRecipients = [],
@@ -85,9 +83,7 @@ export const EmbedSignDocumentV1ClientPage = ({
const [hasFinishedInit, setHasFinishedInit] = useState(false);
const [hasDocumentLoaded, setHasDocumentLoaded] = useState(false);
const [hasCompletedDocument, setHasCompletedDocument] = useState(isCompleted);
const [hasRejectedDocument, setHasRejectedDocument] = useState(
isRejected ?? recipient.signingStatus === SigningStatus.REJECTED,
);
const [hasRejectedDocument, setHasRejectedDocument] = useState(recipient.signingStatus === SigningStatus.REJECTED);
const [selectedSignerId, setSelectedSignerId] = useState<number | null>(
allRecipients.length > 0 ? allRecipients[0].id : null,
);
@@ -267,44 +263,6 @@ 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
}, []);
useEffect(() => {
if (hasFinishedInit && hasDocumentLoaded && window.parent) {
window.parent.postMessage(
@@ -40,18 +40,12 @@ 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(
{
@@ -118,8 +112,6 @@ export const EmbedSignDocumentV2ClientPage = ({
recipientId: number;
reason?: string;
}) => {
setHasRejectedDocument(true);
if (window.parent) {
window.parent.postMessage(
{
@@ -227,26 +219,23 @@ export const EmbedSignDocumentV2ClientPage = ({
}
}, [isRejected, envelope.id, recipient.id, recipient.token]);
if (hasRejectedDocument) {
if (isRejected) {
return <EmbedDocumentRejected />;
}
if (hasCompletedDocument) {
const completedSignature =
recipient.fields.find((field) => field.signature)?.signature ?? recipientSignature ?? null;
if (isCompleted) {
return (
<EmbedDocumentCompleted
name={fullName}
signature={
completedSignature
recipientSignature
? {
id: 1,
fieldId: 1,
recipientId: recipient.id,
created: new Date(),
signatureImageAsBase64: completedSignature.signatureImageAsBase64,
typedSignature: completedSignature.typedSignature,
signatureImageAsBase64: recipientSignature.signatureImageAsBase64,
typedSignature: recipientSignature.typedSignature,
}
: undefined
}
@@ -5,8 +5,8 @@ import { cn } from '@documenso/ui/lib/utils';
import { useToast } from '@documenso/ui/primitives/use-toast';
import { Trans, useLingui } from '@lingui/react/macro';
import pMap from 'p-map';
import * as pdfjsLib from 'pdfjs-dist/legacy/build/pdf.mjs';
import pdfjsWorker from 'pdfjs-dist/legacy/build/pdf.worker.mjs?url';
import * as pdfjsLib from 'pdfjs-dist';
import pdfjsWorker from 'pdfjs-dist/build/pdf.worker?url';
import type React from 'react';
import { useEffect, useMemo, useRef, useState } from 'react';
@@ -19,7 +19,7 @@ import { isDocumentCompleted } from '@documenso/lib/utils/document';
import { extractDocumentAuthMethods } from '@documenso/lib/utils/document-auth';
import { isRecipientExpired } from '@documenso/lib/utils/recipients';
import { prisma } from '@documenso/prisma';
import { RecipientRole, SigningStatus } from '@prisma/client';
import { RecipientRole } from '@prisma/client';
import { data } from 'react-router';
import { match } from 'ts-pattern';
@@ -80,11 +80,7 @@ async function handleV1Loader({ params, request }: Route.LoaderArgs) {
);
}
const isCompleted = recipient.signingStatus === SigningStatus.SIGNED || isDocumentCompleted(document.status);
const isRejected = recipient.signingStatus === SigningStatus.REJECTED;
const hasRecipientActioned = isCompleted || isRejected;
if (!hasRecipientActioned && isRecipientExpired(recipient)) {
if (isRecipientExpired(recipient)) {
throw data(
{
type: 'embed-recipient-expired',
@@ -119,7 +115,7 @@ async function handleV1Loader({ params, request }: Route.LoaderArgs) {
);
}
const isRecipientsTurnToSign = hasRecipientActioned || (await getIsRecipientsTurnToSign({ token }));
const isRecipientsTurnToSign = await getIsRecipientsTurnToSign({ token });
if (!isRecipientsTurnToSign) {
throw data(
@@ -177,8 +173,6 @@ async function handleV1Loader({ params, request }: Route.LoaderArgs) {
recipient,
fields,
completedFields,
isCompleted,
isRejected,
hidePoweredBy,
allowEmbedSigningWhitelabel,
};
@@ -398,8 +392,6 @@ const EmbedSignDocumentPageV1 = ({ data }: { data: Awaited<ReturnType<typeof han
recipient,
fields,
completedFields,
isCompleted,
isRejected,
hidePoweredBy,
allowEmbedSigningWhitelabel,
} = data;
@@ -423,8 +415,7 @@ const EmbedSignDocumentPageV1 = ({ data }: { data: Awaited<ReturnType<typeof han
fields={fields}
completedFields={completedFields}
metadata={document.documentMeta}
isCompleted={isCompleted}
isRejected={isRejected}
isCompleted={isDocumentCompleted(document.status)}
hidePoweredBy={hidePoweredBy}
allowWhitelabelling={allowEmbedSigningWhitelabel}
allRecipients={allRecipients}
+1 -1
View File
@@ -106,5 +106,5 @@
"vite-plugin-babel-macros": "^1.0.6",
"vite-tsconfig-paths": "^5.1.4"
},
"version": "2.19.0"
"version": "2.18.0"
}
+3
View File
@@ -24,6 +24,9 @@ printf "🏥 Health check: http://localhost:3000/api/health\n"
printf "📊 Certificate status: http://localhost:3000/api/certificate-status\n"
printf "👥 Community: https://github.com/documenso/documenso\n\n"
# The Prisma schema requires a direct URL. Without a connection pooler, it is the same as the database URL.
export NEXT_PRIVATE_DIRECT_DATABASE_URL="${NEXT_PRIVATE_DIRECT_DATABASE_URL:-$NEXT_PRIVATE_DATABASE_URL}"
printf "🗄️ Running database migrations...\n"
npx prisma migrate deploy --schema ../../packages/prisma/schema.prisma
+3 -3
View File
@@ -1,12 +1,12 @@
{
"name": "@documenso/root",
"version": "2.19.0",
"version": "2.18.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@documenso/root",
"version": "2.19.0",
"version": "2.18.0",
"hasInstallScript": true,
"workspaces": [
"apps/*",
@@ -193,7 +193,7 @@
},
"apps/remix": {
"name": "@documenso/remix",
"version": "2.19.0",
"version": "2.18.0",
"dependencies": {
"@cantoo/pdf-lib": "^2.5.3",
"@documenso/api": "*",
+1 -1
View File
@@ -5,7 +5,7 @@
"apps/*",
"packages/*"
],
"version": "2.19.0",
"version": "2.18.0",
"scripts": {
"postinstall": "patch-package",
"build": "turbo run build",