import { useEffect, useMemo, useState } from 'react'; import { Trans } from '@lingui/react/macro'; import type { Field, Recipient, Signature } from '@prisma/client'; import { FieldType } from '@prisma/client'; import { DateTime } from 'luxon'; import { match } from 'ts-pattern'; import { DEFAULT_DOCUMENT_DATE_FORMAT } from '@documenso/lib/constants/date-formats'; import { PDF_VIEWER_PAGE_SELECTOR } from '@documenso/lib/constants/pdf-viewer'; import { DEFAULT_DOCUMENT_TIME_ZONE } from '@documenso/lib/constants/time-zones'; import { ZCheckboxFieldMeta, ZDropdownFieldMeta, ZNumberFieldMeta, ZRadioFieldMeta, ZTextFieldMeta, } from '@documenso/lib/types/field-meta'; 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 type { TRemovedSignedFieldWithTokenMutationSchema, TSignFieldWithTokenMutationSchema, } from '@documenso/trpc/server/field-router/schema'; import { FieldToolTip } from '@documenso/ui/components/field/field-tooltip'; import { Button } from '@documenso/ui/primitives/button'; import { DocumentFlowFormContainerContent, DocumentFlowFormContainerFooter, DocumentFlowFormContainerHeader, DocumentFlowFormContainerStep, } from '@documenso/ui/primitives/document-flow/document-flow-root'; import type { DocumentFlowStep } from '@documenso/ui/primitives/document-flow/types'; import { ElementVisible } from '@documenso/ui/primitives/element-visible'; import { Input } from '@documenso/ui/primitives/input'; import { Label } from '@documenso/ui/primitives/label'; import { SignaturePadDialog } from '@documenso/ui/primitives/signature-pad/signature-pad-dialog'; import { useStep } from '@documenso/ui/primitives/stepper'; import { DocumentSigningCheckboxField } from '~/components/general/document-signing/document-signing-checkbox-field'; import { DocumentSigningCompleteDialog } from '~/components/general/document-signing/document-signing-complete-dialog'; import { DocumentSigningDateField } from '~/components/general/document-signing/document-signing-date-field'; import { DocumentSigningDropdownField } from '~/components/general/document-signing/document-signing-dropdown-field'; import { DocumentSigningEmailField } from '~/components/general/document-signing/document-signing-email-field'; import { DocumentSigningInitialsField } from '~/components/general/document-signing/document-signing-initials-field'; import { DocumentSigningNameField } from '~/components/general/document-signing/document-signing-name-field'; import { DocumentSigningNumberField } from '~/components/general/document-signing/document-signing-number-field'; import { useRequiredDocumentSigningContext } from '~/components/general/document-signing/document-signing-provider'; import { DocumentSigningRadioField } from '~/components/general/document-signing/document-signing-radio-field'; import { DocumentSigningSignatureField } from '~/components/general/document-signing/document-signing-signature-field'; import { DocumentSigningTextField } from '~/components/general/document-signing/document-signing-text-field'; import { DocumentSigningRecipientProvider } from '../document-signing/document-signing-recipient-provider'; export type DirectTemplateSigningFormProps = { flowStep: DocumentFlowStep; directRecipient: Recipient; directRecipientFields: Field[]; template: Omit; onSubmit: (_data: DirectTemplateLocalField[]) => Promise; }; export type DirectTemplateLocalField = Field & { signedValue?: TSignFieldWithTokenMutationSchema; signature?: Signature; }; export const DirectTemplateSigningForm = ({ flowStep, directRecipient, directRecipientFields, template, onSubmit, }: DirectTemplateSigningFormProps) => { const { fullName, signature, setFullName, setSignature } = useRequiredDocumentSigningContext(); const [localFields, setLocalFields] = useState(directRecipientFields); const [validateUninsertedFields, setValidateUninsertedFields] = useState(false); const [isSubmitting, setIsSubmitting] = useState(false); const highestPageNumber = Math.max(...localFields.map((field) => field.page)); const fieldsRequiringValidation = useMemo(() => { return localFields.filter((field) => isFieldUnsignedAndRequired(field)); }, [localFields]); const { currentStep, totalSteps, previousStep } = useStep(); const onSignField = (value: TSignFieldWithTokenMutationSchema) => { setLocalFields( localFields.map((field) => { if (field.id !== value.fieldId) { return field; } const tempField: DirectTemplateLocalField = { ...field, customText: value.value ?? '', inserted: true, signedValue: value, }; if (field.type === FieldType.SIGNATURE) { tempField.signature = { id: 1, created: new Date(), recipientId: 1, fieldId: 1, signatureImageAsBase64: value.value?.startsWith('data:') ? value.value : null, typedSignature: value.value && !value.value.startsWith('data:') ? value.value : null, } satisfies Signature; } if (field.type === FieldType.DATE) { tempField.customText = DateTime.now() .setZone(template.templateMeta?.timezone ?? DEFAULT_DOCUMENT_TIME_ZONE) .toFormat(template.templateMeta?.dateFormat ?? DEFAULT_DOCUMENT_DATE_FORMAT); } return tempField; }), ); }; const onUnsignField = (value: TRemovedSignedFieldWithTokenMutationSchema) => { setLocalFields( localFields.map((field) => { if (field.id !== value.fieldId) { return field; } return { ...field, customText: '', inserted: false, signedValue: undefined, signature: undefined, }; }), ); }; const uninsertedFields = useMemo(() => { return sortFieldsByPosition(fieldsRequiringValidation); }, [localFields]); const fieldsValidated = () => { setValidateUninsertedFields(true); validateFieldsInserted(fieldsRequiringValidation); }; const handleSubmit = async () => { setValidateUninsertedFields(true); const isFieldsValid = validateFieldsInserted(fieldsRequiringValidation); if (!isFieldsValid) { return; } setIsSubmitting(true); try { await onSubmit(localFields); } catch { setIsSubmitting(false); } // Do not reset to false since we do a redirect. }; useEffect(() => { const updatedFields = [...localFields]; localFields.forEach((field) => { const index = updatedFields.findIndex((f) => f.id === field.id); let value = ''; match(field.type) .with(FieldType.TEXT, () => { const meta = field.fieldMeta ? ZTextFieldMeta.safeParse(field.fieldMeta) : null; if (meta?.success) { value = meta.data.text ?? ''; } }) .with(FieldType.NUMBER, () => { const meta = field.fieldMeta ? ZNumberFieldMeta.safeParse(field.fieldMeta) : null; if (meta?.success) { value = meta.data.value ?? ''; } }) .with(FieldType.DROPDOWN, () => { const meta = field.fieldMeta ? ZDropdownFieldMeta.safeParse(field.fieldMeta) : null; if (meta?.success) { value = meta.data.defaultValue ?? ''; } }); if (value) { const signedValue = { token: directRecipient.token, fieldId: field.id, value, }; updatedFields[index] = { ...field, customText: value, inserted: true, signedValue, }; } }); setLocalFields(updatedFields); }, []); return ( {validateUninsertedFields && uninsertedFields[0] && ( Click to insert field )} {localFields.map((field) => match(field.type) .with(FieldType.SIGNATURE, () => ( )) .with(FieldType.INITIALS, () => ( )) .with(FieldType.NAME, () => ( )) .with(FieldType.DATE, () => ( )) .with(FieldType.EMAIL, () => ( )) .with(FieldType.TEXT, () => { const parsedFieldMeta = field.fieldMeta ? ZTextFieldMeta.parse(field.fieldMeta) : null; return ( ); }) .with(FieldType.NUMBER, () => { const parsedFieldMeta = field.fieldMeta ? ZNumberFieldMeta.parse(field.fieldMeta) : null; return ( ); }) .with(FieldType.DROPDOWN, () => { const parsedFieldMeta = field.fieldMeta ? ZDropdownFieldMeta.parse(field.fieldMeta) : null; return ( ); }) .with(FieldType.RADIO, () => { const parsedFieldMeta = field.fieldMeta ? ZRadioFieldMeta.parse(field.fieldMeta) : null; return ( ); }) .with(FieldType.CHECKBOX, () => { const parsedFieldMeta = field.fieldMeta ? ZCheckboxFieldMeta.parse(field.fieldMeta) : null; return ( ); }) .otherwise(() => null), )}
setFullName(e.target.value.trimStart())} />
setSignature(value)} typedSignatureEnabled={template.templateMeta?.typedSignatureEnabled} uploadSignatureEnabled={template.templateMeta?.uploadSignatureEnabled} drawSignatureEnabled={template.templateMeta?.drawSignatureEnabled} />
handleSubmit()} documentTitle={template.title} fields={localFields} fieldsValidated={fieldsValidated} recipient={directRecipient} />
); };