import { Field, IGeolocationField, IQuantityField, } from "@/widget/shared/types/fields"; import { validateEmail } from "@/widget/shared/utils/validate-email.utils"; import { validateUrl } from "@/widget/shared/utils/validate-url.utils"; import { useFieldsStore } from "@/widget/app/providers/stores/fieldsStore"; interface IUseFieldsRequiredResult { hasRequiredFields: (fields: Field[]) => boolean; resetRequiredFields: () => void; maybeStartValidation: (fields: Field[]) => void; checkFieldRequired: (field: Field) => boolean; } const hasActivePricingStructure = (field: IQuantityField): boolean => { const structure = (field as any).pricingStructure as | "tiered_discounts" | "cumulative_discounts" | "flat_rate_discounts" | "no_discounts" | undefined; const ranges = (((field as any).pricingRanges as any[]) || []) as any[]; return ( !field.multiply && !!structure && structure !== "no_discounts" && Array.isArray(ranges) && ranges.length > 0 ); }; const isValueInMinMax = (field: IQuantityField): boolean => { if (hasActivePricingStructure(field)) { return true; } let value: number = field.value; if (field.unit && field.multiply) { value = value / field.unit; } if (field.min && value < field.min) { return false; } return !(field.max && value > field.max); }; const fileUploadFieldValidation = (field: Field): boolean => { return ( field.required && field.fieldName === "file_upload" && (!("files" in field) || ("files" in field && Array.isArray(field.files) && field.files.length === 0)) ); }; const quantityFieldValidation = (field: Field): boolean => { return ( field.fieldName === "quantity" && ((field.required && (field.value || 0) <= 0) || !isValueInMinMax(field as IQuantityField)) ); }; const singleOptionFieldValidation = (field: Field): boolean => { return ( field.required && ["dropDown", "radio", "radio_with_img", "dropDown_with_img"].includes( field.fieldName, ) && "selectedOption" in field && !field.selectedOption ); }; const multiOptionsFieldValidation = (field: Field): boolean => { if (field.required) { return ( ["checkbox", "toggle", "checkbox_with_img"].includes(field.fieldName) && "selectedOption" in field && Array.isArray(field.selectedOption) && (field.selectedOption.length <= 0 || ("minAllowedOptions" in field && field.selectedOption.length < +field.minAllowedOptions)) ); } return ( ["checkbox", "toggle", "checkbox_with_img"].includes(field.fieldName) && "selectedOption" in field && Array.isArray(field.selectedOption) && "minAllowedOptions" in field && field.selectedOption.length < +field.minAllowedOptions ); }; const datePickerFieldValidation = (field: Field): boolean => { let fieldRange = false; if ("range" in field) { fieldRange = typeof field.range === "boolean" ? field.range : field.range === "1"; } if (field.required) { return ( field.fieldName === "datePicker" && "selectedDate" in field && ((!fieldRange && field.selectedDate === null) || (fieldRange && !Array.isArray(field.selectedDate)) || (fieldRange && Array.isArray(field.selectedDate) && (field.selectedDate[0] === null || field.selectedDate[1] === null))) ); } return ( field.fieldName === "datePicker" && "selectedDate" in field && fieldRange && Array.isArray(field.selectedDate) && (field.selectedDate[0] === null || field.selectedDate[1] === null) ); }; const textFieldValidation = (field: Field): boolean => { if (field.required) { return ( field.fieldName === "text" && ((field.displayValue as string).trim() === "" || ("numberOfCharacters" in field && field.numberOfCharacters < field.displayValue.length)) ); } return ( field.fieldName === "text" && "numberOfCharacters" in field && field.displayValue !== "" && (field.numberOfCharacters || 0) !== 0 && field.numberOfCharacters < field.displayValue.length ); }; const rangeFieldValidation = (field: Field): boolean => { return ( field.required && ["multi_range", "range"].includes(field.fieldName) && !field.value ); }; const validatedFormFieldValidation = (field: Field): boolean => { if (field.required) { return ( field.fieldName === "validated_form" && "fieldType" in field && ((field.displayValue as string).trim() === "" || (field.fieldType === "email" && !validateEmail(field.displayValue)) || (field.fieldType === "website_url" && !validateUrl(field.displayValue))) ); } if ( "fieldType" in field && ["email", "website_url"].includes(field.fieldType) ) { if (field.fieldType === "email" && "" !== field.displayValue) { return !validateEmail(field.displayValue); } if (field.fieldType === "website_url" && "" !== field.displayValue) { return !validateUrl(field.displayValue); } return false; } return false; }; const timePickerFieldValidation = (field: Field): boolean => { return ( field.required && field.fieldName === "timePicker" && !Array.isArray(field.displayValue) && field.displayValue.trim() === "" ); }; type SelectedPoint = { addressName?: string; label?: string; }; const geolocationFieldValidation = (field: Field): boolean => { if (!field.required || field.fieldName !== "geolocation") { return false; } const geolocationField = field as IGeolocationField; if (geolocationField.geoType === "multiplyLocation") { const selectedPoint = geolocationField.selectedPoint as | SelectedPoint | undefined; return !selectedPoint?.addressName && !selectedPoint?.label; } return "extraDisplayView" in field && field.extraDisplayView?.length === 0; }; const applyRequiredFieldsValidation = (fields: Field[]) => { return fields.filter((field: Field) => { return ( quantityFieldValidation(field) || rangeFieldValidation(field) || multiOptionsFieldValidation(field) || singleOptionFieldValidation(field) || textFieldValidation(field) || validatedFormFieldValidation(field) || datePickerFieldValidation(field) || timePickerFieldValidation(field) || fileUploadFieldValidation(field) || geolocationFieldValidation(field) ); }); }; const resetRequiredFields = (): void => { const fieldsStore = useFieldsStore(); fieldsStore.setRequiredFields([]); }; const filterRequiredFields = (fieldsData: Field[]): Field[] => { let fields: Field[] = []; fieldsData.forEach((sectionField: Field) => { if ("fields" in sectionField) { sectionField.fields.forEach((field: Field) => { if ( ["repeater", "group"].includes(field.fieldName) && "groupElements" in field && !field.hidden ) { const groupElements: Field[] = []; field.groupElements.forEach((element) => { if ("entries" in element && typeof element.entries === "function") { for (const [_, existingField] of element.entries()) { groupElements.push(existingField as Field); } } }); fields = [...fields, ...groupElements]; } fields = [...fields, field]; }); } }); fields = fields.filter( (field: Field) => !field.hidden && field.fieldName !== "total" && field.fieldName !== "repeater" && field.fieldName !== "group", ); fields = applyRequiredFieldsValidation(fields); return fields; }; const hasRequiredFields = (fieldsData: Field[]): boolean => { const fieldsStore = useFieldsStore(); resetRequiredFields(); const fields = filterRequiredFields(fieldsData); fieldsStore.setRequiredFields(fields); return fieldsStore.getRequiredFields.length > 0; }; const maybeStartValidation = (fieldsData: Field[]): void => { const fieldsStore = useFieldsStore(); if (fieldsStore.getRequiredFields.length > 0) { const fields = filterRequiredFields(fieldsData); fieldsStore.setRequiredFields(fields); } }; const checkFieldRequired = (field: Field): boolean => { const fieldsStore = useFieldsStore(); if ("repeaterIdx" in field && typeof field.repeaterIdx !== "undefined") { return ( fieldsStore.getRequiredFields.filter( (f) => f.alias === field.alias && f.repeaterIdx === field.repeaterIdx, ).length > 0 ); } return ( fieldsStore.getRequiredFields.filter((f) => f.alias === field.alias) .length > 0 ); }; export function useFieldsRequired(): IUseFieldsRequiredResult { return { hasRequiredFields, resetRequiredFields, maybeStartValidation, checkFieldRequired, }; }