From 31be54893986653e41baf47d2c636ad582e28b6a Mon Sep 17 00:00:00 2001 From: David Nguyen Date: Tue, 25 Mar 2025 21:59:13 +1100 Subject: [PATCH] fix: duplicate webhook calls on document complete (#1721) Fix webhooks being sent twice due to duplicate frontend calls Updated the assistant confirmation dialog so the next signer is always visible (if dictate is enabled). Because if the form is invalid (due to no name) there is no visual queue that the form is invalid (since it's hidden) ## Notes Didn't bother to remove the weird assistants form since it currently works for now ![image](https://github.com/user-attachments/assets/47910fec-e05e-486a-a61d-16078d948893) ## Tests - Currently running locally - Tested webhooks via network tab and via webhook.site --- .../dialogs/assistant-confirmation-dialog.tsx | 161 +++++++----------- .../document-signing-form.tsx | 59 +------ .../next-recipient-dictation.spec.ts | 3 - .../e2e/teams/team-signature-settings.spec.ts | 4 + packages/lib/translations/de/web.po | 6 - packages/lib/translations/en/web.po | 6 - packages/lib/translations/es/web.po | 6 - packages/lib/translations/fr/web.po | 6 - packages/lib/translations/it/web.po | 6 - packages/lib/translations/pl/web.po | 6 - .../server/document-router/update-document.ts | 1 + packages/ui/primitives/toast.tsx | 1 + 12 files changed, 76 insertions(+), 189 deletions(-) diff --git a/apps/remix/app/components/dialogs/assistant-confirmation-dialog.tsx b/apps/remix/app/components/dialogs/assistant-confirmation-dialog.tsx index 6c9c1dd57..1240a0dc0 100644 --- a/apps/remix/app/components/dialogs/assistant-confirmation-dialog.tsx +++ b/apps/remix/app/components/dialogs/assistant-confirmation-dialog.tsx @@ -1,5 +1,3 @@ -import { useState } from 'react'; - import { zodResolver } from '@hookform/resolvers/zod'; import { Trans } from '@lingui/react/macro'; import { useForm } from 'react-hook-form'; @@ -57,8 +55,6 @@ export function AssistantConfirmationDialog({ allowDictateNextSigner = false, defaultNextSigner, }: ConfirmationDialogProps) { - const [isEditingNextSigner, setIsEditingNextSigner] = useState(false); - const form = useForm({ resolver: zodResolver(ZNextSignerFormSchema), defaultValues: { @@ -68,7 +64,7 @@ export function AssistantConfirmationDialog({ }); const onOpenChange = () => { - if (form.formState.isSubmitting) { + if (isSubmitting) { return; } @@ -77,32 +73,25 @@ export function AssistantConfirmationDialog({ email: defaultNextSigner?.email ?? '', }); - setIsEditingNextSigner(false); onClose(); }; - const onFormSubmit = async (data: TNextSignerFormSchema) => { - if (allowDictateNextSigner && data.name && data.email) { - await onConfirm({ - name: data.name, - email: data.email, - }); - } else { - await onConfirm(); + const handleSubmit = () => { + // Validate the form and submit it if dictate signer is enabled. + if (allowDictateNextSigner) { + void form.handleSubmit(onConfirm)(); + return; } - }; - const isNextSignerValid = !allowDictateNextSigner || (form.watch('name') && form.watch('email')); + onConfirm(); + }; return (
- -
+ +
Complete Document @@ -118,71 +107,53 @@ export function AssistantConfirmationDialog({
{allowDictateNextSigner && ( -
- {!isEditingNextSigner && ( -
-

- The next recipient to sign this document will be{' '} - {form.watch('name')} ( - {form.watch('email')}). -

+
+

+ The next recipient to sign this document will be{' '} +

- -
- )} +
+ ( + + + Name + + + + + + + )} + /> - {isEditingNextSigner && ( -
- ( - - - Name - - - - - - - )} - /> - - ( - - - Email - - - - - - - )} - /> -
- )} + ( + + + Email + + + + + + + )} + /> +
)} @@ -190,27 +161,17 @@ export function AssistantConfirmationDialog({
-
diff --git a/apps/remix/app/components/general/document-signing/document-signing-form.tsx b/apps/remix/app/components/general/document-signing/document-signing-form.tsx index 2eee9a043..967c5d725 100644 --- a/apps/remix/app/components/general/document-signing/document-signing-form.tsx +++ b/apps/remix/app/components/general/document-signing/document-signing-form.tsx @@ -1,13 +1,11 @@ import { useId, useMemo, useState } from 'react'; -import { zodResolver } from '@hookform/resolvers/zod'; import { msg } from '@lingui/core/macro'; import { useLingui } from '@lingui/react'; import { Trans } from '@lingui/react/macro'; import { type Field, FieldType, type Recipient, RecipientRole } from '@prisma/client'; import { Controller, useForm } from 'react-hook-form'; import { useNavigate } from 'react-router'; -import { z } from 'zod'; import { useAnalytics } from '@documenso/lib/client-only/hooks/use-analytics'; import { useOptionalSession } from '@documenso/lib/client-only/providers/session'; @@ -33,13 +31,6 @@ import { import { DocumentSigningCompleteDialog } from './document-signing-complete-dialog'; import { useRequiredDocumentSigningContext } from './document-signing-provider'; -export const ZSigningFormSchema = z.object({ - name: z.string().min(1, 'Name is required').optional(), - email: z.string().email('Invalid email address').optional(), -}); - -export type TSigningFormSchema = z.infer; - export type DocumentSigningFormProps = { document: DocumentAndSender; recipient: Recipient; @@ -76,8 +67,11 @@ export const DocumentSigningForm = ({ const [isConfirmationDialogOpen, setIsConfirmationDialogOpen] = useState(false); const [isAssistantSubmitting, setIsAssistantSubmitting] = useState(false); - const { mutateAsync: completeDocumentWithToken } = - trpc.recipient.completeDocumentWithToken.useMutation(); + const { + mutateAsync: completeDocumentWithToken, + isPending, + isSuccess, + } = trpc.recipient.completeDocumentWithToken.useMutation(); const assistantForm = useForm<{ selectedSignerId: number | undefined }>({ defaultValues: { @@ -85,12 +79,8 @@ export const DocumentSigningForm = ({ }, }); - const { handleSubmit, formState } = useForm({ - resolver: zodResolver(ZSigningFormSchema), - }); - // Keep the loading state going if successful since the redirect may take some time. - const isSubmitting = formState.isSubmitting || formState.isSubmitSuccessful; + const isSubmitting = isPending || isSuccess; const fieldsRequiringValidation = useMemo( () => fields.filter(isFieldUnsignedAndRequired), @@ -112,34 +102,6 @@ export const DocumentSigningForm = ({ validateFieldsInserted(fieldsRequiringValidation); }; - const onFormSubmit = async (data: TSigningFormSchema) => { - try { - setValidateUninsertedFields(true); - - const isFieldsValid = validateFieldsInserted(fieldsRequiringValidation); - - if (!isFieldsValid) { - return; - } - - const nextSigner = - data.email && data.name - ? { - email: data.email, - name: data.name, - } - : undefined; - - await completeDocument(undefined, nextSigner); - } catch (error) { - toast({ - title: 'Error', - description: error instanceof Error ? error.message : 'An error occurred while signing', - variant: 'destructive', - }); - } - }; - const onAssistantFormSubmit = () => { if (uninsertedRecipientFields.length > 0) { return; @@ -214,8 +176,6 @@ export const DocumentSigningForm = ({ : undefined; }, [document.documentMeta?.signingOrder, allRecipients, recipient.id]); - console.log('nextRecipient', nextRecipient); - return (
0} > - {isAssistantSubmitting ? Submitting... : Continue} + Continue
@@ -381,7 +340,7 @@ export const DocumentSigningForm = ({ ) : ( <> - +

{recipient.role === RecipientRole.APPROVER && !hasSignatureField ? ( Please review the document before approving. @@ -463,7 +422,7 @@ export const DocumentSigningForm = ({ } />

- + )} diff --git a/packages/app-tests/e2e/document-auth/next-recipient-dictation.spec.ts b/packages/app-tests/e2e/document-auth/next-recipient-dictation.spec.ts index 345359542..e9f3ec6c8 100644 --- a/packages/app-tests/e2e/document-auth/next-recipient-dictation.spec.ts +++ b/packages/app-tests/e2e/document-auth/next-recipient-dictation.spec.ts @@ -341,9 +341,6 @@ test('[NEXT_RECIPIENT_DICTATION]: should allow assistant to dictate next signer' await expect(page.getByRole('dialog')).toBeVisible(); await expect(page.getByText('The next recipient to sign this document will be')).toBeVisible(); - // Update next recipient - await page.locator('button').filter({ hasText: 'Update Recipient' }).click(); - // Use dialog context to ensure we're targeting the correct form fields const dialog = page.getByRole('dialog'); await dialog.getByLabel('Name').fill('New Signer'); diff --git a/packages/app-tests/e2e/teams/team-signature-settings.spec.ts b/packages/app-tests/e2e/teams/team-signature-settings.spec.ts index 0e6605176..6ecce08ad 100644 --- a/packages/app-tests/e2e/teams/team-signature-settings.spec.ts +++ b/packages/app-tests/e2e/teams/team-signature-settings.spec.ts @@ -148,6 +148,10 @@ test('[TEAMS]: check signature modes work for templates', async ({ page }) => { await page.getByRole('button', { name: 'Update' }).first().click(); + // Wait for finish + await page.getByText('Document preferences updated').waitFor({ state: 'visible' }); + await page.getByTestId('toast-close').click(); + const template = await seedTeamTemplateWithMeta(team); await page.goto(`/t/${team.url}/templates/${template.id}`); diff --git a/packages/lib/translations/de/web.po b/packages/lib/translations/de/web.po index ff2b70a0a..75c0a039b 100644 --- a/packages/lib/translations/de/web.po +++ b/packages/lib/translations/de/web.po @@ -5049,11 +5049,6 @@ msgstr "Schritt <0>{step} von {maxStep}" msgid "Subject <0>(Optional)" msgstr "Betreff <0>(Optional)" -#: apps/remix/app/components/general/document-signing/document-signing-form.tsx -#: apps/remix/app/components/dialogs/assistant-confirmation-dialog.tsx -msgid "Submitting..." -msgstr "" - #: apps/remix/app/components/general/billing-plans.tsx msgid "Subscribe" msgstr "" @@ -6121,7 +6116,6 @@ msgstr "Profil aktualisieren" #: apps/remix/app/components/tables/admin-document-recipient-item-table.tsx #: apps/remix/app/components/general/document-signing/document-signing-complete-dialog.tsx -#: apps/remix/app/components/dialogs/assistant-confirmation-dialog.tsx msgid "Update Recipient" msgstr "Empfänger aktualisieren" diff --git a/packages/lib/translations/en/web.po b/packages/lib/translations/en/web.po index 692b265d3..7e4ba7ca8 100644 --- a/packages/lib/translations/en/web.po +++ b/packages/lib/translations/en/web.po @@ -5044,11 +5044,6 @@ msgstr "Step <0>{step} of {maxStep}" msgid "Subject <0>(Optional)" msgstr "Subject <0>(Optional)" -#: apps/remix/app/components/general/document-signing/document-signing-form.tsx -#: apps/remix/app/components/dialogs/assistant-confirmation-dialog.tsx -msgid "Submitting..." -msgstr "Submitting..." - #: apps/remix/app/components/general/billing-plans.tsx msgid "Subscribe" msgstr "Subscribe" @@ -6118,7 +6113,6 @@ msgstr "Update profile" #: apps/remix/app/components/tables/admin-document-recipient-item-table.tsx #: apps/remix/app/components/general/document-signing/document-signing-complete-dialog.tsx -#: apps/remix/app/components/dialogs/assistant-confirmation-dialog.tsx msgid "Update Recipient" msgstr "Update Recipient" diff --git a/packages/lib/translations/es/web.po b/packages/lib/translations/es/web.po index e54cfd54e..af37605a2 100644 --- a/packages/lib/translations/es/web.po +++ b/packages/lib/translations/es/web.po @@ -5049,11 +5049,6 @@ msgstr "Paso <0>{step} de {maxStep}" msgid "Subject <0>(Optional)" msgstr "Asunto <0>(Opcional)" -#: apps/remix/app/components/general/document-signing/document-signing-form.tsx -#: apps/remix/app/components/dialogs/assistant-confirmation-dialog.tsx -msgid "Submitting..." -msgstr "" - #: apps/remix/app/components/general/billing-plans.tsx msgid "Subscribe" msgstr "" @@ -6121,7 +6116,6 @@ msgstr "Actualizar perfil" #: apps/remix/app/components/tables/admin-document-recipient-item-table.tsx #: apps/remix/app/components/general/document-signing/document-signing-complete-dialog.tsx -#: apps/remix/app/components/dialogs/assistant-confirmation-dialog.tsx msgid "Update Recipient" msgstr "Actualizar destinatario" diff --git a/packages/lib/translations/fr/web.po b/packages/lib/translations/fr/web.po index 3dc0de14e..0d94a9579 100644 --- a/packages/lib/translations/fr/web.po +++ b/packages/lib/translations/fr/web.po @@ -5049,11 +5049,6 @@ msgstr "Étape <0>{step} sur {maxStep}" msgid "Subject <0>(Optional)" msgstr "Objet <0>(Optionnel)" -#: apps/remix/app/components/general/document-signing/document-signing-form.tsx -#: apps/remix/app/components/dialogs/assistant-confirmation-dialog.tsx -msgid "Submitting..." -msgstr "" - #: apps/remix/app/components/general/billing-plans.tsx msgid "Subscribe" msgstr "" @@ -6121,7 +6116,6 @@ msgstr "Mettre à jour le profil" #: apps/remix/app/components/tables/admin-document-recipient-item-table.tsx #: apps/remix/app/components/general/document-signing/document-signing-complete-dialog.tsx -#: apps/remix/app/components/dialogs/assistant-confirmation-dialog.tsx msgid "Update Recipient" msgstr "Mettre à jour le destinataire" diff --git a/packages/lib/translations/it/web.po b/packages/lib/translations/it/web.po index 2f48020a8..3aa747cf7 100644 --- a/packages/lib/translations/it/web.po +++ b/packages/lib/translations/it/web.po @@ -5049,11 +5049,6 @@ msgstr "Passo <0>{step} di {maxStep}" msgid "Subject <0>(Optional)" msgstr "Oggetto <0>(Opzionale)" -#: apps/remix/app/components/general/document-signing/document-signing-form.tsx -#: apps/remix/app/components/dialogs/assistant-confirmation-dialog.tsx -msgid "Submitting..." -msgstr "" - #: apps/remix/app/components/general/billing-plans.tsx msgid "Subscribe" msgstr "" @@ -6121,7 +6116,6 @@ msgstr "Aggiorna profilo" #: apps/remix/app/components/tables/admin-document-recipient-item-table.tsx #: apps/remix/app/components/general/document-signing/document-signing-complete-dialog.tsx -#: apps/remix/app/components/dialogs/assistant-confirmation-dialog.tsx msgid "Update Recipient" msgstr "Aggiorna destinatario" diff --git a/packages/lib/translations/pl/web.po b/packages/lib/translations/pl/web.po index 74659f5ab..d791971da 100644 --- a/packages/lib/translations/pl/web.po +++ b/packages/lib/translations/pl/web.po @@ -5049,11 +5049,6 @@ msgstr "Krok <0>{step} z {maxStep}" msgid "Subject <0>(Optional)" msgstr "Temat <0>(Opcjonalnie)" -#: apps/remix/app/components/general/document-signing/document-signing-form.tsx -#: apps/remix/app/components/dialogs/assistant-confirmation-dialog.tsx -msgid "Submitting..." -msgstr "" - #: apps/remix/app/components/general/billing-plans.tsx msgid "Subscribe" msgstr "" @@ -6121,7 +6116,6 @@ msgstr "Zaktualizuj profil" #: apps/remix/app/components/tables/admin-document-recipient-item-table.tsx #: apps/remix/app/components/general/document-signing/document-signing-complete-dialog.tsx -#: apps/remix/app/components/dialogs/assistant-confirmation-dialog.tsx msgid "Update Recipient" msgstr "Zaktualizuj odbiorcę" diff --git a/packages/trpc/server/document-router/update-document.ts b/packages/trpc/server/document-router/update-document.ts index 5fdb02a88..ed2ca6acd 100644 --- a/packages/trpc/server/document-router/update-document.ts +++ b/packages/trpc/server/document-router/update-document.ts @@ -37,6 +37,7 @@ export const updateDocumentRoute = authenticatedProcedure redirectUrl: meta.redirectUrl, distributionMethod: meta.distributionMethod, signingOrder: meta.signingOrder, + allowDictateNextSigner: meta.allowDictateNextSigner, emailSettings: meta.emailSettings, requestMetadata: ctx.metadata, }); diff --git a/packages/ui/primitives/toast.tsx b/packages/ui/primitives/toast.tsx index bddf33165..4cd631414 100644 --- a/packages/ui/primitives/toast.tsx +++ b/packages/ui/primitives/toast.tsx @@ -78,6 +78,7 @@ const ToastClose = React.forwardRef< >(({ className, ...props }, ref) => (