make the card comparison section in selected city page dynamic
This commit is contained in:
@@ -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;
|
||||
@@ -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">
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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 }}
|
||||
|
||||
Reference in New Issue
Block a user