@@ -690,7 +691,7 @@ export default function Navbar({
label: 'My Profile',
icon:
,
action: () => {
- navigate(citySelected?`/${slugify(cityName)}/profile`:'/profile');
+ navigate(citySelected ? `/${slugify(cityName)}/profile` : '/profile');
setActiveUserDropdown(false);
}
},
diff --git a/src/pages/CartPage.tsx b/src/pages/CartPage.tsx
new file mode 100644
index 0000000..497f6ec
--- /dev/null
+++ b/src/pages/CartPage.tsx
@@ -0,0 +1,877 @@
+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 './Navbar';
+import { Footer } from './Footer';
+import { ImageWithFallback } from './figma/ImageWithFallback';
+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
> = {
+ 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 = {
+ 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> = {
+ 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 (
+
+ {/* Card bg */}
+
+ {/* City image */}
+
+

+
+ {/* City name - left aligned */}
+
+ {/* Pricing */}
+
+
+ From
+ ${adultPrice}
+ /Adult
+
+
+ and
+ ${childPrice}
+ /Child
+
+
+ {/* Description */}
+
+
+ Dive into an extensive selection of thrilling destinations!
+
+
+ {/* Side tab - Flexi (pink) */}
+
+ Card
+ Flexi
+
+ {/* Selected checkmark */}
+ {isSelected && (
+
+
+
+ )}
+
+ );
+}
+
+function UnlimitedCardPreview({ city, adultPrice, childPrice, isSelected }: { city: string; adultPrice: number; childPrice: number; isSelected: boolean }) {
+ return (
+
+ {/* Card bg */}
+
+ {/* City image */}
+
+

+
+ {/* City name - left aligned */}
+
+ {/* Pricing */}
+
+
+ From
+ ${adultPrice}
+ /Adult
+
+
+ and
+ ${childPrice}
+ /Child
+
+
+ {/* Description */}
+
+
+ Dive into an extensive selection of thrilling destinations!
+
+
+ {/* Side tab - Unlimited (coral) */}
+
+ Card
+ Unlimited
+
+ {/* Selected checkmark */}
+ {isSelected && (
+
+
+
+ )}
+
+ );
+}
+
+/* ═══════════════════════════════════════════
+ CHECKOUT CONFIGURATION CARD (Mobile-first)
+ ═══════════════════════════════════════════ */
+
+function CheckoutConfigCard({
+ item,
+ onChange,
+ onProceed,
+}: {
+ item: CartItem;
+ onChange: (updates: Partial) => void;
+ onProceed: () => void;
+}) {
+ const [daysOpen, setDaysOpen] = useState(false);
+ const originalPrice = (item.pricePerUnit * item.quantity * 1.35);
+ const totalPrice = item.pricePerUnit * item.quantity;
+
+ return (
+
+ {/* City header */}
+
+
{item.city}
+
+
+ {item.cardType} Card
+
+
+
+
+ {/* Configuration rows */}
+
+ {/* No. of Adults */}
+
+
No. of Adults
+
+
+
{item.adults}
+
+
+
+
+ {/* No. of Children */}
+
+
No. of Children
+
+
+
{item.children}
+
+
+
+
+ {/* No. of Days (dropdown) */}
+
+
+ {item.cardType === 'Flexi' ? 'No. of Attractions' : 'No. of Days'}
+
+
+
+
+ {daysOpen && (
+
+ {dayOptions.map((d) => (
+
+ ))}
+
+ )}
+
+
+
+
+ {/* You Pay */}
+
+
You Pay
+
+
+ ${originalPrice.toFixed(0)}
+
+
+ ${totalPrice.toFixed(0)}
+
+
+
+
+
+ {/* Proceed button */}
+
+
+ Proceed to Pay
+
+
+
+ );
+}
+
+/* ═══════════════════════════════════════════
+ 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(initialCartItems);
+ const [selectedCardId, setSelectedCardId] = useState(null);
+ const [view, setView] = useState<'cart' | 'checkout'>('cart');
+ const [checkoutItem, setCheckoutItem] = useState(null);
+
+ 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 = () => {
+ const item = cartItems.find(i => i.id === selectedCardId);
+ if (item) {
+ setCheckoutItem({ ...item });
+ setView('checkout');
+ window.scrollTo({ top: 0, behavior: 'smooth' });
+ }
+ };
+
+ const handleBackToCart = () => {
+ setView('cart');
+ setCheckoutItem(null);
+ };
+
+ const handleCheckoutItemChange = (updates: Partial) => {
+ 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 => i.id === selectedCardId);
+ const attractions = checkoutItem ? (attractionsData[checkoutItem.city]?.[checkoutItem.cardType] || []) : [];
+ const offers = checkoutItem ? (offersData[checkoutItem.cardType] || []) : [];
+
+ return (
+
+
{}} 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}
+ />
+
+
+ {view === 'cart' ? (
+ /* ─── CART VIEW ─── */
+
+ {/* Header */}
+
+
+ Your{' '}
+ Cart
+
+
+ {isEmpty ? 'Your cart is empty' : `${cartItems.length} ${cartItems.length === 1 ? 'item' : 'items'} in your cart`}
+
+
+
+ {/* Tab switcher */}
+ {/* Cards listed directly below */}
+
+ {/* Content */}
+
+ {activeTab === 'cards' ? (
+
+ {isEmpty ? (
+ } 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} />
+ ) : (
+
+ {/* Table header (desktop) */}
+
+
City Cards
+
Travellers
+
Qty
+
Price
+
+
+
+
+ {cartItems.map((item) => {
+ const isSelected = selectedCardId === item.id;
+ const totalPrice = item.pricePerUnit * item.quantity;
+
+ return (
+ 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 */}
+
+ {isSelected && (
+
+
+
+ )}
+
+
+ {/* Mobile layout */}
+
+
+
+
+
+
+
+
{item.city}
+
+ {item.cardType}
+ {item.days}d
+
+
+
+
+
+
{item.adults}A · {item.children}C · Qty {item.quantity}
+
+ ${totalPrice.toFixed(2)}
+ {item.quantity > 1 && ${item.pricePerUnit.toFixed(2)}/ea}
+
+
+
+
+
+ {/* Desktop layout */}
+
+
+
+
+
+
+
{item.city}
+
+ {item.cardType} Card
+ {item.days} days
+
+
+
+
+
+ {item.adults}
+ {item.children}
+
+
+
+ {item.quantity}
+
+
+ ${totalPrice.toFixed(2)}
+ {item.quantity > 1 && ${item.pricePerUnit.toFixed(2)} per unit}
+
+
+
+
+
+
+ );
+ })}
+
+
+ {/* Bottom checkout bar */}
+
+
+ {selectedItem ? (
+ <>
+
+ Selected: {selectedItem.city} {selectedItem.cardType} · {selectedItem.days}d · Qty {selectedItem.quantity}
+
+
+ ${(selectedItem.pricePerUnit * selectedItem.quantity).toFixed(2)}
+
+ >
+ ) : (
+
Tap a card above to select it for checkout
+ )}
+
+
+ Secure Checkout
+
+
+
+ )}
+
+ ) : (
+
+ } 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} />
+
+ )}
+
+
+ ) : (
+ /* ─── CHECKOUT VIEW ─── */
+
+ {checkoutItem && (
+ <>
+ {/* Back */}
+
+
+ {/* Stepper */}
+
+
+ {/* Checkout heading */}
+
+
+ Checkout{' '}
+ {checkoutItem.city}
+
+
+
+
+
+ {/* Left column */}
+
+
+ {/* ── Card Type Selection (Figma cards) ── */}
+
+
+ Choose Your Card
+
+
+ Select the card type that best suits your travel style
+
+
+ {/* Flexi */}
+
+
+ {/* Unlimited */}
+
+
+
+ {/* ── Config Card (mobile only) — right after card selection ── */}
+
+ checkoutItem && onSecureCheckoutClick?.(checkoutItem)}
+ />
+
+
+ {/* Features Comparison */}
+
+
+ {/* Header */}
+
Features
+
Flexi
+
Unlimited
+ {[
+ { 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) => (
+
+
+ • {row.feature}
+
+
+ {row.flexi ? (
+
+
+
+ ) : (
+
–
+ )}
+
+
+ {row.unlimited ? (
+
+
+
+ ) : (
+
–
+ )}
+
+
+ ))}
+
+
+
+
+ {/* ── Offers ── */}
+
+
+ {checkoutItem.cardType} Card Offers
+
+
+ Exclusive deals and discounts included with your {checkoutItem.cardType} pass
+
+
+ {offers.map((offer, idx) => (
+
+
+
+
+
+
+
+
+ {offer.description}
+
+
+
+
+
+ ))}
+
+
+
+ {/* ── Available Attractions ── */}
+
+
+
Available Attractions
+ {attractions.length} included
+
+
+ Explore all the experiences you can enjoy with your pass
+
+
+ {attractions.map((a) => (
+
+
+
+
+
+ {a.category}
+
+
+
{a.name}
+
+
+
+
+ ))}
+
+
+
+
+ {/* Right column: Config card (desktop only, sticky) */}
+
+
+ checkoutItem && onSecureCheckoutClick?.(checkoutItem)}
+ />
+
+
+
+ >
+ )}
+
+ )}
+
+
+
+
+ );
+}
+
+/* ─── Empty state ─── */
+function EmptyState({ icon, title, description, actionLabel, onAction }: {
+ icon: React.ReactNode; title: string; description: string; actionLabel: string; onAction?: () => void;
+}) {
+ return (
+
+ {icon}
+ {title}
+ {description}
+ {actionLabel}
+
+ );
+}
\ No newline at end of file
diff --git a/src/pages/PaymentDetailsPage.tsx b/src/pages/PaymentDetailsPage.tsx
new file mode 100644
index 0000000..b863f0d
--- /dev/null
+++ b/src/pages/PaymentDetailsPage.tsx
@@ -0,0 +1,494 @@
+import React, { useState } from 'react';
+import { motion, AnimatePresence } from 'motion/react';
+import {
+ ArrowLeft, User, MapPin, Lock, Shield, ChevronDown,
+ Check, AlertCircle, Pencil, UserCheck, Gift
+} from 'lucide-react';
+import Navbar from '../components/Navbar';
+import { Footer } from '../components/Footer';
+import { Card, CardContent, CardHeader } from '../components/ui/card';
+import { Separator } from '../components/ui/separator';
+
+export interface CheckoutOrderItem {
+ city: string;
+ cardType: 'Flexi' | 'Unlimited';
+ days: number;
+ adults: number;
+ children: number;
+ quantity: number;
+ pricePerUnit: number;
+}
+
+interface PaymentDetailsPageProps {
+ checkoutOrder?: CheckoutOrderItem | null;
+ onBackClick: () => void;
+ onPaymentComplete: () => void;
+ onHomeClick: () => void;
+ onPassesClick: () => 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;
+ onCheckoutClick?: () => void;
+ onSignInClick: () => void;
+ onSignOutClick?: () => void;
+ currentPage?: string;
+ user?: { email: string; name: string } | null;
+}
+
+/* ─── Profile data ─── */
+const profileData = {
+ firstName: 'John',
+ lastName: 'Doe',
+ email: 'john.doe@example.com',
+ phone: '+1 (555) 123-4567',
+ address: '123 Main Street',
+ city: 'New York',
+ state: 'NY',
+ postcode: '10001',
+ country: 'United States',
+};
+
+/* ─── Editable field ─── */
+function Field({
+ label, value, onChange, placeholder, type = 'text',
+ error, maxLength, inputMode, prefilled,
+}: {
+ label: string; value: string; onChange: (v: string) => void; placeholder?: string;
+ type?: string; error?: string; maxLength?: number;
+ inputMode?: React.HTMLAttributes['inputMode']; prefilled?: boolean;
+}) {
+ const [focused, setFocused] = useState(false);
+ return (
+
+
+
+
onChange(e.target.value)}
+ onFocus={() => setFocused(true)}
+ onBlur={() => setFocused(false)}
+ placeholder={placeholder}
+ maxLength={maxLength}
+ inputMode={inputMode}
+ className={`w-full border rounded-xl px-4 py-3 pr-10 font-poppins text-base font-normal text-[#2a2a2a] outline-none transition-all duration-200 placeholder:text-[#ccc] ${
+ error
+ ? 'border-red-300 focus:border-red-400 bg-red-50/30'
+ : focused
+ ? 'border-[#F95F62] ring-2 ring-[#F95F62]/10'
+ : prefilled
+ ? 'border-[#F95F62]/25 bg-[#F95F62]/[0.02]'
+ : 'border-gray-200'
+ }`}
+ />
+ {prefilled && !focused && (
+
+ )}
+
+ {error && (
+
+ {error}
+
+ )}
+
+ );
+}
+
+/* ─── Card type badge ─── */
+function CardTypeBadge({ cardType }: { cardType: 'Flexi' | 'Unlimited' }) {
+ return (
+
+ {cardType} Card
+
+ );
+}
+
+/* ─── Main component ─── */
+export function PaymentDetailsPage({
+ checkoutOrder,
+ onBackClick,
+ onPaymentComplete,
+ onHomeClick,
+ onPassesClick,
+ onAttractionsClick,
+ onBlogsClick,
+ onHowItWorksClick,
+ onFAQClick,
+ onPrivacyPolicyClick,
+ onAboutUsClick,
+ onProfileClick,
+ onCityCardsClick,
+ onMagicItineraryClick,
+ onPostCardsClick,
+ onOffersClick,
+ onSuperSavingsClick,
+ onEsimsClick,
+ onHotelDiscountsClick,
+ onContactUsClick,
+ onCartClick,
+ onCheckoutClick,
+ onSignInClick,
+ onSignOutClick,
+ currentPage,
+ user,
+}: PaymentDetailsPageProps) {
+
+ /* ── Purchase type ── */
+ const [selectedTab, setSelectedTab] = useState<'myself' | 'gift'>('myself');
+ const [giftName, setGiftName] = useState('');
+ const [giftEmail, setGiftEmail] = useState('');
+
+ /* ── Personal Info ── */
+ const [firstName, setFirstName] = useState(profileData.firstName);
+ const [lastName, setLastName] = useState(profileData.lastName);
+ const [email, setEmail] = useState(user?.email || profileData.email);
+ const [phone, setPhone] = useState(profileData.phone);
+
+ /* ── Billing Address ── */
+ const [address, setAddress] = useState(profileData.address);
+ const [billingCity, setBillingCity] = useState(profileData.city);
+ const [state, setState] = useState(profileData.state);
+ const [postcode, setPostcode] = useState(profileData.postcode);
+ const [country, setCountry] = useState(profileData.country);
+
+ /* ── Validation ── */
+ const [errors, setErrors] = useState>({});
+ const [submitting, setSubmitting] = useState(false);
+
+ const order = checkoutOrder || {
+ city: 'Melbourne', cardType: 'Flexi' as const,
+ days: 3, adults: 2, children: 0, quantity: 1, pricePerUnit: 49.50,
+ };
+
+ const subtotal = order.pricePerUnit * order.quantity;
+ const tax = subtotal * 0.1;
+ const total = subtotal + tax;
+
+ const validate = () => {
+ const e: Record = {};
+ if (!firstName.trim()) e.firstName = 'Required';
+ if (!lastName.trim()) e.lastName = 'Required';
+ if (!email.trim() || !/\S+@\S+\.\S+/.test(email)) e.email = 'Valid email required';
+ if (!phone.trim()) e.phone = 'Required';
+ if (!address.trim()) e.address = 'Required';
+ if (!billingCity.trim()) e.billingCity = 'Required';
+ if (!postcode.trim()) e.postcode = 'Required';
+ return e;
+ };
+
+ const handleSubmit = () => {
+ const e = validate();
+ setErrors(e);
+ if (Object.keys(e).length > 0) return;
+ setSubmitting(true);
+ setTimeout(() => {
+ setSubmitting(false);
+ onPaymentComplete();
+ }, 1800);
+ };
+
+ return (
+
+
{}} 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}
+ />
+
+
+
+ {/* Back */}
+
+
+ {/* Page heading */}
+
+
+
+ Secure{' '}
+ Checkout
+
+
+
+ SSL Secured
+
+
+
+ Complete your purchase securely. Your payment information is protected.
+
+
+
+
+
+ {/* ── LEFT: Forms ── */}
+
+
+
+ {/* Purchase type tabs */}
+
+
+
+
+
+
+
+
+
+ {/* Pre-filled notice banner */}
+
+
+
+
+
+ Details pre-filled from your profile.{' '}
+ All fields are editable — just tap to make changes.
+
+
+
+
+
+ {/* 1. Personal Information */}
+
+
+
+ 1
+
+
Personal Information
+
+
+
+
+
+
+
+
+
+
+
+ {/* Gift recipient section */}
+
+ {selectedTab === 'gift' && (
+
+
+
+
+
Gift Recipient Details
+
+
+
+
+
+
+
+ )}
+
+
+
+
+ {/* 2. Billing Address */}
+
+
+
+ 2
+
+
Billing Address
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {/* ── RIGHT: Order Summary ── */}
+
+
+
+
+
+
Order Summary
+
+
+ {/* Card info */}
+
+
+
+ {order.cardType}
+
+
+
+
{order.city}
+
+
+
+ {order.cardType === 'Flexi' ? `${order.days} Attractions` : `${order.days} Days`}
+
+
+
+
+ {[
+ { label: 'Adults', value: order.adults },
+ { label: 'Children', value: order.children },
+ { label: 'Qty', value: order.quantity },
+ ].map(({ label, value }) => (
+
+ {label}
+ {value}
+
+ ))}
+
+
+
+ {/* Pricing breakdown */}
+
+
+
+ Subtotal
+ ${subtotal.toFixed(2)}
+
+
+ GST (10%)
+ ${tax.toFixed(2)}
+
+
+ Booking fee
+ Free
+
+
+ Total
+ ${total.toFixed(2)}
+
+
+
+
+
+ {/* CTA */}
+
+ {submitting ? (
+ <>
+
+ Processing…
+ >
+ ) : (
+ <>
+
+ Complete Payment · ${total.toFixed(2)}
+ >
+ )}
+
+
+
+ By completing your purchase you agree to our Terms of Service and Privacy Policy
+
+
+
+
+
+
+
+
+
+ );
+}