diff --git a/src/components/LandingBookAttractionSection.tsx b/src/components/LandingBookAttractionSection.tsx index 841aafe..0b08325 100644 --- a/src/components/LandingBookAttractionSection.tsx +++ b/src/components/LandingBookAttractionSection.tsx @@ -259,7 +259,7 @@ export function LandingBookAttractionSection() { whileHover={{ scale: 1.05 }} whileTap={{ scale: 0.95 }} className={`px-6 py-4 h-14 rounded-full font-medium transition-all duration-300 ${activeCategory === category - ? 'bg-warm-coral text-white shadow-xl shadow-warm-coral/25 ring-2 ring-warm-coral/20' + ? 'bg-red-400 text-white shadow-xl shadow-warm-coral/25 ring-2 ring-warm-coral/20' : 'bg-white/80 backdrop-blur-sm text-gray-700 hover:text-gray-900 hover:shadow-lg border border-gray-200/50 hover:border-warm-coral/20 hover:bg-white' }`} > diff --git a/src/components/LandingMagicItinerary.tsx b/src/components/LandingMagicItinerary.tsx index 49174f1..ae24644 100644 --- a/src/components/LandingMagicItinerary.tsx +++ b/src/components/LandingMagicItinerary.tsx @@ -6,6 +6,7 @@ import { Button } from './ui/button'; // Import your video from assets import cityTourVideo from '../assets/itinenary-animation-vid.mp4'; +import { useNavigate } from 'react-router-dom'; interface ItineraryCard { id: number; @@ -22,6 +23,8 @@ export function LandingMagicItinerary() { const [isPlaying, setIsPlaying] = useState(true); const [videoLoaded, setVideoLoaded] = useState(false); + const navigate = useNavigate() + const handleVideoLoad = () => { setVideoLoaded(true); }; @@ -31,7 +34,7 @@ export function LandingMagicItinerary() { }; return ( -
+
{/* Dynamic Background */}
{/* Background Image as fallback */} @@ -97,7 +100,7 @@ export function LandingMagicItinerary() { {/* Header */}
Plan Your{' '} - + Dream Journey
@@ -250,6 +253,7 @@ export function LandingMagicItinerary() { >
@@ -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/MelbournePage.tsx b/src/pages/MelbournePage.tsx index 75ba85a..8cf53e4 100644 --- a/src/pages/MelbournePage.tsx +++ b/src/pages/MelbournePage.tsx @@ -222,7 +222,7 @@ export function MelbournePage({
*/} -
+
{/* Features Grid */} - AI-Powered Magic Itinerary + Magic Itinerary -

+ {/*

Free to use • No credit card required -

+

*/}
-
+
{/* Testimonials */} 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 = () => {
- - - - - - - - + Recipient First Name *} value={giftFirstName} onChange={setGiftFirstName} placeholder="Enter recipient's first name" error={errors.giftFirstName} /> + Recipient Last Name *} value={giftLastName} onChange={setGiftLastName} placeholder="Enter recipient's last name" error={errors.giftLastName} /> + Recipient ISD Code *} value={giftIsd} onChange={setGiftIsd} placeholder="e.g., +61" error={errors.giftIsd} /> + Recipient Phone *} value={giftPhone} onChange={setGiftPhone} type="tel" placeholder="Enter recipient's phone number" error={errors.giftPhone} /> + Recipient Email *} value={giftEmail} onChange={setGiftEmail} type="email" placeholder="Enter recipient's email" error={errors.giftEmail} /> + Recipient City *} value={giftCity} onChange={setGiftCity} placeholder="Enter recipient's city" error={errors.giftCity} /> + Recipient Country *} value={giftCountry} onChange={setGiftCountry} placeholder="Enter recipient's country" error={errors.giftCountry} /> + Gift Message *} value={giftMessage} onChange={setGiftMessage} placeholder="Write a heartfelt message" error={errors.giftMessage} />
@@ -589,8 +539,8 @@ const validate = () => {
{bookingDetails?.cardMode} diff --git a/src/pages/ProfilePage.tsx b/src/pages/ProfilePage.tsx index ac78311..59645bc 100644 --- a/src/pages/ProfilePage.tsx +++ b/src/pages/ProfilePage.tsx @@ -13,7 +13,8 @@ import { Clock, Star, Badge as BadgeIcon, - Camera + Camera, + AlertCircle } from 'lucide-react'; import { Button } from '../components/ui/button'; import { Input } from '../components/ui/input'; @@ -21,7 +22,6 @@ import { Label } from '../components/ui/label'; import { Card, CardContent, CardHeader, CardTitle } from '../components/ui/card'; import { Separator } from '../components/ui/separator'; import { Tabs, TabsContent, TabsList, TabsTrigger } from '../components/ui/tabs'; -import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '../components/ui/select'; import { Badge } from '../components/ui/badge'; import Navbar from '../components/Navbar'; import { Footer } from '../components/Footer'; @@ -88,6 +88,7 @@ export function ProfilePage({ currentPage }: ProfilePageProps) { const [activeTab, setActiveTab] = useState('profile'); + const [fieldErrors, setFieldErrors] = useState>({}); const [formData, setFormData] = useState({ firstName: '', lastName: '', @@ -106,7 +107,7 @@ export function ProfilePage({ const cityId = localStorage.getItem("cityId") const { data: userDetails, isLoading } = useGetUserProfileDetailsQuery(userId) const [updateUserProfileDetails, { isLoading: savingChanges }] = useUpdateUserProfileDetailsMutation(); - const { data, isLoading: loadingCards } = useGetUserCardsQuery({sort,cityId}) + const { data, isLoading: loadingCards } = useGetUserCardsQuery({ sort, cityId }) const { data: userItineraries, isLoading: loadingItineraries } = useGetUserItinerariesQuery(cityId) const cards = data ?? [] @@ -129,50 +130,90 @@ export function ProfilePage({ }, [userDetails]) + // const validateForm = () => { + // 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'), false; + + // 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'), false; + + // if (!formData.phone.trim()) return toast.error('Mobile number is required'), false; + // if (/\s/.test(formData.phone)) return toast.error('Mobile number must not contain spaces'), false; + // if (!/^\d+$/.test(formData.phone)) return toast.error('Mobile number must contain only digits'), false; + + // 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.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; + + // 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; + + // 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; + + // return true; + // }; + const validateForm = () => { - 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'), false; + const e: Record = {}; - 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'), false; + 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'; - if (!formData.phone.trim()) return toast.error('Mobile number is required'), false; - if (/\s/.test(formData.phone)) return toast.error('Mobile number must not contain spaces'), false; - if (!/^\d+$/.test(formData.phone)) return toast.error('Mobile number must contain only digits'), false; + 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'; - 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.phone.trim()) e.phone = 'Mobile number is required'; + else if (/\s/.test(formData.phone)) e.phone = 'Mobile number must not contain spaces'; + else if (!/^\d+$/.test(formData.phone)) e.phone = 'Mobile number must contain only digits'; - 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; + if (!formData.address1.trim()) e.address1 = 'Address is required'; + else if (!/^[A-Za-z0-9\s,\-.]+$/.test(formData.address1)) e.address1 = 'Address contains invalid characters'; - 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; + 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.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.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'; - return true; -}; + 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'; + setFieldErrors(e); + return Object.keys(e).length === 0; + }; + + // inside ProfilePage function body: + const FieldError = ({ name }: { name: string }) => + fieldErrors[name] ? ( +

+ {fieldErrors[name]} +

+ ) : null; const handleInputChange = (field: string, value: string) => { setFormData(prev => ({ ...prev, [field]: value })); }; const handleSaveProfile = async () => { - 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."); - } -}; + 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); @@ -259,27 +300,35 @@ export function ProfilePage({
- + handleInputChange('firstName', e.target.value)} - className="mt-1 font-poppins font-light" + className={`mt-1 font-poppins font-light ${fieldErrors.firstName ? 'border-red-400' : ''}`} /> +
- + handleInputChange('lastName', e.target.value)} - className="mt-1 font-poppins font-light" + className={`mt-1 font-poppins font-light ${fieldErrors.lastName ? 'border-red-400' : ''}`} /> +
- +
- + handleInputChange('phone', e.target.value)} - className="mt-1 font-poppins font-light" + className={`mt-1 font-poppins font-light ${fieldErrors.phone ? 'border-red-400' : ''}`} /> +
@@ -306,29 +358,31 @@ export function ProfilePage({

Billing Address

- + handleInputChange('country', e.target.value)} - className="mt-1 font-poppins font-light" + className={`mt-1 font-poppins font-light ${fieldErrors.country ? 'border-red-400' : ''}`} /> +
handleInputChange('address1', e.target.value)} - className="mt-1 font-poppins font-light mb-4" + className={`mt-1 font-poppins font-light mb-4 ${fieldErrors.address1 ? 'border-red-400' : ''}`} /> + - +
- + handleInputChange('city', e.target.value)} - className="mt-1 font-poppins font-light" + className={`mt-1 font-poppins font-light ${fieldErrors.city ? 'border-red-400' : ''}`} /> +
- + handleInputChange('postalCode', e.target.value)} - className="mt-1 font-poppins font-light" + className={`mt-1 font-poppins font-light ${fieldErrors.postalCode ? 'border-red-400' : ''}`} /> +
@@ -569,7 +629,7 @@ export function ProfilePage({