make the card comparison section in selected city page dynamic

This commit is contained in:
aryabenade
2026-04-15 11:07:13 +05:30
parent dbdf974475
commit 743462d088
4 changed files with 137 additions and 70 deletions

View File

@@ -23,8 +23,12 @@ export const citiesApi = createApi({
query: (listType) => `/cities/list/all?listType=${listType}`,
}),
getSelectedCityDetails:builder.query({
query: (cityId) => `/website/${cityId}`,
})
}),
});
export const { useGetCityListWithBannerQuery,useGetUpcomingCitiesQuery } = citiesApi;
export const { useGetCityListWithBannerQuery,useGetUpcomingCitiesQuery,useGetSelectedCityDetailsQuery } = citiesApi;

View File

@@ -3,52 +3,52 @@ import { Check, X, Star, Users, MapPin, Calendar, Clock, Zap, Eye } from 'lucide
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<string>('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
</div>
);
}
return (
<div className="text-center text-sm text-gray-600 px-2">
{value}
@@ -122,17 +170,17 @@ export function MelbourneCardComparison({ onCheckoutClick }: MelbourneCardCompar
Choose Your Adventure
</span>
</div>
<h2 className="font-merchant text-4xl md:text-5xl lg:text-6xl text-gray-900 mb-6">
<span className="font-bold bg-gradient-to-r from-primary to-secondary bg-clip-text text-transparent italic pr-2">
Buy
</span>{' '}
<span className="font-normal">Now</span>
</h2>
<p className="text-xl text-gray-600 max-w-4xl mx-auto leading-relaxed">
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.
</p>
</motion.div>
@@ -179,7 +227,7 @@ export function MelbourneCardComparison({ onCheckoutClick }: MelbourneCardCompar
<FeatureIcon feature={feature} />
<span className="font-medium text-gray-900">{feature.label}</span>
</div>
{cardOptions.map((card) => (
<div key={card.id} className="text-center">
{renderFeatureValue(card.features[feature.key as keyof typeof card.features], card.id)}
@@ -196,7 +244,7 @@ export function MelbourneCardComparison({ onCheckoutClick }: MelbourneCardCompar
<div className="font-medium text-gray-600 text-sm mb-2">Ready to explore?</div>
<div className="text-xs text-gray-500">Compare features above</div>
</div>
{cardOptions.map((card) => (
<motion.div key={card.id} className="text-center">
<div className="mb-4">

View File

@@ -91,6 +91,10 @@ export default function Navbar({
const { user, login, logout } = useAuth(); // from AuthContext
const cityLogo = sessionStorage.getItem("cityLogo") || ""
const baseUrl = import.meta.env.VITE_BASE_URL;
const protectedPaths = ["/passes", "/whats-included", "/", "/melbourne"];
const handleOpenLoginModal = () => {
@@ -563,8 +567,7 @@ export default function Navbar({
>
<Link to={currentSource === 'melbourne' ? '/' : '/'}>
<ImageWithFallback
src={currentSource === 'melbourne' ? melbourneLogo : logoImage}
alt={
src={`${baseUrl}${cityLogo}`} alt={
currentSource === 'melbourne'
? 'Melbourne CityCards Logo'
: 'CityCards Logo'
@@ -575,7 +578,7 @@ export default function Navbar({
</motion.div>
<div className="absolute -translate-x-1/2 flex items-center gap-5"
style={{ left: '45%', }}
style={{ left: '42%', }}
>
{/* Enhanced Navigation Items with source tracking */}
{navigationItems.map((item) => {
@@ -625,12 +628,13 @@ export default function Navbar({
onClick={handleOpenCityDialogFromNavbar}
>
<span>
{!activeCity || activeCity === 'shared'
{/* {!activeCity || activeCity === 'shared'
? 'City'
: ['landing', 'landingpage'].includes(activeCity.toLowerCase())
? 'City'
: activeCity.charAt(0).toUpperCase() + activeCity.slice(1)
}
} */}
{cityName ? cityName : "City"}
</span>
<ChevronDown className="w-3.5 h-3.5" />
@@ -786,10 +790,11 @@ export default function Navbar({
trigger={
<div className="flex items-center space-x-1 text-gray-700 hover:text-gray-900 text-sm font-medium transition-colors duration-200 cursor-pointer rounded-lg hover:bg-gray-50/50 px-2 py-1">
<span>
{activeCity && activeCity !== 'shared' ?
{/* {activeCity && activeCity !== 'shared' ?
activeCity.charAt(0).toUpperCase() + activeCity.slice(1) :
currentSource === 'melbourne' ? 'Melbourne' : 'Select City'
}
} */}
{cityName ? cityName : "City"}
</span>
<ChevronDown className={`w-3.5 h-3.5 transition-transform duration-200 ${activeCityDropdown ? 'rotate-180' : ''}`} />
</div>

View File

@@ -1,8 +1,7 @@
import { motion, useAnimationControls, AnimatePresence } from 'motion/react';
import { Button } from '../components/ui/button';
import { ArrowRight, Calendar, Thermometer, Eye, MapPin, Clock, Users, Ticket, Wand2, Plane, Sparkles } from 'lucide-react';
import { useEffect, useRef, useState } from 'react';
import Navbar from '../components/Navbar';
import { useState } from 'react';
import { ImageWithFallback } from '../components/figma/ImageWithFallback';
import { MelbourneAttractions } from '../components/MelbourneAttractions';
import { MelbourneCardComparison } from '../components/MelbourneCardComparison';
@@ -12,11 +11,10 @@ import { CustomPostcards } from '../components/CustomPostcards';
import { EnhancedTestimonials } from '../components/EnhancedTestimonials';
import { MobileAppPromotion } from '../components/MobileAppPromotion';
import { MelbourneFAQ } from '../components/MelbourneFAQ';
import { Footer } from '../components/Footer';
// import { MinimalHeroBanner } from './MinimalHeroBanner';
import { Layout } from '../Layout';
import { HeroBannerCarousel } from '../components/HeroBannerCarousel';
import { HotelEsimOffers } from '../components/HotelEsimOffers';
import { useGetSelectedCityDetailsQuery } from '../Redux/services/cities.service';
interface User {
email: string;
@@ -149,6 +147,18 @@ export function MelbournePage({
const [currentCardIndex, setCurrentCardIndex] = useState(0);
const [isAnimating, setIsAnimating] = useState(false);
const cityId = localStorage.getItem("cityId")
const { data: cityDetails, isLoading: loadingCityDetails } = useGetSelectedCityDetailsQuery(cityId)
if (loadingCityDetails) {
return <div>Loading...</div>
}
const cards = cityDetails?.city?.cards
sessionStorage.setItem("cityLogo", String(cityDetails?.city?.cityIconPath))
const currentCard = itineraryCards[currentCardIndex];
const nextCard = itineraryCards[(currentCardIndex + 1) % itineraryCards.length];
const thirdCard = itineraryCards[(currentCardIndex + 2) % itineraryCards.length];
@@ -257,12 +267,12 @@ export function MelbournePage({
{/* Attractions Section */}
<div id="attractions" className="scroll-mt-32">
<MelbourneAttractions />
<MelbourneAttractions />
</div>
{/* Pass Comparison */}
<div id="passes" className="scroll-mt-32">
<MelbourneCardComparison />
<MelbourneCardComparison cards={cards} />
</div>
{/* Tour Overview */}
@@ -740,8 +750,8 @@ export function MelbournePage({
}, 400);
}}
className={`font-poppins group relative transition-all duration-300 px-4 py-2 rounded-full font-medium ${idx === currentCardIndex
? 'bg-gradient-to-r from-primary to-orange-500 text-white shadow-lg scale-110'
: 'bg-white/80 backdrop-blur-sm text-gray-600 hover:text-primary hover:bg-white border border-gray-200 hover:border-primary/30 hover:scale-105'
? 'bg-gradient-to-r from-primary to-orange-500 text-white shadow-lg scale-110'
: 'bg-white/80 backdrop-blur-sm text-gray-600 hover:text-primary hover:bg-white border border-gray-200 hover:border-primary/30 hover:scale-105'
}`}
whileHover={{ y: -2 }}
whileTap={{ scale: 0.95 }}