chore: add more field types (#1141)

Adds a number of new field types and capabilities to existing fields.

A massive change with far too many moving pieces to document in a single commit.
This commit is contained in:
Catalin Pit
2024-07-18 16:45:44 +03:00
committed by GitHub
parent a3ee732a9b
commit 7b5c57e8af
74 changed files with 5234 additions and 829 deletions

View File

@ -0,0 +1,82 @@
import { checkboxValidationSigns } from '@documenso/ui/primitives/document-flow/field-items-advanced-settings/constants';
interface CheckboxFieldMeta {
readOnly?: boolean;
required?: boolean;
validationRule?: string;
validationLength?: number;
}
export const validateCheckboxField = (
values: string[],
fieldMeta: CheckboxFieldMeta,
isSigningPage: boolean = false,
): string[] => {
const errors = [];
const { readOnly, required, validationRule, validationLength } = fieldMeta;
if (readOnly && required) {
errors.push('A field cannot be both read-only and required');
}
if (values.length === 0) {
errors.push('At least one option must be added');
}
if (readOnly && values.length === 0) {
errors.push('A read-only field must have at least one value');
}
if (isSigningPage && required && values.length === 0) {
errors.push('Selecting an option is required');
}
if (validationRule && !validationLength) {
errors.push('You need to specify the number of options for validation');
}
if (validationLength && !validationRule) {
errors.push('You need to specify the validation rule');
}
if (validationRule && validationLength) {
const validation = checkboxValidationSigns.find((sign) => sign.label === validationRule);
if (validation) {
let lengthCondition = false;
switch (validation.value) {
case '=':
lengthCondition = isSigningPage
? values.length !== validationLength
: values.length < validationLength;
break;
case '>=':
lengthCondition = values.length < validationLength;
break;
case '<=':
lengthCondition = isSigningPage
? values.length > validationLength
: values.length < validationLength;
break;
}
if (lengthCondition) {
let errorMessage;
if (isSigningPage) {
errorMessage = `You need to ${validationRule.toLowerCase()} ${validationLength} options`;
} else {
errorMessage =
validation.value === '<='
? `You need to select at least ${validationLength} options`
: `You need to add at least ${validationLength} options`;
}
errors.push(errorMessage);
}
}
}
return errors;
};

View File

@ -0,0 +1,54 @@
interface DropdownFieldMeta {
readOnly?: boolean;
required?: boolean;
values?: { value: string }[];
defaultValue?: string;
}
export const validateDropdownField = (
value: string | undefined,
fieldMeta: DropdownFieldMeta,
isSigningPage: boolean = false,
): string[] => {
const errors = [];
const { readOnly, required, values, defaultValue } = fieldMeta;
if (readOnly && required) {
errors.push('A field cannot be both read-only and required');
}
if (readOnly && (!values || values.length === 0)) {
errors.push('A read-only field must have at least one value');
}
if (isSigningPage && required && !value) {
errors.push('Choosing an option is required');
}
if (values && values.length === 0) {
errors.push('Select field must have at least one option');
}
if (values && values.length === 0 && defaultValue) {
errors.push('Default value must be one of the available options');
}
if (value && values && !values.find((item) => item.value === value)) {
errors.push('Selected value must be one of the available options');
}
if (values && defaultValue && !values.find((item) => item.value === defaultValue)) {
errors.push('Default value must be one of the available options');
}
if (values && values.some((item) => item.value.length < 1)) {
errors.push('Option value cannot be empty');
}
if (values && new Set(values.map((item) => item.value)).size !== values.length) {
errors.push('Duplicate values are not allowed');
}
return errors;
};

View File

@ -0,0 +1,67 @@
// import { numberFormatValues } from '@documenso/ui/primitives/document-flow/field-items-advanced-settings/constants';
interface NumberFieldMeta {
minValue?: number;
maxValue?: number;
readOnly?: boolean;
required?: boolean;
numberFormat?: string;
}
export const validateNumberField = (
value: string,
fieldMeta?: NumberFieldMeta,
isSigningPage: boolean = false,
): string[] => {
const errors = [];
const { minValue, maxValue, readOnly, required, numberFormat } = fieldMeta || {};
const formatRegex: { [key: string]: RegExp } = {
'123,456,789.00': /^(?:\d{1,3}(?:,\d{3})*|\d+)(?:\.\d{1,2})?$/,
'123.456.789,00': /^(?:\d{1,3}(?:\.\d{3})*|\d+)(?:,\d{1,2})?$/,
'123456,789.00': /^(?:\d+)(?:,\d{1,3}(?:\.\d{1,2})?)?$/,
};
const isValidFormat = numberFormat ? formatRegex[numberFormat].test(value) : true;
if (!isValidFormat) {
errors.push(`Value ${value} does not match the number format - ${numberFormat}`);
}
const numberValue = parseFloat(value);
if (isSigningPage && required && !value) {
errors.push('Value is required');
}
if (!/^[0-9,.]+$/.test(value.trim())) {
errors.push(`Value is not a valid number`);
}
if (minValue !== undefined && minValue > 0 && numberValue < minValue) {
errors.push(`Value ${value} is less than the minimum value of ${minValue}`);
}
if (maxValue !== undefined && maxValue > 0 && numberValue > maxValue) {
errors.push(`Value ${value} is greater than the maximum value of ${maxValue}`);
}
if (minValue !== undefined && maxValue !== undefined && minValue > maxValue) {
errors.push('Minimum value cannot be greater than maximum value');
}
if (maxValue !== undefined && minValue !== undefined && maxValue < minValue) {
errors.push('Maximum value cannot be less than minimum value');
}
if (readOnly && numberValue < 1) {
errors.push('A read-only field must have a value greater than 0');
}
if (readOnly && required) {
errors.push('A field cannot be both read-only and required');
}
return errors;
};

View File

@ -0,0 +1,41 @@
interface RadioFieldMeta {
readOnly?: boolean;
required?: boolean;
values?: { checked: boolean; value: string }[];
}
export const validateRadioField = (
value: string | undefined,
fieldMeta: RadioFieldMeta,
isSigningPage: boolean = false,
): string[] => {
const errors = [];
const { readOnly, required, values } = fieldMeta;
if (readOnly && required) {
errors.push('A field cannot be both read-only and required');
}
if (readOnly && (!values || values.length === 0)) {
errors.push('A read-only field must have at least one value');
}
if (isSigningPage && required && !value) {
errors.push('Choosing an option is required');
}
if (values) {
const checkedRadioFieldValues = values.filter((option) => option.checked);
if (values.length === 0) {
errors.push('Radio field must have at least one option');
}
if (checkedRadioFieldValues.length > 1) {
errors.push('There cannot be more than one checked option');
}
}
return errors;
};

View File

@ -0,0 +1,33 @@
interface TextFieldMeta {
characterLimit?: number;
readOnly?: boolean;
required?: boolean;
}
export const validateTextField = (
value: string,
fieldMeta: TextFieldMeta,
isSigningPage: boolean = false,
): string[] => {
const errors = [];
const { characterLimit, readOnly, required } = fieldMeta;
if (required && !value && isSigningPage) {
errors.push('Value is required');
}
if (characterLimit !== undefined && characterLimit > 0 && value.length > characterLimit) {
errors.push(`Value length (${value.length}) exceeds the character limit (${characterLimit})`);
}
if (readOnly && value.length < 1) {
errors.push('A read-only field must have text');
}
if (readOnly && required) {
errors.push('A field cannot be both read-only and required');
}
return errors;
};