diff --git a/RestroHub-FrontEnd/src/components/admin/menu/menuCard/CategoryFormModal.jsx b/RestroHub-FrontEnd/src/components/admin/menu/menuCard/CategoryFormModal.jsx index e1effa26..82013ce7 100644 --- a/RestroHub-FrontEnd/src/components/admin/menu/menuCard/CategoryFormModal.jsx +++ b/RestroHub-FrontEnd/src/components/admin/menu/menuCard/CategoryFormModal.jsx @@ -10,6 +10,7 @@ const CategoryFormModal = ({ isOpen, onClose }) => { }); const [submitting, setSubmitting] = useState(false); + const [fieldErrors, setFieldErrors] = useState({}); const updateField = (field, value) => { setFormData(prev => ({ @@ -20,6 +21,16 @@ const CategoryFormModal = ({ isOpen, onClose }) => { const handleSubmit = async (e) => { e.preventDefault(); + // client-side validation + const errs = {}; + if (!formData.name.trim()) errs.name = 'Category name is required'; + else if (formData.name.trim().length < 2) errs.name = 'Minimum 2 characters'; + + if (Object.keys(errs).length) { + setFieldErrors(errs); + return; + } + try { setSubmitting(true); @@ -82,16 +93,22 @@ const CategoryFormModal = ({ isOpen, onClose }) => { updateField("name", e.target.value)} - className="w-full px-4 py-3 bg-gray-50 border border-gray-200 rounded-xl + onChange={(e) => { + updateField("name", e.target.value); + setFieldErrors((p) => ({ ...p, name: undefined })); + }} + className={`w-full px-4 py-3 bg-gray-50 rounded-xl focus:ring-2 focus:ring-blue-500 focus:border-transparent focus:bg-white outline-none transition-all text-gray-800 - placeholder:text-gray-400" + placeholder:text-gray-400 ${fieldErrors.name ? 'border-red-500' : 'border-gray-200'}`} placeholder="e.g. Starters" - required + aria-required="true" + aria-invalid={fieldErrors.name ? 'true' : 'false'} + aria-describedby={fieldErrors.name ? 'err-cat-name' : undefined} minLength={2} maxLength={50} /> + {fieldErrors.name &&
{fieldErrors.name}
} {/* Description */} diff --git a/RestroHub-FrontEnd/src/components/admin/menu/menuCard/FoodItemFormModal.jsx b/RestroHub-FrontEnd/src/components/admin/menu/menuCard/FoodItemFormModal.jsx index ec410b39..f88cd1b7 100644 --- a/RestroHub-FrontEnd/src/components/admin/menu/menuCard/FoodItemFormModal.jsx +++ b/RestroHub-FrontEnd/src/components/admin/menu/menuCard/FoodItemFormModal.jsx @@ -235,9 +235,10 @@ const MenuFormModal = ({ isOpen, onClose, editingItem, allCategories }) => { placeholder:text-gray-400" placeholder="e.g. Paneer Tikka, Chicken Biryani" required - aria-invalid={Boolean(errors.name)} + aria-invalid={Boolean(errors.name)} + aria-describedby={errors.name ? 'err-food-name' : undefined} /> - {errors.name &&{errors.name}
} + {errors.name &&{errors.name}
} {/* Price */} @@ -263,9 +264,10 @@ const MenuFormModal = ({ isOpen, onClose, editingItem, allCategories }) => { placeholder="250.00" required aria-invalid={Boolean(errors.price)} + aria-describedby={errors.price ? 'err-food-price' : undefined} /> - {errors.price &&{errors.price}
} + {errors.price &&{errors.price}
} @@ -285,10 +287,11 @@ const MenuFormModal = ({ isOpen, onClose, editingItem, allCategories }) => { placeholder:text-gray-400 resize-none" placeholder="Describe the food item, ingredients, taste..." aria-invalid={Boolean(errors.description)} + aria-describedby={errors.description ? 'err-food-description' : undefined} />{errors.description}
+{errors.description}
) : ( )} @@ -314,7 +317,8 @@ const MenuFormModal = ({ isOpen, onClose, editingItem, allCategories }) => { focus:border-transparent focus:bg-white outline-none transition-all text-gray-800 appearance-none cursor-pointer" required - aria-invalid={Boolean(errors.categoryId)} + aria-invalid={Boolean(errors.categoryId)} + aria-describedby={errors.categoryId ? 'err-food-category' : undefined} >{errors.categoryId}
} + {errors.categoryId &&{errors.categoryId}
} {/* Image Upload */} @@ -378,7 +382,7 @@ const MenuFormModal = ({ isOpen, onClose, editingItem, allCategories }) => { className="absolute top-0 left-0 w-full h-full opacity-0 cursor-pointer" /> - {errors.imageFile &&{errors.imageFile}
} + {errors.imageFile &&{errors.imageFile}
} @@ -399,9 +403,10 @@ const MenuFormModal = ({ isOpen, onClose, editingItem, allCategories }) => { placeholder:text-gray-400 disabled:opacity-60" placeholder="https://example.com/food-image.jpg" aria-invalid={Boolean(errors.imageUrl)} + aria-describedby={errors.imageUrl ? 'err-food-imageurl' : undefined} /> {errors.imageUrl ? ( -{errors.imageUrl}
+{errors.imageUrl}
) : (Use a URL when no image file is uploaded.
)} diff --git a/RestroHub-FrontEnd/src/components/admin/profile/profileComponents/PersonalInfoCard.jsx b/RestroHub-FrontEnd/src/components/admin/profile/profileComponents/PersonalInfoCard.jsx index 468bc2a7..6942bc02 100644 --- a/RestroHub-FrontEnd/src/components/admin/profile/profileComponents/PersonalInfoCard.jsx +++ b/RestroHub-FrontEnd/src/components/admin/profile/profileComponents/PersonalInfoCard.jsx @@ -35,6 +35,18 @@ const PersonalInfoCard = ({ profile, onSave }) => { const handleSubmit = async (e) => { e.preventDefault(); + // client-side validation + const errs = {}; + if (!formData.firstName.trim()) errs.firstName = 'First name is required'; + if (!formData.phoneNumber.trim()) errs.phoneNumber = 'Phone number is required'; + else if (!/^\+?[0-9\s-]{7,15}$/.test(formData.phoneNumber)) errs.phoneNumber = 'Enter a valid phone number'; + + if (Object.keys(errs).length) { + setSaving(false); + setFieldErrors(errs); + return; + } + try { setSaving(true); if (onSave) { @@ -81,6 +93,8 @@ const PersonalInfoCard = ({ profile, onSave }) => { const labelClass = 'mb-1.5 block text-sm font-medium text-gray-800'; + const [fieldErrors, setFieldErrors] = useState({}); + // View Mode Row const InfoRow = ({ label, value }) => ({fieldErrors.firstName}
}{fieldErrors.phoneNumber}
}+
{fieldErrors.name}
} {/* Description */} @@ -211,15 +233,22 @@ const BranchFormModal = ({ isOpen, onClose, editingBranch, restaurantId }) => { updateField('add1', e.target.value)} - className="w-full px-4 py-3 bg-gray-50 border border-gray-200 rounded-xl + onChange={(e) => { + updateField('add1', e.target.value); + setFieldErrors((p) => ({ ...p, add1: undefined })); + }} + className={`w-full px-4 py-3 bg-gray-50 rounded-xl focus:ring-2 focus:ring-blue-500 focus:border-transparent focus:bg-white outline-none transition-all text-gray-800 - placeholder:text-gray-400" + placeholder:text-gray-400 ${fieldErrors.add1 ? 'border-red-500' : 'border-gray-200'}`} + aria-required="true" + aria-invalid={fieldErrors.add1 ? 'true' : 'false'} + aria-describedby={fieldErrors.add1 ? 'err-branch-add1' : undefined} placeholder="Street address, building number" required maxLength={255} /> + {fieldErrors.add1 &&{fieldErrors.add1}
} {/* Address Line 2 */} @@ -254,15 +283,22 @@ const BranchFormModal = ({ isOpen, onClose, editingBranch, restaurantId }) => { updateField('city', e.target.value)} - className="w-full px-4 py-3 bg-gray-50 border border-gray-200 rounded-xl - focus:ring-2 focus:ring-blue-500 focus:border-transparent - focus:bg-white outline-none transition-all text-gray-800 - placeholder:text-gray-400" + onChange={(e) => { + updateField('city', e.target.value); + setFieldErrors((p) => ({ ...p, city: undefined })); + }} + className={`w-full px-4 py-3 bg-gray-50 rounded-xl + focus:ring-2 focus:ring-blue-500 focus:border-transparent + focus:bg-white outline-none transition-all text-gray-800 + placeholder:text-gray-400 ${fieldErrors.city ? 'border-red-500' : 'border-gray-200'}`} + aria-required="true" + aria-invalid={fieldErrors.city ? 'true' : 'false'} + aria-describedby={fieldErrors.city ? 'err-branch-city' : undefined} placeholder="e.g., Mumbai" required maxLength={100} /> + {fieldErrors.city &&{fieldErrors.city}
} {/* State */} @@ -275,15 +311,22 @@ const BranchFormModal = ({ isOpen, onClose, editingBranch, restaurantId }) => { updateField('state', e.target.value)} - className="w-full px-4 py-3 bg-gray-50 border border-gray-200 rounded-xl - focus:ring-2 focus:ring-blue-500 focus:border-transparent - focus:bg-white outline-none transition-all text-gray-800 - placeholder:text-gray-400" + onChange={(e) => { + updateField('state', e.target.value); + setFieldErrors((p) => ({ ...p, state: undefined })); + }} + className={`w-full px-4 py-3 bg-gray-50 rounded-xl + focus:ring-2 focus:ring-blue-500 focus:border-transparent + focus:bg-white outline-none transition-all text-gray-800 + placeholder:text-gray-400 ${fieldErrors.state ? 'border-red-500' : 'border-gray-200'}`} + aria-required="true" + aria-invalid={fieldErrors.state ? 'true' : 'false'} + aria-describedby={fieldErrors.state ? 'err-branch-state' : undefined} placeholder="e.g., Maharashtra" required maxLength={100} /> + {fieldErrors.state &&{fieldErrors.state}
} @@ -299,15 +342,22 @@ const BranchFormModal = ({ isOpen, onClose, editingBranch, restaurantId }) => { updateField('postalCode', e.target.value)} - className="w-full px-4 py-3 bg-gray-50 border border-gray-200 rounded-xl - focus:ring-2 focus:ring-blue-500 focus:border-transparent - focus:bg-white outline-none transition-all text-gray-800 - placeholder:text-gray-400" + onChange={(e) => { + updateField('postalCode', e.target.value); + setFieldErrors((p) => ({ ...p, postalCode: undefined })); + }} + className={`w-full px-4 py-3 bg-gray-50 rounded-xl + focus:ring-2 focus:ring-blue-500 focus:border-transparent + focus:bg-white outline-none transition-all text-gray-800 + placeholder:text-gray-400 ${fieldErrors.postalCode ? 'border-red-500' : 'border-gray-200'}`} + aria-required="true" + aria-invalid={fieldErrors.postalCode ? 'true' : 'false'} + aria-describedby={fieldErrors.postalCode ? 'err-branch-postal' : undefined} placeholder="e.g., 400001" required maxLength={20} /> + {fieldErrors.postalCode &&{fieldErrors.postalCode}
} {/* Country */} diff --git a/RestroHub-FrontEnd/src/components/admin/store/tables/TableFormModal.jsx b/RestroHub-FrontEnd/src/components/admin/store/tables/TableFormModal.jsx index c44c2beb..3fa35e72 100644 --- a/RestroHub-FrontEnd/src/components/admin/store/tables/TableFormModal.jsx +++ b/RestroHub-FrontEnd/src/components/admin/store/tables/TableFormModal.jsx @@ -12,6 +12,7 @@ const TableFormModal = ({ isOpen, onClose, onSaved, branchId, editingTable }) => }); const [submitting, setSubmitting] = useState(false); const [error, setError] = useState(null); + const [fieldErrors, setFieldErrors] = useState({}); useEffect(() => { if (editingTable) { @@ -31,6 +32,18 @@ const TableFormModal = ({ isOpen, onClose, onSaved, branchId, editingTable }) => e.preventDefault(); setSubmitting(true); setError(null); + setFieldErrors({}); + + const errs = {}; + if (!formData.number) errs.number = 'Table number is required'; + if (!formData.capacity) errs.capacity = 'Seating capacity is required'; + else if (Number(formData.capacity) <= 0) errs.capacity = 'Enter a valid capacity'; + + if (Object.keys(errs).length) { + setFieldErrors(errs); + setSubmitting(false); + return; + } try { const payload = { @@ -113,11 +126,17 @@ const TableFormModal = ({ isOpen, onClose, onSaved, branchId, editingTable }) => type="number" min="1" value={formData.number} - onChange={(e) => updateField('number', e.target.value)} - className={inputClass} + onChange={(e) => { + updateField('number', e.target.value); + setFieldErrors((p) => ({ ...p, number: undefined })); + }} + className={`${inputClass} ${fieldErrors.number ? 'border-red-500' : ''}`} placeholder="9" - required + aria-required="true" + aria-invalid={fieldErrors.number ? 'true' : 'false'} + aria-describedby={fieldErrors.number ? 'err-table-number' : undefined} /> + {fieldErrors.number &&{fieldErrors.number}
}{fieldErrors.upiId}
}
{fieldErrors.name}
+ )} +{fieldErrors.email}
+ )} +{fieldErrors.phone}
+ )} +{fieldErrors.date}
+ )} +{fieldErrors.time}
+ )} +{fieldErrors.guests}
+ )} +