fix: reviewed

This commit is contained in:
David Nguyen
2026-07-28 21:50:18 +09:00
parent 7f85388eb7
commit c767ec9fd9
74 changed files with 421 additions and 20 deletions
@@ -463,6 +463,7 @@ const response = await fetch(`${BASE_URL}/template/use`, {
typedSignatureEnabled: true,
uploadSignatureEnabled: false,
drawSignatureEnabled: true,
qrSignatureEnabled: true,
},
distributeDocument: true,
}),
@@ -483,6 +484,7 @@ const response = await fetch(`${BASE_URL}/template/use`, {
| `typedSignatureEnabled` | boolean | Allow typed signatures |
| `uploadSignatureEnabled` | boolean | Allow uploaded signature images |
| `drawSignatureEnabled` | boolean | Allow drawn signatures |
| `qrSignatureEnabled` | boolean | Allow QR code handoff to a mobile device |
---
@@ -390,6 +390,7 @@ const response = await fetch(`${BASE_URL}/template/update`, {
typedSignatureEnabled: true, // Allow typed signatures
drawSignatureEnabled: true, // Allow drawn signatures
uploadSignatureEnabled: false, // Disable uploaded signatures
qrSignatureEnabled: true, // Allow QR code handoff to a mobile device
},
}),
});
@@ -68,6 +68,7 @@ All webhook events share a common structure:
| `typedSignatureEnabled` | boolean | Whether typed signatures are allowed |
| `uploadSignatureEnabled` | boolean | Whether uploaded signatures are allowed |
| `drawSignatureEnabled` | boolean | Whether drawn signatures are allowed |
| `qrSignatureEnabled` | boolean | Whether QR code handoff to a mobile device is allowed |
| `language` | string | Document language code |
| `distributionMethod` | string | How document is distributed |
| `emailSettings` | object? | Custom email settings for this document |
@@ -138,6 +139,7 @@ Triggered when a new document is created.
"typedSignatureEnabled": true,
"uploadSignatureEnabled": true,
"drawSignatureEnabled": true,
"qrSignatureEnabled": true,
"language": "en",
"distributionMethod": "EMAIL",
"emailSettings": null
@@ -230,6 +232,7 @@ The document status changes to `PENDING` and recipients have `sendStatus: "SENT"
"typedSignatureEnabled": true,
"uploadSignatureEnabled": true,
"drawSignatureEnabled": true,
"qrSignatureEnabled": true,
"language": "en",
"distributionMethod": "EMAIL",
"emailSettings": null
@@ -422,6 +425,7 @@ The document status changes to `COMPLETED` and `completedAt` is set.
"typedSignatureEnabled": true,
"uploadSignatureEnabled": true,
"drawSignatureEnabled": true,
"qrSignatureEnabled": true,
"language": "en",
"distributionMethod": "EMAIL",
"emailSettings": null
@@ -599,6 +603,7 @@ This event is **not** triggered when a recipient hides a document from their inb
"typedSignatureEnabled": true,
"uploadSignatureEnabled": true,
"drawSignatureEnabled": true,
"qrSignatureEnabled": true,
"language": "en",
"distributionMethod": "EMAIL",
"emailSettings": null
@@ -13,10 +13,19 @@ export type SignFieldSignatureDialogProps = {
typedSignatureEnabled?: boolean;
uploadSignatureEnabled?: boolean;
drawSignatureEnabled?: boolean;
qrSignatureEnabled?: boolean;
};
export const SignFieldSignatureDialog = createCallable<SignFieldSignatureDialogProps, string | null>(
({ call, fullName, typedSignatureEnabled, uploadSignatureEnabled, drawSignatureEnabled, initialSignature }) => {
({
call,
fullName,
typedSignatureEnabled,
uploadSignatureEnabled,
drawSignatureEnabled,
qrSignatureEnabled,
initialSignature,
}) => {
const [localSignature, setLocalSignature] = useState(initialSignature);
return (
@@ -36,6 +45,7 @@ export const SignFieldSignatureDialog = createCallable<SignFieldSignatureDialogP
typedSignatureEnabled={typedSignatureEnabled}
uploadSignatureEnabled={uploadSignatureEnabled}
drawSignatureEnabled={drawSignatureEnabled}
qrSignatureEnabled={qrSignatureEnabled}
/>
</div>
@@ -470,6 +470,7 @@ export const EmbedDirectTemplateClientPage = ({
typedSignatureEnabled={metadata?.typedSignatureEnabled}
uploadSignatureEnabled={metadata?.uploadSignatureEnabled}
drawSignatureEnabled={metadata?.drawSignatureEnabled}
qrSignatureEnabled={metadata?.qrSignatureEnabled}
/>
</div>
)}
@@ -33,7 +33,12 @@ export type EmbedDocumentFieldsProps = {
fields: Field[];
metadata?: Pick<
DocumentMeta,
'timezone' | 'dateFormat' | 'typedSignatureEnabled' | 'uploadSignatureEnabled' | 'drawSignatureEnabled'
| 'timezone'
| 'dateFormat'
| 'typedSignatureEnabled'
| 'uploadSignatureEnabled'
| 'drawSignatureEnabled'
| 'qrSignatureEnabled'
> | null;
onSignField?: (value: TSignFieldWithTokenMutationSchema) => Promise<void> | void;
onUnsignField?: (value: TRemovedSignedFieldWithTokenMutationSchema) => Promise<void> | void;
@@ -53,6 +58,7 @@ export const EmbedDocumentFields = ({ fields, metadata, onSignField, onUnsignFie
typedSignatureEnabled={metadata?.typedSignatureEnabled}
uploadSignatureEnabled={metadata?.uploadSignatureEnabled}
drawSignatureEnabled={metadata?.drawSignatureEnabled}
qrSignatureEnabled={metadata?.qrSignatureEnabled}
/>
))
.with(FieldType.INITIALS, () => (
@@ -461,6 +461,7 @@ export const EmbedSignDocumentV1ClientPage = ({
typedSignatureEnabled={metadata?.typedSignatureEnabled}
uploadSignatureEnabled={metadata?.uploadSignatureEnabled}
drawSignatureEnabled={metadata?.drawSignatureEnabled}
qrSignatureEnabled={metadata?.qrSignatureEnabled}
/>
</div>
)}
@@ -313,6 +313,7 @@ export const MultiSignDocumentSigningView = ({
typedSignatureEnabled={document.documentMeta?.typedSignatureEnabled}
uploadSignatureEnabled={document.documentMeta?.uploadSignatureEnabled}
drawSignatureEnabled={document.documentMeta?.drawSignatureEnabled}
qrSignatureEnabled={document.documentMeta?.qrSignatureEnabled}
/>
</div>
)}
@@ -79,6 +79,7 @@ type SettingsSubset = Pick<
| 'typedSignatureEnabled'
| 'uploadSignatureEnabled'
| 'drawSignatureEnabled'
| 'qrSignatureEnabled'
| 'defaultRecipients'
| 'delegateDocumentOwnership'
| 'aiFeaturesEnabled'
@@ -156,6 +156,10 @@ export const AdminGlobalSettingsSection = ({
</DetailsValue>
</DetailsCard>
<DetailsCard label={<Trans>QR signature</Trans>}>
<DetailsValue>{booleanValue(settings.qrSignatureEnabled, inheritedSettings?.qrSignatureEnabled)}</DetailsValue>
</DetailsCard>
<DetailsCard label={<Trans>Branding</Trans>}>
<DetailsValue>{booleanValue(settings.brandingEnabled, inheritedSettings?.brandingEnabled)}</DetailsValue>
</DetailsCard>
@@ -269,6 +269,7 @@ export const DirectTemplateSigningForm = ({
typedSignatureEnabled={template.templateMeta?.typedSignatureEnabled}
uploadSignatureEnabled={template.templateMeta?.uploadSignatureEnabled}
drawSignatureEnabled={template.templateMeta?.drawSignatureEnabled}
qrSignatureEnabled={template.templateMeta?.qrSignatureEnabled}
/>
))
.with(FieldType.INITIALS, () => (
@@ -408,6 +409,7 @@ export const DirectTemplateSigningForm = ({
typedSignatureEnabled={template.templateMeta?.typedSignatureEnabled}
uploadSignatureEnabled={template.templateMeta?.uploadSignatureEnabled}
drawSignatureEnabled={template.templateMeta?.drawSignatureEnabled}
qrSignatureEnabled={template.templateMeta?.qrSignatureEnabled}
/>
</div>
</div>
@@ -254,6 +254,7 @@ export const DocumentSigningForm = ({
typedSignatureEnabled={document.documentMeta?.typedSignatureEnabled}
uploadSignatureEnabled={document.documentMeta?.uploadSignatureEnabled}
drawSignatureEnabled={document.documentMeta?.drawSignatureEnabled}
qrSignatureEnabled={document.documentMeta?.qrSignatureEnabled}
/>
</div>
)}
@@ -408,6 +408,7 @@ export const DocumentSigningPageViewV1 = ({
typedSignatureEnabled={documentMeta?.typedSignatureEnabled}
uploadSignatureEnabled={documentMeta?.uploadSignatureEnabled}
drawSignatureEnabled={documentMeta?.drawSignatureEnabled}
qrSignatureEnabled={documentMeta?.qrSignatureEnabled}
/>
))
.with(FieldType.INITIALS, () => <DocumentSigningInitialsField key={field.id} field={field} />)
@@ -33,6 +33,7 @@ export interface DocumentSigningProviderProps {
typedSignatureEnabled?: boolean;
uploadSignatureEnabled?: boolean;
drawSignatureEnabled?: boolean;
qrSignatureEnabled?: boolean;
children: React.ReactNode;
}
@@ -43,6 +44,7 @@ export const DocumentSigningProvider = ({
typedSignatureEnabled = true,
uploadSignatureEnabled = true,
drawSignatureEnabled = true,
qrSignatureEnabled = true,
children,
}: DocumentSigningProviderProps) => {
const [fullName, setFullName] = useState(initialFullName || '');
@@ -54,7 +56,7 @@ export const DocumentSigningProvider = ({
const sig = initialSignature || '';
const isBase64 = isBase64Image(sig);
if (isBase64 && (uploadSignatureEnabled || drawSignatureEnabled)) {
if (isBase64 && (uploadSignatureEnabled || drawSignatureEnabled || qrSignatureEnabled)) {
return sig;
}
@@ -34,6 +34,7 @@ export type DocumentSigningSignatureFieldProps = {
typedSignatureEnabled?: boolean;
uploadSignatureEnabled?: boolean;
drawSignatureEnabled?: boolean;
qrSignatureEnabled?: boolean;
};
export const DocumentSigningSignatureField = ({
@@ -43,6 +44,7 @@ export const DocumentSigningSignatureField = ({
typedSignatureEnabled,
uploadSignatureEnabled,
drawSignatureEnabled,
qrSignatureEnabled,
}: DocumentSigningSignatureFieldProps) => {
const { _ } = useLingui();
const { toast } = useToast();
@@ -279,6 +281,7 @@ export const DocumentSigningSignatureField = ({
typedSignatureEnabled={typedSignatureEnabled}
uploadSignatureEnabled={uploadSignatureEnabled}
drawSignatureEnabled={drawSignatureEnabled}
qrSignatureEnabled={qrSignatureEnabled}
/>
<DocumentSigningDisclosure />
@@ -172,7 +172,9 @@ export const EnvelopeSigningProvider = ({
if (
!sig &&
(envelope.documentMeta.uploadSignatureEnabled || envelope.documentMeta.drawSignatureEnabled) &&
(envelope.documentMeta.uploadSignatureEnabled ||
envelope.documentMeta.drawSignatureEnabled ||
envelope.documentMeta.qrSignatureEnabled) &&
envelopeData.recipientSignature?.signatureImageAsBase64
) {
return envelopeData.recipientSignature.signatureImageAsBase64;
@@ -182,7 +184,12 @@ export const EnvelopeSigningProvider = ({
return envelopeData.recipientSignature.typedSignature;
}
if (isBase64 && (envelope.documentMeta.uploadSignatureEnabled || envelope.documentMeta.drawSignatureEnabled)) {
if (
isBase64 &&
(envelope.documentMeta.uploadSignatureEnabled ||
envelope.documentMeta.drawSignatureEnabled ||
envelope.documentMeta.qrSignatureEnabled)
) {
return sig;
}
@@ -174,6 +174,7 @@ export const DocumentEditForm = ({ className, initialDocument, documentRootPath
typedSignatureEnabled: signatureTypes.includes(DocumentSignatureType.TYPE),
uploadSignatureEnabled: signatureTypes.includes(DocumentSignatureType.UPLOAD),
drawSignatureEnabled: signatureTypes.includes(DocumentSignatureType.DRAW),
qrSignatureEnabled: signatureTypes.includes(DocumentSignatureType.QR),
},
});
};
@@ -278,6 +278,7 @@ export const EnvelopeEditorSettingsDialog = ({ trigger, ...props }: EnvelopeEdit
drawSignatureEnabled: signatureTypes.includes(DocumentSignatureType.DRAW),
typedSignatureEnabled: signatureTypes.includes(DocumentSignatureType.TYPE),
uploadSignatureEnabled: signatureTypes.includes(DocumentSignatureType.UPLOAD),
qrSignatureEnabled: signatureTypes.includes(DocumentSignatureType.QR),
envelopeExpirationPeriod,
reminderSettings,
},
@@ -121,6 +121,7 @@ export default function EnvelopeSignerForm() {
typedSignatureEnabled={envelope.documentMeta.typedSignatureEnabled}
uploadSignatureEnabled={envelope.documentMeta.uploadSignatureEnabled}
drawSignatureEnabled={envelope.documentMeta.drawSignatureEnabled}
qrSignatureEnabled={envelope.documentMeta.qrSignatureEnabled}
/>
</div>
)}
@@ -384,6 +384,7 @@ export const EnvelopeSignerPageRenderer = ({ pageData }: { pageData: PageRenderD
typedSignatureEnabled: envelope.documentMeta.typedSignatureEnabled,
uploadSignatureEnabled: envelope.documentMeta.uploadSignatureEnabled,
drawSignatureEnabled: envelope.documentMeta.drawSignatureEnabled,
qrSignatureEnabled: envelope.documentMeta.qrSignatureEnabled,
})
.then(async (payload) => {
if (!payload) {
@@ -137,6 +137,7 @@ export const TemplateEditForm = ({ initialTemplate, className, templateRootPath
typedSignatureEnabled: signatureTypes.includes(DocumentSignatureType.TYPE),
uploadSignatureEnabled: signatureTypes.includes(DocumentSignatureType.UPLOAD),
drawSignatureEnabled: signatureTypes.includes(DocumentSignatureType.DRAW),
qrSignatureEnabled: signatureTypes.includes(DocumentSignatureType.QR),
language: isValidLanguageCode(data.meta.language) ? data.meta.language : undefined,
},
});
@@ -88,6 +88,7 @@ export default function OrganisationSettingsDocumentPage() {
typedSignatureEnabled: signatureTypes.includes(DocumentSignatureType.TYPE),
uploadSignatureEnabled: signatureTypes.includes(DocumentSignatureType.UPLOAD),
drawSignatureEnabled: signatureTypes.includes(DocumentSignatureType.DRAW),
qrSignatureEnabled: signatureTypes.includes(DocumentSignatureType.QR),
delegateDocumentOwnership,
aiFeaturesEnabled,
envelopeExpirationPeriod: envelopeExpirationPeriod ?? undefined,
@@ -76,11 +76,13 @@ export default function TeamsSettingsPage() {
typedSignatureEnabled: null,
uploadSignatureEnabled: null,
drawSignatureEnabled: null,
qrSignatureEnabled: null,
}
: {
typedSignatureEnabled: signatureTypes.includes(DocumentSignatureType.TYPE),
uploadSignatureEnabled: signatureTypes.includes(DocumentSignatureType.UPLOAD),
drawSignatureEnabled: signatureTypes.includes(DocumentSignatureType.DRAW),
qrSignatureEnabled: signatureTypes.includes(DocumentSignatureType.QR),
}),
delegateDocumentOwnership,
},
@@ -215,6 +215,7 @@ const DirectSigningPageV1 = ({ data }: { data: Awaited<ReturnType<typeof handleV
typedSignatureEnabled={template.templateMeta?.typedSignatureEnabled}
uploadSignatureEnabled={template.templateMeta?.uploadSignatureEnabled}
drawSignatureEnabled={template.templateMeta?.drawSignatureEnabled}
qrSignatureEnabled={template.templateMeta?.qrSignatureEnabled}
>
<DocumentSigningAuthProvider
documentAuthOptions={template.authOptions}
@@ -474,6 +474,7 @@ const SigningPageV1 = ({ data }: { data: Awaited<ReturnType<typeof handleV1Loade
typedSignatureEnabled={document.documentMeta?.typedSignatureEnabled}
uploadSignatureEnabled={document.documentMeta?.uploadSignatureEnabled}
drawSignatureEnabled={document.documentMeta?.drawSignatureEnabled}
qrSignatureEnabled={document.documentMeta?.qrSignatureEnabled}
>
<DocumentSigningAuthProvider documentAuthOptions={document.authOptions} recipient={recipient} user={user}>
{sessionData?.user && <AuthenticatedHeader />}
@@ -266,6 +266,7 @@ const EmbedDirectTemplatePageV1 = ({ data }: { data: Awaited<ReturnType<typeof h
typedSignatureEnabled={template.templateMeta?.typedSignatureEnabled}
uploadSignatureEnabled={template.templateMeta?.uploadSignatureEnabled}
drawSignatureEnabled={template.templateMeta?.drawSignatureEnabled}
qrSignatureEnabled={template.templateMeta?.qrSignatureEnabled}
>
<DocumentSigningAuthProvider documentAuthOptions={template.authOptions} recipient={recipient} user={user}>
<DocumentSigningRecipientProvider recipient={recipient}>
@@ -354,6 +354,7 @@ const EmbedSignDocumentPageV1 = ({ data }: { data: Awaited<ReturnType<typeof han
typedSignatureEnabled={document.documentMeta?.typedSignatureEnabled}
uploadSignatureEnabled={document.documentMeta?.uploadSignatureEnabled}
drawSignatureEnabled={document.documentMeta?.drawSignatureEnabled}
qrSignatureEnabled={document.documentMeta?.qrSignatureEnabled}
>
<DocumentSigningAuthProvider documentAuthOptions={document.authOptions} recipient={recipient} user={user}>
<EmbedSignDocumentV1ClientPage
@@ -83,6 +83,7 @@ export default function EmbeddingAuthoringDocumentCreatePage() {
drawSignatureEnabled: signatureTypes.length === 0 || signatureTypes.includes(DocumentSignatureType.DRAW),
typedSignatureEnabled: signatureTypes.length === 0 || signatureTypes.includes(DocumentSignatureType.TYPE),
uploadSignatureEnabled: signatureTypes.length === 0 || signatureTypes.includes(DocumentSignatureType.UPLOAD),
qrSignatureEnabled: signatureTypes.length === 0 || signatureTypes.includes(DocumentSignatureType.QR),
},
recipients: configuration.signers.map((signer) => ({
name: signer.name,
@@ -101,6 +101,10 @@ export default function EmbeddingAuthoringDocumentEditPage() {
types.push(DocumentSignatureType.UPLOAD);
}
if (document.documentMeta?.qrSignatureEnabled) {
types.push(DocumentSignatureType.QR);
}
return types;
}, [document.documentMeta]);
@@ -216,6 +220,10 @@ export default function EmbeddingAuthoringDocumentEditPage() {
? configuration.meta.signatureTypes.length === 0 ||
configuration.meta.signatureTypes.includes(DocumentSignatureType.UPLOAD)
: undefined,
qrSignatureEnabled: configuration.meta.signatureTypes
? configuration.meta.signatureTypes.length === 0 ||
configuration.meta.signatureTypes.includes(DocumentSignatureType.QR)
: undefined,
},
recipients: configuration.signers.map((signer) => ({
id: signer.nativeId,
@@ -101,6 +101,10 @@ export default function EmbeddingAuthoringTemplateEditPage() {
types.push(DocumentSignatureType.UPLOAD);
}
if (template.templateMeta?.qrSignatureEnabled) {
types.push(DocumentSignatureType.QR);
}
return types;
}, [template.templateMeta]);
@@ -215,6 +219,10 @@ export default function EmbeddingAuthoringTemplateEditPage() {
? configuration.meta.signatureTypes.length === 0 ||
configuration.meta.signatureTypes.includes(DocumentSignatureType.UPLOAD)
: undefined,
qrSignatureEnabled: configuration.meta.signatureTypes
? configuration.meta.signatureTypes.length === 0 ||
configuration.meta.signatureTypes.includes(DocumentSignatureType.QR)
: undefined,
},
recipients: configuration.signers.map((signer) => ({
id: signer.nativeId,
@@ -238,6 +238,7 @@ export default function MultisignPage() {
typedSignatureEnabled={selectedDocument.documentMeta?.typedSignatureEnabled}
uploadSignatureEnabled={selectedDocument.documentMeta?.uploadSignatureEnabled}
drawSignatureEnabled={selectedDocument.documentMeta?.drawSignatureEnabled}
qrSignatureEnabled={selectedDocument.documentMeta?.qrSignatureEnabled}
>
<DocumentSigningAuthProvider
documentAuthOptions={selectedDocument.authOptions}
@@ -224,6 +224,7 @@ const EnvelopeCreatePage = ({ embedAuthoringOptions }: EnvelopeCreatePageProps)
typedSignatureEnabled: envelope.documentMeta.typedSignatureEnabled ?? undefined,
uploadSignatureEnabled: envelope.documentMeta.uploadSignatureEnabled ?? undefined,
drawSignatureEnabled: envelope.documentMeta.drawSignatureEnabled ?? undefined,
qrSignatureEnabled: envelope.documentMeta.qrSignatureEnabled ?? undefined,
dateFormat: (envelope.documentMeta.dateFormat as TDocumentMetaDateFormat) ?? undefined,
language: envelope.documentMeta.language as SupportedLanguageCodes,
},
@@ -239,6 +239,7 @@ const EnvelopeEditPage = ({ embedAuthoringOptions }: EnvelopeEditPageProps) => {
typedSignatureEnabled: envelope.documentMeta.typedSignatureEnabled, //
uploadSignatureEnabled: envelope.documentMeta.uploadSignatureEnabled, //
drawSignatureEnabled: envelope.documentMeta.drawSignatureEnabled, //
qrSignatureEnabled: envelope.documentMeta.qrSignatureEnabled, //
dateFormat: (envelope.documentMeta.dateFormat as TDocumentMetaDateFormat) ?? undefined,
language: envelope.documentMeta.language as SupportedLanguageCodes,
},
@@ -12,12 +12,21 @@ type HandleSignatureFieldClickOptions = {
typedSignatureEnabled?: boolean;
uploadSignatureEnabled?: boolean;
drawSignatureEnabled?: boolean;
qrSignatureEnabled?: boolean;
};
export const handleSignatureFieldClick = async (
options: HandleSignatureFieldClickOptions,
): Promise<Extract<TSignEnvelopeFieldValue, { type: typeof FieldType.SIGNATURE }> | null> => {
const { field, fullName, signature, typedSignatureEnabled, uploadSignatureEnabled, drawSignatureEnabled } = options;
const {
field,
fullName,
signature,
typedSignatureEnabled,
uploadSignatureEnabled,
drawSignatureEnabled,
qrSignatureEnabled,
} = options;
if (field.type !== FieldType.SIGNATURE) {
throw new AppError(AppErrorCode.INVALID_REQUEST, {
@@ -40,6 +49,7 @@ export const handleSignatureFieldClick = async (
typedSignatureEnabled,
uploadSignatureEnabled,
drawSignatureEnabled,
qrSignatureEnabled,
});
}