Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: force signature fields for document signers #1139

Open
wants to merge 13 commits into
base: main
Choose a base branch
from
24 changes: 24 additions & 0 deletions packages/lib/server-only/document/send-document.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import {
RECIPIENT_ROLE_TO_EMAIL_TYPE,
} from '../../constants/recipient-roles';
import { getFile } from '../../universal/upload/get-file';
import { getFieldsForDocument } from '../field/get-fields-for-document';
import { insertFormValuesInPdf } from '../pdf/insert-form-values-in-pdf';
import { triggerWebhook } from '../webhooks/trigger/trigger-webhook';

Expand Down Expand Up @@ -120,6 +121,29 @@ export const sendDocument = async ({
Object.assign(document, result);
}

const fields = await getFieldsForDocument({
documentId: documentId,
userId: userId,
});

const fieldsWithSignerEmail = fields.map((field) => ({
...field,
signerEmail:
document.Recipient.find((recipient) => recipient.id === field.recipientId)?.email ?? '',
}));

const everySignerHasSignature = document?.Recipient.every(
(recipient) =>
recipient.role !== RecipientRole.SIGNER ||
fieldsWithSignerEmail.some(
(field) => field.type === 'SIGNATURE' && field.signerEmail === recipient.email,
),
);

if (!everySignerHasSignature) {
throw new Error('Some signers have not been assigned a signature field.');
}
Comment on lines +124 to +145
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will mean API users will also have to follow these conventions, we should consider if that's something we want @ElTimuro


await Promise.all(
document.Recipient.map(async (recipient) => {
if (recipient.sendStatus === SendStatus.SENT || recipient.role === RecipientRole.CC) {
Expand Down
28 changes: 27 additions & 1 deletion packages/ui/primitives/document-flow/add-fields.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import {
DocumentFlowFormContainerStep,
} from './document-flow-root';
import { FieldItem } from './field-item';
import { MissingSignatureFieldDialog } from './missing-signature-field-dialog';
import { type DocumentFlowStep, FRIENDLY_FIELD_TYPE } from './types';

const fontCaveat = Caveat({
Expand Down Expand Up @@ -64,6 +65,8 @@ export const AddFieldsFormPartial = ({
onSubmit,
isDocumentPdfLoaded,
}: AddFieldsFormProps) => {
const [isMissingSignatureDialogVisible, setIsMissingSignatureDialogVisible] = useState(false);

const { isWithinPageBounds, getFieldPosition, getPage } = useDocumentElement();
const { currentStep, totalSteps, previousStep } = useStep();

Expand Down Expand Up @@ -313,6 +316,22 @@ export const AddFieldsFormPartial = ({
);
}, [recipientsByRole]);

const everySignerHasSignature = recipientsByRole.SIGNER.every((signer) =>
localFields.some((field) => field.type === 'SIGNATURE' && field.signerEmail === signer.email),
);

const handleGoNextClick = () => {
if (!everySignerHasSignature) {
setIsMissingSignatureDialogVisible(true);
} else {
void onFormSubmit();
}
};

const handleOpenChange = () => {
setIsMissingSignatureDialogVisible((prev) => !prev);
};

return (
<>
<DocumentFlowFormContainerHeader
Expand Down Expand Up @@ -596,9 +615,16 @@ export const AddFieldsFormPartial = ({
previousStep();
remove();
}}
onGoNextClick={() => void onFormSubmit()}
onGoNextClick={handleGoNextClick}
/>
</DocumentFlowFormContainerFooter>

{!everySignerHasSignature && (
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any reason to conditionally render here? I get why it's there but it seems easier to just let it always render since we toggle visibility anyways.

<MissingSignatureFieldDialog
isOpen={isMissingSignatureDialogVisible}
onOpenChange={handleOpenChange}
/>
)}
</>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
'use client';

import { Button } from '@documenso/ui/primitives/button';
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
} from '@documenso/ui/primitives/dialog';

export type MissingSignatureFieldDialogProps = {
isOpen: boolean;
onOpenChange: () => void;
};

export const MissingSignatureFieldDialog = ({
isOpen,
onOpenChange,
}: MissingSignatureFieldDialogProps) => {
return (
<Dialog open={isOpen} onOpenChange={onOpenChange}>
<DialogContent className="max-w-lg" position="center">
<DialogHeader>
<DialogTitle>No signature field found</DialogTitle>
<DialogDescription>
<p className="mt-2">
Some signers have not been assigned a signature field. Please assign at least 1 signature field
to each signer before proceeding.
</p>
</DialogDescription>
</DialogHeader>
<DialogFooter>
<Button type="button" variant="secondary" onClick={onOpenChange}>
Cancel
</Button>
<Button type="submit" onClick={onOpenChange}>
Proceed
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we still need both options if you can't proceed? Why the 2 buttons?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

True that. I kept it like this for consistency. Should I just go ahead and remove the proceed button?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • Since this is not just a hint why you can't contine it can be a alert actually

</Button>
</DialogFooter>
</DialogContent>
</Dialog>
);
};