Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ const CategoryFormModal = ({ isOpen, onClose }) => {
});

const [submitting, setSubmitting] = useState(false);
const [fieldErrors, setFieldErrors] = useState({});

const updateField = (field, value) => {
setFormData(prev => ({
Expand All @@ -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);

Expand Down Expand Up @@ -82,16 +93,22 @@ const CategoryFormModal = ({ isOpen, onClose }) => {
<input
type="text"
value={formData.name}
onChange={(e) => 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 && <p id="err-cat-name" className="mt-1.5 text-xs text-red-500">{fieldErrors.name}</p>}
</div>

{/* Description */}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 && <p className="text-xs text-red-500">{errors.name}</p>}
{errors.name && <p id="err-food-name" className="text-xs text-red-500">{errors.name}</p>}
</div>

{/* Price */}
Expand All @@ -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}
/>
</div>
{errors.price && <p className="text-xs text-red-500">{errors.price}</p>}
{errors.price && <p id="err-food-price" className="text-xs text-red-500">{errors.price}</p>}
</div>
</div>

Expand All @@ -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}
/>
<div className="flex justify-between gap-3">
{errors.description ? (
<p className="text-xs text-red-500">{errors.description}</p>
<p id="err-food-description" className="text-xs text-red-500">{errors.description}</p>
) : (
<span />
)}
Expand All @@ -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}
>
<option value="" disabled>
Select a category
Expand All @@ -333,7 +337,7 @@ const MenuFormModal = ({ isOpen, onClose, editingItem, allCategories }) => {
<path strokeLinecap="round" strokeLinejoin="round" d="M19 9l-7 7-7-7" />
</svg>
</div>
{errors.categoryId && <p className="text-xs text-red-500">{errors.categoryId}</p>}
{errors.categoryId && <p id="err-food-category" className="text-xs text-red-500">{errors.categoryId}</p>}
</div>

{/* Image Upload */}
Expand Down Expand Up @@ -378,7 +382,7 @@ const MenuFormModal = ({ isOpen, onClose, editingItem, allCategories }) => {
className="absolute top-0 left-0 w-full h-full opacity-0 cursor-pointer"
/>
</label>
{errors.imageFile && <p className="text-xs text-red-500">{errors.imageFile}</p>}
{errors.imageFile && <p id="err-food-imagefile" className="text-xs text-red-500">{errors.imageFile}</p>}
</div>
</div>

Expand All @@ -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 ? (
<p className="text-xs text-red-500">{errors.imageUrl}</p>
<p id="err-food-imageurl" className="text-xs text-red-500">{errors.imageUrl}</p>
) : (
<p className="text-xs text-gray-400">Use a URL when no image file is uploaded.</p>
)}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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 }) => (
<div>
Expand Down Expand Up @@ -135,11 +149,18 @@ const PersonalInfoCard = ({ profile, onSave }) => {
<input
type="text"
value={formData.firstName}
onChange={(e) => updateField('firstName', e.target.value)}
className={inputClass}
onChange={(e) => {
updateField('firstName', e.target.value);
setFieldErrors((p) => ({ ...p, firstName: undefined }));
}}
className={`${inputClass} ${fieldErrors.firstName ? 'border-red-500' : 'border-gray-200'}`}
placeholder="First name"
required
aria-required="true"
aria-invalid={fieldErrors.firstName ? 'true' : 'false'}
aria-describedby={fieldErrors.firstName ? 'err-personal-firstname' : undefined}
/>
{fieldErrors.firstName && <p id="err-personal-firstname" className="mt-1 text-xs text-red-500">{fieldErrors.firstName}</p>}
</div>
<div>
<label className={labelClass}>Last Name</label>
Expand Down Expand Up @@ -170,11 +191,18 @@ const PersonalInfoCard = ({ profile, onSave }) => {
<input
type="tel"
value={formData.phoneNumber}
onChange={(e) => updateField('phoneNumber', e.target.value)}
className={inputClass}
onChange={(e) => {
updateField('phoneNumber', e.target.value);
setFieldErrors((p) => ({ ...p, phoneNumber: undefined }));
}}
className={`${inputClass} ${fieldErrors.phoneNumber ? 'border-red-500' : 'border-gray-200'}`}
placeholder="9876543210"
required
aria-required="true"
aria-invalid={fieldErrors.phoneNumber ? 'true' : 'false'}
aria-describedby={fieldErrors.phoneNumber ? 'err-personal-phone' : undefined}
/>
{fieldErrors.phoneNumber && <p id="err-personal-phone" className="mt-1 text-xs text-red-500">{fieldErrors.phoneNumber}</p>}
</div>
</div>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,9 @@ const SecurityCard = () => {
className={`${inputClass} ${error ? 'border-red-300' : 'border-gray-200'}`}
placeholder="••••••••"
required
aria-required="true"
aria-invalid={error ? 'true' : 'false'}
aria-describedby={error ? `err-${field}` : undefined}
/>
<button
type="button"
Expand All @@ -75,7 +78,7 @@ const SecurityCard = () => {
</button>
</div>
{error && (
<p className="mt-1 flex items-center gap-1 text-xs text-red-600">
<p id={`err-${field}`} className="mt-1 flex items-center gap-1 text-xs text-red-600">
<AlertCircle className="h-3 w-3" /> {error}
</p>
)}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ const BranchFormModal = ({ isOpen, onClose, editingBranch, restaurantId }) => {
});
const [submitting, setSubmitting] = useState(false);
const [error, setError] = useState(null);
const [fieldErrors, setFieldErrors] = useState({});

// ========== POPULATE FORM ON EDIT ==========
useEffect(() => {
Expand Down Expand Up @@ -61,6 +62,20 @@ const BranchFormModal = ({ isOpen, onClose, editingBranch, restaurantId }) => {
e.preventDefault();
setSubmitting(true);
setError(null);
setFieldErrors({});

const errs = {};
if (!formData.name.trim()) errs.name = 'Branch name is required';
if (!formData.add1.trim()) errs.add1 = 'Address line 1 is required';
if (!formData.city.trim()) errs.city = 'City is required';
if (!formData.state.trim()) errs.state = 'State is required';
if (!formData.postalCode.trim()) errs.postalCode = 'Postal code is required';

if (Object.keys(errs).length) {
setFieldErrors(errs);
setSubmitting(false);
return;
}

try {
// Build payload matching BranchRequestDTO
Expand Down Expand Up @@ -161,16 +176,23 @@ const BranchFormModal = ({ isOpen, onClose, editingBranch, restaurantId }) => {
<input
type="text"
value={formData.name}
onChange={(e) => 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'}`}
aria-required="true"
aria-invalid={fieldErrors.name ? 'true' : 'false'}
aria-describedby={fieldErrors.name ? 'err-branch-name' : undefined}
placeholder="e.g., Main Branch, Downtown Outlet"
required
minLength={2}
maxLength={100}
/>
{fieldErrors.name && <p id="err-branch-name" className="mt-1.5 text-xs text-red-500">{fieldErrors.name}</p>}
</div>

{/* Description */}
Expand Down Expand Up @@ -211,15 +233,22 @@ const BranchFormModal = ({ isOpen, onClose, editingBranch, restaurantId }) => {
<input
type="text"
value={formData.add1}
onChange={(e) => 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 && <p id="err-branch-add1" className="mt-1.5 text-xs text-red-500">{fieldErrors.add1}</p>}
</div>

{/* Address Line 2 */}
Expand Down Expand Up @@ -254,15 +283,22 @@ const BranchFormModal = ({ isOpen, onClose, editingBranch, restaurantId }) => {
<input
type="text"
value={formData.city}
onChange={(e) => 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 && <p id="err-branch-city" className="mt-1.5 text-xs text-red-500">{fieldErrors.city}</p>}
</div>

{/* State */}
Expand All @@ -275,15 +311,22 @@ const BranchFormModal = ({ isOpen, onClose, editingBranch, restaurantId }) => {
<input
type="text"
value={formData.state}
onChange={(e) => 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 && <p id="err-branch-state" className="mt-1.5 text-xs text-red-500">{fieldErrors.state}</p>}
</div>
</div>

Expand All @@ -299,15 +342,22 @@ const BranchFormModal = ({ isOpen, onClose, editingBranch, restaurantId }) => {
<input
type="text"
value={formData.postalCode}
onChange={(e) => 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 && <p id="err-branch-postal" className="mt-1.5 text-xs text-red-500">{fieldErrors.postalCode}</p>}
</div>

{/* Country */}
Expand Down
Loading