mirror of
https://github.com/documenso/documenso.git
synced 2025-11-14 08:42:12 +10:00
chore: disable form on last signer
This commit is contained in:
@ -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>
|
||||||
|
|||||||
@ -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>
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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>
|
||||||
|
|||||||
46
packages/lib/server-only/recipient/get-is-last-recipient.ts
Normal file
46
packages/lib/server-only/recipient/get-is-last-recipient.ts
Normal 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;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user