mirror of
https://github.com/documenso/documenso.git
synced 2026-07-26 18:04:55 +10:00
f82bf97480
For document signing embeds, the hash-provided name and email should only be used when the recipient doesn't already have values set. For template signing, the hash values are always allowed. Also makes the email input editable in V1 embeds when the recipient has no email, matching V2 behavior. Ref: documenso/embeds#53
252 lines
6.9 KiB
TypeScript
252 lines
6.9 KiB
TypeScript
import { useEffect, useLayoutEffect, useState } from 'react';
|
|
|
|
import { useLingui } from '@lingui/react';
|
|
import { EnvelopeType } from '@prisma/client';
|
|
|
|
import { ZSignDocumentEmbedDataSchema } from '@documenso/lib/types/embed-document-sign-schema';
|
|
import { mapSecondaryIdToDocumentId } from '@documenso/lib/utils/envelope';
|
|
|
|
import { injectCss } from '~/utils/css-vars';
|
|
|
|
import { DocumentSigningPageViewV2 } from '../general/document-signing/document-signing-page-view-v2';
|
|
import { useRequiredEnvelopeSigningContext } from '../general/document-signing/envelope-signing-provider';
|
|
import { EmbedClientLoading } from './embed-client-loading';
|
|
import { EmbedDocumentCompleted } from './embed-document-completed';
|
|
import { EmbedDocumentRejected } from './embed-document-rejected';
|
|
import { EmbedSigningProvider } from './embed-signing-context';
|
|
|
|
export type EmbedSignDocumentV2ClientPageProps = {
|
|
hidePoweredBy?: boolean;
|
|
allowWhitelabelling?: boolean;
|
|
};
|
|
|
|
export const EmbedSignDocumentV2ClientPage = ({
|
|
hidePoweredBy = false,
|
|
allowWhitelabelling = false,
|
|
}: EmbedSignDocumentV2ClientPageProps) => {
|
|
const { _ } = useLingui();
|
|
|
|
const { envelope, recipient, envelopeData, setFullName, setEmail, fullName, email } =
|
|
useRequiredEnvelopeSigningContext();
|
|
|
|
const { isCompleted, isRejected, recipientSignature } = envelopeData;
|
|
|
|
// !: Not used at the moment, may be removed in the future.
|
|
// const [hasDocumentLoaded, setHasDocumentLoaded] = useState(false);
|
|
const [hasFinishedInit, setHasFinishedInit] = useState(false);
|
|
const [allowDocumentRejection, setAllowDocumentRejection] = useState(false);
|
|
const [isNameLocked, setIsNameLocked] = useState(false);
|
|
const [isEmailLocked, setIsEmailLocked] = useState(
|
|
envelope.type === EnvelopeType.DOCUMENT && !!email,
|
|
);
|
|
|
|
const onDocumentCompleted = (data: {
|
|
token: string;
|
|
documentId: number;
|
|
envelopeId: string;
|
|
recipientId: number;
|
|
}) => {
|
|
if (window.parent) {
|
|
window.parent.postMessage(
|
|
{
|
|
action: 'document-completed',
|
|
data,
|
|
},
|
|
'*',
|
|
);
|
|
}
|
|
};
|
|
|
|
const onDocumentError = () => {
|
|
if (window.parent) {
|
|
window.parent.postMessage(
|
|
{
|
|
action: 'document-error',
|
|
data: null,
|
|
},
|
|
'*',
|
|
);
|
|
}
|
|
};
|
|
|
|
const onDocumentReady = () => {
|
|
if (window.parent) {
|
|
window.parent.postMessage(
|
|
{
|
|
action: 'document-ready',
|
|
data: null,
|
|
},
|
|
'*',
|
|
);
|
|
}
|
|
};
|
|
|
|
const onFieldSigned = (data: { fieldId?: number; value?: string; isBase64?: boolean }) => {
|
|
if (window.parent) {
|
|
window.parent.postMessage(
|
|
{
|
|
action: 'field-signed',
|
|
data,
|
|
},
|
|
'*',
|
|
);
|
|
}
|
|
};
|
|
|
|
const onFieldUnsigned = (data: { fieldId?: number }) => {
|
|
if (window.parent) {
|
|
window.parent.postMessage(
|
|
{
|
|
action: 'field-unsigned',
|
|
data,
|
|
},
|
|
'*',
|
|
);
|
|
}
|
|
};
|
|
|
|
const onDocumentRejected = (data: {
|
|
token: string;
|
|
documentId: number;
|
|
envelopeId: string;
|
|
recipientId: number;
|
|
reason?: string;
|
|
}) => {
|
|
if (window.parent) {
|
|
window.parent.postMessage(
|
|
{
|
|
action: 'document-rejected',
|
|
data,
|
|
},
|
|
'*',
|
|
);
|
|
}
|
|
};
|
|
|
|
useLayoutEffect(() => {
|
|
const hash = window.location.hash.slice(1);
|
|
|
|
try {
|
|
const data = ZSignDocumentEmbedDataSchema.parse(JSON.parse(decodeURIComponent(atob(hash))));
|
|
|
|
if (!isCompleted && data.name) {
|
|
// For documents, only use the hash name if the recipient doesn't already have one.
|
|
// For templates, always allow the hash name to be used.
|
|
if (envelope.type === EnvelopeType.TEMPLATE || !fullName) {
|
|
setFullName(data.name);
|
|
}
|
|
}
|
|
|
|
// Since a recipient can be provided a name we can lock it without requiring
|
|
// a to be provided by the parent application, unlike direct templates.
|
|
setIsNameLocked(!!data.lockName);
|
|
|
|
if (!isCompleted && data.email) {
|
|
// For documents, only use the hash email if the recipient doesn't already have one.
|
|
// For templates, always allow the hash email to be used.
|
|
if (envelope.type === EnvelopeType.TEMPLATE || !email) {
|
|
setEmail(data.email);
|
|
setIsEmailLocked(!!data.lockEmail);
|
|
}
|
|
}
|
|
|
|
setAllowDocumentRejection(!!data.allowDocumentRejection);
|
|
|
|
if (data.darkModeDisabled) {
|
|
document.documentElement.classList.add('dark-mode-disabled');
|
|
}
|
|
|
|
if (allowWhitelabelling) {
|
|
injectCss({
|
|
css: data.css,
|
|
cssVars: data.cssVars,
|
|
});
|
|
}
|
|
} catch (err) {
|
|
console.error(err);
|
|
}
|
|
|
|
setHasFinishedInit(true);
|
|
|
|
// !: While the setters are stable we still want to ensure we're avoiding
|
|
// !: re-renders.
|
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
}, [allowWhitelabelling]);
|
|
|
|
useEffect(() => {
|
|
if (hasFinishedInit) {
|
|
onDocumentReady();
|
|
}
|
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
}, [hasFinishedInit]);
|
|
|
|
// Listen for document completion events from the envelope signing context
|
|
useEffect(() => {
|
|
if (isCompleted) {
|
|
onDocumentCompleted({
|
|
token: recipient.token,
|
|
envelopeId: envelope.id,
|
|
documentId: mapSecondaryIdToDocumentId(envelope.secondaryId),
|
|
recipientId: recipient.id,
|
|
});
|
|
}
|
|
}, [isCompleted, envelope.id, recipient.id, recipient.token]);
|
|
|
|
// Listen for document rejection events
|
|
useEffect(() => {
|
|
if (isRejected) {
|
|
onDocumentRejected({
|
|
token: recipient.token,
|
|
envelopeId: envelope.id,
|
|
documentId: mapSecondaryIdToDocumentId(envelope.secondaryId),
|
|
recipientId: recipient.id,
|
|
});
|
|
}
|
|
}, [isRejected, envelope.id, recipient.id, recipient.token]);
|
|
|
|
if (isRejected) {
|
|
return <EmbedDocumentRejected />;
|
|
}
|
|
|
|
if (isCompleted) {
|
|
return (
|
|
<EmbedDocumentCompleted
|
|
name={fullName}
|
|
signature={
|
|
recipientSignature
|
|
? {
|
|
id: 1,
|
|
fieldId: 1,
|
|
recipientId: recipient.id,
|
|
created: new Date(),
|
|
signatureImageAsBase64: recipientSignature.signatureImageAsBase64,
|
|
typedSignature: recipientSignature.typedSignature,
|
|
}
|
|
: undefined
|
|
}
|
|
/>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<EmbedSigningProvider
|
|
isNameLocked={isNameLocked}
|
|
isEmailLocked={isEmailLocked}
|
|
hidePoweredBy={hidePoweredBy}
|
|
allowDocumentRejection={allowDocumentRejection}
|
|
onDocumentCompleted={onDocumentCompleted}
|
|
onDocumentError={onDocumentError}
|
|
onDocumentRejected={onDocumentRejected}
|
|
onDocumentReady={onDocumentReady}
|
|
onFieldSigned={onFieldSigned}
|
|
onFieldUnsigned={onFieldUnsigned}
|
|
>
|
|
<div className="embed--Root relative">
|
|
{!hasFinishedInit && <EmbedClientLoading />}
|
|
|
|
<DocumentSigningPageViewV2 />
|
|
</div>
|
|
</EmbedSigningProvider>
|
|
);
|
|
};
|