import { useCallback, useRef } from 'react'; import type { DropResult, SensorAPI } from '@hello-pangea/dnd'; import { DragDropContext, Draggable, Droppable } from '@hello-pangea/dnd'; import { msg } from '@lingui/core/macro'; import { useLingui } from '@lingui/react'; import { Trans } from '@lingui/react/macro'; import { DocumentSigningOrder, RecipientRole } from '@prisma/client'; import { motion } from 'framer-motion'; import { GripVertical, HelpCircle, Plus, Trash } from 'lucide-react'; import { nanoid } from 'nanoid'; import type { Control } from 'react-hook-form'; import { useFieldArray, useFormContext, useFormState } from 'react-hook-form'; import { RecipientRoleSelect } from '@documenso/ui/components/recipient/recipient-role-select'; import { cn } from '@documenso/ui/lib/utils'; import { Button } from '@documenso/ui/primitives/button'; import { Checkbox } from '@documenso/ui/primitives/checkbox'; import { FormControl, FormField, FormItem, FormLabel, FormMessage, } from '@documenso/ui/primitives/form/form'; import { Input } from '@documenso/ui/primitives/input'; import { Tooltip, TooltipContent, TooltipTrigger } from '@documenso/ui/primitives/tooltip'; import { useConfigureDocument } from './configure-document-context'; import type { TConfigureEmbedFormSchema } from './configure-document-view.types'; // Define a type for signer items type SignerItem = TConfigureEmbedFormSchema['signers'][number]; export interface ConfigureDocumentRecipientsProps { control: Control; isSubmitting: boolean; } export const ConfigureDocumentRecipients = ({ control, isSubmitting, }: ConfigureDocumentRecipientsProps) => { const { _ } = useLingui(); const { isTemplate } = useConfigureDocument(); const $sensorApi = useRef(null); const { fields: signers, append: appendSigner, remove: removeSigner, replace, move, } = useFieldArray({ control, name: 'signers', }); const { getValues, watch, setValue } = useFormContext(); const signingOrder = watch('meta.signingOrder'); const { errors } = useFormState({ control, }); const onAddSigner = useCallback(() => { const signerNumber = signers.length + 1; const recipientSigningOrder = signers.length > 0 ? (signers[signers.length - 1]?.signingOrder || 0) + 1 : 1; appendSigner({ formId: nanoid(8), name: isTemplate ? `Recipient ${signerNumber}` : '', email: isTemplate ? `recipient.${signerNumber}@document.com` : '', role: RecipientRole.SIGNER, signingOrder: signingOrder === DocumentSigningOrder.SEQUENTIAL ? recipientSigningOrder : undefined, }); }, [appendSigner, signers]); const isSigningOrderEnabled = signingOrder === DocumentSigningOrder.SEQUENTIAL; const handleSigningOrderChange = useCallback( (index: number, newOrderString: string) => { const trimmedOrderString = newOrderString.trim(); if (!trimmedOrderString) { return; } const newOrder = Number(trimmedOrderString); if (!Number.isInteger(newOrder) || newOrder < 1) { return; } // Get current form values to preserve unsaved input data const currentSigners = getValues('signers') || [...signers]; const signer = currentSigners[index]; // Remove signer from current position and insert at new position const remainingSigners = currentSigners.filter((_: unknown, idx: number) => idx !== index); const newPosition = Math.min(Math.max(0, newOrder - 1), currentSigners.length - 1); remainingSigners.splice(newPosition, 0, signer); // Update signing order for each item const updatedSigners = remainingSigners.map((s: SignerItem, idx: number) => ({ ...s, signingOrder: signingOrder === DocumentSigningOrder.SEQUENTIAL ? idx + 1 : undefined, })); // Update the form replace(updatedSigners); }, [signers, replace, getValues], ); const onDragEnd = useCallback( (result: DropResult) => { if (!result.destination) return; // Use the move function from useFieldArray which preserves input values move(result.source.index, result.destination.index); // Update signing orders after move const currentSigners = getValues('signers'); const updatedSigners = currentSigners.map((signer: SignerItem, index: number) => ({ ...signer, signingOrder: signingOrder === DocumentSigningOrder.SEQUENTIAL ? index + 1 : undefined, })); // Update the form with new ordering replace(updatedSigners); }, [move, replace, getValues], ); const onSigningOrderChange = (signingOrder: DocumentSigningOrder) => { setValue('meta.signingOrder', signingOrder); if (signingOrder === DocumentSigningOrder.SEQUENTIAL) { signers.forEach((_signer, index) => { setValue(`signers.${index}.signingOrder`, index + 1); }); } }; return (

Recipients

Add signers and configure signing preferences

( onSigningOrderChange( checked ? DocumentSigningOrder.SEQUENTIAL : DocumentSigningOrder.PARALLEL, ) } disabled={isSubmitting} /> Enable signing order )} /> (
Allow signers to dictate next signer

When enabled, signers can choose who should sign next in the sequence instead of following the predefined order.

)} /> { $sensorApi.current = api; }, ]} > {(provided) => (
{signers.map((signer, index) => ( {(provided, snapshot) => (
{isSigningOrderEnabled && ( ( { field.onChange(e); }} onBlur={(e) => { field.onBlur(); handleSigningOrderChange(index, e.target.value); }} /> )} /> )} ( Name )} /> ( Email )} /> ( Role )} />
)}
))} {provided.placeholder}
)}
); };