feat: enable optional fields (#1470)

This commit is contained in:
Catalin Pit
2024-12-27 10:30:44 +02:00
committed by GitHub
parent 39b1c5bbec
commit 487f52e194
7 changed files with 76 additions and 13 deletions

View File

@ -11,6 +11,7 @@ import { useForm } from 'react-hook-form';
import { useAnalytics } from '@documenso/lib/client-only/hooks/use-analytics';
import type { DocumentAndSender } from '@documenso/lib/server-only/document/get-document-by-token';
import type { TRecipientActionAuth } from '@documenso/lib/types/document-auth';
import { isFieldUnsignedAndRequired } from '@documenso/lib/utils/advanced-fields-helpers';
import { sortFieldsByPosition, validateFieldsInserted } from '@documenso/lib/utils/fields';
import { type Field, FieldType, type Recipient, RecipientRole } from '@documenso/prisma/client';
import { trpc } from '@documenso/trpc/react';
@ -57,26 +58,31 @@ export const SigningForm = ({
// Keep the loading state going if successful since the redirect may take some time.
const isSubmitting = formState.isSubmitting || formState.isSubmitSuccessful;
const fieldsRequiringValidation = useMemo(
() => fields.filter(isFieldUnsignedAndRequired),
[fields],
);
const hasSignatureField = fields.some((field) => field.type === FieldType.SIGNATURE);
const uninsertedFields = useMemo(() => {
return sortFieldsByPosition(fields.filter((field) => !field.inserted));
return sortFieldsByPosition(fieldsRequiringValidation.filter((field) => !field.inserted));
}, [fields]);
const fieldsValidated = () => {
setValidateUninsertedFields(true);
validateFieldsInserted(fields);
validateFieldsInserted(fieldsRequiringValidation);
};
const onFormSubmit = async () => {
setValidateUninsertedFields(true);
const isFieldsValid = validateFieldsInserted(fieldsRequiringValidation);
if (hasSignatureField && !signatureValid) {
return;
}
const isFieldsValid = validateFieldsInserted(fields);
if (!isFieldsValid) {
return;
}

View File

@ -1,7 +1,8 @@
import { useState } from 'react';
import { useMemo, useState } from 'react';
import { Trans } from '@lingui/macro';
import { fieldsContainUnsignedRequiredField } from '@documenso/lib/utils/advanced-fields-helpers';
import type { Field } from '@documenso/prisma/client';
import { RecipientRole } from '@documenso/prisma/client';
import { Button } from '@documenso/ui/primitives/button';
@ -36,7 +37,7 @@ export const SignDialog = ({
}: SignDialogProps) => {
const [showDialog, setShowDialog] = useState(false);
const isComplete = fields.every((field) => field.inserted);
const isComplete = useMemo(() => !fieldsContainUnsignedRequiredField(fields), [fields]);
const handleOpenChange = (open: boolean) => {
if (isSubmitting || !isComplete) {

View File

@ -23,6 +23,7 @@ import { DOCUMENT_AUDIT_LOG_TYPE } from '../../../types/document-audit-logs';
import { ZWebhookDocumentSchema } from '../../../types/webhook-payload';
import { getFile } from '../../../universal/upload/get-file';
import { putPdfFile } from '../../../universal/upload/put-file';
import { fieldsContainUnsignedRequiredField } from '../../../utils/advanced-fields-helpers';
import { createDocumentAuditLogData } from '../../../utils/document-audit-logs';
import type { JobRunIO } from '../../client/_internal/job';
import type { TSealDocumentJobDefinition } from './seal-document';
@ -105,8 +106,8 @@ export const run = async ({
},
});
if (fields.some((field) => !field.inserted)) {
throw new Error(`Document ${document.id} has unsigned fields`);
if (fieldsContainUnsignedRequiredField(fields)) {
throw new Error(`Document ${document.id} has unsigned required fields`);
}
if (isResealing) {
@ -147,8 +148,10 @@ export const run = async ({
}
for (const field of fields) {
if (field.inserted) {
await insertFieldInPDF(pdfDoc, field);
}
}
// Re-flatten the form to handle our checkbox and radio fields that
// create native arcoFields

View File

@ -1,5 +1,6 @@
import { DOCUMENT_AUDIT_LOG_TYPE } from '@documenso/lib/types/document-audit-logs';
import type { RequestMetadata } from '@documenso/lib/universal/extract-request-metadata';
import { fieldsContainUnsignedRequiredField } from '@documenso/lib/utils/advanced-fields-helpers';
import { createDocumentAuditLogData } from '@documenso/lib/utils/document-audit-logs';
import { prisma } from '@documenso/prisma';
import {
@ -85,7 +86,7 @@ export const completeDocumentWithToken = async ({
},
});
if (fields.some((field) => !field.inserted)) {
if (fieldsContainUnsignedRequiredField(fields)) {
throw new Error(`Recipient ${recipient.id} has unsigned fields`);
}

View File

@ -18,6 +18,7 @@ import { ZWebhookDocumentSchema } from '../../types/webhook-payload';
import type { RequestMetadata } from '../../universal/extract-request-metadata';
import { getFile } from '../../universal/upload/get-file';
import { putPdfFile } from '../../universal/upload/put-file';
import { fieldsContainUnsignedRequiredField } from '../../utils/advanced-fields-helpers';
import { getCertificatePdf } from '../htmltopdf/get-certificate-pdf';
import { flattenAnnotations } from '../pdf/flatten-annotations';
import { flattenForm } from '../pdf/flatten-form';
@ -92,8 +93,8 @@ export const sealDocument = async ({
},
});
if (fields.some((field) => !field.inserted)) {
throw new Error(`Document ${document.id} has unsigned fields`);
if (fieldsContainUnsignedRequiredField(fields)) {
throw new Error(`Document ${document.id} has unsigned required fields`);
}
if (isResealing) {

View File

@ -0,0 +1,51 @@
import { type Field, FieldType } from '@documenso/prisma/client';
import { ZFieldMetaSchema } from '../types/field-meta';
// Currently it seems that the majority of fields have advanced fields for font reasons.
// This array should only contain fields that have an optional setting in the fieldMeta.
const ADVANCED_FIELD_TYPES_WITH_OPTIONAL_SETTING: FieldType[] = [
FieldType.NUMBER,
FieldType.TEXT,
FieldType.DROPDOWN,
FieldType.RADIO,
FieldType.CHECKBOX,
];
/**
* Whether a field is required to be inserted.
*/
export const isRequiredField = (field: Field) => {
// All fields without the optional metadata are assumed to be required.
if (!ADVANCED_FIELD_TYPES_WITH_OPTIONAL_SETTING.includes(field.type)) {
return true;
}
// Not sure why fieldMeta can be optional for advanced fields, but it is.
// Therefore we must assume if there is no fieldMeta, then the field is optional.
if (!field.fieldMeta) {
return false;
}
const parsedData = ZFieldMetaSchema.safeParse(field.fieldMeta);
// If it fails, assume the field is optional.
// This needs to be logged somewhere.
if (!parsedData.success) {
return false;
}
return parsedData.data?.required === true;
};
/**
* Whether the provided field is required and not inserted.
*/
export const isFieldUnsignedAndRequired = (field: Field) =>
isRequiredField(field) && !field.inserted;
/**
* Whether the provided fields contains a field that is required to be inserted.
*/
export const fieldsContainUnsignedRequiredField = (fields: Field[]) =>
fields.some(isFieldUnsignedAndRequired);

View File

@ -245,7 +245,7 @@ export const AddTemplateSettingsFormPartial = ({
</li>
<li>
<Trans>
<strong>Links</strong> - We will generate links which you can send to
<strong>None</strong> - We will generate links which you can send to
the recipients manually.
</Trans>
</li>