diff --git a/index.html b/index.html index 000821d..daa01f8 100644 --- a/index.html +++ b/index.html @@ -4,7 +4,7 @@ - CityCards Travel 22-8-2025 + CityCards Customer-web diff --git a/package-lock.json b/package-lock.json index 13d84a8..071ee2a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -54,7 +54,7 @@ "react-resizable-panels": "^2.1.7", "react-router-dom": "^7.9.4", "recharts": "^2.15.2", - "sonner": "^2.0.3", + "sonner": "^2.0.7", "tailwind-merge": "*", "tailwindcss": "^4.1.14", "vaul": "^1.1.2" diff --git a/package.json b/package.json index 2e11959..078328a 100644 --- a/package.json +++ b/package.json @@ -49,7 +49,7 @@ "react-resizable-panels": "^2.1.7", "react-router-dom": "^7.9.4", "recharts": "^2.15.2", - "sonner": "^2.0.3", + "sonner": "^2.0.7", "tailwind-merge": "*", "tailwindcss": "^4.1.14", "vaul": "^1.1.2" diff --git a/src/AppRouter.tsx b/src/AppRouter.tsx index 1a3c767..12945be 100644 --- a/src/AppRouter.tsx +++ b/src/AppRouter.tsx @@ -2,38 +2,36 @@ import { Routes, Route, useParams, useLocation, useNavigate } from 'react-router import { motion, AnimatePresence } from 'motion/react'; // Import all your pages -import { LoginModal } from './components/LoginModal'; -import { MelbournePage } from './components/MelbournePage'; -import { PassesPage } from './components/PassesPage'; -import { AttractionsPage } from './components/AttractionsPage'; -import { AttractionDetailsPage } from './components/AttractionDetailsPage'; -import { CheckoutPage } from './components/CheckoutPage'; -import { SecureCheckoutPage } from './components/SecureCheckoutPage'; -import { BlogsPage } from './components/BlogsPage'; -import { BlogDetailsPage } from './components/BlogDetailsPage'; +import { MelbournePage } from './pages/MelbournePage'; +import { PassesPage } from './pages/PassesPage'; +import { AttractionsPage } from './pages/AttractionsPage'; +import { AttractionDetailsPage } from './pages/AttractionDetailsPage'; +import { CheckoutPage } from './pages/CheckoutPage'; +import { SecureCheckoutPage } from './pages/SecureCheckoutPage'; +import { BlogsPage } from './pages/BlogsPage'; +import { BlogDetailsPage } from './pages/BlogDetailsPage'; import { HowItWorksPage } from './components/HowItWorksPage'; import { FAQPage } from './components/FAQPage'; -import { PrivacyPolicyPage } from './components/PrivacyPolicyPage'; -import { AboutUsPage } from './components/AboutUsPage'; -import { ProfilePage } from './components/ProfilePage'; -import { CreateMagicItineraryPage } from './components/CreateMagicItineraryPage'; -import { ItineraryViewPage } from './components/ItineraryViewPage'; -import { OffersPage } from './components/OffersPage'; -import { CityCardsPage } from './components/CityCardsPage'; -import { MagicItineraryPage } from './components/MagicItineraryPage'; -import { PostCardsPage } from './components/PostCardsPage'; -import { DownloadAppPage } from './components/DownloadAppPage'; -import { EsimsPage } from './components/EsimsPage'; -import { HotelDiscountsPage } from './components/HotelDiscountsPage'; -import { ContactUsPage } from './components/ContactUsPage'; +import { PrivacyPolicyPage } from './pages/PrivacyPolicyPage'; +import { AboutUsPage } from './pages/AboutUsPage'; +import { ProfilePage } from './pages/ProfilePage'; +import { CreateMagicItineraryPage } from './pages/CreateMagicItineraryPage'; +import { ItineraryViewPage } from './pages/ItineraryViewPage'; +import { OffersPage } from './pages/OffersPage'; +import { CityCardsPage } from './pages/CityCardsPage'; +import { MagicItineraryPage } from './pages/MagicItineraryPage'; +import { PostCardsPage } from './pages/PostCardsPage'; +import { DownloadAppPage } from './pages/DownloadAppPage'; +import { HotelDiscountsPage } from './pages/HotelDiscountsPage'; +import { ContactUsPage } from './pages/ContactUsPage'; import { pageTransition } from './utils/animations'; import { LandingPage } from './pages/landingPage'; import ComingSoonPage from './pages/ComingSoonPage'; -import { SuperSavingsPage } from './components/SuperSavingsPage'; -import { WhatsIncluded } from './components/WhatsIncluded'; -import { LandingMagicItineraryPage } from './components/LandingMagicItineraryPage'; -import { DiscoverPage } from './components/DiscoverPage'; +import { SuperSavingsPage } from './pages/SuperSavingsPage'; +import { WhatsIncluded } from './pages/WhatsIncluded'; +import { LandingMagicItineraryPage } from './pages/LandingMagicItineraryPage'; +import { DiscoverPage } from './pages/DiscoverPage'; // User type definition interface User { @@ -90,11 +88,11 @@ export function AppRouter({ } /> {/* Home Route */} - - } /> + } /> {/* Passes Route */} getDefaultMiddleware().concat( - -fakeApi.middleware, -attractionsApi.middleware, -citiesApi.middleware + attractionsApi.middleware, + citiesApi.middleware, + authApi.middleware, + profileApi.middleware ), }); export type RootState = ReturnType; diff --git a/src/Redux/baseQuery.ts b/src/Redux/baseQuery.ts index 37c41cc..36c9339 100644 --- a/src/Redux/baseQuery.ts +++ b/src/Redux/baseQuery.ts @@ -8,10 +8,9 @@ export const baseQuery = fetchBaseQuery({ const token = localStorage.getItem("accessToken"); if (token) { headers.set("Authorization", `Bearer ${token}`); - // headers.set("access-token", token); + headers.set("access-token", token); } // headers.set("Content-Type", "application/json"); return headers; }, -}); - +}); \ No newline at end of file diff --git a/src/Redux/services/auth.service.ts b/src/Redux/services/auth.service.ts new file mode 100644 index 0000000..100cd07 --- /dev/null +++ b/src/Redux/services/auth.service.ts @@ -0,0 +1,53 @@ + +import { createApi, fetchBaseQuery } from "@reduxjs/toolkit/query/react"; +import { baseQuery } from "../baseQuery"; + +export const authApi = createApi({ + reducerPath: "authApi", + baseQuery: baseQuery, + + tagTypes: ["profile", "Transaction"], + + + endpoints: (builder) => ({ + // Login + + login: builder.mutation({ + query: (credentials) => ({ + url: "/website/send-otp", + method: "POST", + body: credentials, + }), + }), + + verifyOtp: builder.mutation({ + query: (credentials) => ({ + url: "/website/user/verify-otp", + method: "POST", + body: credentials, + }), + }), + + register: builder.mutation({ + query: (credentials) => ({ + url: "/website/user/register", + method: "POST", + body: credentials, + }), + }), + + logoutUser: builder.mutation({ + query: () => ({ + url: "/website/user/logout", + method: "POST" + }) + }) + }), +}); + +export const { + useLoginMutation, + useVerifyOtpMutation, + useRegisterMutation, + useLogoutUserMutation +} = authApi; \ No newline at end of file diff --git a/src/Redux/services/cities.service.ts b/src/Redux/services/cities.service.ts index e1dfb06..acc5b31 100644 --- a/src/Redux/services/cities.service.ts +++ b/src/Redux/services/cities.service.ts @@ -20,11 +20,28 @@ export const citiesApi = createApi({ }), getUpcomingCities: builder.query({ - query: (listType) => `/cities/list/all?listType=${listType}`, - }) + }), + + getSelectedCityDetails: builder.query({ + query: (cityId) => `/website/${cityId}`, + }), + + getSelectedCityOffers: builder.query({ + query: ({ cityId, categoryId, page, limit }) => { + const params = new URLSearchParams() + + params.append('cityXid', cityId); + + if (categoryId) params.append('categoryXid', categoryId); + if (page) params.append('page', page); + if (limit) params.append('limit', limit); + + return `/website/super-savings/list/offers?${params.toString()}`; + } + }), }), }); -export const { useGetCityListWithBannerQuery,useGetUpcomingCitiesQuery } = citiesApi; \ No newline at end of file +export const { useGetCityListWithBannerQuery, useGetUpcomingCitiesQuery, useGetSelectedCityDetailsQuery, useGetSelectedCityOffersQuery } = citiesApi; \ No newline at end of file diff --git a/src/Redux/services/fakeApi.service.ts b/src/Redux/services/fakeApi.service.ts deleted file mode 100644 index b5f90ae..0000000 --- a/src/Redux/services/fakeApi.service.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react' - -export const fakeApi = createApi({ - reducerPath: 'fakeApi', - baseQuery: fetchBaseQuery({ - baseUrl: " https://fakestoreapi.com", - - }), - endpoints: (builder) => ({ - getProducts: builder.query({ - query: () => ({ - url: 'products', - method: 'GET', - }), - }), - }), -}) - -export const { useGetProductsQuery} = fakeApi diff --git a/src/Redux/services/profile.service.ts b/src/Redux/services/profile.service.ts new file mode 100644 index 0000000..afa50f0 --- /dev/null +++ b/src/Redux/services/profile.service.ts @@ -0,0 +1,45 @@ + +import { createApi, fetchBaseQuery } from "@reduxjs/toolkit/query/react"; +import { baseQuery } from "../baseQuery"; + +export const profileApi = createApi({ + reducerPath: "profileApi", + baseQuery, + + tagTypes: ["userDetails"], + + endpoints: (builder) => ({ + + getUserProfileDetails: builder.query({ + query: (id) => `/website/user/${id}`, + providesTags: ["userDetails"] + }), + + updateUserProfileDetails: builder.mutation({ + query: ({ userDetails, userId }) => ({ // keep the name of the variables being passed here same as when calling the mutation hook + url: `/website/user/${userId}`, + method: "PUT", + body: userDetails + }), + invalidatesTags: ["userDetails"] + }), + + getUserPasses: builder.query({ + query: ({ cardMode, sort }) => { + const params = new URLSearchParams() + + if(cardMode) params.append('cardMode',cardMode); + if(sort) params.append('sort',sort); + + return `/website/passes/all?${params.toString()}` + } + }) + + }) +}); + +export const { + useGetUserProfileDetailsQuery, + useUpdateUserProfileDetailsMutation, + useGetUserPassesQuery +} = profileApi; \ No newline at end of file diff --git a/src/imports/AfterLogin.tsx b/src/components/AfterLogin.tsx similarity index 100% rename from src/imports/AfterLogin.tsx rename to src/components/AfterLogin.tsx diff --git a/src/imports/BeforeLogin.tsx b/src/components/BeforeLogin.tsx similarity index 100% rename from src/imports/BeforeLogin.tsx rename to src/components/BeforeLogin.tsx diff --git a/src/components/CTAButton.tsx b/src/components/CTAButton.tsx index 9b80d9f..0c16431 100644 --- a/src/components/CTAButton.tsx +++ b/src/components/CTAButton.tsx @@ -1,6 +1,6 @@ import { motion } from 'motion/react'; -import BeforeLogin from '../imports/BeforeLogin'; -import AfterLogin from '../imports/AfterLogin'; +import BeforeLogin from './BeforeLogin'; +import AfterLogin from './AfterLogin'; interface User { email: string; diff --git a/src/components/CitySelectionDialog.tsx b/src/components/CitySelectionDialog.tsx index 203aa72..188d4a0 100644 --- a/src/components/CitySelectionDialog.tsx +++ b/src/components/CitySelectionDialog.tsx @@ -7,6 +7,7 @@ import { Input } from './ui/input'; import { motion, AnimatePresence } from 'motion/react'; import { ImageWithFallback } from './figma/ImageWithFallback'; import { useGetCityListWithBannerQuery } from '../Redux/services/cities.service'; +import LoadingSpinner from './LoadingSpinner'; interface City { id: number; @@ -20,6 +21,9 @@ interface CitySelectionDialogProps { onCitySelect?: (cityId: string) => void; // ✅ Updated to pass cityId } +export const slugify = (name: string | null) => + name?.toLowerCase().replace(/\s+/g, '-'); + export function CitySelectionDialog({ isOpen, onClose, @@ -31,21 +35,16 @@ export function CitySelectionDialog({ const { data: cities, isLoading } = useGetCityListWithBannerQuery({ search }) if (isLoading) { - return
Loading...
+ return ( + + ); } - const handleCityClick = (city: City) => { console.log('Selected city:', city.cityName); - - // ✅ Call the onCitySelect callback if provided (passing cityId) - if (onCitySelect) { - onCitySelect(String(city.id)); - } else { - // ✅ Default behavior: navigate to passes page - navigate(`/passes?city=${encodeURIComponent(city.cityName)}`); - } - + navigate(`/${slugify(city.cityName)}`); + localStorage.setItem("cityId", String(city.id)) + localStorage.setItem("cityName", String(city.cityName)) onClose(); }; diff --git a/src/components/LandingMagicItinerary.tsx b/src/components/LandingMagicItinerary.tsx index a67a853..8ce3422 100644 --- a/src/components/LandingMagicItinerary.tsx +++ b/src/components/LandingMagicItinerary.tsx @@ -31,7 +31,7 @@ export function LandingMagicItinerary() { }; return ( -
+
{/* Dynamic Background */}
{/* Background Image as fallback */} diff --git a/src/components/LandingUpcomingCities.tsx b/src/components/LandingUpcomingCities.tsx index 2d724a0..c5c70a1 100644 --- a/src/components/LandingUpcomingCities.tsx +++ b/src/components/LandingUpcomingCities.tsx @@ -4,6 +4,7 @@ import { Button } from './ui/button'; import { useRef, useState, useEffect } from 'react'; import Image592Traced from '../imports/Image592Traced-5025-559'; import { useGetUpcomingCitiesQuery } from '../Redux/services/cities.service'; +import LoadingSpinner from './LoadingSpinner'; // const upcomingCities = [ // { @@ -113,8 +114,10 @@ export function LandingUpcomingCities() { const { data, isLoading } = useGetUpcomingCitiesQuery(listType) - if(isLoading){ - return
Loading...
+ if (isLoading) { + return ( + + ); } const handleMouseDown = (e: React.MouseEvent) => { diff --git a/src/components/LandingVarietyOfAdventures.tsx b/src/components/LandingVarietyOfAdventures.tsx index 3e7ab94..4af0519 100644 --- a/src/components/LandingVarietyOfAdventures.tsx +++ b/src/components/LandingVarietyOfAdventures.tsx @@ -154,7 +154,7 @@ export function LandingVarietyOfAdventures() {
{/* Header */}
- Experience{' '} for Every Traveller - - From iconic laneways and world-class coffee to stunning gardens and vibrant markets, + From iconic laneways and world-class coffee to stunning gardens and vibrant markets, discover Melbourne's unique character through curated experiences that showcase the city's soul.
@@ -180,7 +180,7 @@ export function LandingVarietyOfAdventures() { {/* Carousel Container - Full Width */}
{/* Scrolling Track */} -
- + {/* Bottom Content Card */}
-
- + {/* Icon */} -
- + {/* Attraction Info */}
@@ -332,19 +332,19 @@ export function LandingVarietyOfAdventures() { {/* Gradient Fade Edges */} -
-
+
+
{/* CTA Button */} - - + +
+
+ -

- Login -

-

- Enter your email Id and verify with OTP sent on it. -

-
+

+ Login +

+

+ Enter your email and verify with OTP +

+
- {/* Content */} -
- {step === 'email' ? ( -
- {/* Email Input */} -
- - setEmail(e.target.value)} - className="font-poppins text-base h-12 bg-gray-50 border-0 rounded-xl placeholder:text-gray-400" - onKeyDown={(e) => e.key === 'Enter' && handleSendOTP()} - /> - {helperText && ( -

- {helperText} -

- )} -
- - {/* Send OTP Button */} - -
- ) : ( -
- {/* Email Display */} -
- -
- {email} -
- {helperText && ( -

- {helperText} -

- )} -
- - {/* OTP Input */} -
- -
- {otp.map((digit, index) => ( - handleOTPChange(index, e.target.value.replace(/\D/g, ''))} - onKeyDown={(e) => handleOTPKeyDown(index, e)} - data-otp-index={index} - className="w-12 h-12 text-center font-poppins font-semibold text-lg bg-gray-300 border-0 rounded-xl focus:bg-white focus:ring-2 focus:ring-primary focus:outline-none transition-colors" - /> - ))} +
+ {step === 'email' ? ( + // ... Email step (unchanged) +
+
+ + setEmail(e.target.value)} + onKeyDown={(e) => e.key === 'Enter' && handleSendOTP()} + className="font-poppins text-base h-12 bg-gray-50 border-0 rounded-xl placeholder:text-gray-400" + /> + {error &&

{error}

} + {helperText &&

{helperText}

}
- {/* Countdown */} - {countdown > 0 && ( -

- {formatCountdown(countdown)} -

- )} -
- - {/* Verify Button */} - - - {/* Resend OTP */} - {countdown === 0 && ( - - )} -
- )} + {isSendingOtp ? 'Sending OTP...' : 'Send OTP'} + +
+ +
+
+ ) : ( +
+ {/* Email Display */} +
+ +
+ {email} +
+
+ + {/* OTP Inputs with Paste Support */} +
+ +
+ {otp.map((digit, index) => ( + handleOTPChange(index, e.target.value.replace(/\D/g, ''))} + onKeyDown={(e) => handleOTPKeyDown(index, e)} + onPaste={handlePaste} // ← Paste support added here + data-otp-index={index} + className="w-12 h-12 text-center font-poppins font-semibold text-lg bg-gray-300 border-0 rounded-xl focus:bg-white focus:ring-2 focus:ring-gray-800 focus:outline-none transition-all" + /> + ))} +
+ + {countdown > 0 && ( +

+ Resend OTP in {formatCountdown(countdown)} +

+ )} +
+ + {error &&

{error}

} + + + + {countdown === 0 && ( + + )} +
+ )} +
-
-
- - )} - +
+ + ) + } + + setShowRegisterModal(false)} + onLoginClick={() => { + setShowRegisterModal(false); + setStep('email'); + setEmail(''); + }} + /> + ); } \ No newline at end of file diff --git a/src/components/MelbourneCardComparison.tsx b/src/components/MelbourneCardComparison.tsx index d12ac03..5f5c6eb 100644 --- a/src/components/MelbourneCardComparison.tsx +++ b/src/components/MelbourneCardComparison.tsx @@ -1,54 +1,54 @@ -import { useState } from 'react'; +import { useEffect, useState } from 'react'; import { Check, X, Star, Users, MapPin, Calendar, Clock, Zap, Eye } from 'lucide-react'; import { Button } from './ui/button'; import { motion } from 'motion/react'; -const cardOptions = [ - { - id: 'selective', - name: 'Flexi Card', - subtitle: 'Pick 5-10 things to do from a choice of 102 attractions tours and activities', - priceRange: '$89-159', - duration: '3-7 days', - popular: false, - color: 'from-blue-500 to-cyan-500', - features: { - passCategory: 'Selective Card', - accessToAttractions: true, - entryToAttractions: true, - accessToExperiences: true, - entryToSites: true, - accessToVenues: false, - entryToEvents: 'Pass Category', - accessToLocations: 'Pass Category', - entryToActivities: true, - accessToExhibits: true, - entryToActivitiesSecond: true - } - }, - { - id: 'unlimited', - name: 'Melbourne Unlimited Card', - subtitle: 'Pick 5-30 things to do from a choice of 102 attractions tours and activities', - priceRange: '$159-299', - duration: '3-7 days', - popular: true, - color: 'from-purple-500 to-pink-500', - features: { - passCategory: 'Pass Category', - accessToAttractions: true, - entryToAttractions: true, - accessToExperiences: true, - entryToSites: true, - accessToVenues: true, - entryToEvents: 'Pass Category', - accessToLocations: 'Pass Category', - entryToActivities: true, - accessToExhibits: true, - entryToActivitiesSecond: true - } - } -]; +// const cardOptions = [ +// { +// id: 'selective', +// name: 'Flexi Card', +// subtitle: 'Pick 5-10 things to do from a choice of 102 attractions tours and activities', +// priceRange: '$89-159', +// duration: '3-7 days', +// popular: false, +// color: 'from-blue-500 to-cyan-500', +// features: { +// passCategory: 'Selective Card', +// accessToAttractions: true, +// entryToAttractions: true, +// accessToExperiences: true, +// entryToSites: true, +// accessToVenues: false, +// entryToEvents: 'Pass Category', +// accessToLocations: 'Pass Category', +// entryToActivities: true, +// accessToExhibits: true, +// entryToActivitiesSecond: true +// } +// }, +// { +// id: 'unlimited', +// name: 'Melbourne Unlimited Card', +// subtitle: 'Pick 5-30 things to do from a choice of 102 attractions tours and activities', +// priceRange: '$159-299', +// duration: '3-7 days', +// popular: true, +// color: 'from-purple-500 to-pink-500', +// features: { +// passCategory: 'Pass Category', +// accessToAttractions: true, +// entryToAttractions: true, +// accessToExperiences: true, +// entryToSites: true, +// accessToVenues: true, +// entryToEvents: 'Pass Category', +// accessToLocations: 'Pass Category', +// entryToActivities: true, +// accessToExhibits: true, +// entryToActivitiesSecond: true +// } +// } +// ]; const features = [ { key: 'passCategory', label: 'Pass Category', icon: Star }, @@ -71,11 +71,59 @@ const FeatureIcon = ({ feature }: { feature: typeof features[0] }) => { interface MelbourneCardComparisonProps { onCheckoutClick?: () => void; + cards: any[] } -export function MelbourneCardComparison({ onCheckoutClick }: MelbourneCardComparisonProps) { +export function MelbourneCardComparison({ onCheckoutClick, cards }: MelbourneCardComparisonProps) { const [selectedCard, setSelectedCard] = useState('unlimited'); + const cardOptions = [ + { + id: cards[0]?.id, + name: cards[0]?.title, + subtitle: cards[0]?.description, + priceRange: `$${cards[0]?.adultPrice}`, + duration: '3-7 days', + popular: false, + color: 'from-blue-500 to-cyan-500', + features: { + passCategory: 'Selective Card', + accessToAttractions: true, + entryToAttractions: true, + accessToExperiences: true, + entryToSites: true, + accessToVenues: false, + entryToEvents: 'Pass Category', + accessToLocations: 'Pass Category', + entryToActivities: true, + accessToExhibits: true, + entryToActivitiesSecond: true + } + }, + { + id: cards[1]?.id, + name: cards[1]?.title, + subtitle: cards[1]?.description, + priceRange: `$${cards[1]?.adultPrice}`, + duration: '3-7 days', + popular: true, + color: 'from-purple-500 to-pink-500', + features: { + passCategory: 'Pass Category', + accessToAttractions: true, + entryToAttractions: true, + accessToExperiences: true, + entryToSites: true, + accessToVenues: true, + entryToEvents: 'Pass Category', + accessToLocations: 'Pass Category', + entryToActivities: true, + accessToExhibits: true, + entryToActivitiesSecond: true + } + } + ]; + const renderFeatureValue = (value: boolean | string, cardId: string) => { if (typeof value === 'boolean') { return value ? ( @@ -92,7 +140,7 @@ export function MelbourneCardComparison({ onCheckoutClick }: MelbourneCardCompar
); } - + return (
{value} @@ -122,17 +170,17 @@ export function MelbourneCardComparison({ onCheckoutClick }: MelbourneCardCompar Choose Your Adventure
- +

Buy {' '} Now

- +

- Melbourne is a must-visit cultural epicenter, and this spectacular trip unlocks - your access around the city in one easy. Save over the cost of visiting Melbourne's + Melbourne is a must-visit cultural epicenter, and this spectacular trip unlocks + your access around the city in one easy. Save over the cost of visiting Melbourne's landmarks, have lunch at Phi Phi Leh, snorkel at Bamboo Island, and visit Monkey Beach.

@@ -151,8 +199,8 @@ export function MelbourneCardComparison({ onCheckoutClick }: MelbourneCardCompar
Features
- {cardOptions.map((card) => ( -
+ {cardOptions.map((card,index) => ( +
{card.name}
@@ -179,9 +227,9 @@ export function MelbourneCardComparison({ onCheckoutClick }: MelbourneCardCompar {feature.label}
- - {cardOptions.map((card) => ( -
+ + {cardOptions.map((card, index) => ( +
{renderFeatureValue(card.features[feature.key as keyof typeof card.features], card.id)}
))} @@ -196,16 +244,16 @@ export function MelbourneCardComparison({ onCheckoutClick }: MelbourneCardCompar
Ready to explore?
Compare features above
- - {cardOptions.map((card) => ( - + + {cardOptions.map((card,index) => ( +
{card.priceRange}
{card.duration}
+ +

+ Create Account +

+

+ Register to get started with City Cards +

+
+ +
+
+ {/* Personal Information */} +
+

Personal Information

+
+
+ + handleInputChange('firstName', e.target.value)} + className="font-poppins text-base h-12 bg-gray-50 border-0 rounded-xl placeholder:text-gray-400" + /> +
+ +
+ + handleInputChange('lastName', e.target.value)} + className="font-poppins text-base h-12 bg-gray-50 border-0 rounded-xl placeholder:text-gray-400" + /> +
+
+ +
+ + handleInputChange('emailAddress', e.target.value)} + className="font-poppins text-base h-12 bg-gray-50 border-0 rounded-xl placeholder:text-gray-400" + /> +
+ +
+
+ + +
+ +
+ + handleInputChange('mobileNumber', e.target.value)} + className="font-poppins text-base h-12 bg-gray-50 border-0 rounded-xl placeholder:text-gray-400" + /> +
+
+
+ + {/* Address Information */} +
+

Address Information

+ +
+ + handleInputChange('address1', e.target.value)} + className="font-poppins text-base h-12 bg-gray-50 border-0 rounded-xl placeholder:text-gray-400" + /> +
+ +
+ + handleInputChange('address2', e.target.value)} + className="font-poppins text-base h-12 bg-gray-50 border-0 rounded-xl placeholder:text-gray-400" + /> +
+ +
+
+ + handleInputChange('city', e.target.value)} + className="font-poppins text-base h-12 bg-gray-50 border-0 rounded-xl placeholder:text-gray-400" + /> +
+ +
+ + handleInputChange('state', e.target.value)} + className="font-poppins text-base h-12 bg-gray-50 border-0 rounded-xl placeholder:text-gray-400" + /> +
+
+ +
+
+ + +
+ +
+ + handleInputChange('postalCode', e.target.value)} + className="font-poppins text-base h-12 bg-gray-50 border-0 rounded-xl placeholder:text-gray-400" + /> +
+
+
+ + {helperText && ( +

+ {helperText} +

+ )} + + + +
+ +
+
+
+
+ + + )} + + ); +} diff --git a/src/components/SuperSavingsPage.tsx b/src/components/SuperSavingsPage.tsx deleted file mode 100644 index 6743d5d..0000000 --- a/src/components/SuperSavingsPage.tsx +++ /dev/null @@ -1,745 +0,0 @@ -import { useState } from 'react'; -import { motion } from 'motion/react'; -import { ArrowLeft, Search, Filter, Star, MapPin, Clock, Tag, Heart, Share2, ChevronDown, ChevronRight, Check, Hotel, Plane, Building2, MapPinned, Home, Gift, Percent } from 'lucide-react'; -import { Button } from './ui/button'; -import { Input } from './ui/input'; -import { Card, CardContent, CardHeader, CardTitle } from './ui/card'; -import { Badge } from './ui/badge'; -import { Separator } from './ui/separator'; -import { Checkbox } from './ui/checkbox'; -import Navbar from './Navbar'; -import { Footer } from './Footer'; -import { ImageWithFallback } from './figma/ImageWithFallback'; -import { TrustSection } from './TrustSection'; -import { MobileAppSection } from './MobileAppSection'; -import { ReviewsSection } from './ReviewsSection'; -import { TrustedCompanies } from './TrustedCompanies'; -import { Layout } from '../Layout'; - -interface SuperSavingsPageProps { - onBackClick: () => void; - onHomeClick: () => void; - onMelbourneClick: () => void; - onPassesClick: () => void; - onCheckoutClick: () => void; - onSignInClick: () => void; - onSignOutClick: () => void; - onAttractionsClick: () => void; - onBlogsClick: () => void; - onHowItWorksClick: () => void; - onFAQClick: () => void; - onPrivacyPolicyClick: () => void; - onAboutUsClick: () => void; - onProfileClick: () => void; - onCityCardsClick: () => void; - onMagicItineraryClick: () => void; - onPostCardsClick: () => void; - onOffersClick: () => void; - onSuperSavingsClick: () => void; - onContactUsClick?: () => void; - onEsimsClick?: () => void; - onHotelDiscountsClick?: () => void; - fromSource?: 'products' | 'passes'; - currentPage: string; - user?: { email: string; name: string; } | null; -} - -// Mock super savings data -const savingsData = [ - { - id: '1', - business: 'Grand Hotels Melbourne', - title: 'Up to 50% Off on luxury hotel stays across Melbourne', - discount: '50% OFF', - savedAmount: 'Save up to $300', - image: 'https://images.unsplash.com/photo-1566073771259-6a8506099945?w=400', - category: 'hotels' - }, - { - id: '2', - business: 'Adventure Tours', - title: '40% Off on guided adventure tours and experiences', - discount: '40% OFF', - savedAmount: 'Save up to $150', - image: 'https://images.unsplash.com/photo-1571019613454-1cb2f99b2d8b?w=400', - category: 'tours' - }, - { - id: '3', - business: 'Premium Spa & Wellness', - title: '45% Off on spa packages and wellness treatments', - discount: '45% OFF', - savedAmount: 'Save up to $200', - image: 'https://images.unsplash.com/photo-1544161515-4ab6ce6db874?w=400', - category: 'wellness' - }, - { - id: '4', - business: 'Culinary Delights', - title: '35% Off on fine dining at Michelin-starred restaurants', - discount: '35% OFF', - savedAmount: 'Save up to $120', - image: 'https://images.unsplash.com/photo-1414235077428-338989a2e8c0?w=400', - category: 'dining' - }, - { - id: '5', - business: 'Entertainment Pass', - title: '60% Off on theater shows and concert tickets', - discount: '60% OFF', - savedAmount: 'Save up to $250', - image: 'https://images.unsplash.com/photo-1514306191717-452ec28c7814?w=400', - category: 'entertainment' - }, - { - id: '6', - business: 'Museum Pass', - title: '55% Off on museum entries and special exhibitions', - discount: '55% OFF', - savedAmount: 'Save up to $180', - image: 'https://images.unsplash.com/photo-1566127992631-137a642a90f4?w=400', - category: 'museums' - }, - { - id: '7', - business: 'Luxury Shopping', - title: '30% Off on designer boutiques and luxury shopping', - discount: '30% OFF', - savedAmount: 'Save up to $500', - image: 'https://images.unsplash.com/photo-1441986300917-64674bd600d8?w=400', - category: 'shopping' - }, - { - id: '8', - business: 'Water Sports', - title: '45% Off on water sports and beach activities', - discount: '45% OFF', - savedAmount: 'Save up to $175', - image: 'https://images.unsplash.com/photo-1476514525535-07fb3b4ae5f1?w=400', - category: 'sports' - }, - { - id: '9', - business: 'Wine Tasting Tours', - title: '40% Off on wine country tours and tastings', - discount: '40% OFF', - savedAmount: 'Save up to $160', - image: 'https://images.unsplash.com/photo-1506377247377-2a5b3b417ebb?w=400', - category: 'tours' - }, - { - id: '10', - business: 'Family Fun Parks', - title: '50% Off on family entertainment and theme parks', - discount: '50% OFF', - savedAmount: 'Save up to $220', - image: 'https://images.unsplash.com/photo-1524850011238-e3d235c7d4c9?w=400', - category: 'entertainment' - }, - { - id: '11', - business: 'Boutique Stays', - title: '55% Off on boutique hotels and bed & breakfasts', - discount: '55% OFF', - savedAmount: 'Save up to $280', - image: 'https://images.unsplash.com/photo-1551882547-ff40c63fe5fa?w=400', - category: 'hotels' - }, - { - id: '12', - business: 'Art Galleries', - title: '35% Off on contemporary art galleries and workshops', - discount: '35% OFF', - savedAmount: 'Save up to $140', - image: 'https://images.unsplash.com/photo-1561214115-f2f134cc4912?w=400', - category: 'museums' - }, - { - id: '13', - business: 'Luxury Cruises', - title: '65% Off on harbor cruises and yacht experiences', - discount: '65% OFF', - savedAmount: 'Save up to $400', - image: 'https://images.unsplash.com/photo-1544551763-46a013bb70d5?w=400', - category: 'tours' - } -]; - -const filterCategories = [ - { value: 'hotels', label: 'Hotels', count: 2 }, - { value: 'tours', label: 'Tours', count: 3 }, - { value: 'wellness', label: 'Wellness', count: 1 }, - { value: 'dining', label: 'Dining', count: 1 }, - { value: 'entertainment', label: 'Entertainment', count: 2 }, - { value: 'museums', label: 'Museums', count: 2 }, - { value: 'shopping', label: 'Shopping', count: 1 }, - { value: 'sports', label: 'Sports', count: 1 } -]; - -// Categories data for the Super Savings Categories section -const categoriesData = [ - { - icon: Hotel, - title: 'Luxury Hotels', - description: 'Premium stays at unbeatable prices', - savings: 'Up to 50% off', - color: 'from-primary to-primary/80' - }, - { - icon: Plane, - title: 'Travel Tours', - description: 'Guided experiences worth your time', - savings: 'Up to 45% off', - color: 'from-primary to-primary/80' - }, - { - icon: MapPinned, - title: 'Attractions', - description: 'Must-see landmarks and experiences', - savings: 'Up to 60% off', - color: 'from-primary to-primary/80' - }, - { - icon: Building2, - title: 'Shopping', - description: 'Designer brands and local boutiques', - savings: 'Up to 35% off', - color: 'from-primary to-primary/80' - }, - { - icon: Gift, - title: 'Wellness', - description: 'Spa treatments and relaxation', - savings: 'Up to 45% off', - color: 'from-primary to-primary/80' - } -]; - -export function SuperSavingsPage({ - onBackClick, - onHomeClick, - onMelbourneClick, - onPassesClick, - onCheckoutClick, - onSignInClick, - onSignOutClick, - onAttractionsClick, - onBlogsClick, - onHowItWorksClick, - onFAQClick, - onPrivacyPolicyClick, - onAboutUsClick, - onProfileClick, - onCityCardsClick, - onMagicItineraryClick, - onPostCardsClick, - onOffersClick, - onSuperSavingsClick, - onContactUsClick, - onEsimsClick, - onHotelDiscountsClick, - fromSource = 'products', - currentPage, - user -}: SuperSavingsPageProps) { - const [searchQuery, setSearchQuery] = useState(''); - const [selectedCategories, setSelectedCategories] = useState([]); - const [currentPage_, setCurrentPage_] = useState(1); - const [showLoadMore, setShowLoadMore] = useState(true); - - const toggleCategory = (category: string) => { - setSelectedCategories(prev => - prev.includes(category) - ? prev.filter(c => c !== category) - : [...prev, category] - ); - }; - - const filteredSavings = savingsData.filter(saving => { - const matchesSearch = saving.title.toLowerCase().includes(searchQuery.toLowerCase()) || - saving.business.toLowerCase().includes(searchQuery.toLowerCase()); - const matchesCategory = selectedCategories.length === 0 || selectedCategories.includes(saving.category); - - return matchesSearch && matchesCategory; - }); - - const itemsPerPage = 12; - const displayedSavings = filteredSavings.slice(0, currentPage_ * itemsPerPage); - const hasMoreItems = filteredSavings.length > displayedSavings.length; - - const handleLoadMore = () => { - setCurrentPage_(prev => prev + 1); - if (!hasMoreItems) setShowLoadMore(false); - }; - - // Show different layouts based on login state - if (!user) { - // Not logged in - show marketing/landing page - return ( - -
- - {/* Hero Section */} -
- {/* Background gradient */} -
- -
-
- -
- -

- Unlock{' '} - - Super Savings - -

-

- Experience incredible discounts up to 65% off on premium experiences, luxury stays, and unforgettable attractions. -

- -
-
- - {/* Decorative elements */} -
-
-
- - {/* Trusted By Companies Section */} -
-
-
-
-

- Trusted by the - world's best -

-

- Join thousands of savvy travelers enjoying massive savings on premium experiences -

-
- -
-
-
- - {/* Featured Super Savings Section */} -
-
- -

- Featured{' '} - - Super Savings - -

-

- Check out our biggest discounts and start saving on premium experiences -

-
- -
-
- {/* Left Sidebar - Filters */} -
- -
- {/* Search by header */} -
-
-

Search by

-
- - {/* Filter categories */} -
- {filterCategories.map(category => ( -
- toggleCategory(category.value)} - className="border-gray-400" - /> - -
- ))} -
-
-
-
- - {/* Main Content */} -
- {/* Breadcrumb */} -
-

- {fromSource === 'passes' ? ( - <> - My Profile{'>'}My passes{'>'} - Super Savings - - ) : ( - <> - Our Products{'>'} - Super Savings - - )} -

-
- - {/* Header Section */} -
-

- Super Savings -

-

- Exclusive discounts up to 65% off on premium experiences -

-
- - {/* Savings Grid */} -
- {displayedSavings.map((saving, index) => ( - - - {/* Image */} -
- - - - {/* Discount Badge */} -
- {saving.discount} -
-
- - - {/* Business Name */} -
-
- {saving.business} -
- - {/* Title */} -

- {saving.title} -

- - {/* Saved Amount Display */} -
-
- - - {saving.savedAmount} - -
-
-
-
-
- ))} -
- - {/* Minimal Pagination */} -
-
- - -
- {[1, 2, 3].map((page) => ( - - ))} -
- - -
-
-
-
-
- -
- -
-
-
- - {/* How It Works Section */} -
- {/* Background decorative elements */} -
-
- -
- -
-
- Simple Process -
-
-

- Start Saving in Minutes -

-

- Your journey to smarter travel and bigger savings begins with three simple steps. -

-
- -
- {/* Connecting line for desktop */} -
- -
- {[ - { - step: '01', - title: 'Unlock Access', - description: 'Get your CityCards pass to instantly activate membership perks.', - icon: MapPinned - }, - { - step: '02', - title: 'Discover Deals', - description: 'Browse exclusive offers on hotels, flights, and experiences.', - icon: Search - }, - { - step: '03', - title: 'Enjoy Savings', - description: 'Redeem discounts instantly and watch your travel budget grow.', - icon: Percent - } - ].map((item, index) => ( - -
- {/* Icon Container */} -
-
-
- -
-
- {index + 1} -
-
- -

- {item.title} -

-

- {item.description} -

-
-
- ))} -
-
- - -
-
- - {/* Categories Section */} -
- {/* Abstract Travel Patterns */} -
- -
- {/* Section Header */} -
- -
-
- Explore Collections -
-

- Curated for the Modern Traveler -

-

- Discover exclusive savings across our most sought-after travel categories. -

-
- - - - -
- - {/* Bento Grid Layout */} -
- {categoriesData.map((category, index) => ( - -
- {/* Background Gradient Hover */} -
- - {/* Large Watermark Icon for visual depth */} - - -
-
-
- -
- - {category.savings} - -
- -

- {category.title} -

-

- {category.description} -

-
- -
- Explore Deals - -
-
-
- ))} -
- - {/* Mobile View All Button */} -
- -
-
-
- - {/* Access Your CityCards Section */} -
- -
- -
-
- ); - } - -} diff --git a/src/context/AuthContext.tsx b/src/context/AuthContext.tsx index 8cabeb8..50dba2a 100644 --- a/src/context/AuthContext.tsx +++ b/src/context/AuthContext.tsx @@ -3,7 +3,9 @@ import { useNavigate } from 'react-router-dom'; interface User { email: string; - name: string + name: string; + accessToken:string; + userId:string; } interface AuthContextType { @@ -29,11 +31,15 @@ export const AuthProvider = ({ children }: { children: React.ReactNode }) => { const login = (userData: User) => { setUser(userData) localStorage.setItem("user", JSON.stringify(userData)) + localStorage.setItem("accessToken", userData?.accessToken) + localStorage.setItem("userId", userData?.userId) } const logout = () => { setUser(null) localStorage.removeItem("user") + localStorage.removeItem("accessToken") + localStorage.removeItem("userId") navigate("/") } diff --git a/src/global.d.ts b/src/global.d.ts index 903f3f8..eb5a13c 100644 --- a/src/global.d.ts +++ b/src/global.d.ts @@ -32,3 +32,5 @@ declare module '*.mp4' { const src: string; export default src; } + +declare module "*.css"; diff --git a/src/guidelines/Guidelines.md b/src/guidelines/Guidelines.md deleted file mode 100644 index 4a00ca3..0000000 --- a/src/guidelines/Guidelines.md +++ /dev/null @@ -1,345 +0,0 @@ -# Image Reference Guidelines - -**IMPORTANT**: When I provide an image for reference, it is for design reference only. Do NOT use that image inside any image section in the code. The image is provided to help understand the layout, styling, and visual direction - not to be embedded as an actual image in the application. - -# CityCards Typography Guidelines - -**Project**: Typography Guidelines for CityCards Travel Website - -## Font System - -### Primary Font -- **Poppins**: Used for all text including headings (H1–H6), body text, buttons, labels, and forms - Clean, readable, and consistent throughout - -### Font Weight Scale & Usage -- **font-light (300)**: Hero headings only - For creating dynamic contrast in H1/H2 -- **font-normal (400)**: Standard body text - Default weight for paragraphs -- **font-medium (500)**: Buttons, navigation links, subtle emphasis -- **font-semibold (600)**: Section headings, primary buttons -- **font-bold (700)**: Hero keywords, strong emphasis - -## Heading Typography Specifications - -### H1 - Hero/Main Page Headings -- **Font**: Poppins -- **Size**: `text-5xl md:text-6xl lg:text-7xl` (48px/60px/72px - targeting ~64px) -- **Line Height**: `leading-tight` -- **Pattern**: Dynamic multi-weight with gradient/italic accents (max 2 emphasis styles) - -```jsx -

- Discover{' '} - - Melbourne's - {' '} - Best Experiences -

-``` - -### H2 - Section Headings -- **Font**: Poppins -- **Size**: `text-2xl md:text-3xl lg:text-4xl` (24px/36px/48px) -- **Line Height**: `leading-tight` -- **Pattern**: Mixed weights with gradient emphasis - -```jsx -

- Explore{' '} - - Amazing - {' '} - Cities -

-``` - -### H3 - Subsection Headings -- **Font**: Poppins -- **Size**: `text-xl md:text-2xl` (24px/30px) -- **Line Height**: `leading-snug` -- **Weight**: `font-semibold` - -```jsx -

- Feature Title -

-``` - -### H4 - Component Headings -- **Font**: Poppins -- **Size**: `text-lg md:text-xl` (20px/24px) -- **Line Height**: `leading-snug` -- **Weight**: `font-medium` or `font-semibold` - -```jsx -

- Component Heading -

-``` - -### H5 - Card/Item Titles -- **Font**: Poppins -- **Size**: `text-lg` (18px) -- **Line Height**: `leading-snug` -- **Weight**: `font-medium` - -```jsx -
- Card Title -
-``` - -### H6 - Small Headings -- **Font**: Poppins -- **Size**: `text-base` (16px) -- **Line Height**: `leading-snug` -- **Weight**: `font-medium` - -```jsx -
- Small Heading -
-``` - -## Body Typography Specifications - -### Large Body Text -- **Font**: Poppins -- **Size**: `text-xl` (20px) -- **Line Height**: `leading-relaxed` -- **Weight**: `font-normal` - -```jsx -

- Large descriptive text for important sections -

-``` - -### Regular Body Text -- **Font**: Poppins -- **Size**: `text-base` (16px) -- **Line Height**: `leading-relaxed` -- **Weight**: `font-normal` - -```jsx -

- Regular body text content -

-``` - -### Small Body Text -- **Font**: Poppins -- **Size**: `text-sm` (14px) -- **Line Height**: `leading-relaxed` -- **Weight**: `font-normal` or `font-light` - -```jsx -

- Caption or meta information -

-``` - -## Interactive Element Typography - -### Buttons -- **Font**: Poppins -- **Primary Weight**: `font-semibold` -- **Secondary Weight**: `font-medium` -- **Min Size**: 16px - -```jsx -// Primary Button - - -// Secondary Button - -``` - -### Navigation Links -- **Font**: Poppins -- **Weight**: `font-medium` -- **Size**: `text-base` (16px) - -```jsx - - Navigation Link - -``` - -### Form Labels -- **Font**: Poppins -- **Weight**: `font-light` or `font-normal` -- **Size**: `text-sm` or `text-base` (14px/16px) - -```jsx - -``` - -### Form Inputs -- **Font**: Poppins -- **Weight**: `font-normal` -- **Size**: `text-base` (16px) - -```jsx - -``` - -## Accessibility Standards - -### Text Size Requirements -- **Minimum Text Size**: 14px -- **Interactive Minimum Size**: 16px -- **Contrast**: WCAG AA or higher -- **Heading Hierarchy**: Maintain semantic order (H1 → H2 → H3 etc.) - -### Implementation Requirements -```jsx -// Always include explicit font and size classes to override defaults -

- Content with explicit styling -

-``` - -## Typography Rules - -### DO ✅ -- Use Poppins for all text (headings and body) -- Apply max 2 emphasis styles per heading -- Use gradient effects sparingly -- Keep line-heights consistent -- Always specify explicit font classes to override component defaults - -### DON'T ❌ -- Don't use font-light in small text -- Don't mix more than 3 weights in one heading -- Don't go below 14px for captions -- Don't override font sizes without Tailwind classes -- Don't break semantic heading hierarchy - -## Implementation Guidelines - -### Component Styling Override -**IMPORTANT**: Always explicitly set typography classes to override component defaults: - -```jsx -// ✅ CORRECT - Explicit typography classes - - - - Card Title - - - -

- Card content with explicit styling -

-
-
- -// ❌ INCORRECT - Relying on defaults - - - Card Title - - -

Card content without explicit styling

-
-
-``` - -### Dynamic Heading Patterns -```jsx -// Pattern 1: Light → Bold (H1/H2 only) -

- Discover{' '} - - Amazing - {' '} - Destinations -

- -// Pattern 2: Normal → Semibold (H3/H4) -

- Experience{' '} - Melbourne's Culture -

-``` - -### Responsive Typography -```jsx -// Mobile-first responsive scaling -

- Responsive Heading -

- -

- Responsive body text -

-``` - -**Add your own guidelines here** - diff --git a/src/main.tsx b/src/main.tsx index 241b0b3..cfff6d7 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -4,10 +4,12 @@ import App from "./App"; import "./index.css"; import { Provider } from "react-redux"; import { store } from "./Redux/Store"; +import { Toaster } from "sonner"; createRoot(document.getElementById("root")!).render( + diff --git a/src/components/AboutUsPage.tsx b/src/pages/AboutUsPage.tsx similarity index 98% rename from src/components/AboutUsPage.tsx rename to src/pages/AboutUsPage.tsx index b68a755..29b87fd 100644 --- a/src/components/AboutUsPage.tsx +++ b/src/pages/AboutUsPage.tsx @@ -1,13 +1,13 @@ import React from 'react'; import { motion } from 'motion/react'; import { ArrowLeft, Heart, MapPin, Zap, Globe, Users, Camera, Coffee } from 'lucide-react'; -import { Button } from './ui/button'; -import { ImageWithFallback } from './figma/ImageWithFallback'; -import Navbar from './Navbar'; -import { Footer } from './Footer'; -import { MobileAppSection } from './MobileAppSection'; -import { EnhancedTestimonials } from './EnhancedTestimonials'; -import { ReviewsSection } from './ReviewsSection'; +// import { Button } from './ui/button'; +import { ImageWithFallback } from '../components/figma/ImageWithFallback'; +import Navbar from '../components/Navbar'; +import { Footer } from '../components/Footer'; +import { MobileAppSection } from '../components/MobileAppSection'; +import { EnhancedTestimonials } from '../components/EnhancedTestimonials'; +import { ReviewsSection } from '../components/ReviewsSection'; interface User { email: string; diff --git a/src/components/AttractionDetailsPage.tsx b/src/pages/AttractionDetailsPage.tsx similarity index 65% rename from src/components/AttractionDetailsPage.tsx rename to src/pages/AttractionDetailsPage.tsx index 4962d84..64223b9 100644 --- a/src/components/AttractionDetailsPage.tsx +++ b/src/pages/AttractionDetailsPage.tsx @@ -1,12 +1,25 @@ import { ArrowLeft, Calendar, Check, ChevronLeft, ChevronRight, Clock, MapPin, Users, X } from 'lucide-react'; import { motion } from 'motion/react'; +<<<<<<< HEAD:src/components/AttractionDetailsPage.tsx +======= +import { ArrowLeft, Clock, Users, Calendar, MapPin, Star, Check, X, ChevronLeft, ChevronRight } from 'lucide-react'; +import { Button } from '../components/ui/button'; +import { Badge } from '../components/ui/badge'; +import { Card, } from '../components/ui/card'; +import { ImageWithFallback } from '../components/figma/ImageWithFallback'; +import { Layout } from '../Layout'; +>>>>>>> cdadee5df40960af2263c2e5e6f4a8f234b7d5b0:src/pages/AttractionDetailsPage.tsx import { useParams } from 'react-router-dom'; import { Layout } from '../Layout'; import { useGetAttractionDetailsByIdQuery } from '../Redux/services/attractions.service'; +<<<<<<< HEAD:src/components/AttractionDetailsPage.tsx import { ImageWithFallback } from './figma/ImageWithFallback'; import { Badge } from './ui/badge'; import { Button } from './ui/button'; import { Card, } from './ui/card'; +======= +import LoadingSpinner from '../components/LoadingSpinner'; +>>>>>>> cdadee5df40960af2263c2e5e6f4a8f234b7d5b0:src/pages/AttractionDetailsPage.tsx interface AttractionDetailsPageProps { onBackClick: () => void; @@ -30,7 +43,9 @@ export function AttractionDetailsPage({ const { data: attraction, isLoading } = useGetAttractionDetailsByIdQuery(Number(attractionId)); if (isLoading) { - return
loading...
+ return ( + + ); } return ( @@ -39,7 +54,7 @@ export function AttractionDetailsPage({ onSignInClick={onSignInClick} onSignOutClick={onSignOutClick} user={user} - // showCitySubmenu={false} + // showCitySubmenu={false} >
{/* Back Button */} @@ -81,7 +96,7 @@ export function AttractionDetailsPage({ {attraction.title} {' '} - Day Trip by {attraction.partner.businessName} + Day Trip by {attraction.partner.businessName}
@@ -98,10 +113,10 @@ export function AttractionDetailsPage({
{/* Gallery images */} - {attraction.attractionGalleries.slice().map((image:any) => ( + {attraction.attractionGalleries.slice().map((image: any) => (
@@ -254,131 +269,7 @@ export function AttractionDetailsPage({
{/* Right Sidebar - Calendar and Booking */} -
- {/* Calendar Widget with Custom Design */} - -
-

Select Date

-

Choose your preferred visit date

-
- - {/* Custom Calendar Design */} -
- {/* Calendar Header */} -
- - September 2025 - -
- - {/* Days of week */} -
-
Su
-
Mo
-
Tu
-
We
-
Th
-
Fr
-
Sa
-
- - {/* Calendar Grid */} -
- {/* Previous month */} - - - {/* Current month */} - {Array.from({ length: 30 }, (_, i) => { - const day = i + 1; - const isSelected = day === 27; - const isToday = day === 15; - return ( - - ); - })} - - {/* Next month */} - {Array.from({ length: 4 }, (_, i) => ( - - ))} -
-
- - {/* Selected Date Display */} -
-
- -
-

Selected Date

-

September 27, 2025

-
-
-
-
- - {/* Pricing Card */} - -
-
- Adult Ticket - {attraction.ticketPriceAdult} -
-
- Service Fee - $5 -
-
-
- Total - ${attraction.ticketPriceAdult + 5} -
-
-
-
- - {/* Confirm Booking Button */} - - - {/* Trust Indicators */} -
-
- - Instant Confirmation -
-
- - Free Cancellation -
-
-
+ diff --git a/src/components/AttractionsPage.tsx b/src/pages/AttractionsPage.tsx similarity index 59% rename from src/components/AttractionsPage.tsx rename to src/pages/AttractionsPage.tsx index 79ec7df..b8d3d8c 100644 --- a/src/components/AttractionsPage.tsx +++ b/src/pages/AttractionsPage.tsx @@ -2,14 +2,15 @@ import { useEffect, useState } from 'react'; import { motion } from 'motion/react'; import { Search, Star, Clock } from 'lucide-react'; import { useNavigate, useParams } from 'react-router-dom'; -import { Button } from './ui/button'; -import { Input } from './ui/input'; -import { Card, CardContent } from './ui/card'; -import { Badge } from './ui/badge'; -import { Checkbox } from './ui/checkbox'; -import { ImageWithFallback } from './figma/ImageWithFallback'; +import { Button } from '../components/ui/button'; +import { Input } from '../components/ui/input'; +import { Card, CardContent } from '../components/ui/card'; +import { Badge } from '../components/ui/badge'; +import { Checkbox } from '../components/ui/checkbox'; +import { ImageWithFallback } from '../components/figma/ImageWithFallback'; import { Layout } from '../Layout'; import { useGetAttractionFiltersQuery, useGetCustomerAttractionsQuery } from '../Redux/services/attractions.service'; +import LoadingSpinner from '../components/LoadingSpinner'; interface User { email: string; name: string; @@ -29,187 +30,6 @@ interface Attraction { passType: string; } -// { -// id: '1', -// name: 'Centipede Tour - Guided Arizona Desert Tour by ATV', -// description: 'Experience the thrill of off-road adventure through the stunning Arizona desert landscape', -// image: 'https://images.unsplash.com/photo-1682687220742-aba13b6e50ba?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHxhdHYlMjBkZXNlcnQlMjB0b3VyfGVufDF8fHx8MTc1ODEwNDg5Nnww&ixlib=rb-4.1.0&q=80&w=1080&utm_source=figma&utm_medium=referral', -// location: 'Paris, France', -// duration: '4 days', -// rating: 4.8, -// price: 189.25, -// category: 'adventure', -// hasReservation: true, -// reviewCount: 243, -// passType: 'unlimited' -// }, -// { -// id: '2', -// name: 'Molokini and Turtle Town Snorkeling Adventure Aboard', -// description: 'Snorkel in crystal-clear waters and swim alongside sea turtles in this unforgettable marine adventure', -// image: 'https://images.unsplash.com/photo-1559827260-dc66d52bef19?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHxzbm9ya2VsaW5nJTIwdHVydGxlJTIwYWR2ZW50dXJlfGVufDF8fHx8MTc1ODEwNDkwMHww&ixlib=rb-4.1.0&q=80&w=1080&utm_source=figma&utm_medium=referral', -// location: 'New York, USA', -// duration: '4 days', -// rating: 4.8, -// price: 225, -// category: 'adventure', -// hasReservation: false, -// reviewCount: 167, -// passType: 'selective' -// }, -// { -// id: '3', -// name: 'Westminster Walking Tour & Westminster Abbey Entry', -// description: 'Explore the heart of London with guided tours of historic Westminster and the famous Abbey', -// image: 'https://images.unsplash.com/photo-1533929736458-ca588d08c8be?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHx3ZXN0bWluc3RlciUyMGFiYmV5JTIwbG9uZG9ufGVufDF8fHx8MTc1ODEwNDkwNnww&ixlib=rb-4.1.0&q=80&w=1080&utm_source=figma&utm_medium=referral', -// location: 'London, UK', -// duration: '4 days', -// rating: 4.8, -// price: 343, -// category: 'culture', -// hasReservation: true, -// reviewCount: 343, -// passType: 'unlimited' -// }, -// { -// id: '4', -// name: 'All Inclusive Ultimate Circle Island Day Tour with Lunch', -// description: 'Comprehensive island tour including all major attractions, lunch, and transportation', -// image: 'https://images.unsplash.com/photo-1571019613454-1cb2f99b2d8b?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHxpc2xhbmQlMjB0b3VyJTIwYWRvJTIwdHJvcGljYWx8ZW58MXx8fHwxNzU4MTA0OTEwfDA&ixlib=rb-4.1.0&q=80&w=1080&utm_source=figma&utm_medium=referral', -// location: 'New York, USA', -// duration: '4 days', -// rating: 4.8, -// price: 225, -// category: 'adventure', -// hasReservation: false, -// reviewCount: 243, -// passType: 'unlimited' -// }, -// { -// id: '5', -// name: 'Space Center Houston Admission Ticket', -// description: 'Explore NASA\'s Johnson Space Center and discover the wonders of space exploration', -// image: 'https://images.unsplash.com/photo-1446776653964-20c1d3a81b06?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHxzcGFjZSUyMGNlbnRlciUyMG5hc2ElMjBob3VzdG9ufGVufDF8fHx8MTc1ODEwNDkxM3ww&ixlib=rb-4.1.0&q=80&w=1080&utm_source=figma&utm_medium=referral', -// location: 'Paris, France', -// duration: '4 days', -// rating: 4.8, -// price: 225, -// category: 'family', -// hasReservation: true, -// reviewCount: 243, -// passType: 'selective' -// }, -// { -// id: '6', -// name: 'Melbourne Skydeck Observatory', -// description: 'Experience breathtaking 360-degree views from the Southern Hemisphere\'s highest viewing platform', -// image: 'https://images.unsplash.com/photo-1677200922658-d0df5b2ac91e?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHxtZWxib3VybmUlMjBhdHRyYWN0aW9ucyUyMGZhbW91cyUyMGxhbmRtYXJrc3xlbnwxfHx8fDE3NTc0MDEwODV8MA&ixlib=rb-4.1.0&q=80&w=1080&utm_source=figma&utm_medium=referral', -// location: 'Melbourne CBD', -// duration: '2 hours', -// rating: 4.5, -// price: 25, -// category: 'adventure', -// hasReservation: true, -// reviewCount: 892, -// passType: 'selective' -// }, -// { -// id: '7', -// name: 'Royal Botanic Gardens Melbourne', -// description: 'Explore 38 hectares of stunning gardens featuring over 8,500 species of plants', -// image: 'https://images.unsplash.com/photo-1721272962395-a848331ce92d?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHxtZWxib3VybmUlMjByb3lhbCUyMGJvdGFuaWMlMjBnYXJkZW5zfGVufDF8fHx8MTc1NzMzNzc4OXww&ixlib=rb-4.1.0&q=80&w=1080&utm_source=figma&utm_medium=referral', -// location: 'South Yarra', -// duration: '3 hours', -// rating: 4.7, -// price: 0, -// category: 'nature', -// hasReservation: false, -// reviewCount: 1245, -// passType: 'selective' -// }, -// { -// id: '8', -// name: 'Federation Square Cultural Precinct', -// description: 'Melbourne\'s cultural precinct featuring galleries, museums, and unique architecture', -// image: 'https://images.unsplash.com/photo-1580688027085-8220709e3d84?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHxmZWRlcmF0aW9uJTIwc3F1YXJlJTIwbWVsYm91cm5lfGVufDF8fHx8MTc1NzQwMTA5Mnww&ixlib=rb-4.1.0&q=80&w=1080&utm_source=figma&utm_medium=referral', -// location: 'Melbourne CBD', -// duration: '3 hours', -// rating: 4.3, -// price: 0, -// category: 'culture', -// hasReservation: true, -// reviewCount: 672, -// passType: 'unlimited' -// }, -// { -// id: '9', -// name: 'St Kilda Pier & Little Penguins', -// description: 'Watch little penguins return home at sunset while enjoying the scenic pier', -// image: 'https://images.unsplash.com/photo-1597889790884-2bb63cfbd4f6?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHxzdCUyMGtpbGRhJTIwcGllciUyMG1lbGJvdXJuZXxlbnwxfHx8fDE3NTc0MDEwOTV8MA&ixlib=rb-4.1.0&q=80&w=1080&utm_source=figma&utm_medium=referral', -// location: 'St Kilda', -// duration: '2 hours', -// rating: 4.4, -// price: 0, -// category: 'nature', -// hasReservation: false, -// reviewCount: 543, -// passType: 'unlimited' -// }, -// { -// id: '10', -// name: 'Queen Victoria Market Experience', -// description: 'Historic market offering fresh produce, gourmet foods, and unique souvenirs', -// image: 'https://images.unsplash.com/photo-1676454953709-e0be46f62490?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHxxdWVlbiUyMHZpY3RvcmlhJTIwbWFya2V0JTIwbWVsYm91cm5lfGVufDF8fHx8MTc1NzQwMTA5OHww&ixlib=rb-4.1.0&q=80&w=1080&utm_source=figma&utm_medium=referral', -// location: 'Melbourne CBD', -// duration: '2 hours', -// rating: 4.6, -// price: 0, -// category: 'culture', -// hasReservation: true, -// reviewCount: 987, -// passType: 'selective' -// }, -// { -// id: '11', -// name: 'Melbourne Zoo Adventure', -// description: 'Meet over 320 animal species from around the world in naturalistic habitats', -// image: 'https://images.unsplash.com/photo-1681429477985-30dc7b88dd5b?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHxtZWxib3VybmUlMjB6b28lMjBhbmltYWxzfGVufDF8fHx8MTc1NzMzNzgxMHww&ixlib=rb-4.1.0&q=80&w=1080&utm_source=figma&utm_medium=referral', -// location: 'Parkville', -// duration: '4 hours', -// rating: 4.5, -// price: 40, -// category: 'family', -// hasReservation: false, -// reviewCount: 1156, -// passType: 'selective' -// }, -// { -// id: '12', -// name: 'Great Ocean Road Day Tour', -// description: 'Scenic coastal drive featuring the famous Twelve Apostles and stunning ocean views', -// image: 'https://images.unsplash.com/photo-1506905925346-21bda4d32df4?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHxncmVhdCUyMG9jZWFuJTIwcm9hZCUyMGF1c3RyYWxpYXxlbnwxfHx8fDE3NTgxMDQ5Mzd8MA&ixlib=rb-4.1.0&q=80&w=1080&utm_source=figma&utm_medium=referral', -// location: 'Great Ocean Road', -// duration: '12 hours', -// rating: 4.9, -// price: 85, -// category: 'adventure', -// hasReservation: true, -// reviewCount: 678, -// passType: 'unlimited' -// } -// ]; -// const filterCategories = [ -// { value: 'with-reservation', label: 'With Reservation', count: 3 }, -// { value: 'without-reservation', label: 'Without Reservation', count: 3 }, -// { value: 'beach', label: 'Beach', count: 3 }, -// { value: 'adventure', label: 'Adventure', count: 3 }, -// { value: 'mountains', label: 'Mountains', count: 3 }, -// { value: 'family', label: 'Family Friendly', count: 3 } -// ]; -// const passTypeCategories = [ -// { value: 'selective', label: 'Flexi Pass', count: 6 }, -// { value: 'unlimited', label: 'Unlimited Pass', count: 6 } -// ]; interface AttractionsPageProps { onSignInClick: () => void; onSignOutClick?: () => void; @@ -229,8 +49,11 @@ export function AttractionsPage({ const [selectedCategory, setSelectedCategory] = useState(null); const [selectedPassType, setSelectedPassType] = useState(null); - const cityId = 1 - + const cityId = localStorage.getItem("cityId") + const cityName = localStorage.getItem("cityName") + + console.log(cityName) + const { data: filterData, isLoading } = useGetAttractionFiltersQuery(cityId) const { data: attractions } = useGetCustomerAttractionsQuery({ cityId, // required @@ -239,9 +62,11 @@ export function AttractionsPage({ cardType: selectedPassType, // optional search, // optional }); - + if (isLoading) { - return
Loading...
+ return ( + + ); } const handleAttractionClick = (attractionId: string) => { @@ -254,7 +79,7 @@ export function AttractionsPage({ const showingFrom = 1; const showingTo = Math.min(12, attractions?.length); const totalItems = attractions?.length; - + function handlePassTypeSelection(key: string, checked: boolean) { if (checked) { setSelectedPassType(key); // only keep the newly selected one @@ -301,12 +126,12 @@ export function AttractionsPage({

Discover{' '} - Melbourne's{' '} + {cityName}'s{' '} Best{' '} Attractions

- Skip the lines and explore Melbourne's most iconic destinations with your CityCard pass + Skip the lines and explore {cityName}'s most iconic destinations with your CityCard pass

{/* City Card Promotional Banner */} @@ -403,7 +228,7 @@ export function AttractionsPage({ htmlFor={key} className="font-poppins text-sm text-[#414141] cursor-pointer flex-1 font-normal" > - {key==="selective_pass" ?"Selective":"Unlimited"} ({count as number}) + {key === "selective_pass" ? "Selective" : "Unlimited"} ({count as number}) ))} @@ -416,7 +241,7 @@ export function AttractionsPage({
{/* Header */}
-

Attractions in Melbourne

+

Attractions in {cityName}

{/* Results count */}

Showing {showingFrom}-{showingTo} of {totalItems} Item(s) @@ -493,7 +318,7 @@ export function AttractionsPage({

+

+ ✓ Free cancellation up to 24 hours • Instant delivery +

+
+ + + + + {/* Unlimited Pass Card */} +
+ setSelectedPass(passTypes[1].id)} + + > + {passTypes[1].popular && ( +
+ + Most Popular + +
+ )} + +
+ {/* */} +
+ + + + {cards[1].title} + + + {cards[1].description} + + + + {/* Pricing */} +
+
+ ${cards[1].adultPrice} + / {passTypes[1].period} +
+
+ {cards[1].adultPrice && ( +
+ {/* Strikethrough price = originalPrice + $5 */} + + ${parseFloat(cards[1].adultPrice) + 5} + + + Save{" "} + {Math.round( + ((5) / (parseFloat(cards[1].adultPrice) + 5)) * 100 + )} + % +
-
+ )} +
+
- {/* CTA Button - Pushed to bottom */} -
- - -

- ✓ Free cancellation up to 24 hours • Instant delivery -

+ +
+
+ {passTypes[1].features.map((feature, index) => ( +
+ + {feature} +
+ ))}
- - -
- ))} +
+ +
+ +

+ ✓ Free cancellation up to 24 hours • Instant delivery +

+
+ + + + {/* Good to Know Section */}
diff --git a/src/components/PostCardsPage.tsx b/src/pages/PostCardsPage.tsx similarity index 91% rename from src/components/PostCardsPage.tsx rename to src/pages/PostCardsPage.tsx index 3eb05f6..2228d7f 100644 --- a/src/components/PostCardsPage.tsx +++ b/src/pages/PostCardsPage.tsx @@ -1,19 +1,19 @@ import React from 'react'; import { motion } from 'motion/react'; import { ArrowLeft, Camera, Edit3, Upload, Heart, Star, Download, Share2 } from 'lucide-react'; -import { Button } from './ui/button'; -import { Card, CardContent } from './ui/card'; -import { Badge } from './ui/badge'; -import Navbar from './Navbar'; +import { Button } from '../components/ui/button'; +import { Card, CardContent } from '../components/ui/card'; +import { Badge } from '../components/ui/badge'; +import Navbar from '../components/Navbar'; // import SubNavbar from './SubNavbar'; -import { Footer } from './Footer'; -import { MobileAppSection } from './MobileAppSection'; -import { EnhancedTestimonials } from './EnhancedTestimonials'; -import { CustomPostcards } from './CustomPostcards'; -import { HowItWorks } from './HowItWorks'; -import { ImageWithFallback } from './figma/ImageWithFallback'; +import { Footer } from '../components/Footer'; +import { MobileAppSection } from '../components/MobileAppSection'; +import { EnhancedTestimonials } from '../components/EnhancedTestimonials'; +import { CustomPostcards } from '../components/CustomPostcards'; +import { HowItWorks } from '../components/HowItWorks'; +import { ImageWithFallback } from '../components/figma/ImageWithFallback'; import { Layout } from '../Layout'; -import front from '../assets/front.jpg' +// import front from '../assets/front.jpg' interface User { @@ -89,7 +89,7 @@ export function PostCardsPage({
*/} - < img src={front} alt='Postcard image' /> + {/* < img src={front} alt='Postcard image' /> */}
{/* Decorative elements */} diff --git a/src/components/PrivacyPolicyPage.tsx b/src/pages/PrivacyPolicyPage.tsx similarity index 97% rename from src/components/PrivacyPolicyPage.tsx rename to src/pages/PrivacyPolicyPage.tsx index 5117319..9787bc2 100644 --- a/src/components/PrivacyPolicyPage.tsx +++ b/src/pages/PrivacyPolicyPage.tsx @@ -1,13 +1,13 @@ import { useState, useEffect } from 'react'; import { motion, useScroll, useTransform } from 'motion/react'; import { ArrowLeft } from 'lucide-react'; -import Navbar from './Navbar'; +import Navbar from '../components/Navbar'; // import { CitySubmenu } from './CitySubmenu'; -import { Footer } from './Footer'; -import { MobileAppSection } from './MobileAppSection'; -import { WhyChooseCityCards } from './WhyChooseCityCards'; -import { EnhancedTestimonials } from './EnhancedTestimonials'; -import { ReviewsSection } from './ReviewsSection'; +import { Footer } from '../components/Footer'; +import { MobileAppSection } from '../components/MobileAppSection'; +import { WhyChooseCityCards } from '../components/WhyChooseCityCards'; +import { EnhancedTestimonials } from '../components/EnhancedTestimonials'; +import { ReviewsSection } from '../components/ReviewsSection'; interface User { email: string; diff --git a/src/components/ProfilePage.tsx b/src/pages/ProfilePage.tsx similarity index 87% rename from src/components/ProfilePage.tsx rename to src/pages/ProfilePage.tsx index 02842d6..2295a7d 100644 --- a/src/components/ProfilePage.tsx +++ b/src/pages/ProfilePage.tsx @@ -1,11 +1,11 @@ -import { useState } from 'react'; +import { useEffect, useState } from 'react'; import { motion } from 'motion/react'; -import { - ArrowLeft, - User, - CreditCard, - Calendar, - MapPin, +import { + ArrowLeft, + User, + CreditCard, + Calendar, + MapPin, Settings, Download, QrCode, @@ -15,17 +15,21 @@ import { Badge as BadgeIcon, Camera } from 'lucide-react'; -import { Button } from './ui/button'; -import { Input } from './ui/input'; -import { Label } from './ui/label'; -import { Card, CardContent, CardHeader, CardTitle } from './ui/card'; -import { Separator } from './ui/separator'; -import { Tabs, TabsContent, TabsList, TabsTrigger } from './ui/tabs'; -import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from './ui/select'; -import { Badge } from './ui/badge'; -import Navbar from './Navbar'; -import { Footer } from './Footer'; -import { ImageWithFallback } from './figma/ImageWithFallback'; +import { Button } from '../components/ui/button'; +import { Input } from '../components/ui/input'; +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'; +import { ImageWithFallback } from '../components/figma/ImageWithFallback'; +import { useGetUserProfileDetailsQuery, useUpdateUserProfileDetailsMutation } from '../Redux/services/profile.service'; +import { toast } from 'sonner'; +import { useNavigate } from 'react-router-dom'; +import LoadingSpinner from '../components/LoadingSpinner'; interface ProfilePageProps { onBackClick: () => void; @@ -55,18 +59,6 @@ interface ProfilePageProps { currentPage: string; } -// Mock user data -const mockUserData = { - firstName: 'John', - lastName: 'Doe', - email: 'john.doe@example.com', - phone: '+1 (555) 123-4567', - country: 'us', - address: '123 Main Street', - city: 'New York', - postalCode: '10001' -}; - // Mock passes data const mockPasses = [ { @@ -86,7 +78,7 @@ const mockPasses = [ usedAttractions: 8 }, { - id: '2', + id: '2', name: 'Melbourne Selective Card', city: 'Melbourne', type: 'Flexi Pass', @@ -160,55 +152,87 @@ export function ProfilePage({ currentPage }: ProfilePageProps) { const [activeTab, setActiveTab] = useState('profile'); - const [formData, setFormData] = useState(mockUserData); + const [formData, setFormData] = useState({ + firstName: '', + lastName: '', + email: '', + phone: '', + country: '', + address1: '', + address2: '', + city: '', + postalCode: '' + }); + + const navigate = useNavigate() + const userId = localStorage.getItem("userId") + const { data: userDetails, isLoading } = useGetUserProfileDetailsQuery(userId) + const [updateUserProfileDetails, { isLoading: savingChanges }] = useUpdateUserProfileDetailsMutation(); + const { data: passes, isLoading: loadingPasses } = useGetUserProfileDetailsQuery(userId) + + useEffect(() => { + if (userDetails) { + setFormData({ + firstName: userDetails?.firstName, + lastName: userDetails?.lastName, + email: userDetails?.emailAddress, + phone: userDetails?.mobileNumber, + country: userDetails?.country, + address1: userDetails?.address1, + address2: userDetails?.address2, + city: userDetails?.cityName, + postalCode: userDetails?.zipCode + }) + } + + }, [userDetails]) + const handleInputChange = (field: string, value: string) => { setFormData(prev => ({ ...prev, [field]: value })); }; - const handleSaveProfile = () => { - console.log('Saving profile...', formData); - // Handle profile save + 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."); + } }; const activePasses = mockPasses.filter(pass => pass.status === 'active'); const expiredPasses = mockPasses.filter(pass => pass.status === 'expired'); + if (isLoading && loadingPasses) { + return ( + + ); + } + return (
{/* Navbar */} - {}} - onHomeClick={onHomeClick} - onMelbourneClick={onMelbourneClick} - onPassesClick={onPassesClick} - onCheckoutClick={onCheckoutClick} onSignInClick={onSignInClick} onSignOutClick={onSignOutClick} - onAttractionsClick={onAttractionsClick} - onBlogsClick={onBlogsClick} - onHowItWorksClick={onHowItWorksClick} - onFAQClick={onFAQClick} - onPrivacyPolicyClick={onPrivacyPolicyClick} - onAboutUsClick={onAboutUsClick} - onProfileClick={onProfileClick} - onCityCardsClick={onCityCardsClick} - onMagicItineraryClick={onMagicItineraryClick} - onPostCardsClick={onPostCardsClick} - onOffersClick={onOffersClick} - currentPage={currentPage} isUserSignedIn={true} user={{ email: "user@example.com", name: "John Doe" }} - /> + onCityChange={function (city: string): void { + throw new Error('Function not implemented.'); + }} /> {/* Header Section */}
{/* Back Button */} navigate(-1)} + className="flex items-center gap-2 text-gray-600 hover:text-gray-900 mb-6 transition-colors duration-200 cursor-pointer" initial={{ opacity: 0, x: -20 }} animate={{ opacity: 1, x: 0 }} transition={{ duration: 0.5 }} @@ -225,7 +249,7 @@ export function ProfilePage({ >

My{' '} - Profile + Profile

Manage your account, passes, and travel itineraries @@ -312,7 +336,7 @@ export function ProfilePage({

- handleInputChange('country', value)}> @@ -326,15 +350,33 @@ export function ProfilePage({ India Japan - + */} + handleInputChange('country', e.target.value)} + className="mt-1 font-poppins font-light" + />
- + handleInputChange('address', e.target.value)} + id="address1" + value={formData.address1} + onChange={(e) => handleInputChange('address1', e.target.value)} + className="mt-1 font-poppins font-light mb-4" + /> + + + handleInputChange('address2', e.target.value)} className="mt-1 font-poppins font-light" />
@@ -362,9 +404,9 @@ export function ProfilePage({ @@ -384,7 +426,7 @@ export function ProfilePage({ // Determine which pass type to show const hasUnlimitedPass = activePasses.some(pass => pass.type === 'Unlimited Pass'); const hasSelectivePass = activePasses.some(pass => pass.type === 'Flexi Pass'); - + if (hasUnlimitedPass) { return ( <> @@ -394,7 +436,7 @@ export function ProfilePage({ Melbourne Unlimited Card

- Unlimited access to 25+ attractions. Visit as many places as you want with one simple card. + Unlimited access to 25+ attractions. Visit as many places as you want with one simple card. Save up to 40% compared to individual tickets.

@@ -423,13 +465,13 @@ export function ProfilePage({ {/* Purchase CTA */}
- -
@@ -478,13 +520,13 @@ export function ProfilePage({ {/* Purchase CTA */}
- -
@@ -535,13 +577,13 @@ export function ProfilePage({ {/* Purchase CTA */}
- -
- +
Attractions: @@ -616,10 +658,10 @@ export function ProfilePage({ ))}
- + {/* Offers Button */}
-
- +
Attractions visited: @@ -689,7 +731,7 @@ export function ProfilePage({ >

My Itineraries

-
- +
@@ -728,8 +770,8 @@ export function ProfilePage({
-
{/* Footer */} -