From d2a009d52e2ff81f984f9ab0e348f8f5c7c7a17a Mon Sep 17 00:00:00 2001 From: David Nguyen Date: Sat, 1 Nov 2025 12:44:34 +1100 Subject: [PATCH] fix: allow direct template recipient dictation (#2108) --- .../direct-template/direct-template-page.tsx | 6 +- .../direct-template-signing-form.tsx | 41 +++- .../document-signing-auth-page.tsx | 22 +- .../document-signing-auth-provider.tsx | 5 +- .../document-signing-complete-dialog.tsx | 1 - .../envelope-signing-provider.tsx | 2 - .../envelope-signing-complete-dialog.tsx | 1 + .../routes/_recipient+/d.$token+/_index.tsx | 21 +- .../e2e/templates/direct-templates.spec.ts | 225 +++++++++++++++++- ...et-envelope-for-direct-template-signing.ts | 31 ++- .../get-envelope-required-access-data.ts | 51 ---- .../create-document-from-direct-template.ts | 90 ++++++- packages/prisma/seed/templates.ts | 4 +- .../trpc/server/template-router/router.ts | 2 + .../trpc/server/template-router/schema.ts | 6 + 15 files changed, 418 insertions(+), 90 deletions(-) diff --git a/apps/remix/app/components/general/direct-template/direct-template-page.tsx b/apps/remix/app/components/general/direct-template/direct-template-page.tsx index 686074a27..9bddf9c0d 100644 --- a/apps/remix/app/components/general/direct-template/direct-template-page.tsx +++ b/apps/remix/app/components/general/direct-template/direct-template-page.tsx @@ -89,7 +89,10 @@ export const DirectTemplatePageView = ({ setStep('sign'); }; - const onSignDirectTemplateSubmit = async (fields: DirectTemplateLocalField[]) => { + const onSignDirectTemplateSubmit = async ( + fields: DirectTemplateLocalField[], + nextSigner?: { name: string; email: string }, + ) => { try { let directTemplateExternalId = searchParams?.get('externalId') || undefined; @@ -98,6 +101,7 @@ export const DirectTemplatePageView = ({ } const { token } = await createDocumentFromDirectTemplate({ + nextSigner, directTemplateToken, directTemplateExternalId, directRecipientName: fullName, diff --git a/apps/remix/app/components/general/direct-template/direct-template-signing-form.tsx b/apps/remix/app/components/general/direct-template/direct-template-signing-form.tsx index 3ab3046ae..932a693fd 100644 --- a/apps/remix/app/components/general/direct-template/direct-template-signing-form.tsx +++ b/apps/remix/app/components/general/direct-template/direct-template-signing-form.tsx @@ -55,10 +55,13 @@ import { DocumentSigningRecipientProvider } from '../document-signing/document-s export type DirectTemplateSigningFormProps = { flowStep: DocumentFlowStep; - directRecipient: Pick; + directRecipient: Pick; directRecipientFields: Field[]; template: Omit; - onSubmit: (_data: DirectTemplateLocalField[]) => Promise; + onSubmit: ( + _data: DirectTemplateLocalField[], + _nextSigner?: { name: string; email: string }, + ) => Promise; }; export type DirectTemplateLocalField = Field & { @@ -149,7 +152,7 @@ export const DirectTemplateSigningForm = ({ validateFieldsInserted(fieldsRequiringValidation); }; - const handleSubmit = async () => { + const handleSubmit = async (nextSigner?: { name: string; email: string }) => { setValidateUninsertedFields(true); const isFieldsValid = validateFieldsInserted(fieldsRequiringValidation); @@ -161,7 +164,7 @@ export const DirectTemplateSigningForm = ({ setIsSubmitting(true); try { - await onSubmit(localFields); + await onSubmit(localFields, nextSigner); } catch { setIsSubmitting(false); } @@ -218,6 +221,30 @@ export const DirectTemplateSigningForm = ({ setLocalFields(updatedFields); }, []); + const nextRecipient = useMemo(() => { + if ( + !template.templateMeta?.signingOrder || + template.templateMeta.signingOrder !== 'SEQUENTIAL' || + !template.templateMeta.allowDictateNextSigner + ) { + return undefined; + } + + const sortedRecipients = template.recipients.sort((a, b) => { + // Sort by signingOrder first (nulls last), then by id + if (a.signingOrder === null && b.signingOrder === null) return a.id - b.id; + if (a.signingOrder === null) return 1; + if (b.signingOrder === null) return -1; + if (a.signingOrder === b.signingOrder) return a.id - b.id; + return a.signingOrder - b.signingOrder; + }); + + const currentIndex = sortedRecipients.findIndex((r) => r.id === directRecipient.id); + return currentIndex !== -1 && currentIndex < sortedRecipients.length - 1 + ? sortedRecipients[currentIndex + 1] + : undefined; + }, [template.templateMeta?.signingOrder, template.recipients, directRecipient.id]); + return ( @@ -417,11 +444,15 @@ export const DirectTemplateSigningForm = ({ handleSubmit()} + onSignatureComplete={async (nextSigner) => handleSubmit(nextSigner)} documentTitle={template.title} fields={localFields} fieldsValidated={fieldsValidated} recipient={directRecipient} + allowDictateNextSigner={nextRecipient && template.templateMeta?.allowDictateNextSigner} + defaultNextSigner={ + nextRecipient ? { name: nextRecipient.name, email: nextRecipient.email } : undefined + } /> diff --git a/apps/remix/app/components/general/document-signing/document-signing-auth-page.tsx b/apps/remix/app/components/general/document-signing/document-signing-auth-page.tsx index 21b1be6ca..123baafa5 100644 --- a/apps/remix/app/components/general/document-signing/document-signing-auth-page.tsx +++ b/apps/remix/app/components/general/document-signing/document-signing-auth-page.tsx @@ -9,7 +9,7 @@ import { Button } from '@documenso/ui/primitives/button'; import { useToast } from '@documenso/ui/primitives/use-toast'; export type DocumentSigningAuthPageViewProps = { - email: string; + email?: string; emailHasAccount?: boolean; }; @@ -22,12 +22,18 @@ export const DocumentSigningAuthPageView = ({ const [isSigningOut, setIsSigningOut] = useState(false); - const handleChangeAccount = async (email: string) => { + const handleChangeAccount = async (email?: string) => { try { setIsSigningOut(true); + let redirectPath = '/signin'; + + if (email) { + redirectPath = emailHasAccount ? `/signin#email=${email}` : `/signup#email=${email}`; + } + await authClient.signOut({ - redirectPath: emailHasAccount ? `/signin#email=${email}` : `/signup#email=${email}`, + redirectPath, }); } catch { toast({ @@ -49,9 +55,13 @@ export const DocumentSigningAuthPageView = ({

- - You need to be logged in as {email} to view this page. - + {email ? ( + + You need to be logged in as {email} to view this page. + + ) : ( + You need to be logged in to view this page. + )}