mirror of
https://github.com/documenso/documenso.git
synced 2026-10-03 18:23:54 +10:00
fix: reviewed
This commit is contained in:
@@ -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 { getNextDictatableRecipient } from '@documenso/lib/utils/recipient-groups';
|
||||
import type {
|
||||
TRemovedSignedFieldWithTokenMutationSchema,
|
||||
TSignFieldWithTokenMutationSchema,
|
||||
@@ -223,27 +224,10 @@ 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;
|
||||
return getNextDictatableRecipient({
|
||||
recipients: template.recipients,
|
||||
currentRecipientId: directRecipient.id,
|
||||
});
|
||||
|
||||
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 (
|
||||
@@ -435,7 +419,7 @@ export const DirectTemplateSigningForm = ({
|
||||
fields={localFields}
|
||||
fieldsValidated={fieldsValidated}
|
||||
recipient={directRecipient}
|
||||
allowDictateNextSigner={nextRecipient && template.templateMeta?.allowDictateNextSigner}
|
||||
allowDictateNextSigner={Boolean(nextRecipient && template.templateMeta?.allowDictateNextSigner)}
|
||||
defaultNextSigner={nextRecipient ? { name: nextRecipient.name, email: nextRecipient.email } : undefined}
|
||||
/>
|
||||
</div>
|
||||
|
||||
+4
-2
@@ -100,8 +100,10 @@ export const DocumentSigningCompleteDialog = ({
|
||||
|
||||
const { isNameLocked, isEmailLocked } = useEmbedSigningContext() || {};
|
||||
|
||||
const canDictateNextSigner = allowDictateNextSigner && Boolean(defaultNextSigner);
|
||||
|
||||
const form = useForm<TNextSignerFormSchema>({
|
||||
resolver: allowDictateNextSigner ? zodResolver(ZNextSignerFormSchema) : undefined,
|
||||
resolver: canDictateNextSigner ? zodResolver(ZNextSignerFormSchema) : undefined,
|
||||
defaultValues: {
|
||||
name: defaultNextSigner?.name ?? '',
|
||||
email: defaultNextSigner?.email ?? '',
|
||||
@@ -317,7 +319,7 @@ export const DocumentSigningCompleteDialog = ({
|
||||
|
||||
<Form {...form}>
|
||||
<form onSubmit={form.handleSubmit(onFormSubmit)}>
|
||||
{allowDictateNextSigner && defaultNextSigner && (
|
||||
{canDictateNextSigner && (
|
||||
<div className="mb-4 flex flex-col gap-4">
|
||||
<div className="flex flex-col gap-4 md:flex-row">
|
||||
<FormField
|
||||
|
||||
@@ -39,7 +39,11 @@ export type DocumentSigningFormProps = {
|
||||
}) => Promise<void>;
|
||||
isSubmitting: boolean;
|
||||
fieldsValidated: () => void;
|
||||
nextRecipient?: RecipientWithFields;
|
||||
/**
|
||||
* The dictatable next recipient, decided server-side. Only their identity
|
||||
* is needed — for the dictation flag and the prefilled inputs.
|
||||
*/
|
||||
nextRecipient?: Pick<Recipient, 'name' | 'email'>;
|
||||
};
|
||||
|
||||
export const DocumentSigningForm = ({
|
||||
@@ -84,6 +88,10 @@ export const DocumentSigningForm = ({
|
||||
return fieldsRequiringValidation.filter((field) => field.recipientId === recipient.id);
|
||||
}, [fieldsRequiringValidation, recipient]);
|
||||
|
||||
const allowDictateNextSigner = Boolean(nextRecipient && document.documentMeta?.allowDictateNextSigner);
|
||||
|
||||
const defaultNextSigner = nextRecipient ? { name: nextRecipient.name, email: nextRecipient.email } : undefined;
|
||||
|
||||
const localFieldsValidated = () => {
|
||||
setValidateUninsertedFields(true);
|
||||
fieldsValidated();
|
||||
@@ -151,10 +159,8 @@ export const DocumentSigningForm = ({
|
||||
completeDocument({ nextSigner, accessAuthOptions })
|
||||
}
|
||||
recipient={recipient}
|
||||
allowDictateNextSigner={document.documentMeta?.allowDictateNextSigner}
|
||||
defaultNextSigner={
|
||||
nextRecipient ? { name: nextRecipient.name, email: nextRecipient.email } : undefined
|
||||
}
|
||||
allowDictateNextSigner={allowDictateNextSigner}
|
||||
defaultNextSigner={defaultNextSigner}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -223,8 +229,8 @@ export const DocumentSigningForm = ({
|
||||
onClose={() => !isAssistantSubmitting && setIsConfirmationDialogOpen(false)}
|
||||
onConfirm={handleAssistantConfirmDialogSubmit}
|
||||
isSubmitting={isAssistantSubmitting}
|
||||
allowDictateNextSigner={nextRecipient && document.documentMeta?.allowDictateNextSigner}
|
||||
defaultNextSigner={nextRecipient ? { name: nextRecipient.name, email: nextRecipient.email } : undefined}
|
||||
allowDictateNextSigner={allowDictateNextSigner}
|
||||
defaultNextSigner={defaultNextSigner}
|
||||
/>
|
||||
</form>
|
||||
) : (
|
||||
@@ -291,10 +297,8 @@ export const DocumentSigningForm = ({
|
||||
})
|
||||
}
|
||||
recipient={recipient}
|
||||
allowDictateNextSigner={nextRecipient && document.documentMeta?.allowDictateNextSigner}
|
||||
defaultNextSigner={
|
||||
nextRecipient ? { name: nextRecipient.name, email: nextRecipient.email } : undefined
|
||||
}
|
||||
allowDictateNextSigner={allowDictateNextSigner}
|
||||
defaultNextSigner={defaultNextSigner}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
|
||||
+8
-29
@@ -23,7 +23,7 @@ import { Button } from '@documenso/ui/primitives/button';
|
||||
import { Card, CardContent } from '@documenso/ui/primitives/card';
|
||||
import { ElementVisible } from '@documenso/ui/primitives/element-visible';
|
||||
import { Trans } from '@lingui/react/macro';
|
||||
import type { Field } from '@prisma/client';
|
||||
import type { Field, Recipient } from '@prisma/client';
|
||||
import { FieldType, RecipientRole } from '@prisma/client';
|
||||
import { LucideChevronDown, LucideChevronUp } from 'lucide-react';
|
||||
import { useMemo, useState } from 'react';
|
||||
@@ -61,6 +61,12 @@ export type DocumentSigningPageViewV1Props = {
|
||||
completedFields: CompletedField[];
|
||||
isRecipientsTurn: boolean;
|
||||
allRecipients?: RecipientWithFields[];
|
||||
/**
|
||||
* The dictatable next recipient, computed server-side over the FULL
|
||||
* recipient list — must not be re-derived from the role-scoped
|
||||
* `allRecipients`.
|
||||
*/
|
||||
nextRecipient?: Pick<Recipient, 'name' | 'email'>;
|
||||
branding: DocumentSigningBranding;
|
||||
includeSenderDetails: boolean;
|
||||
};
|
||||
@@ -72,6 +78,7 @@ export const DocumentSigningPageViewV1 = ({
|
||||
completedFields,
|
||||
isRecipientsTurn,
|
||||
allRecipients = [],
|
||||
nextRecipient,
|
||||
includeSenderDetails,
|
||||
branding,
|
||||
}: DocumentSigningPageViewV1Props) => {
|
||||
@@ -142,34 +149,6 @@ export const DocumentSigningPageViewV1 = ({
|
||||
const selectedSigner = allRecipients?.find((r) => r.id === selectedSignerId);
|
||||
const targetSigner = recipient.role === RecipientRole.ASSISTANT && selectedSigner ? selectedSigner : null;
|
||||
|
||||
const nextRecipient = useMemo(() => {
|
||||
if (!documentMeta?.signingOrder || 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;
|
||||
}, [document.documentMeta?.signingOrder, allRecipients, recipient.id]);
|
||||
|
||||
const pendingFields = fieldsRequiringValidation.filter((field) => !field.inserted);
|
||||
const hasPendingFields = pendingFields.length > 0;
|
||||
|
||||
|
||||
@@ -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 { getNextDictatableRecipient } 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 getNextDictatableRecipient({
|
||||
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 (
|
||||
|
||||
@@ -43,6 +43,10 @@ export async function loader({ params, request }: Route.LoaderArgs) {
|
||||
throw new Response('Not Found', { status: 404 });
|
||||
}
|
||||
|
||||
if (document.internalVersion !== 1) {
|
||||
throw redirect(`${documentRootPath}/${document.envelopeId}/edit`);
|
||||
}
|
||||
|
||||
const documentVisibility = document.visibility;
|
||||
const currentTeamMemberRole = team.currentTeamRole;
|
||||
const isRecipient = document.recipients.find((recipient) => recipient.email === user.email);
|
||||
|
||||
@@ -42,6 +42,10 @@ export async function loader({ params, request }: Route.LoaderArgs) {
|
||||
throw redirect(templateRootPath);
|
||||
}
|
||||
|
||||
if (template.internalVersion !== 1) {
|
||||
throw redirect(`${templateRootPath}/${template.envelopeId}/edit`);
|
||||
}
|
||||
|
||||
return superLoaderJson({
|
||||
template: {
|
||||
...template,
|
||||
|
||||
Reference in New Issue
Block a user