import { useMemo } from 'react'; import { useLingui } from '@lingui/react/macro'; import { FieldType } from '@prisma/client'; import { useNavigate, useRevalidator, useSearchParams } from 'react-router'; import { useAnalytics } from '@documenso/lib/client-only/hooks/use-analytics'; import { useCurrentEnvelopeRender } from '@documenso/lib/client-only/providers/envelope-render-provider'; import { isBase64Image } from '@documenso/lib/constants/signatures'; import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error'; import type { TRecipientAccessAuth } from '@documenso/lib/types/document-auth'; import { mapSecondaryIdToDocumentId } from '@documenso/lib/utils/envelope'; import { trpc } from '@documenso/trpc/react'; import { useToast } from '@documenso/ui/primitives/use-toast'; import { useEmbedSigningContext } from '~/components/embed/embed-signing-context'; import { DocumentSigningCompleteDialog } from '../document-signing/document-signing-complete-dialog'; import { useRequiredEnvelopeSigningContext } from '../document-signing/envelope-signing-provider'; export const EnvelopeSignerCompleteDialog = () => { const navigate = useNavigate(); const analytics = useAnalytics(); const { t } = useLingui(); const { toast } = useToast(); const { revalidate } = useRevalidator(); const [searchParams] = useSearchParams(); const { isDirectTemplate, envelope, setShowPendingFieldTooltip, recipientFieldsRemaining, recipient, nextRecipient, email, fullName, } = useRequiredEnvelopeSigningContext(); const { currentEnvelopeItem, setCurrentEnvelopeItem } = useCurrentEnvelopeRender(); const { onDocumentCompleted, onDocumentError } = useEmbedSigningContext() || {}; const { mutateAsync: completeDocument, isPending } = trpc.recipient.completeDocumentWithToken.useMutation(); const { mutateAsync: createDocumentFromDirectTemplate } = trpc.template.createDocumentFromDirectTemplate.useMutation(); const handleOnNextFieldClick = () => { const nextField = recipientFieldsRemaining[0]; if (!nextField) { setShowPendingFieldTooltip(false); return; } if (nextField.envelopeItemId !== currentEnvelopeItem?.id) { setCurrentEnvelopeItem(nextField.envelopeItemId); } const fieldTooltip = document.querySelector(`#field-tooltip`); if (fieldTooltip) { fieldTooltip.scrollIntoView({ behavior: 'smooth', block: 'center' }); } setShowPendingFieldTooltip(true); }; const handleOnCompleteClick = async ( nextSigner?: { name: string; email: string }, accessAuthOptions?: TRecipientAccessAuth, ) => { try { await completeDocument({ token: recipient.token, documentId: mapSecondaryIdToDocumentId(envelope.secondaryId), accessAuthOptions, ...(nextSigner?.email && nextSigner?.name ? { nextSigner } : {}), }); analytics.capture('App: Recipient has completed signing', { signerId: recipient.id, documentId: envelope.id, timestamp: new Date().toISOString(), }); if (onDocumentCompleted) { onDocumentCompleted({ token: recipient.token, documentId: mapSecondaryIdToDocumentId(envelope.secondaryId), recipientId: recipient.id, envelopeId: envelope.id, }); await revalidate(); return; } if (envelope.documentMeta.redirectUrl) { window.location.href = envelope.documentMeta.redirectUrl; } else { await navigate(`/sign/${recipient.token}/complete`); } } catch (err) { const error = AppError.parseError(err); if (error.code !== AppErrorCode.TWO_FACTOR_AUTH_FAILED) { toast({ title: t`Something went wrong`, description: t`We were unable to submit this document at this time. Please try again later.`, variant: 'destructive', }); onDocumentError?.(); } throw err; } }; /** * Direct template completion flow. */ const handleDirectTemplateCompleteClick = async ( nextSigner?: { name: string; email: string }, accessAuthOptions?: TRecipientAccessAuth, recipientDetails?: { name: string; email: string }, ) => { try { let directTemplateExternalId = searchParams?.get('externalId') || undefined; if (directTemplateExternalId) { directTemplateExternalId = decodeURIComponent(directTemplateExternalId); } if (!recipient.directToken) { throw new Error('Recipient direct token is required'); } const { token } = await createDocumentFromDirectTemplate({ directTemplateToken: recipient.directToken, // The direct template token is inserted into the recipient token for ease of use. directTemplateExternalId, directRecipientName: recipientDetails?.name || fullName, directRecipientEmail: recipientDetails?.email || email, templateUpdatedAt: envelope.updatedAt, signedFieldValues: recipient.fields.map((field) => { let value = field.customText; let isBase64 = false; if (field.type === FieldType.SIGNATURE && field.signature) { value = field.signature.signatureImageAsBase64 || field.signature.typedSignature || ''; isBase64 = isBase64Image(value); } return { token: '', fieldId: field.id, value, isBase64, }; }), nextSigner, }); const redirectUrl = envelope.documentMeta.redirectUrl; if (onDocumentCompleted) { await navigate({ pathname: `/embed/sign/${token}`, search: window.location.search, hash: window.location.hash, }); return; } if (redirectUrl) { window.location.href = redirectUrl; } else { await navigate(`/sign/${token}/complete`); } } catch (err) { console.log('err', err); toast({ title: t`Something went wrong`, description: t`We were unable to submit this document at this time. Please try again later.`, variant: 'destructive', }); onDocumentError?.(); throw err; } }; const directTemplatePayload = useMemo(() => { if (!isDirectTemplate) { return; } return { name: fullName, email: email, }; }, [email, fullName, isDirectTemplate]); return ( ); };