add validations in forms

This commit is contained in:
aryabenade
2026-04-24 21:30:04 +05:30
parent b276dec0f5
commit a651186276
3 changed files with 138 additions and 251 deletions

View File

@@ -46,54 +46,60 @@ 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 (!/^[A-Za-z]+$/.test(formData.firstName)) return toast.error('First name must contain only alphabets'), 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 (!/^[A-Za-z]+$/.test(formData.firstName)) return toast.error('First name must contain only alphabets'), 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 (!/^[A-Za-z]+$/.test(formData.lastName)) return toast.error('Last name must contain only alphabets'), 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 (!/^[A-Za-z]+$/.test(formData.lastName)) return toast.error('Last name must contain only alphabets'), 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.includes('@')) return toast.error('Invalid email address'), false;
// Email (IMPROVED from 1st function)
if (!formData.emailAddress.trim()) return toast.error('Email is required'), false;
if (!/\S+@\S+\.\S+/.test(formData.emailAddress)) return toast.error('Valid email address required'), false;
// ISD Code
if (!formData.isdCode.startsWith("+")) return toast.error("ISD code must start with '+'"), false;
if (!/^\+\d+$/.test(formData.isdCode)) return toast.error("ISD code must contain only numbers after '+'"), false;
// ISD Code
if (!formData.isdCode.trim()) return toast.error('ISD code is required'), false;
if (!formData.isdCode.startsWith("+")) return toast.error("ISD code must start with '+'"), false;
if (!/^\+\d+$/.test(formData.isdCode)) return toast.error("ISD code must contain only numbers after '+'"), false;
// Mobile Number
if (!/^\d+$/.test(formData.mobileNumber)) return toast.error('Invalid mobile number'), false;
if (formData.mobileNumber.length < 7 || formData.mobileNumber.length > 15) return toast.error('Mobile number must be between 7 and 15 digits'), false;
// Mobile Number (IMPROVED with required check)
if (!formData.mobileNumber.trim()) return toast.error('Mobile number is required'), false;
if (!/^\d+$/.test(formData.mobileNumber)) return toast.error('Invalid mobile number'), false;
if (formData.mobileNumber.length < 7 || formData.mobileNumber.length > 15) return toast.error('Mobile number must be between 7 and 15 digits'), false;
// Address Line 1
if (!formData.address1.trim()) return toast.error('Address required'), false;
if (!/^[A-Za-z0-9\s]+$/.test(formData.address1)) return toast.error('Address must be alphanumeric'), false;
if (formData.address1.length < 5 || formData.address1.length > 100) return toast.error('Address must be between 5 and 100 characters'), false;
// Address Line 1 (UPDATED regex from 3rd function)
if (!formData.address1.trim()) return toast.error('Address required'), false;
if (!/^[A-Za-z0-9\s,\-.]+$/.test(formData.address1)) return toast.error('Address contains invalid characters'), 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 required'), false;
if (!/^[A-Za-z\s]+$/.test(formData.city)) return toast.error('City must contain only alphabets'), false;
if (formData.city.length < 2 || formData.city.length > 50) return toast.error('City must be between 2 and 50 characters'), false;
// City (IMPROVED with no multiple spaces)
if (!formData.city.trim()) return toast.error('City required'), false;
if (!/^[A-Za-z\s\-'À-ÿ]+$/.test(formData.city)) return toast.error('City must contain only alphabets'), false;
if (/\s{2,}/.test(formData.city)) return toast.error('City must not contain multiple consecutive spaces'), false;
if (formData.city.length < 2 || formData.city.length > 50) return toast.error('City must be between 2 and 50 characters'), false;
// State
if (!formData.state.trim()) return toast.error('State required'), false;
if (!/^[A-Za-z\s]+$/.test(formData.state)) return toast.error('State must contain only alphabets'), false;
if (formData.state.length < 2 || formData.state.length > 50) return toast.error('State must be between 2 and 50 characters'), false;
// State (IMPROVED similar to city)
if (!formData.state.trim()) return toast.error('State required'), false;
if (!/^[A-Za-z\s\-'À-ÿ]+$/.test(formData.state)) return toast.error('State must contain only alphabets'), false;
if (/\s{2,}/.test(formData.state)) return toast.error('State must not contain multiple consecutive spaces'), false;
if (formData.state.length < 2 || formData.state.length > 50) return toast.error('State must be between 2 and 50 characters'), false;
// Country
if (!formData.country.trim()) return toast.error('Country required'), false;
if (!/^[A-Za-z\s]+$/.test(formData.country)) return toast.error('Country must contain only alphabets'), false;
if (formData.country.length < 2 || formData.country.length > 50) return toast.error('Country must be between 2 and 50 characters'), false;
// Country (IMPROVED regex from 3rd)
if (!formData.country.trim()) return toast.error('Country required'), false;
if (!/^[A-Za-z\s\-'À-ÿ]+$/.test(formData.country)) return toast.error('Country must contain only alphabets'), false;
if (formData.country.length < 2 || formData.country.length > 50) return toast.error('Country must be between 2 and 50 characters'), false;
// Postal Code
if (!/^\d+$/.test(formData.postalCode)) return toast.error('Postal code should only contain numbers'), false;
if (formData.postalCode.length < 4 || formData.postalCode.length > 10) return toast.error('Postal code must be between 4 and 10 digits'), false;
// Postal Code (IMPROVED from 3rd: alphanumeric allowed)
if (!formData.postalCode.trim()) return toast.error('Postal code is required'), false;
if (!/^[A-Za-z0-9\s\-]+$/.test(formData.postalCode)) return toast.error('Postal code contains invalid characters'), 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;
};

View File

@@ -231,23 +231,51 @@ export function PaymentDetailsPage({
const [errors, setErrors] = useState<Record<string, string>>({});
const validate = () => {
const e: Record<string, string> = {};
if (selectedTab === 'gift') {
if (!giftFirstName.trim()) e.giftFirstName = 'Required';
if (!giftLastName.trim()) e.giftLastName = 'Required';
if (!giftIsd.trim()) e.giftIsd = 'Required';
if (!giftMessage.trim()) e.giftMessage = 'Required';
if (!giftEmail.trim() || !/\S+@\S+\.\S+/.test(giftEmail)) {
e.giftEmail = 'Valid email required';
}
if (!giftPhone.trim() || !/^\+?[0-9]{7,15}$/.test(giftPhone)) {
e.giftPhone = 'Valid phone required';
}
if (!giftCity.trim()) e.giftCity = 'Required';
if (!giftCountry.trim()) e.giftCountry = 'Required';
}
return e;
};
const e: Record<string, string> = {};
if (selectedTab === 'gift') {
// First Name
if (!giftFirstName.trim()) e.giftFirstName = 'Required';
else if (!/^[A-Za-z]+$/.test(giftFirstName)) e.giftFirstName = 'Only alphabets allowed';
else if (giftFirstName.length < 2 || giftFirstName.length > 50) e.giftFirstName = 'Must be 250 characters';
// Last Name
if (!giftLastName.trim()) e.giftLastName = 'Required';
else if (!/^[A-Za-z]+$/.test(giftLastName)) e.giftLastName = 'Only alphabets allowed';
else if (giftLastName.length < 2 || giftLastName.length > 50) e.giftLastName = 'Must be 250 characters';
// ISD Code
if (!giftIsd.trim()) e.giftIsd = 'Required';
else if (!giftIsd.startsWith('+')) e.giftIsd = "Must start with '+'";
else if (!/^\+\d+$/.test(giftIsd)) e.giftIsd = "Only numbers allowed after '+'";
// Email
if (!giftEmail.trim()) e.giftEmail = 'Required';
else if (!/\S+@\S+\.\S+/.test(giftEmail)) e.giftEmail = 'Valid email required';
// Phone
if (!giftPhone.trim()) e.giftPhone = 'Required';
else if (!/^\d+$/.test(giftPhone)) e.giftPhone = 'Only numbers allowed';
else if (giftPhone.length < 7 || giftPhone.length > 15) e.giftPhone = 'Must be 715 digits';
// Message
if (!giftMessage.trim()) e.giftMessage = 'Required';
else if (giftMessage.length < 5 || giftMessage.length > 500) e.giftMessage = 'Must be 5500 characters';
// City
if (!giftCity.trim()) e.giftCity = 'Required';
else if (!/^[A-Za-z\s\-'À-ÿ]+$/.test(giftCity)) e.giftCity = 'Only alphabets allowed';
else if (/\s{2,}/.test(giftCity)) e.giftCity = 'No multiple spaces';
else if (giftCity.length < 2 || giftCity.length > 50) e.giftCity = 'Must be 250 characters';
// Country
if (!giftCountry.trim()) e.giftCountry = 'Required';
else if (!/^[A-Za-z\s\-'À-ÿ]+$/.test(giftCountry)) e.giftCountry = 'Only alphabets allowed';
else if (giftCountry.length < 2 || giftCountry.length > 50) e.giftCountry = 'Must be 250 characters';
}
return e;
};
const [isRedirecting, setIsRedirecting] = useState(false);

View File

@@ -129,22 +129,60 @@ export function ProfilePage({
}, [userDetails])
const validateForm = () => {
// First Name (TC-013)
if (!formData.firstName.trim()) return toast.error('First name is required'), false;
if (!/^[A-Za-z]+$/.test(formData.firstName)) return toast.error('First name must contain only alphabets'), 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 (TC-014)
if (!formData.lastName.trim()) return toast.error('Last name is required'), false;
if (!/^[A-Za-z]+$/.test(formData.lastName)) return toast.error('Last name must contain only alphabets'), false;
if (formData.lastName.length < 2 || formData.lastName.length > 50) return toast.error('Last name must be between 2 and 50 characters'), false;
// Mobile Number (TC-016)
if (!formData.phone.trim()) return toast.error('Mobile number is required'), false;
if (!/^\d+$/.test(formData.phone)) return toast.error('Mobile number must contain only numbers'), false;
if (formData.phone.length < 7 || formData.phone.length > 15) return toast.error('Mobile number must be between 7 and 15 digits'), false;
// Address Line 1 (TC-019)
if (!formData.address1.trim()) return toast.error('Address is required'), false;
if (!/^[A-Za-z0-9\s,\-.]+$/.test(formData.address1)) return toast.error('Address contains invalid characters'), false;
if (formData.address1.length < 5 || formData.address1.length > 100) return toast.error('Address must be between 5 and 100 characters'), false;
// City (TC-018)
if (!formData.city.trim()) return toast.error('City is required'), false;
if (!/^[A-Za-z\s\-'À-ÿ]+$/.test(formData.city)) return toast.error('City must contain only alphabets'), false;
if (/\s{2,}/.test(formData.city)) return toast.error('City must not contain multiple consecutive spaces'), false;
if (formData.city.length < 2 || formData.city.length > 50) return toast.error('City must be between 2 and 50 characters'), false;
// Country (TC-017) — free text validation since no dropdown yet
if (!formData.country.trim()) return toast.error('Country is required'), false;
if (!/^[A-Za-z\s\-'À-ÿ]+$/.test(formData.country)) return toast.error('Country must contain only alphabets'), false;
if (formData.country.length < 2 || formData.country.length > 50) return toast.error('Country must be between 2 and 50 characters'), false;
// Postal Code (TC-020)
if (!formData.postalCode.trim()) return toast.error('Postal code is required'), false;
if (!/^[A-Za-z0-9\s\-]+$/.test(formData.postalCode)) return toast.error('Postal code contains invalid characters'), 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;
};
const handleInputChange = (field: string, value: string) => {
setFormData(prev => ({ ...prev, [field]: value }));
};
const handleSaveProfile = async () => {
try {
console.log("Saving profile...", formData);
const response = await updateUserProfileDetails({ userDetails: formData, userId });
console.log(response)
toast.success("Profile updated successfully!");
} catch (error) {
console.error("Error saving profile:", error);
toast.error("Failed to update profile. Please try again.");
}
};
if (!validateForm()) return;
try {
const response = await updateUserProfileDetails({ userDetails: formData, userId });
toast.success("Profile updated successfully!");
} catch (error) {
toast.error("Failed to update profile. Please try again.");
}
};
const activeCards = cards.filter((card: any) => card.isActive === true);
const expiredCards = cards.filter((card: any) => card.isActive === false);
@@ -256,6 +294,7 @@ export function ProfilePage({
id="email"
type="email"
value={formData.email}
disabled
onChange={(e) => handleInputChange('email', e.target.value)}
className="mt-1 font-poppins font-light"
/>
@@ -340,192 +379,6 @@ export function ProfilePage({
</motion.div>
</div>
{/* App Download Section */}
<div className="lg:col-span-1">
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.5, delay: 0.4 }}
>
<Card className="border border-gray-200 shadow-sm">
<CardContent className="p-8 space-y-6">
{(() => {
// Determine which pass type to show
const hasUnlimitedPass = activeCards.some((card: any) => card.cardType.cardTypeName === 'selective_pass');
const hasSelectivePass = activeCards.some((card: any) => card.cardType.cardTypeName === 'unlimited_card');
if (hasUnlimitedPass) {
return (
<>
<div className="space-y-3">
<h3 className="font-poppins text-xl font-normal">
Get{' '}
<span className="text-primary">Melbourne Unlimited Card</span>
</h3>
<p className="text-sm text-gray-600 font-poppins leading-relaxed font-light">
Unlimited access to 25+ attractions. Visit as many places as you want with one simple card.
Save up to 40% compared to individual tickets.
</p>
</div>
{/* Card Benefits */}
<div className="bg-gradient-to-br from-primary/5 to-secondary/5 rounded-lg p-4 space-y-2">
<div className="flex items-center gap-2 text-sm font-poppins font-normal">
<div className="w-5 h-5 rounded-full bg-primary flex items-center justify-center flex-shrink-0">
<CreditCard className="w-3 h-3 text-white" />
</div>
<span>Unlimited entries to all attractions</span>
</div>
<div className="flex items-center gap-2 text-sm font-poppins font-normal">
<div className="w-5 h-5 rounded-full bg-primary flex items-center justify-center flex-shrink-0">
<Calendar className="w-3 h-3 text-white" />
</div>
<span>Valid for 7 consecutive days</span>
</div>
<div className="flex items-center gap-2 text-sm font-poppins font-normal">
<div className="w-5 h-5 rounded-full bg-primary flex items-center justify-center flex-shrink-0">
<MapPin className="w-3 h-3 text-white" />
</div>
<span>Skip the queue at major venues</span>
</div>
</div>
{/* Purchase CTA */}
<div className="space-y-3">
<Button
onClick={onPassesClick}
className="w-full bg-gradient-to-r from-primary to-secondary hover:from-primary/90 hover:to-secondary/90 text-white font-poppins font-medium h-12"
>
Purchase Unlimited Card
</Button>
<Button
variant="outline"
onClick={onCityCardsClick}
className="w-full font-poppins font-normal"
>
Learn More
</Button>
</div>
</>
);
} else if (hasSelectivePass) {
return (
<>
<div className="space-y-3">
<h3 className="font-poppins text-xl font-normal">
Get{' '}
<span className="text-primary">Selective Card</span>
{' '}now
</h3>
<p className="text-sm text-gray-600 font-poppins leading-relaxed font-light">
Choose your own adventure with 12 hand-picked attractions. Perfect for visitors
who want flexibility and value.
</p>
</div>
{/* Card Benefits */}
<div className="bg-gradient-to-br from-primary/5 to-secondary/5 rounded-lg p-4 space-y-2">
<div className="flex items-center gap-2 text-sm font-poppins font-normal">
<div className="w-5 h-5 rounded-full bg-primary flex items-center justify-center flex-shrink-0">
<CreditCard className="w-3 h-3 text-white" />
</div>
<span>Choose from 12 curated attractions</span>
</div>
<div className="flex items-center gap-2 text-sm font-poppins font-normal">
<div className="w-5 h-5 rounded-full bg-primary flex items-center justify-center flex-shrink-0">
<Calendar className="w-3 h-3 text-white" />
</div>
<span>Flexible 7-day validity period</span>
</div>
<div className="flex items-center gap-2 text-sm font-poppins font-normal">
<div className="w-5 h-5 rounded-full bg-primary flex items-center justify-center flex-shrink-0">
<Star className="w-3 h-3 text-white" />
</div>
<span>Save 40% on combined ticket price</span>
</div>
</div>
{/* Purchase CTA */}
<div className="space-y-3">
<Button
onClick={onPassesClick}
className="w-full bg-gradient-to-r from-primary to-secondary hover:from-primary/90 hover:to-secondary/90 text-white font-poppins font-medium h-12"
>
Purchase Selective Card
</Button>
<Button
variant="outline"
onClick={onCityCardsClick}
className="w-full font-poppins font-normal"
>
View All Attractions
</Button>
</div>
</>
);
} else {
return (
<>
<div className="space-y-3">
<h3 className="font-poppins text-xl font-normal">
Get{' '}
<span className="text-primary">CityCards</span>
{' '}now
</h3>
<p className="text-sm text-gray-600 font-poppins leading-relaxed font-light">
Explore Melbourne's best attractions with our flexible card options.
Choose unlimited access or select your favorites.
</p>
</div>
{/* Card Options */}
<div className="space-y-3">
<div className="bg-gradient-to-br from-primary/5 to-secondary/5 rounded-lg p-4">
<div className="flex items-start justify-between mb-2">
<div>
<h4 className="font-poppins text-base font-medium mb-1">Unlimited Card</h4>
<p className="text-xs text-gray-600 font-poppins font-light">25+ attractions, unlimited visits</p>
</div>
<Badge className="bg-primary text-white">Popular</Badge>
</div>
<div className="text-2xl font-poppins font-semibold text-primary">$149</div>
</div>
<div className="bg-gray-50 rounded-lg p-4">
<div className="flex items-start justify-between mb-2">
<div>
<h4 className="font-poppins text-base font-medium mb-1">Selective Card</h4>
<p className="text-xs text-gray-600 font-poppins font-light">12 attractions of your choice</p>
</div>
</div>
<div className="text-2xl font-poppins font-semibold text-primary">$89</div>
</div>
</div>
{/* Purchase CTA */}
<div className="space-y-3">
<Button
onClick={onPassesClick}
className="w-full bg-gradient-to-r from-primary to-secondary hover:from-primary/90 hover:to-secondary/90 text-white font-poppins font-medium h-12"
>
Explore All Cards
</Button>
<Button
variant="outline"
onClick={onCityCardsClick}
className="w-full font-poppins font-normal"
>
Learn More
</Button>
</div>
</>
);
}
})()}
</CardContent>
</Card>
</motion.div>
</div>
</div>
</TabsContent>