From 16481bc34e977f944b8a385d16d041b7bc46ee70 Mon Sep 17 00:00:00 2001 From: David Nguyen Date: Tue, 2 Dec 2025 18:52:02 +1100 Subject: [PATCH] feat: add empty emails for envelopes --- .../dialogs/envelope-distribute-dialog.tsx | 51 +- .../dialogs/template-use-dialog.tsx | 25 +- .../configure-document-view.types.ts | 3 +- .../document-signing-complete-dialog.tsx | 34 +- .../envelope-editor-recipient-form.tsx | 46 +- .../envelope-recipient-selector.tsx | 57 +- .../envelope-signing-complete-dialog.tsx | 17 +- .../e2e/api/v2/envelopes-api.spec.ts | 542 ++++++++++++++++++ .../send-document-cancelled-emails.handler.ts | 4 +- .../send-recipient-signed-email.handler.ts | 5 +- .../emails/send-rejection-emails.handler.ts | 57 +- .../emails/send-signing-email.handler.ts | 49 +- .../2fa/email/send-2fa-token-email.ts | 7 + .../admin/admin-super-delete-document.ts | 9 +- .../document/complete-document-with-token.ts | 107 +++- .../server-only/document/delete-document.ts | 3 +- .../server-only/document/resend-document.ts | 3 +- .../document/send-completed-email.ts | 7 +- .../lib/server-only/document/send-document.ts | 20 + .../document/send-pending-email.ts | 6 + .../recipient/delete-envelope-recipient.ts | 5 +- .../recipient/set-document-recipients.ts | 10 +- packages/lib/types/recipient.ts | 11 + packages/lib/utils/recipients.ts | 5 + .../create-embedding-template.types.ts | 3 +- .../update-embedding-template.types.ts | 19 +- .../envelope-router/create-envelope.types.ts | 4 +- .../create-envelope-recipients.types.ts | 22 +- .../update-envelope-recipients.types.ts | 20 +- .../set-envelope-recipients.types.ts | 4 +- .../envelope-router/use-envelope.types.ts | 3 +- .../trpc/server/recipient-router/router.ts | 3 +- .../trpc/server/recipient-router/schema.ts | 6 + .../trpc/server/template-router/schema.ts | 3 +- 34 files changed, 977 insertions(+), 193 deletions(-) diff --git a/apps/remix/app/components/dialogs/envelope-distribute-dialog.tsx b/apps/remix/app/components/dialogs/envelope-distribute-dialog.tsx index 98ffd07e3..a92e17d62 100644 --- a/apps/remix/app/components/dialogs/envelope-distribute-dialog.tsx +++ b/apps/remix/app/components/dialogs/envelope-distribute-dialog.tsx @@ -19,6 +19,7 @@ import * as z from 'zod'; import { useCurrentEnvelopeEditor } from '@documenso/lib/client-only/providers/envelope-editor-provider'; import { useCurrentOrganisation } from '@documenso/lib/client-only/providers/organisation'; +import { extractDocumentAuthMethods } from '@documenso/lib/utils/document-auth'; import { trpc, trpc as trpcReact } from '@documenso/trpc/react'; import { DocumentSendEmailMessageHelper } from '@documenso/ui/components/document/document-send-email-message-helper'; import { cn } from '@documenso/ui/lib/utils'; @@ -129,18 +130,41 @@ export const EnvelopeDistributeDialog = ({ const distributionMethod = watch('meta.distributionMethod'); + const recipientsWithIndex = useMemo( + () => + envelope.recipients.map((recipient, index) => ({ + ...recipient, + index, + })), + [envelope.recipients], + ); + const recipientsMissingSignatureFields = useMemo( () => - envelope.recipients.filter( + recipientsWithIndex.filter( (recipient) => recipient.role === RecipientRole.SIGNER && !envelope.fields.some( (field) => field.type === FieldType.SIGNATURE && field.recipientId === recipient.id, ), ), - [envelope.recipients, envelope.fields], + [recipientsWithIndex, envelope.fields], ); + /** + * List of recipients who must have an email due to having auth enabled. + */ + const recipientsMissingRequiredEmail = useMemo(() => { + return recipientsWithIndex.filter((recipient) => { + const auth = extractDocumentAuthMethods({ + documentAuth: envelope.authOptions, + recipientAuth: recipient.authOptions, + }); + + return auth.recipientAccessAuthRequired || auth.recipientActionAuthRequired; + }); + }, [recipientsWithIndex, envelope.authOptions]); + const invalidEnvelopeCode = useMemo(() => { if (recipientsMissingSignatureFields.length > 0) { return 'MISSING_SIGNATURES'; @@ -150,8 +174,12 @@ export const EnvelopeDistributeDialog = ({ return 'MISSING_RECIPIENTS'; } + if (recipientsMissingRequiredEmail.length > 0) { + return 'MISSING_REQUIRED_EMAIL'; + } + return null; - }, [envelope.recipients, envelope.fields, recipientsMissingSignatureFields]); + }, [envelope.recipients, recipientsMissingRequiredEmail, recipientsMissingSignatureFields]); const onFormSubmit = async ({ meta }: TEnvelopeDistributeFormSchema) => { try { @@ -444,7 +472,22 @@ export const EnvelopeDistributeDialog = ({ + + )) + .with('MISSING_REQUIRED_EMAIL', () => ( + + The following recipients require an email address: + + diff --git a/apps/remix/app/components/dialogs/template-use-dialog.tsx b/apps/remix/app/components/dialogs/template-use-dialog.tsx index 129132dd4..c53d7bac9 100644 --- a/apps/remix/app/components/dialogs/template-use-dialog.tsx +++ b/apps/remix/app/components/dialogs/template-use-dialog.tsx @@ -21,6 +21,7 @@ import { SKIP_QUERY_BATCH_META, } from '@documenso/lib/constants/trpc'; import { AppError } from '@documenso/lib/errors/app-error'; +import { ZRecipientEmailSchema } from '@documenso/lib/types/recipient'; import { putPdfFile } from '@documenso/lib/universal/upload/put-file'; import { trpc } from '@documenso/trpc/react'; import { cn } from '@documenso/ui/lib/utils'; @@ -65,7 +66,7 @@ const ZAddRecipientsForNewDocumentSchema = z.object({ recipients: z.array( z.object({ id: z.number(), - email: z.string().email(), + email: ZRecipientEmailSchema, name: z.string(), signingOrder: z.number().optional(), }), @@ -349,7 +350,7 @@ export function TemplateUseDialog({ {documentDistributionMethod === DocumentDistributionMethod.EMAIL && (