chore: disable form on last signer

This commit is contained in:
Ephraim Atta-Duncan
2025-02-14 12:18:19 +00:00
parent 4189a34de0
commit 1e90ca45a6
5 changed files with 66 additions and 30 deletions

View File

@ -45,6 +45,7 @@ export type SigningFormProps = {
isRecipientsTurn: boolean; isRecipientsTurn: boolean;
allRecipients?: RecipientWithFields[]; allRecipients?: RecipientWithFields[];
setSelectedSignerId?: (id: number | null) => void; setSelectedSignerId?: (id: number | null) => void;
isLastRecipient: boolean;
}; };
type SigningFormData = { type SigningFormData = {
@ -60,6 +61,7 @@ export const SigningForm = ({
isRecipientsTurn, isRecipientsTurn,
allRecipients = [], allRecipients = [],
setSelectedSignerId, setSelectedSignerId,
isLastRecipient,
}: SigningFormProps) => { }: SigningFormProps) => {
const { _ } = useLingui(); const { _ } = useLingui();
const { toast } = useToast(); const { toast } = useToast();
@ -252,7 +254,8 @@ export const SigningForm = ({
disabled={!isRecipientsTurn} disabled={!isRecipientsTurn}
canModifyNextSigner={ canModifyNextSigner={
document.documentMeta?.modifyNextSigner && document.documentMeta?.modifyNextSigner &&
document.documentMeta?.signingOrder === DocumentSigningOrder.SEQUENTIAL document.documentMeta?.signingOrder === DocumentSigningOrder.SEQUENTIAL &&
!isLastRecipient
} }
/> />
</div> </div>
@ -435,7 +438,8 @@ export const SigningForm = ({
disabled={!isRecipientsTurn} disabled={!isRecipientsTurn}
canModifyNextSigner={ canModifyNextSigner={
document.documentMeta?.modifyNextSigner && document.documentMeta?.modifyNextSigner &&
document.documentMeta?.signingOrder === DocumentSigningOrder.SEQUENTIAL document.documentMeta?.signingOrder === DocumentSigningOrder.SEQUENTIAL &&
!isLastRecipient
} }
/> />
</div> </div>

View File

@ -9,6 +9,7 @@ import { isRecipientAuthorized } from '@documenso/lib/server-only/document/is-re
import { viewedDocument } from '@documenso/lib/server-only/document/viewed-document'; import { viewedDocument } from '@documenso/lib/server-only/document/viewed-document';
import { getCompletedFieldsForToken } from '@documenso/lib/server-only/field/get-completed-fields-for-token'; import { getCompletedFieldsForToken } from '@documenso/lib/server-only/field/get-completed-fields-for-token';
import { getFieldsForToken } from '@documenso/lib/server-only/field/get-fields-for-token'; import { getFieldsForToken } from '@documenso/lib/server-only/field/get-fields-for-token';
import { getIsLastRecipient } from '@documenso/lib/server-only/recipient/get-is-last-recipient';
import { getIsRecipientsTurnToSign } from '@documenso/lib/server-only/recipient/get-is-recipient-turn'; import { getIsRecipientsTurnToSign } from '@documenso/lib/server-only/recipient/get-is-recipient-turn';
import { getRecipientByToken } from '@documenso/lib/server-only/recipient/get-recipient-by-token'; import { getRecipientByToken } from '@documenso/lib/server-only/recipient/get-recipient-by-token';
import { getRecipientSignatures } from '@documenso/lib/server-only/recipient/get-recipient-signatures'; import { getRecipientSignatures } from '@documenso/lib/server-only/recipient/get-recipient-signatures';
@ -44,7 +45,7 @@ export default async function SigningPage({ params: { token } }: SigningPageProp
const requestMetadata = extractNextHeaderRequestMetadata(requestHeaders); const requestMetadata = extractNextHeaderRequestMetadata(requestHeaders);
const [document, recipient, fields, completedFields] = await Promise.all([ const [document, recipient, fields, completedFields, isLastRecipient] = await Promise.all([
getDocumentAndSenderByToken({ getDocumentAndSenderByToken({
token, token,
userId: user?.id, userId: user?.id,
@ -53,6 +54,7 @@ export default async function SigningPage({ params: { token } }: SigningPageProp
getRecipientByToken({ token }).catch(() => null), getRecipientByToken({ token }).catch(() => null),
getFieldsForToken({ token }), getFieldsForToken({ token }),
getCompletedFieldsForToken({ token }), getCompletedFieldsForToken({ token }),
getIsLastRecipient({ token }),
]); ]);
if ( if (
@ -169,6 +171,7 @@ export default async function SigningPage({ params: { token } }: SigningPageProp
completedFields={completedFields} completedFields={completedFields}
isRecipientsTurn={isRecipientsTurn} isRecipientsTurn={isRecipientsTurn}
allRecipients={allRecipients} allRecipients={allRecipients}
isLastRecipient={isLastRecipient}
/> />
</DocumentAuthProvider> </DocumentAuthProvider>
</SigningProvider> </SigningProvider>

View File

@ -38,7 +38,7 @@ export type SignDialogProps = {
documentTitle: string; documentTitle: string;
fields: Field[]; fields: Field[];
fieldsValidated: () => void | Promise<void>; fieldsValidated: () => void | Promise<void>;
onSignatureComplete: (nextSigner?: { email: string; name: string }) => void | Promise<void>; onSignatureComplete: (nextSigner?: { email?: string; name?: string }) => void | Promise<void>;
role: RecipientRole; role: RecipientRole;
disabled?: boolean; disabled?: boolean;
canModifyNextSigner?: boolean; canModifyNextSigner?: boolean;
@ -48,20 +48,10 @@ const formSchema = z.object({
nextSigner: z nextSigner: z
.object({ .object({
email: z.string().email({ message: 'Please enter a valid email address' }).optional(), email: z.string().email({ message: 'Please enter a valid email address' }).optional(),
name: z.string().min(1, { message: 'Name is required' }).optional(), name: z.string().optional(),
}) })
.refine( .optional()
(data) => { .default({}),
if (data.name) {
return !!data.email;
}
return true;
},
{
message: 'Email is required when name is provided',
path: ['email'],
},
),
}); });
type TFormSchema = z.infer<typeof formSchema>; type TFormSchema = z.infer<typeof formSchema>;
@ -99,26 +89,15 @@ export function SignDialog({
const form = useForm<TFormSchema>({ const form = useForm<TFormSchema>({
resolver: zodResolver(formSchema), resolver: zodResolver(formSchema),
defaultValues: {
nextSigner: {
email: '',
name: '',
},
},
}); });
const onFormSubmit = async (data: TFormSchema) => { const onFormSubmit = async (data: TFormSchema) => {
try { try {
await fieldsValidated(); await fieldsValidated();
if (!canModifyNextSigner || !data.nextSigner.email) {
await onSignatureComplete();
return;
}
await onSignatureComplete({ await onSignatureComplete({
email: data.nextSigner.email.trim().toLowerCase(), email: data.nextSigner.email?.trim().toLowerCase(),
name: data.nextSigner.name?.trim() ?? '', name: data.nextSigner.name?.trim(),
}); });
setShowDialog(false); setShowDialog(false);
@ -378,6 +357,7 @@ export function SignDialog({
Cancel Cancel
</Button> </Button>
</DialogClose> </DialogClose>
{step === 1 && ( {step === 1 && (
<Button className="group" type="button" onClick={handleContinue}> <Button className="group" type="button" onClick={handleContinue}>
Next Next

View File

@ -49,6 +49,7 @@ export type SigningPageViewProps = {
completedFields: CompletedField[]; completedFields: CompletedField[];
isRecipientsTurn: boolean; isRecipientsTurn: boolean;
allRecipients?: RecipientWithFields[]; allRecipients?: RecipientWithFields[];
isLastRecipient: boolean;
}; };
export const SigningPageView = ({ export const SigningPageView = ({
@ -58,6 +59,7 @@ export const SigningPageView = ({
completedFields, completedFields,
isRecipientsTurn, isRecipientsTurn,
allRecipients = [], allRecipients = [],
isLastRecipient,
}: SigningPageViewProps) => { }: SigningPageViewProps) => {
const { documentData, documentMeta } = document; const { documentData, documentMeta } = document;
@ -159,6 +161,7 @@ export const SigningPageView = ({
redirectUrl={documentMeta?.redirectUrl} redirectUrl={documentMeta?.redirectUrl}
isRecipientsTurn={isRecipientsTurn} isRecipientsTurn={isRecipientsTurn}
allRecipients={allRecipients} allRecipients={allRecipients}
isLastRecipient={isLastRecipient}
setSelectedSignerId={setSelectedSignerId} setSelectedSignerId={setSelectedSignerId}
/> />
</div> </div>

View File

@ -0,0 +1,46 @@
import { prisma } from '@documenso/prisma';
import { DocumentSigningOrder, RecipientRole, SigningStatus } from '@documenso/prisma/client';
export type GetIsLastRecipientOptions = {
token: string;
};
export async function getIsLastRecipient({ token }: GetIsLastRecipientOptions) {
const document = await prisma.document.findFirstOrThrow({
where: {
recipients: {
some: {
token,
},
},
},
include: {
documentMeta: true,
recipients: {
where: {
role: {
not: RecipientRole.CC,
},
},
orderBy: [{ signingOrder: { sort: 'asc', nulls: 'last' } }, { id: 'asc' }],
},
},
});
if (document.documentMeta?.signingOrder !== DocumentSigningOrder.SEQUENTIAL) {
const unsignedRecipients = document.recipients.filter(
(recipient) => recipient.signingStatus !== SigningStatus.SIGNED,
);
return unsignedRecipients.length <= 1;
}
const { recipients } = document;
const currentRecipientIndex = recipients.findIndex((r) => r.token === token);
if (currentRecipientIndex === -1) {
return false;
}
return currentRecipientIndex === recipients.length - 1;
}