'use client'; import React, { useId, useMemo, useState } from 'react'; import { useRouter } from 'next/navigation'; import { zodResolver } from '@hookform/resolvers/zod'; import { motion } from 'framer-motion'; import { Plus, Trash } from 'lucide-react'; import { useSession } from 'next-auth/react'; import { useFieldArray, useForm } from 'react-hook-form'; import { useLimits } from '@documenso/ee/server-only/limits/provider/client'; import { DO_NOT_INVALIDATE_QUERY_ON_MUTATION } from '@documenso/lib/constants/trpc'; import { ZRecipientAuthOptionsSchema } from '@documenso/lib/types/document-auth'; import { nanoid } from '@documenso/lib/universal/id'; import type { Field, Recipient } from '@documenso/prisma/client'; import { RecipientRole, SendStatus } from '@documenso/prisma/client'; import type { DocumentWithData } from '@documenso/prisma/types/document-with-data'; import { trpc } from '@documenso/trpc/react'; import { AnimateGenericFadeInOut } from '@documenso/ui/components/animate/animate-generic-fade-in-out'; import { RecipientActionAuthSelect } from '@documenso/ui/components/recipient/recipient-action-auth-select'; import { RecipientRoleSelect } from '@documenso/ui/components/recipient/recipient-role-select'; import { cn } from '@documenso/ui/lib/utils'; import { Button } from '../button'; import { Checkbox } from '../checkbox'; import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from '../form/form'; import { FormErrorMessage } from '../form/form-error-message'; import { Input } from '../input'; import { useStep } from '../stepper'; import { useToast } from '../use-toast'; import type { TAddSignersFormSchema } from './add-signers.types'; import { ZAddSignersFormSchema } from './add-signers.types'; import { DocumentFlowFormContainerActions, DocumentFlowFormContainerContent, DocumentFlowFormContainerFooter, DocumentFlowFormContainerHeader, DocumentFlowFormContainerStep, } from './document-flow-root'; import { ShowFieldItem } from './show-field-item'; import type { DocumentFlowStep } from './types'; export type AddSignersFormProps = { documentFlow: DocumentFlowStep; document: DocumentWithData; recipients: Recipient[]; fields: Field[]; isDocumentEnterprise: boolean; onSubmit: (_data: TAddSignersFormSchema) => void; isDocumentPdfLoaded: boolean; teamId?: number; }; export const AddSignersFormPartial = ({ documentFlow, document, recipients, fields, isDocumentEnterprise, onSubmit, isDocumentPdfLoaded, teamId, }: AddSignersFormProps) => { const { toast } = useToast(); const { remaining } = useLimits(); const { data: session } = useSession(); const router = useRouter(); const user = session?.user; const initialId = useId(); const utils = trpc.useUtils(); const { currentStep, totalSteps, previousStep } = useStep(); const { mutateAsync: addSigners } = trpc.recipient.addSigners.useMutation({ ...DO_NOT_INVALIDATE_QUERY_ON_MUTATION, onSuccess: (newRecipients) => { utils.document.getDocumentWithDetailsById.setData( { id: document.id, teamId, }, /* TODO: Fix TS error */ (oldData) => ({ ...(oldData || document), Recipient: newRecipients }), ); }, }); const { mutateAsync: deleteSigner } = trpc.recipient.removeSigner.useMutation({ ...DO_NOT_INVALIDATE_QUERY_ON_MUTATION, onSuccess: (data) => { /* TODO: Add optimistic update */ console.log('removeSigner onSuccess', data); }, }); const form = useForm({ resolver: zodResolver(ZAddSignersFormSchema), defaultValues: { signers: recipients.length > 0 ? recipients.map((recipient) => ({ nativeId: recipient.id, formId: String(recipient.id), name: recipient.name, email: recipient.email, role: recipient.role, actionAuth: ZRecipientAuthOptionsSchema.parse(recipient.authOptions)?.actionAuth ?? undefined, })) : [ { formId: initialId, name: '', email: '', role: RecipientRole.SIGNER, actionAuth: undefined, }, ], }, }); // Always show advanced settings if any recipient has auth options. const alwaysShowAdvancedSettings = useMemo(() => { const recipientHasAuthOptions = recipients.find((recipient) => { const recipientAuthOptions = ZRecipientAuthOptionsSchema.parse(recipient.authOptions); return recipientAuthOptions?.accessAuth || recipientAuthOptions?.actionAuth; }); const formHasActionAuth = form.getValues('signers').find((signer) => signer.actionAuth); return recipientHasAuthOptions !== undefined || formHasActionAuth !== undefined; }, [recipients, form]); const [showAdvancedSettings, setShowAdvancedSettings] = useState(alwaysShowAdvancedSettings); const { setValue, formState: { errors, isSubmitting }, control, watch, } = form; const watchedSigners = watch('signers'); const onFormSubmit = form.handleSubmit(onSubmit); const { append: appendSigner, fields: signers, remove: removeSigner, } = useFieldArray({ control, name: 'signers', }); const emptySignerIndex = watchedSigners.findIndex((signer) => !signer.name && !signer.email); const isUserAlreadyARecipient = watchedSigners.some( (signer) => signer.email.toLowerCase() === user?.email?.toLowerCase(), ); const hasBeenSentToRecipientId = (id?: number) => { if (!id) { return false; } return recipients.some( (recipient) => recipient.id === id && recipient.sendStatus === SendStatus.SENT && recipient.role !== RecipientRole.CC, ); }; const onAddSigner = () => { appendSigner({ formId: nanoid(12), name: '', email: '', role: RecipientRole.SIGNER, actionAuth: undefined, }); }; /* TODO: Add self-signer to db on blur */ const onAddSelfSigner = () => { if (emptySignerIndex !== -1) { setValue(`signers.${emptySignerIndex}.name`, user?.name ?? ''); setValue(`signers.${emptySignerIndex}.email`, user?.email ?? ''); } else { appendSigner({ formId: nanoid(12), name: user?.name ?? '', email: user?.email ?? '', role: RecipientRole.SIGNER, actionAuth: undefined, }); } }; const onKeyDown = (event: React.KeyboardEvent) => { if (event.key === 'Enter' && event.target instanceof HTMLInputElement) { onAddSigner(); } }; const handleOnBlur = async (index: number) => { try { const currentSigner = form.getValues(`signers.${index}`); if (!currentSigner.email) { return; } await addSigners({ documentId: document.id, teamId: teamId, signers: form.getValues('signers').map((signer) => ({ ...signer, actionAuth: signer.actionAuth || null, })), }); const isNewSigner = !currentSigner.nativeId; toast({ title: isNewSigner ? 'Signer added' : 'Signer updated', description: isNewSigner ? 'The signer has been added to the document.' : 'The signer information has been updated.', }); router.refresh(); } catch (e) { console.error(e); toast({ title: 'Error', description: 'An error occurred while updating the document settings.', variant: 'destructive', }); } }; const handleRemoveSigner = async (index: number) => { const signer = signers[index]; if (hasBeenSentToRecipientId(signer.nativeId)) { toast({ title: 'Cannot remove signer', description: 'This signer has already received the document.', variant: 'destructive', }); return; } removeSigner(index); if (signer.nativeId) { await deleteSigner({ documentId: document.id, teamId: teamId, recipientId: signer.nativeId, }); toast({ title: 'Signer removed', description: 'The signer has been removed from the document.', }); } }; return ( <> {isDocumentPdfLoaded && fields.map((field, index) => ( ))}
{signers.map((signer, index) => ( ( {!showAdvancedSettings && index === 0 && ( Email )} void handleOnBlur(index)} /> )} /> ( {!showAdvancedSettings && index === 0 && Name} void handleOnBlur(index)} /> )} /> {showAdvancedSettings && isDocumentEnterprise && ( ( )} /> )} ( )} /> ))}
{!alwaysShowAdvancedSettings && isDocumentEnterprise && (
setShowAdvancedSettings(Boolean(value))} />
)}
void onFormSubmit()} /> ); };