mirror of
https://github.com/documenso/documenso.git
synced 2025-11-09 20:12:31 +10:00
236 lines
7.1 KiB
TypeScript
236 lines
7.1 KiB
TypeScript
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 {
|
|
const payload = {
|
|
token: recipient.token,
|
|
documentId: mapSecondaryIdToDocumentId(envelope.secondaryId),
|
|
authOptions: accessAuthOptions,
|
|
...(nextSigner?.email && nextSigner?.name ? { nextSigner } : {}),
|
|
};
|
|
|
|
await completeDocument(payload);
|
|
|
|
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 (
|
|
<DocumentSigningCompleteDialog
|
|
isSubmitting={isPending}
|
|
directTemplatePayload={directTemplatePayload}
|
|
onSignatureComplete={
|
|
isDirectTemplate ? handleDirectTemplateCompleteClick : handleOnCompleteClick
|
|
}
|
|
documentTitle={envelope.title}
|
|
fields={recipientFieldsRemaining}
|
|
fieldsValidated={handleOnNextFieldClick}
|
|
recipient={recipient}
|
|
allowDictateNextSigner={Boolean(
|
|
nextRecipient && envelope.documentMeta.allowDictateNextSigner,
|
|
)}
|
|
defaultNextSigner={
|
|
nextRecipient ? { name: nextRecipient.name, email: nextRecipient.email } : undefined
|
|
}
|
|
buttonSize="sm"
|
|
position="center"
|
|
/>
|
|
);
|
|
};
|