'use client'; import React, { useEffect, useId, useMemo, useState } from 'react'; import { useRouter } from 'next/navigation'; import { zodResolver } from '@hookform/resolvers/zod'; import { motion } from 'framer-motion'; import { Link2Icon, Plus, Trash } from 'lucide-react'; import { useSession } from 'next-auth/react'; import { useFieldArray, useForm } from 'react-hook-form'; 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 { generateRecipientPlaceholder } from '@documenso/lib/utils/templates'; import type { TemplateDirectLink } from '@documenso/prisma/client'; import { type Field, type Recipient, RecipientRole } from '@documenso/prisma/client'; import type { TemplateWithDetails } from '@documenso/prisma/types/template'; 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 '@documenso/ui/primitives/button'; import { FormErrorMessage } from '@documenso/ui/primitives/form/form-error-message'; import { Input } from '@documenso/ui/primitives/input'; import { useToast } from '@documenso/ui/primitives/use-toast'; import { Checkbox } from '../checkbox'; import { DocumentFlowFormContainerActions, DocumentFlowFormContainerContent, DocumentFlowFormContainerFooter, DocumentFlowFormContainerHeader, DocumentFlowFormContainerStep, } from '../document-flow/document-flow-root'; import { ShowFieldItem } from '../document-flow/show-field-item'; import type { DocumentFlowStep } from '../document-flow/types'; import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from '../form/form'; import { useStep } from '../stepper'; import { Tooltip, TooltipContent, TooltipTrigger } from '../tooltip'; import type { TAddTemplatePlacholderRecipientsFormSchema } from './add-template-placeholder-recipients.types'; import { ZAddTemplatePlacholderRecipientsFormSchema } from './add-template-placeholder-recipients.types'; export type AddTemplatePlaceholderRecipientsFormProps = { documentFlow: DocumentFlowStep; recipients: Recipient[]; fields: Field[]; templateDirectLink: TemplateDirectLink | null; isEnterprise: boolean; isDocumentPdfLoaded: boolean; template: TemplateWithDetails; onSubmit: (_data: TAddTemplatePlacholderRecipientsFormSchema) => void; }; export const AddTemplatePlaceholderRecipientsFormPartial = ({ documentFlow, isEnterprise, recipients, templateDirectLink, fields, isDocumentPdfLoaded, onSubmit, template, }: AddTemplatePlaceholderRecipientsFormProps) => { const { toast } = useToast(); const router = useRouter(); const initialId = useId(); const { data: session } = useSession(); const user = session?.user; const [placeholderRecipientCount, setPlaceholderRecipientCount] = useState(() => recipients.length > 1 ? recipients.length + 1 : 2, ); const { currentStep, totalSteps, previousStep } = useStep(); const utils = trpc.useUtils(); const { mutateAsync: addTemplateSigners } = trpc.recipient.addTemplateSigners.useMutation({ ...DO_NOT_INVALIDATE_QUERY_ON_MUTATION, onSuccess: (newData) => { utils.template.getTemplateWithDetailsById.setData( { id: template.id, }, (oldData) => ({ ...(oldData || template), ...newData }), ); }, }); const { mutateAsync: removeTemplateSigner } = trpc.recipient.removeTemplateSigner.useMutation({ ...DO_NOT_INVALIDATE_QUERY_ON_MUTATION, onSuccess: (deletedRecipient) => { utils.template.getTemplateWithDetailsById.setData( { id: template.id, }, (oldData) => { if (!oldData) return template; return { ...oldData, recipients: oldData.Recipient.filter((r) => r.id !== deletedRecipient.id), }; }, ); }, }); const generateDefaultFormSigners = () => { if (recipients.length === 0) { return [ { formId: initialId, role: RecipientRole.SIGNER, actionAuth: undefined, ...generateRecipientPlaceholder(1), }, ]; } return 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, })); }; const form = useForm({ resolver: zodResolver(ZAddTemplatePlacholderRecipientsFormSchema), defaultValues: { signers: generateDefaultFormSigners(), }, }); useEffect(() => { form.reset({ signers: generateDefaultFormSigners(), }); // eslint-disable-next-line react-hooks/exhaustive-deps }, [recipients]); // 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 { formState: { errors, isSubmitting }, control, } = form; const onFormSubmit = form.handleSubmit(onSubmit); const { append: appendSigner, fields: signers, remove: removeSigner, } = useFieldArray({ control, name: 'signers', }); const onAddPlaceholderSelfRecipient = () => { appendSigner({ formId: nanoid(12), name: user?.name ?? '', email: user?.email ?? '', role: RecipientRole.SIGNER, }); }; const onAddPlaceholderRecipient = () => { appendSigner({ formId: nanoid(12), role: RecipientRole.SIGNER, ...generateRecipientPlaceholder(placeholderRecipientCount), }); setPlaceholderRecipientCount((count) => count + 1); }; const isSignerDirectRecipient = ( signer: TAddTemplatePlacholderRecipientsFormSchema['signers'][number], ): boolean => { return ( templateDirectLink !== null && signer.nativeId === templateDirectLink?.directTemplateRecipientId ); }; const handleOnBlur = async (index: number) => { try { const currentSigner = form.getValues(`signers.${index}`); console.log('currentSigner', currentSigner); if (!currentSigner.email) { return; } await addTemplateSigners({ templateId: template.id, teamId: template.teamId ?? undefined, signers: form.getValues('signers'), }); router.refresh(); } catch (e) { console.error(e); toast({ title: 'Error', description: 'An error occurred while updating the template recipient.', variant: 'destructive', }); } }; const handleRemoveSigner = async (index: number) => { const signer = signers[index]; if (!signer) { return; } removeSigner(index); if (signer.nativeId) { await removeTemplateSigner({ templateId: template.id, teamId: template.teamId ?? undefined, 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 && isEnterprise && ( ( )} /> )} ( { field.onChange(value); void handleOnBlur(index); }} disabled={isSubmitting} hideCCRecipients={isSignerDirectRecipient(signer)} /> )} /> {isSignerDirectRecipient(signer) ? (

Direct link receiver

This field cannot be modified or deleted. When you share this template's direct link or add it to your public profile, anyone who accesses it can input their name and email, and fill in the fields assigned to them.

) : ( )}
))}
{!alwaysShowAdvancedSettings && isEnterprise && (
setShowAdvancedSettings(Boolean(value))} />
)}
1} onGoBackClick={() => previousStep()} onGoNextClick={() => void onFormSubmit()} /> ); };