From fc1f76b5431b10a52a3c03fa9f8fc572c9514bc3 Mon Sep 17 00:00:00 2001 From: Catalin Pit Date: Mon, 23 Dec 2024 12:06:47 +0200 Subject: [PATCH] fix: checkbox logic (#1537) ## Description ## Related Issue ## Changes Made - Change 1 - Change 2 - ... ## Testing Performed - Tested feature X in scenario Y. - Ran unit tests for component Z. - Tested on browsers A, B, and C. - ... ## Checklist - [ ] I have tested these changes locally and they work as expected. - [ ] I have added/updated tests that prove the effectiveness of these changes. - [ ] I have updated the documentation to reflect these changes, if applicable. - [ ] I have followed the project's coding style guidelines. - [ ] I have addressed the code review feedback from the previous submission, if applicable. ## Additional Notes --- .../(signing)/sign/[token]/checkbox-field.tsx | 15 ++++++++----- .../field/sign-field-with-token.ts | 4 +++- .../server-only/pdf/insert-field-in-pdf.ts | 3 ++- packages/lib/universal/field-checkbox.ts | 21 +++++++++++++++++++ 4 files changed, 36 insertions(+), 7 deletions(-) create mode 100644 packages/lib/universal/field-checkbox.ts diff --git a/apps/web/src/app/(signing)/sign/[token]/checkbox-field.tsx b/apps/web/src/app/(signing)/sign/[token]/checkbox-field.tsx index 6e213f757..0757eeea7 100644 --- a/apps/web/src/app/(signing)/sign/[token]/checkbox-field.tsx +++ b/apps/web/src/app/(signing)/sign/[token]/checkbox-field.tsx @@ -12,6 +12,7 @@ import { DO_NOT_INVALIDATE_QUERY_ON_MUTATION } from '@documenso/lib/constants/tr import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error'; import type { TRecipientActionAuth } from '@documenso/lib/types/document-auth'; import { ZCheckboxFieldMeta } from '@documenso/lib/types/field-meta'; +import { fromCheckboxValue, toCheckboxValue } from '@documenso/lib/universal/field-checkbox'; import type { Recipient } from '@documenso/prisma/client'; import type { FieldWithSignatureAndFieldMeta } from '@documenso/prisma/types/field-with-signature-and-fieldmeta'; import { trpc } from '@documenso/trpc/react'; @@ -54,6 +55,7 @@ export const CheckboxField = ({ ...item, value: item.value.length > 0 ? item.value : `empty-value-${item.id}`, })); + const [checkedValues, setCheckedValues] = useState( values ?.map((item) => @@ -97,7 +99,7 @@ export const CheckboxField = ({ const payload: TSignFieldWithTokenMutationSchema = { token: recipient.token, fieldId: field.id, - value: checkedValues.join(','), + value: toCheckboxValue(checkedValues), isBase64: true, authOptions, }; @@ -191,7 +193,7 @@ export const CheckboxField = ({ await signFieldWithToken({ token: recipient.token, fieldId: field.id, - value: updatedValues.join(','), + value: toCheckboxValue(checkedValues), isBase64: true, }); } @@ -228,6 +230,11 @@ export const CheckboxField = ({ } }, [checkedValues, isLengthConditionMet, field.inserted]); + const parsedCheckedValues = useMemo( + () => fromCheckboxValue(field.customText), + [field.customText], + ); + return ( {isLoading && ( @@ -277,9 +284,7 @@ export const CheckboxField = ({ className="h-3 w-3" checkClassName="text-white" id={`checkbox-${index}`} - checked={field.customText - .split(',') - .some((customValue) => customValue === itemValue)} + checked={parsedCheckedValues.includes(itemValue)} disabled={isLoading} onCheckedChange={() => void handleCheckboxOptionClick(item)} /> diff --git a/packages/lib/server-only/field/sign-field-with-token.ts b/packages/lib/server-only/field/sign-field-with-token.ts index 192c0c001..937ea8f1d 100644 --- a/packages/lib/server-only/field/sign-field-with-token.ts +++ b/packages/lib/server-only/field/sign-field-with-token.ts @@ -8,6 +8,7 @@ import { validateDropdownField } from '@documenso/lib/advanced-fields-validation import { validateNumberField } from '@documenso/lib/advanced-fields-validation/validate-number'; import { validateRadioField } from '@documenso/lib/advanced-fields-validation/validate-radio'; import { validateTextField } from '@documenso/lib/advanced-fields-validation/validate-text'; +import { fromCheckboxValue } from '@documenso/lib/universal/field-checkbox'; import { prisma } from '@documenso/prisma'; import { DocumentStatus, FieldType, SigningStatus } from '@documenso/prisma/client'; @@ -119,7 +120,8 @@ export const signFieldWithToken = async ({ if (field.type === FieldType.CHECKBOX && field.fieldMeta) { const checkboxFieldParsedMeta = ZCheckboxFieldMeta.parse(field.fieldMeta); - const checkboxFieldValues = value.split(','); + const checkboxFieldValues: string[] = fromCheckboxValue(value); + const errors = validateCheckboxField(checkboxFieldValues, checkboxFieldParsedMeta, true); if (errors.length > 0) { diff --git a/packages/lib/server-only/pdf/insert-field-in-pdf.ts b/packages/lib/server-only/pdf/insert-field-in-pdf.ts index f5c5bc0df..588b3b854 100644 --- a/packages/lib/server-only/pdf/insert-field-in-pdf.ts +++ b/packages/lib/server-only/pdf/insert-field-in-pdf.ts @@ -10,6 +10,7 @@ import { MIN_HANDWRITING_FONT_SIZE, MIN_STANDARD_FONT_SIZE, } from '@documenso/lib/constants/pdf'; +import { fromCheckboxValue } from '@documenso/lib/universal/field-checkbox'; import { FieldType } from '@documenso/prisma/client'; import { isSignatureFieldType } from '@documenso/prisma/guards/is-signature-field'; import type { FieldWithSignature } from '@documenso/prisma/types/field-with-signature'; @@ -194,7 +195,7 @@ export const insertFieldInPDF = async (pdf: PDFDocument, field: FieldWithSignatu value: item.value.length > 0 ? item.value : `empty-value-${item.id}`, })); - const selected = field.customText.split(','); + const selected: string[] = fromCheckboxValue(field.customText); for (const [index, item] of (values ?? []).entries()) { const offsetY = index * 16; diff --git a/packages/lib/universal/field-checkbox.ts b/packages/lib/universal/field-checkbox.ts new file mode 100644 index 000000000..a85ec0fb9 --- /dev/null +++ b/packages/lib/universal/field-checkbox.ts @@ -0,0 +1,21 @@ +export const fromCheckboxValue = (customText: string): string[] => { + if (!customText) { + return []; + } + + try { + const parsed = JSON.parse(customText); + + if (!Array.isArray(parsed)) { + throw new Error('Parsed checkbox values are not an array'); + } + + return parsed; + } catch { + return customText.split(',').filter(Boolean); + } +}; + +export const toCheckboxValue = (values: string[]): string => { + return JSON.stringify(values); +};