From e0c314d3afd7f70ee81ebadd6f14e6184f5b1263 Mon Sep 17 00:00:00 2001 From: aryabenade Date: Mon, 27 Apr 2026 15:48:34 +0530 Subject: [PATCH] show validation errors below the fields instead of toasters --- src/components/RegisterPage.tsx | 219 ++++++++++++++++++++++--------- src/pages/PaymentDetailsPage.tsx | 166 ++++++++--------------- src/pages/ProfilePage.tsx | 162 ++++++++++++++++------- 3 files changed, 323 insertions(+), 224 deletions(-) diff --git a/src/components/RegisterPage.tsx b/src/components/RegisterPage.tsx index 8072f8d..0ced869 100644 --- a/src/components/RegisterPage.tsx +++ b/src/components/RegisterPage.tsx @@ -10,6 +10,7 @@ import { Footer } from './Footer'; import { useNavigate } from 'react-router-dom'; import { Label } from './ui/label'; import { useEffect } from 'react'; +import { AlertCircle } from 'lucide-react'; export default function RegisterPage() { const { login, user } = useAuth(); @@ -30,6 +31,7 @@ export default function RegisterPage() { const [helperText, setHelperText] = useState(''); const [isLoading, setIsLoading] = useState(false); + const [fieldErrors, setFieldErrors] = useState>({}); const navigate = useNavigate() @@ -46,63 +48,120 @@ export default function RegisterPage() { setFormData(prev => ({ ...prev, [field]: value })); }; - const validateForm = () => { - // First Name - if (!formData.firstName.trim()) return toast.error('First name is required'), false; - if (/\s/.test(formData.firstName)) return toast.error('First name must not contain spaces'), false; - if (!/^[A-Za-z]+$/.test(formData.firstName)) return toast.error('First name must contain only letters (A–Z)'), false; - if (formData.firstName.length < 2 || formData.firstName.length > 50) return toast.error('First name must be between 2 and 50 characters'), false; + // const validateForm = () => { + // // First Name + // if (!formData.firstName.trim()) return toast.error('First name is required'), false; + // if (/\s/.test(formData.firstName)) return toast.error('First name must not contain spaces'), false; + // if (!/^[A-Za-z]+$/.test(formData.firstName)) return toast.error('First name must contain only letters (A–Z)'), false; + // if (formData.firstName.length < 2 || formData.firstName.length > 50) return toast.error('First name must be between 2 and 50 characters'), false; - // Last Name - if (!formData.lastName.trim()) return toast.error('Last name is required'), false; - if (/\s/.test(formData.lastName)) return toast.error('Last name must not contain spaces'), false; - if (!/^[A-Za-z]+$/.test(formData.lastName)) return toast.error('Last name must contain only letters (A–Z)'), false; - if (formData.lastName.length < 2 || formData.lastName.length > 50) return toast.error('Last name must be between 2 and 50 characters'), false; + // // Last Name + // if (!formData.lastName.trim()) return toast.error('Last name is required'), false; + // if (/\s/.test(formData.lastName)) return toast.error('Last name must not contain spaces'), false; + // if (!/^[A-Za-z]+$/.test(formData.lastName)) return toast.error('Last name must contain only letters (A–Z)'), false; + // if (formData.lastName.length < 2 || formData.lastName.length > 50) return toast.error('Last name must be between 2 and 50 characters'), false; - // Email - if (!formData.emailAddress.trim()) return toast.error('Email address is required'), false; - if (!/\S+@\S+\.\S+/.test(formData.emailAddress)) return toast.error('Enter a valid email (e.g. name@example.com)'), false; + // // Email + // if (!formData.emailAddress.trim()) return toast.error('Email address is required'), false; + // if (!/\S+@\S+\.\S+/.test(formData.emailAddress)) return toast.error('Enter a valid email (e.g. name@example.com)'), false; - // ISD - if (!formData.isdCode.trim()) return toast.error('ISD code is required'), false; - if (/\s/.test(formData.isdCode)) return toast.error('ISD code must not contain spaces'), false; - if (!formData.isdCode.startsWith('+')) return toast.error("ISD code must start with '+' (e.g. +91)"), false; - if (!/^\+\d+$/.test(formData.isdCode)) return toast.error("ISD code must contain only digits after '+'"), false; + // // ISD + // if (!formData.isdCode.trim()) return toast.error('ISD code is required'), false; + // if (/\s/.test(formData.isdCode)) return toast.error('ISD code must not contain spaces'), false; + // if (!formData.isdCode.startsWith('+')) return toast.error("ISD code must start with '+' (e.g. +91)"), false; + // if (!/^\+\d+$/.test(formData.isdCode)) return toast.error("ISD code must contain only digits after '+'"), false; - // Phone - if (!formData.mobileNumber.trim()) return toast.error('Mobile number is required'), false; - if (/\s/.test(formData.mobileNumber)) return toast.error('Mobile number must not contain spaces'), false; - if (!/^\d+$/.test(formData.mobileNumber)) return toast.error('Mobile number must contain only digits (0–9)'), false; - if (formData.mobileNumber.length < 7 || formData.mobileNumber.length > 15) return toast.error('Mobile number must be between 7 and 15 digits'), false; + // // Phone + // if (!formData.mobileNumber.trim()) return toast.error('Mobile number is required'), false; + // if (/\s/.test(formData.mobileNumber)) return toast.error('Mobile number must not contain spaces'), false; + // if (!/^\d+$/.test(formData.mobileNumber)) return toast.error('Mobile number must contain only digits (0–9)'), false; + // if (formData.mobileNumber.length < 7 || formData.mobileNumber.length > 15) return toast.error('Mobile number must be between 7 and 15 digits'), false; - // Address - if (!formData.address1.trim()) return toast.error('Address is required'), false; - if (!/^[A-Za-z0-9\s,\-.]+$/.test(formData.address1)) return toast.error('Address can only contain letters, numbers, spaces, commas, dots, and hyphens'), false; - if (formData.address1.length < 5 || formData.address1.length > 100) return toast.error('Address must be between 5 and 100 characters'), false; + // // Address + // if (!formData.address1.trim()) return toast.error('Address is required'), false; + // if (!/^[A-Za-z0-9\s,\-.]+$/.test(formData.address1)) return toast.error('Address can only contain letters, numbers, spaces, commas, dots, and hyphens'), false; + // if (formData.address1.length < 5 || formData.address1.length > 100) return toast.error('Address must be between 5 and 100 characters'), false; - // City - if (!formData.city.trim()) return toast.error('City is required'), false; - if (!/^[A-Za-z\s\-'À-ÿ]+$/.test(formData.city)) return toast.error('City can only contain letters and spaces'), false; - if (/\s{2,}/.test(formData.city)) return toast.error('City must not contain multiple consecutive spaces'), false; + // // City + // if (!formData.city.trim()) return toast.error('City is required'), false; + // if (!/^[A-Za-z\s\-'À-ÿ]+$/.test(formData.city)) return toast.error('City can only contain letters and spaces'), false; + // if (/\s{2,}/.test(formData.city)) return toast.error('City must not contain multiple consecutive spaces'), false; - // State - if (!formData.state.trim()) return toast.error('State is required'), false; - if (!/^[A-Za-z\s\-'À-ÿ]+$/.test(formData.state)) return toast.error('State can only contain letters and spaces'), false; - if (/\s{2,}/.test(formData.state)) return toast.error('State must not contain multiple consecutive spaces'), false; + // // State + // if (!formData.state.trim()) return toast.error('State is required'), false; + // if (!/^[A-Za-z\s\-'À-ÿ]+$/.test(formData.state)) return toast.error('State can only contain letters and spaces'), false; + // if (/\s{2,}/.test(formData.state)) return toast.error('State must not contain multiple consecutive spaces'), false; - // Country - if (!formData.country.trim()) return toast.error('Country is required'), false; - if (!/^[A-Za-z\s\-'À-ÿ]+$/.test(formData.country)) return toast.error('Country can only contain letters and spaces'), false; + // // Country + // if (!formData.country.trim()) return toast.error('Country is required'), false; + // if (!/^[A-Za-z\s\-'À-ÿ]+$/.test(formData.country)) return toast.error('Country can only contain letters and spaces'), false; - // Postal Code - if (!formData.postalCode.trim()) return toast.error('Postal code is required'), false; - if (/\s/.test(formData.postalCode)) return toast.error('Postal code must not contain spaces'), false; - if (!/^[A-Za-z0-9]+$/.test(formData.postalCode)) return toast.error('Postal code must contain only letters and numbers'), false; - if (formData.postalCode.length < 4 || formData.postalCode.length > 10) return toast.error('Postal code must be between 4 and 10 characters'), false; + // // Postal Code + // if (!formData.postalCode.trim()) return toast.error('Postal code is required'), false; + // if (/\s/.test(formData.postalCode)) return toast.error('Postal code must not contain spaces'), false; + // if (!/^[A-Za-z0-9]+$/.test(formData.postalCode)) return toast.error('Postal code must contain only letters and numbers'), false; + // if (formData.postalCode.length < 4 || formData.postalCode.length > 10) return toast.error('Postal code must be between 4 and 10 characters'), false; - return true; -}; + // return true; + // }; + const validateForm = () => { + const e: Record = {}; + if (!formData.firstName.trim()) e.firstName = 'First name is required'; + else if (/\s/.test(formData.firstName)) e.firstName = 'First name must not contain spaces'; + else if (!/^[A-Za-z]+$/.test(formData.firstName)) e.firstName = 'First name must contain only letters (A–Z)'; + else if (formData.firstName.length < 2 || formData.firstName.length > 50) e.firstName = 'First name must be between 2 and 50 characters'; + + if (!formData.lastName.trim()) e.lastName = 'Last name is required'; + else if (/\s/.test(formData.lastName)) e.lastName = 'Last name must not contain spaces'; + else if (!/^[A-Za-z]+$/.test(formData.lastName)) e.lastName = 'Last name must contain only letters (A–Z)'; + else if (formData.lastName.length < 2 || formData.lastName.length > 50) e.lastName = 'Last name must be between 2 and 50 characters'; + + if (!formData.emailAddress.trim()) e.emailAddress = 'Email address is required'; + else if (!/\S+@\S+\.\S+/.test(formData.emailAddress)) e.emailAddress = 'Enter a valid email (e.g. name@example.com)'; + + if (!formData.isdCode.trim()) e.isdCode = 'ISD code is required'; + else if (/\s/.test(formData.isdCode)) e.isdCode = 'ISD code must not contain spaces'; + else if (!formData.isdCode.startsWith('+')) e.isdCode = "ISD code must start with '+' (e.g. +91)"; + else if (!/^\+\d+$/.test(formData.isdCode)) e.isdCode = "ISD code must contain only digits after '+'"; + + if (!formData.mobileNumber.trim()) e.mobileNumber = 'Mobile number is required'; + else if (/\s/.test(formData.mobileNumber)) e.mobileNumber = 'Mobile number must not contain spaces'; + else if (!/^\d+$/.test(formData.mobileNumber)) e.mobileNumber = 'Mobile number must contain only digits (0–9)'; + else if (formData.mobileNumber.length < 7 || formData.mobileNumber.length > 15) e.mobileNumber = 'Mobile number must be between 7 and 15 digits'; + + if (!formData.address1.trim()) e.address1 = 'Address is required'; + else if (!/^[A-Za-z0-9\s,\-.]+$/.test(formData.address1)) e.address1 = 'Address can only contain letters, numbers, spaces, commas, dots, and hyphens'; + else if (formData.address1.length < 5 || formData.address1.length > 100) e.address1 = 'Address must be between 5 and 100 characters'; + + if (!formData.city.trim()) e.city = 'City is required'; + else if (!/^[A-Za-z\s\-'À-ÿ]+$/.test(formData.city)) e.city = 'City can only contain letters and spaces'; + else if (/\s{2,}/.test(formData.city)) e.city = 'City must not contain multiple consecutive spaces'; + + if (!formData.state.trim()) e.state = 'State is required'; + else if (!/^[A-Za-z\s\-'À-ÿ]+$/.test(formData.state)) e.state = 'State can only contain letters and spaces'; + else if (/\s{2,}/.test(formData.state)) e.state = 'State must not contain multiple consecutive spaces'; + + if (!formData.country.trim()) e.country = 'Country is required'; + else if (!/^[A-Za-z\s\-'À-ÿ]+$/.test(formData.country)) e.country = 'Country can only contain letters and spaces'; + else if (formData.country.length < 2 || formData.country.length > 50) e.country = 'Country must be between 2 and 50 characters'; + + if (!formData.postalCode.trim()) e.postalCode = 'Postal code is required'; + else if (/\s/.test(formData.postalCode)) e.postalCode = 'Postal code must not contain spaces'; + else if (!/^[A-Za-z0-9]+$/.test(formData.postalCode)) e.postalCode = 'Postal code must contain only letters and numbers'; + else if (formData.postalCode.length < 4 || formData.postalCode.length > 10) e.postalCode = 'Postal code must be between 4 and 10 characters'; + + setFieldErrors(e); + return Object.keys(e).length === 0; + }; + + // Helper to render inline error (add once near top of return or as a small component) + const FieldError = ({ name }: { name: string }) => + fieldErrors[name] ? ( +

+ {fieldErrors[name]} +

+ ) : null; const handleRegister = async () => { @@ -162,28 +221,36 @@ export default function RegisterPage() {
- + handleInputChange('firstName', e.target.value)} - className="h-12 bg-gray-50 border-0 rounded-xl mt-1" + className={`h-12 bg-gray-50 border-0 rounded-xl mt-1 ${fieldErrors.firstName ? 'border border-red-400' : ''}`} /> +
- + handleInputChange('lastName', e.target.value)} - className="h-12 bg-gray-50 border-0 rounded-xl mt-1" + className={`h-12 bg-gray-50 border-0 rounded-xl mt-1 ${fieldErrors.lastName ? 'border border-red-400' : ''}`} /> +
- + handleInputChange('emailAddress', e.target.value)} className="h-12 bg-gray-50 border-0 rounded-xl mt-1" /> +
- + handleInputChange('isdCode', e.target.value)} - className="h-12 bg-gray-50 border-0 rounded-xl mt-1" + className={`h-12 bg-gray-50 border-0 rounded-xl mt-1 ${fieldErrors.isdCode ? 'border border-red-400' : ''}`} /> +
- + handleInputChange('mobileNumber', e.target.value)} - className="h-12 bg-gray-50 border-0 rounded-xl mt-1" + className={`h-12 bg-gray-50 border-0 rounded-xl mt-1 ${fieldErrors.mobileNumber ? 'border border-red-400' : ''}`} /> +
@@ -225,13 +299,16 @@ export default function RegisterPage() {
- + handleInputChange('address1', e.target.value)} - className="h-12 bg-gray-50 border-0 rounded-xl mt-1" + className={`h-12 bg-gray-50 border-0 rounded-xl mt-1 ${fieldErrors.address1 ? 'border border-red-400' : ''}`} /> +
@@ -246,45 +323,57 @@ export default function RegisterPage() {
- + handleInputChange('city', e.target.value)} - className="h-12 bg-gray-50 border-0 rounded-xl mt-1" + className={`h-12 bg-gray-50 border-0 rounded-xl mt-1 ${fieldErrors.city ? 'border border-red-400' : ''}`} /> +
- + handleInputChange('state', e.target.value)} - className="h-12 bg-gray-50 border-0 rounded-xl mt-1" + className={`h-12 bg-gray-50 border-0 rounded-xl mt-1 ${fieldErrors.state ? 'border border-red-400' : ''}`} /> +
- + handleInputChange('country', e.target.value)} - className="h-12 bg-gray-50 border-0 rounded-xl mt-1" + className={`h-12 bg-gray-50 border-0 rounded-xl mt-1 ${fieldErrors.country ? 'border border-red-400' : ''}`} /> +
- + handleInputChange('postalCode', e.target.value)} - className="h-12 bg-gray-50 border-0 rounded-xl mt-1" + className={`h-12 bg-gray-50 border-0 rounded-xl mt-1 ${fieldErrors.postalCode ? 'border border-red-400' : ''}`} /> +
diff --git a/src/pages/PaymentDetailsPage.tsx b/src/pages/PaymentDetailsPage.tsx index cc86e0a..24ef775 100644 --- a/src/pages/PaymentDetailsPage.tsx +++ b/src/pages/PaymentDetailsPage.tsx @@ -85,7 +85,7 @@ function Field({ prefilled, disabled = false, }: { - label: string; + label: React.ReactNode; value: string; onChange: (v: string) => void; placeholder?: string; @@ -230,64 +230,64 @@ export function PaymentDetailsPage({ const [errors, setErrors] = useState>({}); -const validate = () => { - const e: Record = {}; + const validate = () => { + const e: Record = {}; - if (selectedTab === 'gift') { - // First Name - if (!giftFirstName.trim()) e.giftFirstName = 'First name is required'; - else if (/\s/.test(giftFirstName)) e.giftFirstName = 'First name must not contain spaces'; - else if (!/^[A-Za-z]+$/.test(giftFirstName)) e.giftFirstName = 'First name must contain only letters (A–Z)'; - else if (giftFirstName.length < 2 || giftFirstName.length > 50) e.giftFirstName = 'First name must be between 2 and 50 characters'; + if (selectedTab === 'gift') { + // First Name + if (!giftFirstName.trim()) e.giftFirstName = 'First name is required'; + else if (/\s/.test(giftFirstName)) e.giftFirstName = 'First name must not contain spaces'; + else if (!/^[A-Za-z]+$/.test(giftFirstName)) e.giftFirstName = 'First name must contain only letters (A–Z)'; + else if (giftFirstName.length < 2 || giftFirstName.length > 50) e.giftFirstName = 'First name must be between 2 and 50 characters'; - // Last Name - if (!giftLastName.trim()) e.giftLastName = 'Last name is required'; - else if (/\s/.test(giftLastName)) e.giftLastName = 'Last name must not contain spaces'; - else if (!/^[A-Za-z]+$/.test(giftLastName)) e.giftLastName = 'Last name must contain only letters (A–Z)'; - else if (giftLastName.length < 2 || giftLastName.length > 50) e.giftLastName = 'Last name must be between 2 and 50 characters'; + // Last Name + if (!giftLastName.trim()) e.giftLastName = 'Last name is required'; + else if (/\s/.test(giftLastName)) e.giftLastName = 'Last name must not contain spaces'; + else if (!/^[A-Za-z]+$/.test(giftLastName)) e.giftLastName = 'Last name must contain only letters (A–Z)'; + else if (giftLastName.length < 2 || giftLastName.length > 50) e.giftLastName = 'Last name must be between 2 and 50 characters'; - // ISD Code - if (!giftIsd.trim()) e.giftIsd = 'ISD code is required'; - else if (/\s/.test(giftIsd)) e.giftIsd = 'ISD code must not contain spaces'; - else if (!giftIsd.startsWith('+')) e.giftIsd = "ISD code must start with '+' (e.g. +91)"; - else if (!/^\+\d+$/.test(giftIsd)) e.giftIsd = "ISD code must contain only digits after '+'"; + // ISD Code + if (!giftIsd.trim()) e.giftIsd = 'ISD code is required'; + else if (/\s/.test(giftIsd)) e.giftIsd = 'ISD code must not contain spaces'; + else if (!giftIsd.startsWith('+')) e.giftIsd = "ISD code must start with '+' (e.g. +91)"; + else if (!/^\+\d+$/.test(giftIsd)) e.giftIsd = "ISD code must contain only digits after '+'"; - // Email - if (!giftEmail.trim()) e.giftEmail = 'Email address is required'; - else if (!/\S+@\S+\.\S+/.test(giftEmail)) e.giftEmail = 'Enter a valid email (e.g. name@example.com)'; + // Email + if (!giftEmail.trim()) e.giftEmail = 'Email address is required'; + else if (!/\S+@\S+\.\S+/.test(giftEmail)) e.giftEmail = 'Enter a valid email (e.g. name@example.com)'; - // Phone - if (!giftPhone.trim()) e.giftPhone = 'Phone number is required'; - else if (/\s/.test(giftPhone)) e.giftPhone = 'Phone number must not contain spaces'; - else if (!/^\d+$/.test(giftPhone)) e.giftPhone = 'Phone number must contain only digits (0–9)'; - else if (giftPhone.length < 7 || giftPhone.length > 15) e.giftPhone = 'Phone number must be between 7 and 15 digits'; + // Phone + if (!giftPhone.trim()) e.giftPhone = 'Phone number is required'; + else if (/\s/.test(giftPhone)) e.giftPhone = 'Phone number must not contain spaces'; + else if (!/^\d+$/.test(giftPhone)) e.giftPhone = 'Phone number must contain only digits (0–9)'; + else if (giftPhone.length < 7 || giftPhone.length > 15) e.giftPhone = 'Phone number must be between 7 and 15 digits'; - // Message - if (!giftMessage.trim()) e.giftMessage = 'Message is required'; - else if (giftMessage.length < 5) e.giftMessage = 'Message must be at least 5 characters long'; - else if (giftMessage.length > 500) e.giftMessage = 'Message must not exceed 500 characters'; + // Message + if (!giftMessage.trim()) e.giftMessage = 'Message is required'; + else if (giftMessage.length < 5) e.giftMessage = 'Message must be at least 5 characters long'; + else if (giftMessage.length > 500) e.giftMessage = 'Message must not exceed 500 characters'; - // City - if (!giftCity.trim()) e.giftCity = 'City is required'; - else if (!/^[A-Za-z\s\-'À-ÿ]+$/.test(giftCity)) e.giftCity = 'City can only contain letters and spaces'; - else if (/\s{2,}/.test(giftCity)) e.giftCity = 'City must not contain multiple consecutive spaces'; - else if (giftCity.length < 2 || giftCity.length > 50) e.giftCity = 'City must be between 2 and 50 characters'; + // City + if (!giftCity.trim()) e.giftCity = 'City is required'; + else if (!/^[A-Za-z\s\-'À-ÿ]+$/.test(giftCity)) e.giftCity = 'City can only contain letters and spaces'; + else if (/\s{2,}/.test(giftCity)) e.giftCity = 'City must not contain multiple consecutive spaces'; + else if (giftCity.length < 2 || giftCity.length > 50) e.giftCity = 'City must be between 2 and 50 characters'; - // Country - if (!giftCountry.trim()) e.giftCountry = 'Country is required'; - else if (!/^[A-Za-z\s\-'À-ÿ]+$/.test(giftCountry)) e.giftCountry = 'Country can only contain letters and spaces'; - else if (giftCountry.length < 2 || giftCountry.length > 50) e.giftCountry = 'Country must be between 2 and 50 characters'; - } + // Country + if (!giftCountry.trim()) e.giftCountry = 'Country is required'; + else if (!/^[A-Za-z\s\-'À-ÿ]+$/.test(giftCountry)) e.giftCountry = 'Country can only contain letters and spaces'; + else if (giftCountry.length < 2 || giftCountry.length > 50) e.giftCountry = 'Country must be between 2 and 50 characters'; + } - return e; -}; + return e; + }; const [isRedirecting, setIsRedirecting] = useState(false); const handlePayment = async () => { const validationErrors = validate(); setErrors(validationErrors); if (Object.keys(validationErrors).length > 0) { - toast.error('Please fill all required fields'); + // toast.error('Please fill all required fields'); return; } @@ -413,8 +413,8 @@ const validate = () => {