show cards on the passes page coming from backend

This commit is contained in:
aryabenade
2026-04-17 14:45:03 +05:30
parent a7098e81d6
commit ce3c095727
4 changed files with 184 additions and 122 deletions

View File

@@ -106,7 +106,7 @@ export function AppRouter({
} />
{/* Attractions Routes */}
<Route path="/:cityName/attractions" element={
<Route path="/attractions" element={
<motion.div key="attractions" {...pageTransition}>
<AttractionsPage {...commonNavHandlers} />
</motion.div>
@@ -265,7 +265,7 @@ export function AppRouter({
</motion.div>
} />
<Route path="/:cityName/super-savings" element={
<Route path="/super-savings" element={
<motion.div key="super-savings" {...pageTransition}>
<SuperSavingsPage {...commonNavHandlers} />
</motion.div>

View File

@@ -162,25 +162,25 @@ export default function Navbar({
// Position 1
{
label: 'Attractions',
path: `/${slugify(cityName)}/attractions`,
path: `/attractions`,
isShared: false
},
// Position 2
{
label: 'Magic Itinerary',
path: `/${slugify(cityName)}/magic-itinerary`,
path: `/magic-itinerary`,
isShared: false
},
// Position 3
{
label: 'Super Savings',
path: `/${slugify(cityName)}/super-savings`,
path: `/super-savings`,
isShared: false
},
// Position 4 - Shared item
{
label: 'How It Works',
path: `/${slugify(cityName)}/how-it-works`,
path: `/how-it-works`,
isShared: true,
landingLabel: 'Discover',
melbourneLabel: 'How It Works'
@@ -188,14 +188,14 @@ export default function Navbar({
// Position 5 - Shared item
{
label: 'Your Card',
path: `/${slugify(cityName)}/passes`,
path: `/passes`,
isShared: true,
landingLabel: 'Your Card',
melbourneLabel: 'Your Card'
},
{
label: 'Your Postcard',
path: `/${slugify(cityName)}/postcards`,
path: `/postcards`,
isShared: true,
landingLabel: 'Your Postcard',
melbourneLabel: 'Your Postcard'

View File

@@ -12,8 +12,6 @@ import { Checkbox } from '../components/ui/checkbox';
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '../components/ui/select';
import { Badge } from '../components/ui/badge';
import { Textarea } from '../components/ui/textarea';
import Navbar from './Navbar';
import { Footer } from './Footer';
import { ImageWithFallback } from '../components/figma/ImageWithFallback';
import { Layout } from '../Layout';

View File

@@ -11,6 +11,8 @@ import { LoginModal } from '../components/LoginModal';
import { ImageWithFallback } from '../components/figma/ImageWithFallback';
import { useAuth } from '../context/AuthContext';
import { useNavigate } from 'react-router-dom';
import { useGetSelectedCityDetailsQuery } from '../Redux/services/cities.service';
import LoadingSpinner from '../components/LoadingSpinner';
interface PassesPageProps {
onCheckoutClick?: () => void;
@@ -148,21 +150,29 @@ export function PassesPage({
onSignInClick,
onSignOutClick,
}: PassesPageProps) {
const [selectedPass, setSelectedPass] = useState<string>('unlimited');
const [selectedPass, setSelectedPass] = useState<string>(passTypes[1].id);
const [isLoginOpen, setIsLoginOpen] = useState(false);
const { user } = useAuth(); // from AuthContext
const navigate= useNavigate()
const navigate = useNavigate()
const cityId = localStorage.getItem("cityId")
const { data: cityDetails, isLoading: loadingCityDetails } = useGetSelectedCityDetailsQuery(cityId)
const cards = cityDetails?.city?.cards ?? []
console.log(cards)
if (loadingCityDetails) {
return (<LoadingSpinner />)
}
const handleCheckoutClick = () => {
const handleCheckoutClick = () => {
console.log('Proceeding to checkout for user:', user);
// Add your checkout logic here
navigate('/checkout');
};
const handleSignInClick = () => {
const handleSignInClick = () => {
setIsLoginOpen(true);
};
@@ -189,7 +199,7 @@ export function PassesPage({
<div className="text-center mb-16">
<div className="mb-6">
<h1 className="font-merchant font-light text-4xl md:text-5xl lg:text-6xl mb-4">
Buy <span className="font-bold italic bg-gradient-to-r from-primary to-secondary bg-clip-text text-transparent pr-1.5">Passes</span>
Buy <span className="font-bold italic bg-gradient-to-r from-primary to-secondary bg-clip-text text-transparent pr-1.5">Cards</span>
</h1>
<p className="font-poppins text-xl leading-relaxed font-normal text-gray-600 max-w-3xl mx-auto">
Skip the lines, save money, and explore more with our flexible city cards designed for modern travelers
@@ -200,130 +210,184 @@ export function PassesPage({
{/* Pass Comparison Section */}
<div className="mb-20">
<RadioGroup
value={selectedPass}
onValueChange={setSelectedPass}
className="grid md:grid-cols-2 gap-8 max-w-6xl mx-auto"
>
{passTypes.map((pass) => (
<div key={pass.id} className="relative h-full">
<Card className={`relative h-full flex flex-col transition-all duration-300 ${pass.popular
? 'ring-2 ring-primary shadow-xl'
: selectedPass === pass.id
? 'ring-2 ring-primary/50 shadow-lg'
: 'border-gray-200 shadow-md hover:shadow-lg hover:border-primary/30'
}`}>
{/* Flexi Pass Card */}
<div className="relative h-full">
<Card
className={`relative h-full flex flex-col transition-all duration-300 cursor-pointer ${selectedPass === passTypes[0].id
? "ring-2 ring-red-500 shadow-lg" // 🔴 red border when selected
: "border-gray-200 shadow-md hover:shadow-lg hover:border-primary/30"
}`}
onClick={() => setSelectedPass(passTypes[0].id)}
>
<div className="absolute top-5 right-5 z-10">
{/* <RadioGroupItem value={passTypes[0].id} id={passTypes[0].id} className="w-5 h-5" /> */}
</div>
{/* Popular Badge */}
{pass.popular && (
<div className="absolute -top-3 left-1/2 transform -translate-x-1/2 z-10">
<Badge className="bg-gradient-to-r from-yellow-400 to-orange-500 text-black px-6 py-1.5 font-semibold shadow-lg font-poppins">
Most Popular
</Badge>
</div>
)}
<CardHeader className="text-center pb-4 pt-8 flex-shrink-0">
<CardTitle className="font-merchant text-2xl leading-tight mb-3 text-gray-900">
{cards[0].title}
</CardTitle>
<CardDescription className="font-poppins text-sm text-gray-600 leading-relaxed font-normal min-h-[48px] flex items-center justify-center px-4">
{cards[0].description}
</CardDescription>
</CardHeader>
{/* Radio Button */}
<div className="absolute top-5 right-5 z-10">
<RadioGroupItem value={pass.id} id={pass.id} className="w-5 h-5" />
{/* Pricing */}
<div className="px-6 pb-6 flex-shrink-0">
<div className="flex items-baseline justify-center gap-2 mb-2">
<span className="text-5xl font-bold text-gray-900 font-poppins">
${cards[0].adultPrice}
</span>
<span className="text-gray-500 font-poppins text-base">
/ {passTypes[0].period}
</span>
</div>
{/* Header - Fixed Height */}
<CardHeader className="text-center pb-4 pt-8 flex-shrink-0">
<CardTitle className="font-merchant text-2xl leading-tight mb-3 text-gray-900">
{pass.title}
</CardTitle>
<CardDescription className="font-poppins text-sm text-gray-600 leading-relaxed font-normal min-h-[48px] flex items-center justify-center px-4">
{pass.description}
</CardDescription>
</CardHeader>
{/* Attraction Images Grid */}
<div className="px-6 pb-4 flex-shrink-0">
<div className="grid grid-cols-4 gap-3">
<div className="aspect-square rounded-xl overflow-hidden shadow-md hover:shadow-lg transition-all duration-300 ring-1 ring-gray-200 hover:ring-primary/50 group">
<ImageWithFallback
src="https://images.unsplash.com/photo-1639655001512-e4b58d4874b8?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHxNZWxib3VybmUlMjBGZWRlcmF0aW9uJTIwU3F1YXJlfGVufDF8fHx8MTc2MjQyMzkwMHww&ixlib=rb-4.1.0&q=80&w=1080&utm_source=figma&utm_medium=referral"
alt="Federation Square"
className="w-full h-full object-cover group-hover:scale-105 group-hover:brightness-110 transition-all duration-500"
/>
<div className="h-5 flex items-center justify-center">
{cards[0].adultPrice && (
<div className="text-sm text-gray-500 font-poppins">
{/* Strikethrough price = originalPrice + $5 */}
<span className="line-through mr-2">
${parseFloat(cards[0].adultPrice) + 5}
</span>
<span className="text-green-600 font-medium">
Save{" "}
{Math.round(
((5) / (parseFloat(cards[0].adultPrice) + 5)) * 100
)}
%
</span>
</div>
<div className="aspect-square rounded-xl overflow-hidden shadow-md hover:shadow-lg transition-all duration-300 ring-1 ring-gray-200 hover:ring-primary/50 group">
<ImageWithFallback
src="https://images.unsplash.com/photo-1721272962395-a848331ce92d?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHxNZWxib3VybmUlMjBSb3lhbCUyMEJvdGFuaWMlMjBHYXJkZW5zfGVufDF8fHx8MTc2MjQyMzk2NHww&ixlib=rb-4.1.0&q=80&w=1080&utm_source=figma&utm_medium=referral"
alt="Royal Botanic Gardens"
className="w-full h-full object-cover group-hover:scale-105 group-hover:brightness-110 transition-all duration-500"
/>
</div>
<div className="aspect-square rounded-xl overflow-hidden shadow-md hover:shadow-lg transition-all duration-300 ring-1 ring-gray-200 hover:ring-primary/50 group">
<ImageWithFallback
src="https://images.unsplash.com/photo-1720044109127-0aee490512be?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHxNZWxib3VybmUlMjBFdXJla2ElMjBTa3lkZWNrfGVufDF8fHx8MTc2MjQyMzk2NXww&ixlib=rb-4.1.0&q=80&w=1080&utm_source=figma&utm_medium=referral"
alt="Eureka Skydeck"
className="w-full h-full object-cover group-hover:scale-105 group-hover:brightness-110 transition-all duration-500"
/>
</div>
<div className="aspect-square rounded-xl overflow-hidden shadow-md hover:shadow-lg transition-all duration-300 ring-1 ring-gray-200 hover:ring-primary/50 group">
<ImageWithFallback
src="https://images.unsplash.com/photo-1705464079585-0975f0aa5013?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHxNZWxib3VybmUlMjBOYXRpb25hbCUyMEdhbGxlcnl8ZW58MXx8fHwxNzYyNDIzOTY0fDA&ixlib=rb-4.1.0&q=80&w=1080&utm_source=figma&utm_medium=referral"
alt="National Gallery"
className="w-full h-full object-cover group-hover:scale-105 group-hover:brightness-110 transition-all duration-500"
/>
</div>
</div>
)}
</div>
</div>
{/* Pricing Section - Fixed Height */}
<div className="px-6 pb-6 flex-shrink-0">
<div className="flex items-baseline justify-center gap-2 mb-2">
<span className="text-5xl font-bold text-gray-900 font-poppins">{pass.price}</span>
<span className="text-gray-500 font-poppins text-base">/ {pass.period}</span>
</div>
<div className="h-5 flex items-center justify-center">
{pass.originalPrice && (
<div className="text-sm text-gray-500 font-poppins">
<span className="line-through mr-2">{pass.originalPrice}</span>
<span className="text-green-600 font-medium">Save {Math.round(((parseFloat(pass.originalPrice.slice(1)) - parseFloat(pass.price.slice(1))) / parseFloat(pass.originalPrice.slice(1))) * 100)}%</span>
<CardContent className="pt-0 pb-6 px-6 flex-grow flex flex-col">
<div className="flex-grow mb-6">
<div className="space-y-3">
{passTypes[0].features.map((feature, index) => (
<div key={index} className="flex items-start gap-3">
<Check className="w-4 h-4 text-green-500 mt-1 flex-shrink-0" />
<span className="text-sm text-gray-700 font-poppins leading-relaxed font-normal">{feature}</span>
</div>
)}
))}
</div>
</div>
{/* Content - Flexible Height with Fixed Features Area */}
<CardContent className="pt-0 pb-6 px-6 flex-grow flex flex-col">
{/* Features List - Fixed height */}
<div className="flex-grow mb-6">
<div className="space-y-3">
{pass.features.slice(0, 6).map((feature, index) => (
<div key={index} className="flex items-start gap-3">
<Check className="w-4 h-4 text-green-500 mt-1 flex-shrink-0" />
<span className="text-sm text-gray-700 font-poppins leading-relaxed font-normal">{feature}</span>
</div>
))}
<div className="flex-shrink-0 space-y-3">
<Button
className={`w-full h-12 rounded-lg font-semibold transition-all cursor-pointer duration-300 font-poppins ${selectedPass === passTypes[0].id
? "bg-primary hover:bg-primary/90 text-white hover:shadow-lg"
: "bg-gray-400 hover:bg-gray-400 text-white hover:shadow-md"
}`}
onClick={user ? handleCheckoutClick : handleSignInClick}
disabled={selectedPass !== passTypes[0].id}
>
{user ? 'PURCHASE NOW' : 'LOGIN TO BUY PASS'}
</Button>
<p className="text-xs text-gray-500 text-center font-poppins font-normal leading-tight">
Free cancellation up to 24 hours Instant delivery
</p>
</div>
</CardContent>
</Card>
</div>
{/* Unlimited Pass Card */}
<div className="relative h-full">
<Card
className={`relative h-full flex flex-col transition-all duration-300 cursor-pointer ${selectedPass === passTypes[1].id
? "ring-2 ring-red-500 shadow-lg" // 🔴 red border when selected
: "border-gray-200 shadow-md hover:shadow-lg hover:border-primary/30"
}`}
onClick={() => setSelectedPass(passTypes[1].id)}
>
{passTypes[1].popular && (
<div className="absolute -top-3 left-1/2 transform -translate-x-1/2 z-10">
<Badge className="bg-gradient-to-r from-yellow-400 to-orange-500 text-black px-6 py-1.5 font-semibold shadow-lg font-poppins">
Most Popular
</Badge>
</div>
)}
<div className="absolute top-5 right-5 z-10">
{/* <RadioGroupItem value={passTypes[1].id} id={passTypes[1].id} className="w-5 h-5" /> */}
</div>
<CardHeader className="text-center pb-4 pt-8 flex-shrink-0">
<CardTitle className="font-merchant text-2xl leading-tight mb-3 text-gray-900">
{cards[1].title}
</CardTitle>
<CardDescription className="font-poppins text-sm text-gray-600 leading-relaxed font-normal min-h-[48px] flex items-center justify-center px-4">
{cards[1].description}
</CardDescription>
</CardHeader>
{/* Pricing */}
<div className="px-6 pb-6 flex-shrink-0">
<div className="flex items-baseline justify-center gap-2 mb-2">
<span className="text-5xl font-bold text-gray-900 font-poppins">${cards[1].adultPrice}</span>
<span className="text-gray-500 font-poppins text-base">/ {passTypes[1].period}</span>
</div>
<div className="h-5 flex items-center justify-center">
{cards[1].adultPrice && (
<div className="text-sm text-gray-500 font-poppins">
{/* Strikethrough price = originalPrice + $5 */}
<span className="line-through mr-2">
${parseFloat(cards[1].adultPrice) + 5}
</span>
<span className="text-green-600 font-medium">
Save{" "}
{Math.round(
((5) / (parseFloat(cards[1].adultPrice) + 5)) * 100
)}
%
</span>
</div>
</div>
)}
</div>
</div>
{/* CTA Button - Pushed to bottom */}
<div className="flex-shrink-0 space-y-3">
<Button
className={`w-full h-12 rounded-lg font-semibold transition-all cursor-pointer duration-300 font-poppins ${pass.popular
? 'bg-primary hover:bg-primary/90 text-white shadow-md hover:shadow-lg'
: 'bg-gray-900 hover:bg-gray-800 text-white hover:shadow-md'
}`}
onClick={user ? handleCheckoutClick : handleSignInClick}
>
{user ? 'PURCHASE NOW' : 'LOGIN TO BUY PASS'}
</Button>
<p className="text-xs text-gray-500 text-center font-poppins font-normal leading-tight">
Free cancellation up to 24 hours Instant delivery
</p>
<CardContent className="pt-0 pb-6 px-6 flex-grow flex flex-col">
<div className="flex-grow mb-6">
<div className="space-y-3">
{passTypes[1].features.map((feature, index) => (
<div key={index} className="flex items-start gap-3">
<Check className="w-4 h-4 text-green-500 mt-1 flex-shrink-0" />
<span className="text-sm text-gray-700 font-poppins leading-relaxed font-normal">{feature}</span>
</div>
))}
</div>
</CardContent>
</Card>
</div>
))}
</div>
<div className="flex-shrink-0 space-y-3">
<Button
className={`w-full h-12 rounded-lg font-semibold transition-all cursor-pointer duration-300 font-poppins ${selectedPass === passTypes[1].id
? "bg-primary hover:bg-primary/90 text-white hover:shadow-lg"
: "bg-gray-400 hover:bg-gray-400 text-white hover:shadow-md"
}`}
disabled={selectedPass !== passTypes[1].id}
onClick={user ? handleCheckoutClick : handleSignInClick}
>
{user ? 'PURCHASE NOW' : 'LOGIN TO BUY PASS'}
</Button>
<p className="text-xs text-gray-500 text-center font-poppins font-normal leading-tight">
Free cancellation up to 24 hours Instant delivery
</p>
</div>
</CardContent>
</Card>
</div>
</RadioGroup>
</div>
{/* Good to Know Section */}
<div className="mb-24">
<div className="text-center mb-16">