'use client'; import { useEffect, useMemo, useState, useTransition } from 'react'; import { useRouter } from 'next/navigation'; import { Loader } from 'lucide-react'; import type { Recipient } from '@documenso/prisma/client'; import type { FieldWithSignature } from '@documenso/prisma/types/field-with-signature'; import { trpc } from '@documenso/trpc/react'; import { Button } from '@documenso/ui/primitives/button'; import { Dialog, DialogContent, DialogFooter, DialogTitle } from '@documenso/ui/primitives/dialog'; import { Label } from '@documenso/ui/primitives/label'; import { SignaturePad } from '@documenso/ui/primitives/signature-pad'; import { useToast } from '@documenso/ui/primitives/use-toast'; import { useRequiredSigningContext } from './provider'; import { SigningFieldContainer } from './signing-field-container'; type SignatureFieldState = 'empty' | 'signed-image' | 'signed-text'; export type SignatureFieldProps = { field: FieldWithSignature; recipient: Recipient; }; export const SignatureField = ({ field, recipient }: SignatureFieldProps) => { const router = useRouter(); const { toast } = useToast(); const { signature: providedSignature, setSignature: setProvidedSignature } = useRequiredSigningContext(); const [isPending, startTransition] = useTransition(); const { mutateAsync: signFieldWithToken, isLoading: isSignFieldWithTokenLoading } = trpc.field.signFieldWithToken.useMutation(); const { mutateAsync: removeSignedFieldWithToken, isLoading: isRemoveSignedFieldWithTokenLoading, } = trpc.field.removeSignedFieldWithToken.useMutation(); const { Signature: signature } = field; const isLoading = isSignFieldWithTokenLoading || isRemoveSignedFieldWithTokenLoading || isPending; const [showSignatureModal, setShowSignatureModal] = useState(false); const [localSignature, setLocalSignature] = useState(null); const [isLocalSignatureSet, setIsLocalSignatureSet] = useState(false); const state = useMemo(() => { if (!field.inserted) { return 'empty'; } if (signature?.signatureImageAsBase64) { return 'signed-image'; } return 'signed-text'; }, [field.inserted, signature?.signatureImageAsBase64]); useEffect(() => { if (!showSignatureModal && !isLocalSignatureSet) { setLocalSignature(null); } }, [showSignatureModal, isLocalSignatureSet]); const onSign = async (source: 'local' | 'provider' = 'provider') => { try { if (!providedSignature && !localSignature) { setIsLocalSignatureSet(false); setShowSignatureModal(true); return; } const value = source === 'local' && localSignature ? localSignature : providedSignature ?? ''; if (!value) { return; } await signFieldWithToken({ token: recipient.token, fieldId: field.id, value, isBase64: true, }); if (source === 'local' && !providedSignature) { setProvidedSignature(localSignature); } setLocalSignature(null); startTransition(() => router.refresh()); } catch (err) { console.error(err); toast({ title: 'Error', description: 'An error occurred while signing the document.', variant: 'destructive', }); } }; const onRemove = async () => { try { await removeSignedFieldWithToken({ token: recipient.token, fieldId: field.id, }); startTransition(() => router.refresh()); } catch (err) { console.error(err); toast({ title: 'Error', description: 'An error occurred while removing the signature.', variant: 'destructive', }); } }; return ( {isLoading && (
)} {state === 'empty' && (

Signature

)} {state === 'signed-image' && signature?.signatureImageAsBase64 && ( {`Signature )} {state === 'signed-text' && (

{/* This optional chaining is intentional, we don't want to move the check into the condition above */} {signature?.typedSignature}

)} Sign as {recipient.name}{' '} ({recipient.email})
setLocalSignature(value)} />
); };