Files
CityCards-Website/src/pages/CartPage.tsx

889 lines
68 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import React, { useState } from 'react';
import { motion, AnimatePresence } from 'motion/react';
import {
Users, Baby, ShoppingBag, Trash2, Check, CreditCard, Mail,
ChevronRight, ChevronDown, Minus, Plus, Calendar, ArrowLeft, MapPin,
Zap, Shield, Clock, Percent, Sparkles
} from 'lucide-react';
import Navbar from '../components/Navbar';
import { Footer } from '../components/Footer';
import { ImageWithFallback } from '../components/figma/ImageWithFallback';
import { useNavigate } from 'react-router-dom';
import { useGetCardsinCartQuery } from '../Redux/services/cards.service';
import LoadingSpinner from '../components/LoadingSpinner';
// import { CheckoutStepper } from './CheckoutStepper';
// import imgRectangle26 from "figma:asset/2496f45326066d3adf0d5494c1dc1595575894ff.png";
/* ─── Types ─── */
export interface CartItem {
id: string;
city: string;
cardType: 'Flexi' | 'Unlimited';
days: number;
adults: number;
children: number;
quantity: number;
pricePerUnit: number;
image: string;
}
interface Attraction {
id: string;
name: string;
image: string;
category: string;
included: boolean;
}
interface CartPageProps {
onBackClick: () => void;
onHomeClick: () => void;
onPassesClick: () => void;
onCheckoutClick?: () => void;
onSecureCheckoutClick?: (item: CartItem) => 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;
onEsimsClick?: () => void;
onHotelDiscountsClick?: () => void;
onContactUsClick?: () => void;
onCartClick?: () => void;
currentPage?: string;
user?: { email: string; name: string } | null;
}
/* ─── Data ─── */
const initialCartItems: CartItem[] = [
{
id: '1', city: 'Melbourne', cardType: 'Flexi', days: 3, adults: 3, children: 3, quantity: 2, pricePerUnit: 49.50,
image: 'https://images.unsplash.com/photo-1655963754904-2cf2b562a681?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHxNZWxib3VybmUlMjBmbGluZGVycyUyMHN0YXRpb24lMjBzdW5zZXR8ZW58MXx8fHwxNzc2MzE5NDgzfDA&ixlib=rb-4.1.0&q=80&w=1080',
},
{
id: '2', city: 'Sydney', cardType: 'Flexi', days: 3, adults: 3, children: 3, quantity: 2, pricePerUnit: 49.50,
image: 'https://images.unsplash.com/photo-1695018228065-2e0026c654af?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHxTeWRuZXklMjBvcGVyYSUyMGhvdXNlJTIwaGFyYm91ciUyMGJyaWRnZXxlbnwxfHx8fDE3NzYzMTk0ODN8MA&ixlib=rb-4.1.0&q=80&w=1080',
},
{
id: '3', city: 'Melbourne', cardType: 'Unlimited', days: 6, adults: 2, children: 1, quantity: 1, pricePerUnit: 79.00,
image: 'https://images.unsplash.com/photo-1705120624704-0970afc29fea?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHxNZWxib3VybmUlMjBzdHJlZXQlMjBhcnQlMjBsYW5ld2F5c3xlbnwxfHx8fDE3NzYzMTk0ODR8MA&ixlib=rb-4.1.0&q=80&w=1080',
},
];
const dayOptions = [3, 6, 12, 18, 24];
const attractionsData: Record<string, Record<string, Attraction[]>> = {
Melbourne: {
Flexi: [
{ id: 'mel-1', name: 'SEA LIFE Aquarium', image: 'https://images.unsplash.com/photo-1536845111858-bb269af65cb6?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHxNZWxib3VybmUlMjBhcXVhcml1bSUyMHVuZGVyd2F0ZXJ8ZW58MXx8fHwxNzc2MzE5OTcwfDA&ixlib=rb-4.1.0&q=80&w=1080', category: 'Wildlife', included: true },
{ id: 'mel-2', name: 'Melbourne Zoo', image: 'https://images.unsplash.com/photo-1730074888490-31239540bacf?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHxNZWxib3VybmUlMjB6b28lMjB3aWxkbGlmZXxlbnwxfHx8fDE3NzYzMTk5NzB8MA&ixlib=rb-4.1.0&q=80&w=1080', category: 'Wildlife', included: true },
{ id: 'mel-3', name: 'Royal Botanic Gardens', image: 'https://images.unsplash.com/photo-1585894507208-eeead8cb9a56?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHxNZWxib3VybmUlMjBib3RhbmljYWwlMjBnYXJkZW4lMjBncmVlbnxlbnwxfHx8fDE3NzYzMTk5NzF8MA&ixlib=rb-4.1.0&q=80&w=1080', category: 'Nature', included: true },
{ id: 'mel-4', name: 'NGV Art Gallery', image: 'https://images.unsplash.com/photo-1752429242469-55ba7ec210d2?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHxhcnQlMjBnYWxsZXJ5JTIwbXVzZXVtJTIwaW50ZXJpb3J8ZW58MXx8fHwxNzc2MzE5OTczfDA&ixlib=rb-4.1.0&q=80&w=1080', category: 'Culture', included: true },
],
Unlimited: [
{ id: 'mel-1', name: 'SEA LIFE Aquarium', image: 'https://images.unsplash.com/photo-1536845111858-bb269af65cb6?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHxNZWxib3VybmUlMjBhcXVhcml1bSUyMHVuZGVyd2F0ZXJ8ZW58MXx8fHwxNzc2MzE5OTcwfDA&ixlib=rb-4.1.0&q=80&w=1080', category: 'Wildlife', included: true },
{ id: 'mel-2', name: 'Melbourne Zoo', image: 'https://images.unsplash.com/photo-1730074888490-31239540bacf?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHxNZWxib3VybmUlMjB6b28lMjB3aWxkbGlmZXxlbnwxfHx8fDE3NzYzMTk5NzB8MA&ixlib=rb-4.1.0&q=80&w=1080', category: 'Wildlife', included: true },
{ id: 'mel-3', name: 'Royal Botanic Gardens', image: 'https://images.unsplash.com/photo-1585894507208-eeead8cb9a56?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHxNZWxib3VybmUlMjBib3RhbmljYWwlMjBnYXJkZW4lMjBncmVlbnxlbnwxfHx8fDE3NzYzMTk5NzF8MA&ixlib=rb-4.1.0&q=80&w=1080', category: 'Nature', included: true },
{ id: 'mel-4', name: 'NGV Art Gallery', image: 'https://images.unsplash.com/photo-1752429242469-55ba7ec210d2?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHxhcnQlMjBnYWxsZXJ5JTIwbXVzZXVtJTIwaW50ZXJpb3J8ZW58MXx8fHwxNzc2MzE5OTczfDA&ixlib=rb-4.1.0&q=80&w=1080', category: 'Culture', included: true },
{ id: 'mel-5', name: 'Melbourne Star Wheel', image: 'https://images.unsplash.com/photo-1769880659692-fa77e04c5ffa?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHxvYnNlcnZhdGlvbiUyMHdoZWVsJTIwYW11c2VtZW50JTIwbmlnaHR8ZW58MXx8fHwxNzc2MzE5OTc2fDA&ixlib=rb-4.1.0&q=80&w=1080', category: 'Experience', included: true },
{ id: 'mel-6', name: 'Penguin Parade', image: 'https://images.unsplash.com/photo-1670391050251-d1cfbc3891c4?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHxwZW5ndWlucyUyMHdpbGRsaWZlJTIwbmF0dXJlfGVufDF8fHx8MTc3NjMxOTk3Nnww&ixlib=rb-4.1.0&q=80&w=1080', category: 'Wildlife', included: true },
{ id: 'mel-7', name: 'Yarra River Cruise', image: 'https://images.unsplash.com/photo-1562003914-018a4a6c2171?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHxyaXZlciUyMGNydWlzZSUyMGJvYXQlMjBjaXR5fGVufDF8fHx8MTc3NjMxOTk3M3ww&ixlib=rb-4.1.0&q=80&w=1080', category: 'Experience', included: true },
],
},
Sydney: {
Flexi: [
{ id: 'syd-1', name: 'Harbour Bridge Climb', image: 'https://images.unsplash.com/photo-1767974062666-2685a670e353?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHxTeWRuZXklMjBoYXJib3VyJTIwYnJpZGdlJTIwY2xpbWJ8ZW58MXx8fHwxNzc2MzE5OTcxfDA&ixlib=rb-4.1.0&q=80&w=1080', category: 'Adventure', included: true },
{ id: 'syd-2', name: 'Taronga Zoo', image: 'https://images.unsplash.com/photo-1704852168456-b70e08441917?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHxTeWRuZXklMjB0YXJvbmdhJTIwem9vJTIwYW5pbWFsc3xlbnwxfHx8fDE3NzYzMTk5NzJ8MA&ixlib=rb-4.1.0&q=80&w=1080', category: 'Wildlife', included: true },
{ id: 'syd-3', name: 'Art Gallery NSW', image: 'https://images.unsplash.com/photo-1752429242469-55ba7ec210d2?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHxhcnQlMjBnYWxsZXJ5JTIwbXVzZXVtJTIwaW50ZXJpb3J8ZW58MXx8fHwxNzc2MzE5OTczfDA&ixlib=rb-4.1.0&q=80&w=1080', category: 'Culture', included: true },
],
Unlimited: [
{ id: 'syd-1', name: 'Harbour Bridge Climb', image: 'https://images.unsplash.com/photo-1767974062666-2685a670e353?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHxTeWRuZXklMjBoYXJib3VyJTIwYnJpZGdlJTIwY2xpbWJ8ZW58MXx8fHwxNzc2MzE5OTcxfDA&ixlib=rb-4.1.0&q=80&w=1080', category: 'Adventure', included: true },
{ id: 'syd-2', name: 'Taronga Zoo', image: 'https://images.unsplash.com/photo-1704852168456-b70e08441917?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHxTeWRuZXklMjB0YXJvbmdhJTIwem9vJTIwYW5pbWFsc3xlbnwxfHx8fDE3NzYzMTk5NzJ8MA&ixlib=rb-4.1.0&q=80&w=1080', category: 'Wildlife', included: true },
{ id: 'syd-3', name: 'Art Gallery NSW', image: 'https://images.unsplash.com/photo-1752429242469-55ba7ec210d2?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHxhcnQlMjBnYWxsZXJ5JTIwbXVzZXVtJTIwaW50ZXJpb3J8ZW58MXx8fHwxNzc2MzE5OTczfDA&ixlib=rb-4.1.0&q=80&w=1080', category: 'Culture', included: true },
{ id: 'syd-4', name: 'Sydney Harbour Cruise', image: 'https://images.unsplash.com/photo-1562003914-018a4a6c2171?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHxyaXZlciUyMGNydWlzZSUyMGJvYXQlMjBjaXR5fGVufDF8fHx8MTc3NjMxOTk3M3ww&ixlib=rb-4.1.0&q=80&w=1080', category: 'Experience', included: true },
{ id: 'syd-5', name: 'SEA LIFE Aquarium', image: 'https://images.unsplash.com/photo-1536845111858-bb269af65cb6?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHxNZWxib3VybmUlMjBhcXVhcml1bSUyMHVuZGVyd2F0ZXJ8ZW58MXx8fHwxNzc2MzE5OTcwfDA&ixlib=rb-4.1.0&q=80&w=1080', category: 'Wildlife', included: true },
],
},
};
const offersData: Record<string, { title: string; description: string; image: string }[]> = {
Flexi: [
{ title: 'Astor Hotels Ultra Deluxe', description: '15% Discount on all treatments for first-time clients', image: 'https://images.unsplash.com/photo-1715191904112-4a5d9c3089fa?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHxsdXh1cnklMjBob3RlbCUyMHJlc29ydCUyMGV4dGVyaW9yfGVufDF8fHx8MTc3NjMyMTM2MXww&ixlib=rb-4.1.0&q=80&w=1080' },
{ title: 'Green Valley Spa Lux', description: '20% Off on membership plans for new members', image: 'https://images.unsplash.com/photo-1759216853079-831ef8c8b327?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHxzcGElMjB3ZWxsbmVzcyUyMHRyZWF0bWVudCUyMGludGVyaW9yfGVufDF8fHx8MTc3NjMyMTM2M3ww&ixlib=rb-4.1.0&q=80&w=1080' },
{ title: 'Harbour Dining Co.', description: '10% Off your first dining experience at waterfront', image: 'https://images.unsplash.com/photo-1676471932681-45fa972d848a?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHxyZXN0YXVyYW50JTIwZmluZSUyMGRpbmluZ3xlbnwxfHx8fDE3NzYzMTkxNDl8MA&ixlib=rb-4.1.0&q=80&w=1080' },
{ title: 'National Gallery Exhibition', description: 'Free audio guide with every gallery visit', image: 'https://images.unsplash.com/photo-1569342380852-035f42d9ca41?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHxtdXNldW0lMjBnYWxsZXJ5JTIwZXhoaWJpdGlvbnxlbnwxfHx8fDE3NzYyNDYwMjh8MA&ixlib=rb-4.1.0&q=80&w=1080' },
{ title: 'Sunset Harbour Cruise', description: 'Complimentary drink on every sunset cruise booking', image: 'https://images.unsplash.com/photo-1765783800962-83d99ff7b158?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHxjcnVpc2UlMjBib2F0JTIwaGFyYm9yJTIwdG91cnxlbnwxfHx8fDE3NzYzMjE2MDd8MA&ixlib=rb-4.1.0&q=80&w=1080' },
],
Unlimited: [
{ title: 'SkyView Ferris Wheel', description: 'Complimentary second ride for all pass holders', image: 'https://images.unsplash.com/photo-1626209025747-b41ee6ec191f?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHxmZXJyaXMlMjB3aGVlbCUyMGFtdXNlbWVudCUyMHBhcmt8ZW58MXx8fHwxNzc2MzE3NDI2fDA&ixlib=rb-4.1.0&q=80&w=1080' },
{ title: 'City Mall Boutique', description: '15% Off at select boutique stores with your pass', image: 'https://images.unsplash.com/photo-1567966689299-819568579d36?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHxzaG9wcGluZyUyMG1hbGwlMjBib3V0aXF1ZSUyMHJldGFpbHxlbnwxfHx8fDE3NzYzMjEzNjN8MA&ixlib=rb-4.1.0&q=80&w=1080' },
{ title: 'Adventure Outfitters', description: 'Free gear rental on outdoor adventure bookings', image: 'https://images.unsplash.com/photo-1761131221577-0716baffc6ef?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHxhZHZlbnR1cmUlMjBzcG9ydHMlMjBvdXRkb29yJTIwYWN0aXZpdHl8ZW58MXx8fHwxNzc2MzIxMzYzfDA&ixlib=rb-4.1.0&q=80&w=1080' },
{ title: 'Skyline Rooftop Lounge', description: 'Buy one get one free on signature cocktails', image: 'https://images.unsplash.com/photo-1642114955097-8f3d0e141641?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHxyb29mdG9wJTIwYmFyJTIwY2l0eSUyMHNreWxpbmUlMjBuaWdodHxlbnwxfHx8fDE3NzYyNDU2NTl8MA&ixlib=rb-4.1.0&q=80&w=1080' },
{ title: 'Yarra Valley Wines', description: 'Exclusive wine tasting tour with pass holders discount', image: 'https://images.unsplash.com/photo-1764649841527-c8852b63cc53?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHx3aW5lJTIwdGFzdGluZyUyMHZpbmV5YXJkJTIwY2VsbGFyfGVufDF8fHx8MTc3NjMyMTYwOHww&ixlib=rb-4.1.0&q=80&w=1080' },
],
};
const priceTable: Record<string, Record<number, number>> = {
Flexi: { 3: 49.5, 6: 69, 12: 99, 18: 129, 24: 159 },
Unlimited: { 3: 79, 6: 109, 12: 149, 18: 189, 24: 229 },
};
/* ═══════════════════════════════════════════
FIGMA CARD TYPE COMPONENTS
═══════════════════════════════════════════ */
function FlexiCardPreview({ city, adultPrice, childPrice, isSelected }: { city: string; adultPrice: number; childPrice: number; isSelected: boolean }) {
return (
<div className={`relative h-[160px] w-full rounded-lg transition-all duration-200 ${isSelected ? 'ring-2 ring-[#F95F62] shadow-md shadow-[#F95F62]/10' : 'hover:shadow-md'
}`}>
{/* Card bg */}
<div className="absolute inset-0 bg-white border border-[rgba(249,95,175,0.2)] rounded-lg shadow-[0px_4px_20px_0px_rgba(0,0,0,0.06)]" />
{/* City image */}
<div className="absolute h-[158px] left-[1px] top-[1px] w-[103px] rounded-bl-[7px] rounded-tl-[7px] overflow-hidden">
{/* <img alt="" className="absolute inset-0 w-full h-full object-cover" src={imgRectangle26} /> */}
</div>
{/* City name - left aligned */}
<div className="absolute left-[112px] top-[12px]">
<p className="font-['Poppins',sans-serif] font-medium text-[16px] text-[#2a2a2a] leading-[22px] whitespace-nowrap">{city}</p>
</div>
{/* Pricing */}
<div className="absolute left-[112px] top-[40px] flex flex-col gap-[6px]">
<div className="flex gap-[2px] items-center">
<span className="font-['Poppins',sans-serif] text-[11px] text-[rgba(0,0,0,0.6)] tracking-[0.06px]">From</span>
<span className="font-['Poppins',sans-serif] font-medium text-[24px] text-[#f95f62] tracking-[-0.96px] leading-[1.3]">${adultPrice}</span>
<span className="font-['Poppins',sans-serif] text-[11px] text-[rgba(0,0,0,0.8)] tracking-[0.06px]">/Adult</span>
</div>
<div className="flex gap-[2px] items-center">
<span className="font-['Poppins',sans-serif] text-[11px] text-[rgba(0,0,0,0.6)] tracking-[0.06px]">and</span>
<span className="font-['Poppins',sans-serif] font-medium text-[24px] text-[#f95f62] tracking-[-0.96px] leading-[1.3]">${childPrice}</span>
<span className="font-['Poppins',sans-serif] text-[11px] text-[rgba(0,0,0,0.8)] tracking-[0.06px]">/Child</span>
</div>
</div>
{/* Description */}
<div className="absolute left-[112px] top-[112px] right-[44px]">
<p className="font-['Poppins',sans-serif] text-[11px] text-left text-[rgba(0,0,0,0.4)] tracking-[0.06px] leading-[14px]">
Dive into an extensive selection of thrilling destinations!
</p>
</div>
{/* Side tab - Flexi (pink) */}
<div className="absolute bg-[#f95faf] h-full right-0 top-0 w-[35px] rounded-br-lg rounded-tr-lg flex flex-col items-center justify-center gap-[2px]">
<span className="font-['Poppins',sans-serif] text-[12px] text-white/70 [writing-mode:vertical-rl] rotate-180">Card</span>
<span className="font-['Poppins',sans-serif] text-[16px] text-white [writing-mode:vertical-rl] rotate-180">Flexi</span>
</div>
{/* Selected checkmark */}
{isSelected && (
<div className="absolute top-2 right-[44px] w-6 h-6 rounded-full bg-[#F95F62] flex items-center justify-center z-10">
<Check className="w-3.5 h-3.5 text-white" strokeWidth={3} />
</div>
)}
</div>
);
}
function UnlimitedCardPreview({ city, adultPrice, childPrice, isSelected }: { city: string; adultPrice: number; childPrice: number; isSelected: boolean }) {
return (
<div className={`relative h-[160px] w-full rounded-lg transition-all duration-200 ${isSelected ? 'ring-2 ring-[#F95F62] shadow-md shadow-[#F95F62]/10' : 'hover:shadow-md'
}`}>
{/* Card bg */}
<div className="absolute inset-0 bg-white border border-[rgba(0,0,0,0.2)] rounded-lg" />
{/* City image */}
<div className="absolute h-[158px] left-[1px] top-[1px] w-[103px] rounded-bl-[7px] rounded-tl-[7px] overflow-hidden">
{/* <img alt="" className="absolute inset-0 w-full h-full object-cover" src={imgRectangle26} /> */}
</div>
{/* City name - left aligned */}
<div className="absolute left-[112px] top-[12px]">
<p className="font-['Poppins',sans-serif] font-medium text-[16px] text-[#2a2a2a] leading-[20px] whitespace-nowrap">{city}</p>
</div>
{/* Pricing */}
<div className="absolute left-[112px] top-[40px] flex flex-col gap-[6px]">
<div className="flex gap-[2px] items-center">
<span className="font-['Poppins',sans-serif] text-[11px] text-[rgba(0,0,0,0.6)] tracking-[0.06px]">From</span>
<span className="font-['Poppins',sans-serif] font-medium text-[24px] text-[#f95f62] tracking-[-0.96px] leading-[1.3]">${adultPrice}</span>
<span className="font-['Poppins',sans-serif] text-[11px] text-[rgba(0,0,0,0.8)] tracking-[0.06px]">/Adult</span>
</div>
<div className="flex gap-[2px] items-center">
<span className="font-['Poppins',sans-serif] text-[11px] text-[rgba(0,0,0,0.6)] tracking-[0.06px]">and</span>
<span className="font-['Poppins',sans-serif] font-medium text-[24px] text-[#f95f62] tracking-[-0.96px] leading-[1.3]">${childPrice}</span>
<span className="font-['Poppins',sans-serif] text-[11px] text-[rgba(0,0,0,0.8)] tracking-[0.06px]">/Child</span>
</div>
</div>
{/* Description */}
<div className="absolute left-[112px] top-[112px] right-[44px]">
<p className="font-['Poppins',sans-serif] text-[11px] text-left text-[rgba(0,0,0,0.4)] tracking-[0.06px] leading-[14px]">
Dive into an extensive selection of thrilling destinations!
</p>
</div>
{/* Side tab - Unlimited (coral) */}
<div className="absolute bg-[#f95f62] h-full right-0 top-0 w-[35px] rounded-br-lg rounded-tr-lg flex flex-col items-center justify-center gap-[2px]">
<span className="font-['Poppins',sans-serif] text-[12px] text-white/70 [writing-mode:vertical-rl] rotate-180">Card</span>
<span className="font-['Poppins',sans-serif] text-[16px] text-white [writing-mode:vertical-rl] rotate-180">Unlimited</span>
</div>
{/* Selected checkmark */}
{isSelected && (
<div className="absolute top-2 right-[44px] w-6 h-6 rounded-full bg-[#F95F62] flex items-center justify-center z-10">
<Check className="w-3.5 h-3.5 text-white" strokeWidth={3} />
</div>
)}
</div>
);
}
/* ═══════════════════════════════════════════
CHECKOUT CONFIGURATION CARD (Mobile-first)
═══════════════════════════════════════════ */
function CheckoutConfigCard({
item,
onChange,
onProceed,
}: {
item: CartItem;
onChange: (updates: Partial<CartItem>) => void;
onProceed: () => void;
}) {
const [daysOpen, setDaysOpen] = useState(false);
const originalPrice = (item.pricePerUnit * item.quantity * 1.35);
const totalPrice = item.pricePerUnit * item.quantity;
const navigate = useNavigate()
return (
<div className="bg-white rounded-2xl shadow-[0px_4px_24px_0px_rgba(0,0,0,0.06)] overflow-hidden w-full max-w-[400px]">
{/* City header */}
<div className="pt-6 pb-2 text-center">
<h4 className="font-poppins text-lg leading-snug font-medium text-[#2a2a2a]">{item.city}</h4>
<div className="mt-2 flex justify-center">
<span className={`inline-flex items-center px-4 py-1 rounded-full font-poppins text-xs font-medium ${item.cardType === 'Flexi'
? 'bg-[#f95faf]/10 text-[#f95faf]'
: 'bg-[#f95f62]/10 text-[#f95f62]'
}`}>
{item.cardType} Card
</span>
</div>
</div>
{/* Configuration rows */}
<div className="px-6 py-4 space-y-0">
{/* No. of Adults */}
<div className="flex items-center justify-between py-4 border-b border-gray-100">
<span className="font-poppins text-sm font-normal text-[#2a2a2a]">No. of Adults</span>
<div className="flex items-center gap-3">
<button
onClick={() => item.adults > 1 && onChange({ adults: item.adults - 1 })}
disabled={item.adults <= 1}
className={`w-8 h-8 rounded-full flex items-center justify-center transition-colors ${item.adults <= 1 ? 'bg-gray-100 text-gray-300 cursor-not-allowed' : 'bg-[#f95f62]/10 text-[#f95f62] hover:bg-[#f95f62]/20'
}`}
>
<Minus className="w-4 h-4" />
</button>
<span className="font-poppins text-base font-medium text-[#2a2a2a] w-5 text-center tabular-nums">{item.adults}</span>
<button
onClick={() => onChange({ adults: item.adults + 1 })}
className="w-8 h-8 rounded-full bg-[#f95f62]/10 text-[#f95f62] hover:bg-[#f95f62]/20 flex items-center justify-center transition-colors"
>
<Plus className="w-4 h-4" />
</button>
</div>
</div>
{/* No. of Children */}
<div className="flex items-center justify-between py-4 border-b border-gray-100">
<span className="font-poppins text-sm font-normal text-[#2a2a2a]">No. of Children</span>
<div className="flex items-center gap-3">
<button
onClick={() => item.children > 0 && onChange({ children: item.children - 1 })}
disabled={item.children <= 0}
className={`w-8 h-8 rounded-full flex items-center justify-center transition-colors ${item.children <= 0 ? 'bg-gray-100 text-gray-300 cursor-not-allowed' : 'bg-[#f95f62]/10 text-[#f95f62] hover:bg-[#f95f62]/20'
}`}
>
<Minus className="w-4 h-4" />
</button>
<span className="font-poppins text-base font-medium text-[#2a2a2a] w-5 text-center tabular-nums">{item.children}</span>
<button
onClick={() => onChange({ children: item.children + 1 })}
className="w-8 h-8 rounded-full bg-[#f95f62]/10 text-[#f95f62] hover:bg-[#f95f62]/20 flex items-center justify-center transition-colors"
>
<Plus className="w-4 h-4" />
</button>
</div>
</div>
{/* No. of Days (dropdown) */}
<div className="flex items-center justify-between py-4 border-b border-gray-100">
<span className="font-poppins text-sm font-normal text-[#2a2a2a]">
{item.cardType === 'Flexi' ? 'No. of Attractions' : 'No. of Days'}
</span>
<div className="relative">
<button
onClick={() => setDaysOpen(!daysOpen)}
className="flex items-center gap-2 border border-[#f95f62]/30 rounded-lg px-3 py-1.5 min-w-[72px] justify-between hover:border-[#f95f62] transition-colors"
>
<span className="font-poppins text-base font-medium text-[#f95f62] tabular-nums">{item.days}</span>
<ChevronDown className={`w-4 h-4 text-[#f95f62] transition-transform ${daysOpen ? 'rotate-180' : ''}`} />
</button>
<AnimatePresence>
{daysOpen && (
<motion.div
initial={{ opacity: 0, y: -4, scale: 0.95 }}
animate={{ opacity: 1, y: 0, scale: 1 }}
exit={{ opacity: 0, y: -4, scale: 0.95 }}
transition={{ duration: 0.15 }}
className="absolute right-0 top-full mt-1 bg-white rounded-lg shadow-lg border border-gray-100 z-30 min-w-[72px] overflow-hidden"
>
{dayOptions.map((d) => (
<button
key={d}
onClick={() => { onChange({ days: d }); setDaysOpen(false); }}
className={`w-full px-3 py-2 text-left font-poppins text-sm transition-colors ${item.days === d
? 'bg-[#f95f62]/10 text-[#f95f62] font-medium'
: 'text-[#2a2a2a] hover:bg-gray-50 font-normal'
}`}
>
{d}
</button>
))}
</motion.div>
)}
</AnimatePresence>
</div>
</div>
{/* You Pay */}
<div className="flex items-center justify-between py-5">
<span className="font-poppins text-sm font-normal text-[#2a2a2a]">You Pay</span>
<div className="flex items-center gap-2">
<span className="font-poppins text-sm font-normal text-[#aaa] line-through">
${originalPrice.toFixed(0)}
</span>
<span className="font-poppins text-2xl font-medium text-[#f95f62] tracking-tight">
${totalPrice.toFixed(0)}
</span>
</div>
</div>
</div>
{/* Proceed button */}
<div className="px-6 pb-6">
<motion.button
whileHover={{ scale: 1.01 }}
whileTap={{ scale: 0.98 }}
onClick={() => navigate("/payment")}
className="w-full py-4 rounded-full bg-[#f95f62] text-white font-poppins text-base font-medium hover:bg-[#e8545a] transition-colors shadow-lg shadow-[#f95f62]/20"
>
Proceed to Pay
</motion.button>
</div>
</div>
);
}
/* ═══════════════════════════════════════════
MAIN CART PAGE
═══════════════════════════════════════════ */
export function CartPage({
onBackClick,
onHomeClick,
onPassesClick,
onCheckoutClick,
onSecureCheckoutClick,
onSignInClick,
onSignOutClick,
onAttractionsClick,
onBlogsClick,
onHowItWorksClick,
onFAQClick,
onPrivacyPolicyClick,
onAboutUsClick,
onProfileClick,
onCityCardsClick,
onMagicItineraryClick,
onPostCardsClick,
onOffersClick,
onSuperSavingsClick,
onEsimsClick,
onHotelDiscountsClick,
onContactUsClick,
onCartClick,
currentPage,
user,
}: CartPageProps) {
const [activeTab, setActiveTab] = useState<'cards' | 'postcards'>('cards');
const [cartItems, setCartItems] = useState<CartItem[]>(initialCartItems);
const [selectedCardId, setSelectedCardId] = useState<string | null>(null);
const [view, setView] = useState<'cart' | 'checkout'>('cart');
const [checkoutItem, setCheckoutItem] = useState<CartItem | null>(null);
const navigate = useNavigate()
const cityId = localStorage.getItem("cityId")
const { data, isLoading } = useGetCardsinCartQuery(cityId)
const CartItems = data?.cartItems ?? []
if (isLoading) {
return (
<LoadingSpinner />
)
}
const handleRemoveItem = (id: string) => {
setCartItems(prev => prev.filter(item => item.id !== id));
if (selectedCardId === id) setSelectedCardId(null);
};
const handleSelectCard = (id: string) => {
setSelectedCardId(prev => (prev === id ? null : id));
};
const handleGoToCheckout = (selectedItemId:number) => {
// const item = cartItems.find(i => i.id === selectedCardId);
// if (item) {
// setCheckoutItem({ ...item });
// setView('checkout');
// window.scrollTo({ top: 0, behavior: 'smooth' });
// }
navigate(`/payment/${selectedItemId}`)
};
const handleBackToCart = () => {
setView('cart');
setCheckoutItem(null);
};
const handleCheckoutItemChange = (updates: Partial<CartItem>) => {
if (!checkoutItem) return;
const updated = { ...checkoutItem, ...updates };
const prices = priceTable[updated.cardType];
if (prices && prices[updated.days] !== undefined) {
updated.pricePerUnit = prices[updated.days];
}
setCheckoutItem(updated);
};
const isEmpty = cartItems.length === 0;
const selectedItem = CartItems.find((i: any) => i.id === selectedCardId);
const attractions = checkoutItem ? (attractionsData[checkoutItem.city]?.[checkoutItem.cardType] || []) : [];
const offers = checkoutItem ? (offersData[checkoutItem.cardType] || []) : [];
return (
<div className="min-h-screen bg-[#fafafa] font-poppins">
<Navbar
activeCity="Melbourne" onCityChange={() => { }} onSignInClick={onSignInClick} onSignOutClick={onSignOutClick}
onPassesClick={onPassesClick} onCheckoutClick={onCheckoutClick} onHomeClick={onHomeClick}
onAttractionsClick={onAttractionsClick} onBlogsClick={onBlogsClick} onHowItWorksClick={onHowItWorksClick}
onFAQClick={onFAQClick} onPrivacyPolicyClick={onPrivacyPolicyClick} onAboutUsClick={onAboutUsClick}
onProfileClick={onProfileClick} onCityCardsClick={onCityCardsClick} onMagicItineraryClick={onMagicItineraryClick}
onPostCardsClick={onPostCardsClick} onOffersClick={onOffersClick} onSuperSavingsClick={onSuperSavingsClick}
onEsimsClick={onEsimsClick} onHotelDiscountsClick={onHotelDiscountsClick} onCartClick={onCartClick}
currentPage={currentPage as any} user={user}
/>
<AnimatePresence mode="wait">
{view === 'cart' ? (
/* ─── CART VIEW ─── */
<motion.div
key="cart-view"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0, x: -30 }}
transition={{ duration: 0.3 }}
className="w-full px-4 sm:px-6 lg:px-10 xl:px-16 pt-32 pb-24 max-w-[1440px] mx-auto"
>
{/* Header */}
<div className="mb-8">
<h2 className="font-poppins text-2xl md:text-3xl lg:text-4xl leading-tight">
<span className="font-light">Your</span>{' '}
<span className="font-bold italic bg-gradient-to-r from-[#F95F62] to-[#F95FAF] bg-clip-text text-transparent pr-2">Cart</span>
</h2>
<p className="font-poppins text-sm leading-relaxed font-normal text-[#8e8e8e] mt-1">
{isEmpty ? 'Your cart is empty' : `${CartItems.length} ${CartItems.length === 1 ? 'item' : 'items'} in your cart`}
</p>
</div>
{/* Tab switcher */}
{/* Cards listed directly below */}
{/* Content */}
<AnimatePresence mode="wait">
{activeTab === 'cards' ? (
<motion.div key="cards-content" initial={{ opacity: 0, y: 12 }} animate={{ opacity: 1, y: 0 }} exit={{ opacity: 0, y: -12 }} transition={{ duration: 0.2 }}>
{isEmpty ? (
<EmptyState icon={<CreditCard className="w-16 h-16 text-[#F95F62]/20" strokeWidth={1.2} />} title="No cards in your cart" description="Browse our city passes to unlock amazing experiences and savings on your next adventure" actionLabel="Explore Passes" onAction={onPassesClick} />
) : (
<div className="space-y-3">
{/* Table header (desktop) */}
<div className="md:grid md:grid-cols-12 gap-4 px-5 pb-2">
<div className="col-span-5 font-poppins text-xs font-medium text-[#8e8e8e] uppercase tracking-wider">City Cards</div>
<div className="col-span-2 font-poppins text-xs font-medium text-[#8e8e8e] uppercase tracking-wider text-center">Travellers</div>
{/* <div className="col-span-1 font-poppins text-xs font-medium text-[#8e8e8e] uppercase tracking-wider text-center">Qty</div> */}
<div className="col-span-3 font-poppins text-xs font-medium text-[#8e8e8e] uppercase tracking-wider text-right">Price</div>
<div className="col-span-1" />
</div>
<AnimatePresence>
{CartItems.map((item: any) => {
const isSelected = selectedCardId === item.id;
const totalPrice = item.pricePerUnit * item.quantity;
return (
<motion.div
key={item.id}
layout
initial={{ opacity: 0, y: 12 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, x: -60, transition: { duration: 0.25 } }}
onClick={() => handleSelectCard(item.id)}
className={`relative bg-white rounded-2xl overflow-hidden cursor-pointer transition-all duration-300 ${isSelected ? 'ring-2 ring-[#F95F62] shadow-lg shadow-[#F95F62]/8' : 'ring-1 ring-gray-100 hover:ring-gray-200 hover:shadow-md'
}`}
>
{/* Selected badge */}
<AnimatePresence>
{isSelected && (
<motion.div initial={{ scale: 0 }} animate={{ scale: 1 }} exit={{ scale: 0 }} className="absolute top-3 left-3 z-20 w-6 h-6 rounded-full bg-[#F95F62] flex items-center justify-center shadow-md">
<Check className="w-3.5 h-3.5 text-white" strokeWidth={3} />
</motion.div>
)}
</AnimatePresence>
{/* Mobile layout */}
<div className="md:hidden flex gap-4 p-4">
<div className="w-20 h-20 rounded-xl overflow-hidden flex-shrink-0 relative">
<ImageWithFallback src={item?.city?.cityBanners[0]?.imageFilePath} alt={item.city?.cityName} className="absolute inset-0 w-full h-full object-cover" />
</div>
<div className="flex-1 min-w-0">
<div className="flex items-start justify-between">
<div>
<h5 className="font-poppins text-base leading-snug font-medium text-[#2a2a2a]">{item.city?.cityName}</h5>
<div className="flex items-center gap-2 mt-0.5">
<span className={`inline-flex px-2 py-0.5 rounded-full text-[10px] font-medium ${item.cardMode === 'flexi' ? 'bg-[#F95FAF]/10 text-[#F95FAF]' : 'bg-[#F95F62]/10 text-[#F95F62]'}`}>{item.displayCardMode}</span>
<span className="font-poppins text-xs font-normal text-[#8e8e8e]">{item.cardMode === 'flexi' ? `${item.noOfAttractions} ${item.noOfAttractions === 1 ? 'attraction' : 'attractions'}` : `${item.noOfDays} ${item.noOfDays === 1 ? 'day' : 'days'}`}</span>
</div>
</div>
{/* <button onClick={(e) => { e.stopPropagation(); handleRemoveItem(item.id); }} className="p-1.5 rounded-lg text-gray-300 hover:text-[#F95F62] hover:bg-red-50 transition-colors">
<Trash2 className="w-4 h-4" />
</button> */}
</div>
<div className="flex items-center justify-between mt-2">
<span className="font-poppins text-xs font-normal text-[#8e8e8e]">{item.totalAdult}A · {item.totalChild}C</span>
<div className="text-right">
<span className="font-poppins text-base font-medium text-[#F95F62] tracking-tight">${item.totalAmount}</span>
{/* {item.quantity > 1 && <span className="block font-poppins text-[10px] font-normal text-[#aaa]">${item.pricePerUnit.toFixed(2)}/ea</span>} */}
</div>
</div>
</div>
</div>
{/* Desktop layout */}
<div className="md:grid md:grid-cols-12 gap-4 items-center p-5">
<div className="col-span-5 flex items-center gap-4">
<div className="w-16 h-16 rounded-xl overflow-hidden flex-shrink-0 relative">
<ImageWithFallback src={item?.city?.cityBanners[0]?.imageFilePath} alt={item.city?.cityName} className="absolute inset-0 w-full h-full object-cover" />
</div>
<div>
<h5 className="font-poppins text-base leading-snug font-medium text-[#2a2a2a]">{item.city?.cityName}</h5>
<div className="flex items-center gap-2 mt-1">
<span className={`inline-flex px-2.5 py-0.5 rounded-full text-xs font-medium ${item.cardMode === 'flexi' ? 'bg-[#F95FAF]/10 text-[#F95FAF]' : 'bg-[#F95F62]/10 text-[#F95F62]'}`}>{item.displayCardMode}</span>
<span className="flex items-center gap-1 font-poppins text-xs font-normal text-[#8e8e8e]">{item.cardMode === 'flexi' ? `${item.noOfAttractions} ${item.noOfAttractions === 1 ? 'attraction' : 'attractions'}` : `${item.noOfDays} ${item.noOfDays === 1 ? 'day' : 'days'}`}</span>
</div>
</div>
</div>
<div className="col-span-2 text-center">
<div className="flex items-center justify-center gap-3">
<span className="flex items-center gap-1 font-poppins text-sm font-normal text-[#555]"><Users className="w-3.5 h-3.5 text-[#8e8e8e]" />{item.totalAdult}</span>
<span className="flex items-center gap-1 font-poppins text-sm font-normal text-[#555]"><Baby className="w-3.5 h-3.5 text-[#8e8e8e]" />{item.totalChild}</span>
</div>
</div>
{/* <div className="col-span-1 flex justify-center">
<span className="font-poppins text-sm font-medium text-[#2a2a2a] bg-gray-50 px-4 py-1.5 rounded-full">{item.quantity}</span>
</div> */}
<div className="col-span-3 text-right">
<span className="font-poppins text-lg font-medium text-[#F95F62] tracking-tight">${item.totalAmount}</span>
{/* {item.quantity > 1 && <span className="block font-poppins text-xs font-normal text-[#aaa] mt-0.5">${item.pricePerUnit.toFixed(2)} per unit</span>} */}
</div>
{/* <div className="col-span-1 flex justify-end">
<button onClick={(e) => { e.stopPropagation(); handleRemoveItem(item.id); }} className="p-2 rounded-lg text-gray-300 hover:text-[#F95F62] hover:bg-red-50 transition-all" title="Remove from cart">
<Trash2 className="w-4.5 h-4.5" />
</button>
</div> */}
</div>
</motion.div>
);
})}
</AnimatePresence>
{/* Bottom checkout bar */}
<motion.div layout className="mt-6 bg-white rounded-2xl ring-1 ring-gray-100 p-5 flex flex-col sm:flex-row items-center justify-between gap-4">
<div className="text-center sm:text-left">
{selectedItem ? (
<>
<p className="font-poppins text-xs font-normal text-[#8e8e8e]">
Selected: {selectedItem.city.cityName} {selectedItem.displayCardMode} · {selectedItem.cardMode === 'flexi' ? `${selectedItem.noOfAttractions} ${selectedItem.noOfAttractions === 1 ? 'attraction' : 'attractions'}` : `${selectedItem.noOfDays} ${selectedItem.noOfDays === 1 ? 'day' : 'days'}`}
</p>
<p className="font-poppins text-2xl font-medium text-[#F95F62] tracking-tight mt-0.5">
${selectedItem.totalAmount}
</p>
</>
) : (
<p className="font-poppins text-sm font-normal text-[#8e8e8e]">Tap a card above to select it for checkout</p>
)}
</div>
<motion.button
whileHover={selectedItem ? { scale: 1.02 } : {}}
whileTap={selectedItem ? { scale: 0.98 } : {}}
onClick={()=>handleGoToCheckout(selectedItem.id)}
disabled={!selectedItem}
className={`sm:w-auto px-8 py-3.5 rounded-xl font-poppins text-base font-medium flex items-center justify-center gap-2 transition-all duration-200 ${selectedItem ? 'bg-[#F95F62] text-white hover:bg-[#e8545a] shadow-lg shadow-[#F95F62]/20' : 'bg-gray-100 text-gray-400 cursor-not-allowed'
}`}
>
Secure Checkout <ChevronRight className="w-4 h-4" />
</motion.button>
</motion.div>
</div>
)}
</motion.div>
) : (
<motion.div key="postcards-content" initial={{ opacity: 0, y: 12 }} animate={{ opacity: 1, y: 0 }} exit={{ opacity: 0, y: -12 }} transition={{ duration: 0.2 }}>
<EmptyState icon={<Mail className="w-16 h-16 text-[#F95F62]/20" strokeWidth={1.2} />} title="No post cards yet" description="Send beautiful digital post cards to friends and family from your favourite destinations around the world" actionLabel="Browse Post Cards" onAction={onPostCardsClick} />
</motion.div>
)}
</AnimatePresence>
</motion.div>
) : (
/* ─── CHECKOUT VIEW ─── */
<motion.div
key="checkout-view"
initial={{ opacity: 0, x: 40 }}
animate={{ opacity: 1, x: 0 }}
exit={{ opacity: 0, x: 40 }}
transition={{ duration: 0.3 }}
className="w-full px-4 sm:px-6 lg:px-10 xl:px-16 pt-32 pb-24 max-w-[1440px] mx-auto"
>
{checkoutItem && (
<>
{/* Back */}
<button onClick={handleBackToCart} className="flex items-center gap-2 text-[#8e8e8e] hover:text-[#2a2a2a] transition-colors font-poppins text-sm font-normal mb-8">
<ArrowLeft className="w-4 h-4" />Back to Cart
</button>
{/* Stepper */}
{/* <CheckoutStepper currentStep={2} /> */}
{/* Checkout heading */}
<div className="mb-10">
<h2 className="font-poppins text-2xl md:text-3xl lg:text-4xl leading-tight">
<span className="font-light">Checkout</span>{' '}
<span className="font-bold italic bg-gradient-to-r from-[#F95F62] to-[#F95FAF] bg-clip-text text-transparent">{checkoutItem.city}</span>
</h2>
</div>
<div className="flex flex-col lg:flex-row gap-10">
{/* Left column */}
<div className="flex-1 space-y-8">
{/* ── Card Type Selection (Figma cards) ── */}
<div>
<h3 className="font-poppins text-xl md:text-2xl leading-snug font-medium text-[#2a2a2a]">
Choose Your Card
</h3>
<p className="font-poppins text-sm leading-relaxed font-normal text-[#8e8e8e] mt-1 mb-4">
Select the card type that best suits your travel style
</p>
<div className="grid grid-cols-1 sm:grid-cols-2 gap-[16px]">
{/* Flexi */}
<button
onClick={() => handleCheckoutItemChange({ cardType: 'Flexi' })}
className="relative transition-all duration-200"
>
<FlexiCardPreview
city={checkoutItem.city}
adultPrice={priceTable.Flexi[checkoutItem.days] || 80}
childPrice={10}
isSelected={checkoutItem.cardType === 'Flexi'}
/>
</button>
{/* Unlimited */}
<button
onClick={() => handleCheckoutItemChange({ cardType: 'Unlimited' })}
className="relative transition-all duration-200"
>
<UnlimitedCardPreview
city={checkoutItem.city}
adultPrice={priceTable.Unlimited[checkoutItem.days] || 120}
childPrice={20}
isSelected={checkoutItem.cardType === 'Unlimited'}
/>
</button>
</div>
{/* ── Config Card (mobile only) — right after card selection ── */}
<div className="lg:hidden mt-6">
<CheckoutConfigCard
item={checkoutItem}
onChange={handleCheckoutItemChange}
onProceed={() => checkoutItem && onSecureCheckoutClick?.(checkoutItem)}
/>
</div>
{/* Features Comparison */}
<div className="mt-6 bg-[#f5f5f5] rounded-xl p-4">
<div className="grid grid-cols-[1fr_70px_70px] gap-y-0 items-center">
{/* Header */}
<p className="font-poppins font-medium text-sm text-[#2a2a2a] py-3">Features</p>
<p className="font-poppins font-medium text-sm text-[#2a2a2a] text-center py-3">Flexi</p>
<p className="font-poppins font-medium text-sm text-[#2a2a2a] text-center py-3">Unlimited</p>
{[
{ feature: 'Access to attractions', flexi: true, unlimited: true },
{ feature: 'Entry to attractions', flexi: true, unlimited: true },
{ feature: 'Access to experiences', flexi: true, unlimited: true },
{ feature: 'Entry to sites', flexi: false, unlimited: true },
{ feature: 'Access to venues', flexi: true, unlimited: true },
{ feature: 'Entry to events', flexi: true, unlimited: true },
{ feature: 'Access to experiences', flexi: false, unlimited: true },
{ feature: 'Access to Itinerary creation', flexi: false, unlimited: true },
{ feature: 'Access to postcard creation', flexi: false, unlimited: true },
].map((row, i) => (
<React.Fragment key={i}>
<p className="font-poppins font-normal text-[13px] text-[#2a2a2a] py-2.5 border-t border-[rgba(0,0,0,0.08)] flex items-center gap-1.5">
<span className="text-[#2a2a2a]"></span> {row.feature}
</p>
<div className="flex justify-center py-2.5 border-t border-[rgba(0,0,0,0.08)]">
{row.flexi ? (
<div className="w-5 h-5 rounded-full bg-[#F95F62] flex items-center justify-center">
<Check className="w-3 h-3 text-white" strokeWidth={3} />
</div>
) : (
<span className="font-poppins text-[13px] text-[rgba(0,0,0,0.3)]"></span>
)}
</div>
<div className="flex justify-center py-2.5 border-t border-[rgba(0,0,0,0.08)]">
{row.unlimited ? (
<div className="w-5 h-5 rounded-full bg-[#F95F62] flex items-center justify-center">
<Check className="w-3 h-3 text-white" strokeWidth={3} />
</div>
) : (
<span className="font-poppins text-[13px] text-[rgba(0,0,0,0.3)]"></span>
)}
</div>
</React.Fragment>
))}
</div>
</div>
</div>
{/* ── Offers ── */}
<div>
<h3 className="font-poppins text-xl md:text-2xl leading-snug font-medium text-[#2a2a2a]">
{checkoutItem.cardType} Card Offers
</h3>
<p className="font-poppins text-sm leading-relaxed font-normal text-[#8e8e8e] mt-1 mb-4">
Exclusive deals and discounts included with your {checkoutItem.cardType} pass
</p>
<div className="flex gap-3 overflow-x-auto pb-2 -mx-4 px-4 snap-x snap-mandatory scrollbar-hide">
{offers.map((offer, idx) => (
<div key={idx} className="relative bg-white rounded-xl shrink-0 w-[180px] h-[260px] snap-start">
<div className="flex flex-col gap-2 items-start overflow-hidden p-3 rounded-xl h-full">
<div className="h-[120px] w-full rounded-lg overflow-hidden shrink-0 relative">
<ImageWithFallback
src={offer.image}
alt={offer.title}
className="absolute inset-0 w-full h-full object-cover rounded-lg"
/>
</div>
<div className="w-full h-[44px] overflow-hidden">
<p className="font-['Poppins',sans-serif] font-normal text-[18px] text-black tracking-[-0.72px] leading-[22px] line-clamp-2">
{offer.title}
</p>
</div>
<div className="w-full flex-1">
<p className="font-['Poppins',sans-serif] font-normal text-[12px] text-[rgba(0,0,0,0.6)] leading-[16px] line-clamp-3">
{offer.description}
</p>
</div>
</div>
<div className="absolute inset-0 border border-[rgba(249,95,98,0.24)] rounded-xl pointer-events-none" />
</div>
))}
</div>
</div>
{/* ── Available Attractions ── */}
<div>
<div className="flex items-center justify-between">
<h3 className="font-poppins text-xl md:text-2xl leading-snug font-medium text-[#2a2a2a]">Available Attractions</h3>
<span className="font-poppins text-xs font-medium text-[#F95F62] bg-[#F95F62]/10 px-3 py-1 rounded-full">{attractions.length} included</span>
</div>
<p className="font-poppins text-sm leading-relaxed font-normal text-[#8e8e8e] mt-1 mb-4">
Explore all the experiences you can enjoy with your pass
</p>
<div className="grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-4 gap-3">
{attractions.map((a) => (
<div key={a.id} className="group relative rounded-xl overflow-hidden">
<div className="aspect-[4/3] relative">
<ImageWithFallback src={a.image} alt={a.name} className="absolute inset-0 w-full h-full object-cover transition-transform duration-500 group-hover:scale-105" />
<div className="absolute inset-0 bg-gradient-to-t from-black/60 via-black/10 to-transparent" />
<div className="absolute top-2 right-2">
<span className="inline-flex px-2 py-0.5 rounded-full bg-white/90 backdrop-blur-sm text-[10px] font-poppins font-medium text-[#555]">{a.category}</span>
</div>
<div className="absolute bottom-2 left-2 right-2">
<h6 className="font-poppins text-sm leading-snug font-medium text-white drop-shadow-sm">{a.name}</h6>
</div>
</div>
</div>
))}
</div>
</div>
</div>
{/* Right column: Config card (desktop only, sticky) */}
<div className="hidden lg:block lg:w-[420px] flex-shrink-0">
<div className="lg:sticky lg:top-28">
<CheckoutConfigCard
item={checkoutItem}
onChange={handleCheckoutItemChange}
onProceed={() => checkoutItem && onSecureCheckoutClick?.(checkoutItem)}
/>
</div>
</div>
</div>
</>
)}
</motion.div>
)}
</AnimatePresence>
<Footer
onHomeClick={onHomeClick} onPassesClick={onPassesClick} onAttractionsClick={onAttractionsClick}
onBlogsClick={onBlogsClick} onHowItWorksClick={onHowItWorksClick} onFAQClick={onFAQClick}
onPrivacyPolicyClick={onPrivacyPolicyClick} onAboutUsClick={onAboutUsClick} onContactUsClick={onContactUsClick}
/>
</div>
);
}
/* ─── Empty state ─── */
function EmptyState({ icon, title, description, actionLabel, onAction }: {
icon: React.ReactNode; title: string; description: string; actionLabel: string; onAction?: () => void;
}) {
return (
<motion.div initial={{ opacity: 0, scale: 0.98 }} animate={{ opacity: 1, scale: 1 }} transition={{ duration: 0.4 }} className="flex flex-col items-center justify-center py-20 max-w-sm mx-auto text-center">
<motion.div className="w-28 h-28 rounded-3xl bg-[#fee7e7]/50 flex items-center justify-center mb-6" animate={{ y: [0, -6, 0] }} transition={{ duration: 3, repeat: Infinity, ease: 'easeInOut' }}>{icon}</motion.div>
<h3 className="font-poppins text-xl md:text-2xl leading-snug font-medium text-[#2a2a2a] mb-2">{title}</h3>
<p className="font-poppins text-sm leading-relaxed font-normal text-[#8e8e8e] mb-8">{description}</p>
<motion.button whileHover={{ scale: 1.02 }} whileTap={{ scale: 0.98 }} onClick={onAction} className="bg-[#F95F62] text-white font-poppins text-base font-medium px-8 py-3.5 rounded-xl hover:bg-[#e8545a] transition-colors shadow-lg shadow-[#F95F62]/15 w-full">{actionLabel}</motion.button>
</motion.div>
);
}