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
JavaScript
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;