diff --git a/src/AppRouter.tsx b/src/AppRouter.tsx index ed1dd57..5671705 100644 --- a/src/AppRouter.tsx +++ b/src/AppRouter.tsx @@ -293,7 +293,7 @@ export function AppRouter({ - + } /> diff --git a/src/Redux/services/itinerary.service.ts b/src/Redux/services/itinerary.service.ts index ece4428..899a0b9 100644 --- a/src/Redux/services/itinerary.service.ts +++ b/src/Redux/services/itinerary.service.ts @@ -8,8 +8,6 @@ export const itineraryApi = createApi({ endpoints: (builder) => ({ - - createMagicItinerary: builder.mutation({ query: (itineraryDetails) => ({ // keep the name of the variables being passed here same as when calling the mutation hook url: `/website/itinerary`, @@ -24,7 +22,11 @@ export const itineraryApi = createApi({ }), getUserItineraries: builder.query({ - query: () => `/website/itinerary/all-initineraries`, + query: (cityId) => { + const params = new URLSearchParams() + params.append('cityId', cityId); + return `/website/itinerary/all-initineraries?${params.toString()}` + } }), }) diff --git a/src/components/FooterBottom.tsx b/src/components/FooterBottom.tsx index f11d8ca..c32da0b 100644 --- a/src/components/FooterBottom.tsx +++ b/src/components/FooterBottom.tsx @@ -17,13 +17,13 @@ export function FooterBottom({ onPrivacyPolicyClick }: FooterBottomProps) {
{/* Copyright */}

- © 2024 CityCards. All rights reserved. + © 2026 CityCards. All rights reserved.

{/* Right Section - Legal Links and Social Icons */}
{/* Legal Links */} -
+ {/*
Cookie Policy -
+
*/} {/* Social Icons - Horizontal Layout */}
diff --git a/src/components/FooterNavigation.tsx b/src/components/FooterNavigation.tsx index f05d37d..a6dad8f 100644 --- a/src/components/FooterNavigation.tsx +++ b/src/components/FooterNavigation.tsx @@ -1,37 +1,21 @@ import { motion } from 'motion/react'; import { footerSections } from '../utils/footerConstants'; +import { Link } from 'react-router-dom'; -interface FooterNavigationProps { - onHomeClick?: () => void; - onMelbourneClick?: () => void; - onPassesClick?: () => void; - onSignInClick?: () => void; - onAttractionsClick?: () => void; - onBlogsClick?: () => void; - onHowItWorksClick?: () => void; - onFAQClick?: () => void; - onPrivacyPolicyClick?: () => void; - onAboutUsClick?: () => void; - onContactUsClick?: () => void; - currentPage?: string; -} +const linkRoutes: Record = { + 'Home': '/', + // 'Cancellation policy': '/cancellation-policy', + 'How It Works': '/how-it-works', + 'FAQ': '/faq', + 'Blog': '/blogs', + 'Contact Us': '/contact-us', + 'Privacy Policy': '/privacy-policy', + // 'Terms of Service': '/terms', +}; -export function FooterNavigation({ - onHomeClick, - onMelbourneClick, - onPassesClick, - onSignInClick, - onAttractionsClick, - onBlogsClick, - onHowItWorksClick, - onFAQClick, - onPrivacyPolicyClick, - onAboutUsClick, - onContactUsClick, - currentPage -}: FooterNavigationProps) { +export function FooterNavigation() { return ( -
+
{Object.entries(footerSections).map(([key, section]) => (

{section.title}

+
    - {section.links.map((link, index) => { - const getClickHandler = () => { - switch (link) { - case 'Home': return onHomeClick; - case 'Melbourne': return onMelbourneClick; - case 'Passes': return onPassesClick; - case 'Sign In': return onSignInClick; - case 'Attractions': return onAttractionsClick; - case 'Blog': return onBlogsClick; - case 'How It Works': return onHowItWorksClick; - case 'FAQ': return onFAQClick; - case 'Privacy Policy': return onPrivacyPolicyClick; - case 'Contact Us': return onContactUsClick; - default: return undefined; - } - }; - - const clickHandler = getClickHandler(); - - return ( -
  • - {clickHandler ? ( - { - e.preventDefault(); - clickHandler(); - }} - className="text-white/80 hover:text-white transition-colors duration-200 text-sm text-left" - whileHover={{ x: 4 }} - transition={{ duration: 0.2 }} - > - {link} - - ) : ( - - {link} - - )} -
  • - ); - })} + {section.links.map((link) => ( +
  • + + + {link} + + +
  • + ))}
))} diff --git a/src/components/MelbourneFAQ.tsx b/src/components/MelbourneFAQ.tsx index b9c4e83..bf7d1f4 100644 --- a/src/components/MelbourneFAQ.tsx +++ b/src/components/MelbourneFAQ.tsx @@ -19,12 +19,12 @@ import { } from "./ui/accordion"; const faqData = [ - { - id: "refund", - question: "Can I get a refund on my Melbourne CityCard?", - answer: "Yes, you can cancel your Melbourne CityCard and receive a full refund if you cancel at least 24 hours in advance of your selected start date. For cancellations within 24 hours, refunds are subject to our cancellation policy. Digital cards can be refunded instantly through your account.", - icon: CreditCard - }, + // { + // id: "refund", + // question: "Can I get a refund on my Melbourne CityCard?", + // answer: "Yes, you can cancel your Melbourne CityCard and receive a full refund if you cancel at least 24 hours in advance of your selected start date. For cancellations within 24 hours, refunds are subject to our cancellation policy. Digital cards can be refunded instantly through your account.", + // icon: CreditCard + // }, { id: "duration", question: "How long is my Melbourne CityCard valid?", diff --git a/src/components/RegisterPage.tsx b/src/components/RegisterPage.tsx index f6ea833..89adc60 100644 --- a/src/components/RegisterPage.tsx +++ b/src/components/RegisterPage.tsx @@ -38,19 +38,56 @@ export default function RegisterPage() { }; 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; + + // Email if (!formData.emailAddress.includes('@')) return toast.error('Invalid email address'), false; - if (!formData.isdCode.startsWith("+")) toast.error("ISD code must start with '+'"), false; - if (!/^\+\d+$/.test(formData.isdCode)) toast.error("ISD code must contain only numbers after '+'"), 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; + + // 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; + + // 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; + + // 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; + + // 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; + + // 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; + + // 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; + return true; }; + + const handleRegister = async () => { if (!validateForm()) return; @@ -79,182 +116,182 @@ export default function RegisterPage() { }; return ( -
- {/* Navbar */} - +
+ {/* Navbar */} + - {/* Main Content */} -
-
+ {/* Main Content */} +
+
- {/* Header */} -
-

- Create Account -

-

- Register to get started with City Cards -

+ {/* Header */} +
+

+ Create Account +

+

+ Register to get started with City Cards +

+
+ + {/* Form Container */} +
+ + {/* Personal Info */} +
+

+ Personal Information +

+ +
+
+ + handleInputChange('firstName', e.target.value)} + className="h-12 bg-gray-50 border-0 rounded-xl mt-1" + /> +
+ +
+ + handleInputChange('lastName', e.target.value)} + className="h-12 bg-gray-50 border-0 rounded-xl mt-1" + /> +
+
+ +
+ + 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" + /> +
+ +
+ + handleInputChange('mobileNumber', e.target.value)} + className="h-12 bg-gray-50 border-0 rounded-xl mt-1" + /> +
+
+
+ + {/* Address */} +
+

+ Address Information +

+ +
+ + handleInputChange('address1', e.target.value)} + className="h-12 bg-gray-50 border-0 rounded-xl mt-1" + /> +
+ +
+ + handleInputChange('address2', e.target.value)} + className="h-12 bg-gray-50 border-0 rounded-xl mt-1" + /> +
+ +
+
+ + handleInputChange('city', e.target.value)} + className="h-12 bg-gray-50 border-0 rounded-xl mt-1" + /> +
+ +
+ + handleInputChange('state', e.target.value)} + className="h-12 bg-gray-50 border-0 rounded-xl mt-1" + /> +
+
+ +
+
+ + handleInputChange('country', e.target.value)} + className="h-12 bg-gray-50 border-0 rounded-xl mt-1" + /> +
+ +
+ + handleInputChange('postalCode', e.target.value)} + className="h-12 bg-gray-50 border-0 rounded-xl mt-1" + /> +
+
+
+ + {helperText && ( +

{helperText}

+ )} + + + +
+
- {/* Form Container */} -
- - {/* Personal Info */} -
-

- Personal Information -

- -
-
- - handleInputChange('firstName', e.target.value)} - className="h-12 bg-gray-50 border-0 rounded-xl mt-1" - /> -
- -
- - handleInputChange('lastName', e.target.value)} - className="h-12 bg-gray-50 border-0 rounded-xl mt-1" - /> -
-
- -
- - 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" - /> -
- -
- - handleInputChange('mobileNumber', e.target.value)} - className="h-12 bg-gray-50 border-0 rounded-xl mt-1" - /> -
-
-
- - {/* Address */} -
-

- Address Information -

- -
- - handleInputChange('address1', e.target.value)} - className="h-12 bg-gray-50 border-0 rounded-xl mt-1" - /> -
- -
- - handleInputChange('address2', e.target.value)} - className="h-12 bg-gray-50 border-0 rounded-xl mt-1" - /> -
- -
-
- - handleInputChange('city', e.target.value)} - className="h-12 bg-gray-50 border-0 rounded-xl mt-1" - /> -
- -
- - handleInputChange('state', e.target.value)} - className="h-12 bg-gray-50 border-0 rounded-xl mt-1" - /> -
-
- -
-
- - handleInputChange('country', e.target.value)} - className="h-12 bg-gray-50 border-0 rounded-xl mt-1" - /> -
- -
- - handleInputChange('postalCode', e.target.value)} - className="h-12 bg-gray-50 border-0 rounded-xl mt-1" - /> -
-
-
- - {helperText && ( -

{helperText}

- )} - - - + {/* Footer */} +
+
-
- - {/* Footer */} -
-
-
-
); } \ No newline at end of file diff --git a/src/pages/CheckoutPage.tsx b/src/pages/CheckoutPage.tsx index 3464eca..b6b8a79 100644 --- a/src/pages/CheckoutPage.tsx +++ b/src/pages/CheckoutPage.tsx @@ -150,9 +150,9 @@ function CheckoutConfigCard({ const cardMode = item?.cardType?.name === "selective_pass" ? "flexi" : "unlimited" const adultPrice = item?.adultPrice * noOfAdults const childPrice = item?.childPrice * noOfChildren - const basePrice = adultPrice + childPrice - const taxAmount = basePrice * 0.1 - const strikedPrice = basePrice + 20 + const basePrice = Math.round(adultPrice + childPrice) + const taxAmount = Math.round(basePrice * 0.1) + const strikedPrice = Math.round(basePrice + 20) const [addCardToCart] = useAddCardToCartMutation() diff --git a/src/pages/CreateMagicIternaryPage.tsx b/src/pages/CreateMagicIternaryPage.tsx index d99377e..9353b8f 100644 --- a/src/pages/CreateMagicIternaryPage.tsx +++ b/src/pages/CreateMagicIternaryPage.tsx @@ -149,7 +149,7 @@ export function CreateMagicItineraryPage({ const [selectedActivity, setSelectedActivity] = useState(null); const [createMagicItinerary] = useCreateMagicItineraryMutation(); - const navigate= useNavigate() + const navigate = useNavigate() const toggleFavorite = (activityKey: string) => { setFavorites(prev => { @@ -213,17 +213,17 @@ export function CreateMagicItineraryPage({ const generateItinerary = async () => { try { - console.log("creating itinerary...", itineraryDetails); setIsGenerating(true); const response = await createMagicItinerary(itineraryDetails); - console.log(response) setGeneratedItinerary(response); - setShowResults(true); - toast.success("Itinerary created successfully!"); - navigate(`/itinerary-summary/${response?.data?.id}`) - } catch (error) { - console.error("Error creating itinerary:", error); - toast.error("Failed to create itinerary. Please try again."); + if (response?.data?.id) { + navigate(`/itinerary-summary/${response?.data?.id}`) + toast.success("Itinerary created successfully!"); + } else { + throw new Error(response?.error?.data?.message) + } + } catch (error: any) { + toast.error(error.message); } finally { setIsGenerating(false); } diff --git a/src/pages/ProfilePage.tsx b/src/pages/ProfilePage.tsx index ceacdd4..22cf974 100644 --- a/src/pages/ProfilePage.tsx +++ b/src/pages/ProfilePage.tsx @@ -107,7 +107,7 @@ export function ProfilePage({ const { data: userDetails, isLoading } = useGetUserProfileDetailsQuery(userId) const [updateUserProfileDetails, { isLoading: savingChanges }] = useUpdateUserProfileDetailsMutation(); const { data, isLoading: loadingCards } = useGetUserCardsQuery({sort,cityId}) - const { data: userItineraries, isLoading: loadingItineraries } = useGetUserItinerariesQuery({}) + const { data: userItineraries, isLoading: loadingItineraries } = useGetUserItinerariesQuery(cityId) const cards = data ?? [] const itineraries = userItineraries?.itineraries ?? [] diff --git a/src/utils/footerConstants.ts b/src/utils/footerConstants.ts index 86afdeb..ef31a2b 100644 --- a/src/utils/footerConstants.ts +++ b/src/utils/footerConstants.ts @@ -1,16 +1,20 @@ export const footerSections = { explore: { title: 'Explore', - links: ['Home', 'My Adventures', 'Cancellation policy'] + links: ['Home', + 'Cancellation policy' + ] }, learn: { title: 'Learn', - links: ['How It Works', 'Safety Tips', 'FAQ', 'Blog'] - }, - community: { - title: 'Community', - links: ['Testimonials', 'Partner Stories', 'Events & Meetups', 'Newsletter'] + links: ['How It Works', + // 'Safety Tips', + 'FAQ', 'Blog'] }, + // community: { + // title: 'Community', + // links: ['Testimonials', 'Partner Stories', 'Events & Meetups', 'Newsletter'] + // }, support: { title: 'Support', links: ['Contact Us', 'Privacy Policy', 'Terms of Service']