UNPKG

pagamio-frontend-commons-lib

Version:

Pagamio library for Frontend reusable components like the form engine and table container

122 lines (121 loc) 6.48 kB
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime"; import clsx from 'clsx'; import { useForm } from 'react-hook-form'; import { useEffect, useState } from 'react'; import { Button, SheetRoot, SheetTrigger } from '../components'; import { cn } from '../helpers'; import FieldWrapper from './components/FieldWrapper'; import { useFormPersistence } from './hooks/useFormPersistence'; const CancelButton = ({ isSubmitting, onCancel, isNotTrigger, onClearData }) => { const handleCancel = () => { if (onClearData) { onClearData(); } onCancel(); }; const sharedClasses = 'bg-white border-primary-500 text-primary-500 hover:bg-primary-600 hover:text-white w-full mr-3'; return isNotTrigger ? (_jsx(Button, { className: sharedClasses, disabled: isSubmitting, onClick: handleCancel, children: "Cancel" })) : (_jsx(SheetRoot, { children: _jsx(SheetTrigger, { asChild: true, children: _jsx(Button, { className: sharedClasses, disabled: isSubmitting, onClick: handleCancel, children: "Cancel" }) }) })); }; const FormEngine = ({ fields, onSubmit, initialValues, layout = 'vertical', isNotTrigger, showCancelButton = true, submitButtonText = 'Save', showSubmittingText = true, submitButtonClass, onCancel, getFieldValues, formRef, className, persistenceKey, }) => { const { saveFormData, restoreFormData, clearPersistedData, hasPersistedData } = useFormPersistence(persistenceKey); // Determine initial values: persisted data takes precedence over provided initialValues const getEffectiveInitialValues = () => { if (persistenceKey && hasPersistedData()) { const persistedData = restoreFormData(); if (persistedData) { return { ...initialValues, ...persistedData }; } } return initialValues; }; const { control, handleSubmit, watch, formState: { errors, isSubmitting }, reset, setValue, trigger, } = useForm({ mode: 'onBlur', defaultValues: getEffectiveInitialValues() }); const allFields = watch(); const password = watch('password'); const confirmPassword = watch('confirmPassword'); //re-validate confirmPassword when password changes useEffect(() => { if (password && confirmPassword) { trigger('confirmPassword'); } }, [password, trigger, confirmPassword]); useEffect(() => { if (getFieldValues) { getFieldValues(allFields); } }, [allFields, getFieldValues]); // Save form data when fields change (for persistence) useEffect(() => { if (persistenceKey && allFields && Object.keys(allFields).length > 0) { // Only save if there are actual field values (not just empty object) const hasValues = Object.values(allFields).some((value) => value !== undefined && value !== null && value !== ''); if (hasValues) { saveFormData(allFields, true); } } }, [allFields, persistenceKey, saveFormData]); // Expose form control methods via ref useEffect(() => { if (formRef) { formRef.current = { reset: () => reset(getEffectiveInitialValues()), setValue: (name, value) => setValue(name, value), }; } }, [formRef, reset, setValue, getEffectiveInitialValues]); const [submitError, setSubmitError] = useState(null); const handleFormSubmit = async (data) => { setSubmitError(null); try { await onSubmit(data); // Clear persisted data on successful submission if (persistenceKey) { clearPersistedData(); } if (!formRef) { reset(initialValues); } } catch (error) { setSubmitError(error instanceof Error ? error.message : 'An error occurred'); } }; // Enhanced validation functions const validatePassword = (value) => { if (!value) return 'Password is required'; if (value.length < 8) return 'Password must be at least 8 characters'; return true; }; const validateConfirmPassword = (value) => { if (!value) return 'Please confirm your password'; if (!password) return 'Please enter a password first'; if (password.length < 8) return 'Please enter a valid password first (minimum 8 characters)'; if (value !== password) return 'Passwords do not match'; return true; }; return (_jsxs("form", { className: cn(`${layout === 'horizontal' ? 'flex flex-wrap -mx-4' : ''} mb-6 border-gray-400 p-3`, className), onSubmit: handleSubmit(handleFormSubmit), children: [_jsx("div", { className: `${layout === 'vertical' ? 'grid grid-cols-12 gap-2 align-middle' : 'w-full flex flex-wrap'}`, children: fields?.map((field) => { // Extract validation logic const getFieldValidation = () => { if (field.name === 'password') { return validatePassword; } if (field.name === 'confirmPassword') { return validateConfirmPassword; } return field.validation?.validate; }; return (_jsx(FieldWrapper, { field: { ...field, validation: { ...field.validation, validate: getFieldValidation(), }, }, control: control, errors: errors, layout: layout }, field.name)); }) }), _jsxs("div", { className: clsx('flex col-span-2 mt-4 w-full', showCancelButton ? 'space-x-4' : 'justify-end'), children: [showCancelButton && (_jsx(CancelButton, { isSubmitting: isSubmitting, onCancel: onCancel, isNotTrigger: isNotTrigger, onClearData: persistenceKey ? clearPersistedData : undefined })), _jsx(Button, { type: "submit", className: clsx(showCancelButton ? 'w-full ml-3' : 'w-[20%]', submitButtonClass), disabled: isSubmitting, children: submitButtonText }), isSubmitting && showSubmittingText && _jsx("span", { children: "Submitting..." })] }), submitError && _jsx("p", { className: "text-red-500", children: submitError })] })); }; export default FormEngine;