From 8d48c92a273d6e28aa40ae507ef894f0fa577d7a Mon Sep 17 00:00:00 2001 From: David Nguyen Date: Tue, 4 Aug 2026 17:35:24 +1000 Subject: [PATCH] feat: gate dictation UI to single-recipient next steps --- .../direct-template-signing-form.tsx | 28 +++++-------------- .../document-signing-page-view-v1.tsx | 25 ++--------------- .../envelope-signing-provider.tsx | 27 ++++-------------- 3 files changed, 15 insertions(+), 65 deletions(-) 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 179d3306a..3ecaf928d 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 @@ -11,6 +11,7 @@ import { import type { TTemplate } from '@documenso/lib/types/template'; import { isFieldUnsignedAndRequired } from '@documenso/lib/utils/advanced-fields-helpers'; import { sortFieldsByPosition, validateFieldsInserted } from '@documenso/lib/utils/fields'; +import { getDictatableNextRecipient } from '@documenso/lib/utils/recipient-groups'; import type { TRemovedSignedFieldWithTokenMutationSchema, TSignFieldWithTokenMutationSchema, @@ -223,27 +224,12 @@ export const DirectTemplateSigningForm = ({ 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; + return ( + getDictatableNextRecipient({ + recipients: template.recipients, + currentRecipientId: directRecipient.id, + }) ?? undefined + ); }, [template.templateMeta?.signingOrder, template.recipients, directRecipient.id]); return ( diff --git a/apps/remix/app/components/general/document-signing/document-signing-page-view-v1.tsx b/apps/remix/app/components/general/document-signing/document-signing-page-view-v1.tsx index 1979c63a2..c2dd41ab1 100644 --- a/apps/remix/app/components/general/document-signing/document-signing-page-view-v1.tsx +++ b/apps/remix/app/components/general/document-signing/document-signing-page-view-v1.tsx @@ -15,6 +15,7 @@ import type { CompletedField } from '@documenso/lib/types/fields'; import { isFieldUnsignedAndRequired } from '@documenso/lib/utils/advanced-fields-helpers'; import { getDocumentDataUrlForPdfViewer } from '@documenso/lib/utils/envelope-download'; import { validateFieldsInserted } from '@documenso/lib/utils/fields'; +import { getDictatableNextRecipient } from '@documenso/lib/utils/recipient-groups'; import type { FieldWithSignatureAndFieldMeta } from '@documenso/prisma/types/field-with-signature-and-fieldmeta'; import type { RecipientWithFields } from '@documenso/prisma/types/recipient-with-fields'; import { trpc } from '@documenso/trpc/react'; @@ -143,31 +144,11 @@ export const DocumentSigningPageViewV1 = ({ const targetSigner = recipient.role === RecipientRole.ASSISTANT && selectedSigner ? selectedSigner : null; const nextRecipient = useMemo(() => { - if (!documentMeta?.signingOrder || documentMeta.signingOrder !== 'SEQUENTIAL') { + if (documentMeta?.signingOrder !== 'SEQUENTIAL') { return undefined; } - const sortedRecipients = [...allRecipients].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 === recipient.id); - return currentIndex !== -1 && currentIndex < sortedRecipients.length - 1 - ? sortedRecipients[currentIndex + 1] - : undefined; + return getDictatableNextRecipient({ recipients: allRecipients, currentRecipientId: recipient.id }) ?? undefined; }, [document.documentMeta?.signingOrder, allRecipients, recipient.id]); const pendingFields = fieldsRequiringValidation.filter((field) => !field.inserted); diff --git a/apps/remix/app/components/general/document-signing/envelope-signing-provider.tsx b/apps/remix/app/components/general/document-signing/envelope-signing-provider.tsx index f0b00fec9..98b25321b 100644 --- a/apps/remix/app/components/general/document-signing/envelope-signing-provider.tsx +++ b/apps/remix/app/components/general/document-signing/envelope-signing-provider.tsx @@ -6,6 +6,7 @@ import type { EnvelopeForSigningResponse } from '@documenso/lib/server-only/enve import type { TRecipientActionAuth } from '@documenso/lib/types/document-auth'; import { isFieldUnsignedAndRequired, isRequiredField } from '@documenso/lib/utils/advanced-fields-helpers'; import { extractFieldInsertionValues } from '@documenso/lib/utils/envelope-signing'; +import { getDictatableNextRecipient } from '@documenso/lib/utils/recipient-groups'; import { trpc } from '@documenso/trpc/react'; import type { TSignEnvelopeFieldValue } from '@documenso/trpc/server/envelope-router/sign-envelope-field.types'; import { EnvelopeType, type Field, FieldType, type Recipient, RecipientRole, SigningStatus } from '@prisma/client'; @@ -290,32 +291,14 @@ export const EnvelopeSigningProvider = ({ .filter((field) => field.inserted); const nextRecipient = useMemo(() => { - if (!envelope.documentMeta.signingOrder || envelope.documentMeta.signingOrder !== 'SEQUENTIAL') { + if (envelope.documentMeta.signingOrder !== 'SEQUENTIAL') { return null; } - const sortedRecipients = [...envelope.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; + return getDictatableNextRecipient({ + recipients: envelope.recipients, + currentRecipientId: recipient.id, }); - - const currentIndex = sortedRecipients.findIndex((r) => r.id === recipient.id); - - return currentIndex !== -1 && currentIndex < sortedRecipients.length - 1 - ? sortedRecipients[currentIndex + 1] - : null; }, [envelope.documentMeta?.signingOrder, envelope.recipients, recipient.id]); const signField = async (