rename and replace the design files with actual page names
This commit is contained in:
@@ -6,17 +6,13 @@ import { MelbournePage } from './pages/MelbournePage';
|
|||||||
import { PassesPage } from './pages/PassesPage';
|
import { PassesPage } from './pages/PassesPage';
|
||||||
import { AttractionsPage } from './pages/AttractionsPage';
|
import { AttractionsPage } from './pages/AttractionsPage';
|
||||||
import { AttractionDetailsPage } from './pages/AttractionDetailsPage';
|
import { AttractionDetailsPage } from './pages/AttractionDetailsPage';
|
||||||
import { CheckoutPage } from './pages/CheckoutPage';
|
|
||||||
import { SecureCheckoutPage } from './pages/SecureCheckoutPage';
|
import { SecureCheckoutPage } from './pages/SecureCheckoutPage';
|
||||||
import { BlogsPage } from './pages/BlogsPage';
|
import { BlogsPage } from './pages/BlogsPage';
|
||||||
import { BlogDetailsPage } from './pages/BlogDetailsPage';
|
import { BlogDetailsPage } from './pages/BlogDetailsPage';
|
||||||
import { HowItWorksPage } from './components/HowItWorksPage';
|
|
||||||
import { FAQPage } from './components/FAQPage';
|
import { FAQPage } from './components/FAQPage';
|
||||||
import { PrivacyPolicyPage } from './pages/PrivacyPolicyPage';
|
import { PrivacyPolicyPage } from './pages/PrivacyPolicyPage';
|
||||||
import { AboutUsPage } from './pages/AboutUsPage';
|
import { AboutUsPage } from './pages/AboutUsPage';
|
||||||
import { ProfilePage } from './pages/ProfilePage';
|
import { ProfilePage } from './pages/ProfilePage';
|
||||||
import { CreateMagicItineraryPage } from './pages/CreateMagicItineraryPage';
|
|
||||||
import { ItineraryViewPage } from './pages/ItineraryViewPage';
|
|
||||||
import { OffersPage } from './pages/OffersPage';
|
import { OffersPage } from './pages/OffersPage';
|
||||||
import { CityCardsPage } from './pages/CityCardsPage';
|
import { CityCardsPage } from './pages/CityCardsPage';
|
||||||
import { MagicItineraryPage } from './pages/MagicItineraryPage';
|
import { MagicItineraryPage } from './pages/MagicItineraryPage';
|
||||||
@@ -24,7 +20,6 @@ import { PostCardsPage } from './pages/PostCardsPage';
|
|||||||
import { DownloadAppPage } from './pages/DownloadAppPage';
|
import { DownloadAppPage } from './pages/DownloadAppPage';
|
||||||
import { HotelDiscountsPage } from './pages/HotelDiscountsPage';
|
import { HotelDiscountsPage } from './pages/HotelDiscountsPage';
|
||||||
import { ContactUsPage } from './pages/ContactUsPage';
|
import { ContactUsPage } from './pages/ContactUsPage';
|
||||||
|
|
||||||
import { pageTransition } from './utils/animations';
|
import { pageTransition } from './utils/animations';
|
||||||
import { LandingPage } from './pages/landingPage';
|
import { LandingPage } from './pages/landingPage';
|
||||||
import ComingSoonPage from './pages/ComingSoonPage';
|
import ComingSoonPage from './pages/ComingSoonPage';
|
||||||
@@ -34,15 +29,14 @@ import { LandingMagicItineraryPage } from './pages/LandingMagicItineraryPage';
|
|||||||
import { DiscoverPage } from './pages/DiscoverPage';
|
import { DiscoverPage } from './pages/DiscoverPage';
|
||||||
import { CartPage } from './pages/CartPage';
|
import { CartPage } from './pages/CartPage';
|
||||||
import { PaymentDetailsPage } from './pages/PaymentDetailsPage';
|
import { PaymentDetailsPage } from './pages/PaymentDetailsPage';
|
||||||
import { CartPageDesign } from './pages/CartPageDesign';
|
|
||||||
import { CheckoutPage2 } from './pages/CheckoutPage2';
|
|
||||||
import { SuperSavingsDetailsPage } from './pages/SuperSavingsDetailsPage';
|
import { SuperSavingsDetailsPage } from './pages/SuperSavingsDetailsPage';
|
||||||
import { ViewCardDetailsPage } from './pages/ViewCardDetailsPageDesign';
|
import { ViewCardDetailsPage } from './pages/ViewCardDetailsPage';
|
||||||
import { CreateMagicItineraryPageDesign } from './pages/CreateMagicIternaryPageDesign';
|
|
||||||
import { ItineraryViewPageDesign } from './pages/ItineraryViewPageDesign';
|
|
||||||
import ItinerarySummaryPage from './pages/ItinerarySummaryPage';
|
import ItinerarySummaryPage from './pages/ItinerarySummaryPage';
|
||||||
import { PaymentSuccessPage } from './pages/PaymentSuccessPage';
|
import { PaymentSuccessPage } from './pages/PaymentSuccessPage';
|
||||||
import { PaymentCancelPage } from './pages/PaymentCancelPage';
|
import { PaymentCancelPage } from './pages/PaymentCancelPage';
|
||||||
|
import { ItineraryViewPage } from './pages/ItineraryViewPage';
|
||||||
|
import { CheckoutPage } from './pages/CheckoutPage';
|
||||||
|
import { CreateMagicItineraryPage } from './pages/CreateMagicIternaryPage';
|
||||||
|
|
||||||
// User type definition
|
// User type definition
|
||||||
interface User {
|
interface User {
|
||||||
@@ -134,13 +128,6 @@ export function AppRouter({
|
|||||||
</motion.div>
|
</motion.div>
|
||||||
} />
|
} />
|
||||||
|
|
||||||
{/* Checkout Routes */}
|
|
||||||
{/* <Route path="/checkout" element={
|
|
||||||
<motion.div key="checkout" {...pageTransition}>
|
|
||||||
<CheckoutPage {...commonNavHandlers} />
|
|
||||||
</motion.div>
|
|
||||||
} /> */}
|
|
||||||
|
|
||||||
<Route path="/secure-checkout" element={
|
<Route path="/secure-checkout" element={
|
||||||
<motion.div key="secure-checkout" {...pageTransition}>
|
<motion.div key="secure-checkout" {...pageTransition}>
|
||||||
<SecureCheckoutPage {...commonNavHandlers} />
|
<SecureCheckoutPage {...commonNavHandlers} />
|
||||||
@@ -200,7 +187,7 @@ export function AppRouter({
|
|||||||
<ProfilePage {...commonNavHandlers} />
|
<ProfilePage {...commonNavHandlers} />
|
||||||
</motion.div>
|
</motion.div>
|
||||||
} />
|
} />
|
||||||
<Route path="/view-card-design/:cardId" element={
|
<Route path="/view-card-details/:cardId" element={
|
||||||
<motion.div key="profile" {...pageTransition}>
|
<motion.div key="profile" {...pageTransition}>
|
||||||
<ViewCardDetailsPage {...commonNavHandlers} />
|
<ViewCardDetailsPage {...commonNavHandlers} />
|
||||||
</motion.div>
|
</motion.div>
|
||||||
@@ -213,22 +200,12 @@ export function AppRouter({
|
|||||||
<CreateMagicItineraryPage {...commonNavHandlers} />
|
<CreateMagicItineraryPage {...commonNavHandlers} />
|
||||||
</motion.div>
|
</motion.div>
|
||||||
} />
|
} />
|
||||||
<Route path="/create-itinerary-design" element={
|
|
||||||
<motion.div key="create-itinerary" {...pageTransition}>
|
|
||||||
<CreateMagicItineraryPageDesign {...commonNavHandlers} />
|
|
||||||
</motion.div>
|
|
||||||
} />
|
|
||||||
|
|
||||||
<Route path="/itinerary-view" element={
|
<Route path="/view-itinerary/:itineraryId" element={
|
||||||
<motion.div key="itinerary-view" {...pageTransition}>
|
<motion.div key="itinerary-view" {...pageTransition}>
|
||||||
<ItineraryViewPage {...commonNavHandlers} />
|
<ItineraryViewPage {...commonNavHandlers} />
|
||||||
</motion.div>
|
</motion.div>
|
||||||
} />
|
} />
|
||||||
<Route path="/itinerary-view-design/:itineraryId" element={
|
|
||||||
<motion.div key="itinerary-view" {...pageTransition}>
|
|
||||||
<ItineraryViewPageDesign {...commonNavHandlers} />
|
|
||||||
</motion.div>
|
|
||||||
} />
|
|
||||||
<Route path="/itinerary-summary/:itineraryId" element={
|
<Route path="/itinerary-summary/:itineraryId" element={
|
||||||
<motion.div key="itinerary-summary" {...pageTransition}>
|
<motion.div key="itinerary-summary" {...pageTransition}>
|
||||||
<ItinerarySummaryPage {...commonNavHandlers} />
|
<ItinerarySummaryPage {...commonNavHandlers} />
|
||||||
@@ -310,14 +287,10 @@ export function AppRouter({
|
|||||||
} />
|
} />
|
||||||
<Route path="/checkout" element={
|
<Route path="/checkout" element={
|
||||||
<motion.div key="super-savings" {...pageTransition}>
|
<motion.div key="super-savings" {...pageTransition}>
|
||||||
<CheckoutPage2 {...commonNavHandlers} />
|
<CheckoutPage {...commonNavHandlers} />
|
||||||
</motion.div>
|
|
||||||
} />
|
|
||||||
<Route path="/cart-design" element={
|
|
||||||
<motion.div key="super-savings" {...pageTransition}>
|
|
||||||
<CartPageDesign {...commonNavHandlers} />
|
|
||||||
</motion.div>
|
</motion.div>
|
||||||
} />
|
} />
|
||||||
|
|
||||||
<Route path="/payment/:bookingId" element={
|
<Route path="/payment/:bookingId" element={
|
||||||
<motion.div key="super-savings" {...pageTransition}>
|
<motion.div key="super-savings" {...pageTransition}>
|
||||||
<PaymentDetailsPage {...commonNavHandlers} />
|
<PaymentDetailsPage {...commonNavHandlers} />
|
||||||
|
|||||||
@@ -1,878 +0,0 @@
|
|||||||
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'
|
|
||||||
|
|
||||||
/* ─── 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 CartPageDesignProps {
|
|
||||||
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;
|
|
||||||
|
|
||||||
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={onProceed}
|
|
||||||
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 CartPageDesign({
|
|
||||||
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,
|
|
||||||
}: CartPageDesignProps) {
|
|
||||||
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 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<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 => 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">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) => {
|
|
||||||
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.image} alt={item.city} 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}</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.cardType === 'Flexi' ? 'bg-[#F95FAF]/10 text-[#F95FAF]' : 'bg-[#F95F62]/10 text-[#F95F62]'}`}>{item.cardType}</span>
|
|
||||||
<span className="font-poppins text-xs font-normal text-[#8e8e8e]">{item.days}d</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.adults}A · {item.children}C · Qty {item.quantity}</span>
|
|
||||||
<div className="text-right">
|
|
||||||
<span className="font-poppins text-base font-medium text-[#F95F62] tracking-tight">${totalPrice.toFixed(2)}</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.image} alt={item.city} 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}</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.cardType === 'Flexi' ? 'bg-[#F95FAF]/10 text-[#F95FAF]' : 'bg-[#F95F62]/10 text-[#F95F62]'}`}>{item.cardType} Card</span>
|
|
||||||
<span className="flex items-center gap-1 font-poppins text-xs font-normal text-[#8e8e8e]"><Calendar className="w-3 h-3" />{item.days} 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.adults}</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.children}</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">${totalPrice.toFixed(2)}</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} {selectedItem.cardType} · {selectedItem.days}d · Qty {selectedItem.quantity}
|
|
||||||
</p>
|
|
||||||
<p className="font-poppins text-2xl font-medium text-[#F95F62] tracking-tight mt-0.5">
|
|
||||||
${(selectedItem.pricePerUnit * selectedItem.quantity).toFixed(2)}
|
|
||||||
</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}
|
|
||||||
disabled={!selectedItem}
|
|
||||||
className={`w-full 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>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,886 +0,0 @@
|
|||||||
import React, { useEffect, useState } from 'react';
|
|
||||||
import { motion, AnimatePresence } from 'motion/react';
|
|
||||||
import {
|
|
||||||
ArrowLeft, Check, Minus, Plus, ChevronDown
|
|
||||||
} 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 { useAddCardToCartMutation, useGetCheckoutPageDataQuery } from '../Redux/services/cards.service';
|
|
||||||
import LoadingSpinner from '../components/LoadingSpinner';
|
|
||||||
import { toast } from 'sonner';
|
|
||||||
|
|
||||||
/* ─── 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ─── Data (Same as Original) ─── */
|
|
||||||
const dayOptions = [3, 6, 12, 18, 24];
|
|
||||||
|
|
||||||
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 },
|
|
||||||
};
|
|
||||||
|
|
||||||
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' },
|
|
||||||
],
|
|
||||||
};
|
|
||||||
|
|
||||||
/* ─── FIGMA CARD PREVIEWS (Exact Copy) ─── */
|
|
||||||
function FlexiCardPreview({ city, adultPrice, childPrice, isSelected, image }: { city: string; adultPrice: number; childPrice: number; isSelected: boolean, image: string; }) {
|
|
||||||
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={image} />
|
|
||||||
</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, image }: { city: string; adultPrice: number; childPrice: number; isSelected: boolean, image: string; }) {
|
|
||||||
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={image} />
|
|
||||||
</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>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ─── CheckoutConfigCard (Exact Copy) ─── */
|
|
||||||
function CheckoutConfigCard({
|
|
||||||
item,
|
|
||||||
onProceed,
|
|
||||||
}: {
|
|
||||||
item: any;
|
|
||||||
onProceed: () => void;
|
|
||||||
}) {
|
|
||||||
const [dropdownOpen, setDropdownOpen] = useState(false);
|
|
||||||
const [noOfAdults, setNoOfAdults] = useState(1)
|
|
||||||
const [noOfChildren, setNoOfChildren] = useState(0)
|
|
||||||
const [noOfAttractions, setNoOfAttractions] = useState(item?.minNumber);
|
|
||||||
const [noOfDays, setNoOfDays] = useState(item?.minNumber)
|
|
||||||
|
|
||||||
const cityId = localStorage.getItem("cityId")
|
|
||||||
const cityName = localStorage.getItem("cityName")
|
|
||||||
const cardTypeId = item?.cardType?.id
|
|
||||||
const cardId = item?.id
|
|
||||||
const cardMode = item?.cardType?.name === "selective_pass" ? "flexi" : "unlimited"
|
|
||||||
const adultPrice = item?.adultPrice * noOfAdults
|
|
||||||
const childPrice = item?.childPrice * noOfChildren
|
|
||||||
const basePrice = adultPrice + childPrice
|
|
||||||
const taxAmount = basePrice * 0.1
|
|
||||||
const strikedPrice = basePrice + 20
|
|
||||||
|
|
||||||
const [addCardToCart] = useAddCardToCartMutation()
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setNoOfAttractions(item?.minNumber)
|
|
||||||
setNoOfDays(item?.minNumber)
|
|
||||||
}, [item])
|
|
||||||
|
|
||||||
const numberArray = Array.from(
|
|
||||||
{ length: item?.maxNumber - item?.minNumber + 1 },
|
|
||||||
(_, i) => item?.minNumber + i
|
|
||||||
);
|
|
||||||
const navigate = useNavigate();
|
|
||||||
|
|
||||||
const cardBookingDetails = {
|
|
||||||
cityXid: cityId,
|
|
||||||
cardTypeXid: cardTypeId,
|
|
||||||
cardXid: cardId,
|
|
||||||
cardMode, // stays as-is
|
|
||||||
totalAdult: noOfAdults,
|
|
||||||
baseAmount: basePrice, // static value
|
|
||||||
taxAmount,
|
|
||||||
totalChild: noOfChildren,
|
|
||||||
noOfAttractions,
|
|
||||||
noOfDays
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleProceedToPayment = async () => {
|
|
||||||
try {
|
|
||||||
console.log("Adding card to cart", cardBookingDetails);
|
|
||||||
const response = await addCardToCart(cardBookingDetails);
|
|
||||||
console.log(response)
|
|
||||||
const bookingId = response?.data?.id
|
|
||||||
navigate(`/payment/${bookingId}`)
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Error adding card to cart:", error);
|
|
||||||
toast.error("Failed to move forward. Please try again.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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]">
|
|
||||||
<div className="pt-6 pb-2 text-center">
|
|
||||||
<h4 className="font-poppins text-lg leading-snug font-medium text-[#2a2a2a]">{cityName}</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?.name === 'selective_pass' ? 'bg-[#f95faf]/10 text-[#f95faf]' : 'bg-[#f95f62]/10 text-[#f95f62]'}`}>
|
|
||||||
{item?.cardType?.displayName}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="px-6 py-4 space-y-0">
|
|
||||||
<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={() => noOfAdults > 1 && setNoOfAdults((prev) => prev - 1)} disabled={noOfAdults <= 1} className={`w-8 h-8 rounded-full flex items-center justify-center transition-colors ${noOfAdults <= 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">{noOfAdults}</span>
|
|
||||||
<button onClick={() => setNoOfAdults((prev) => prev + 1)} disabled={noOfAdults >= 15} className={`w-8 h-8 rounded-full flex items-center justify-center transition-colors ${noOfAdults >= 15 ? 'bg-gray-100 text-gray-300 cursor-not-allowed' : 'bg-[#f95f62]/10 text-[#f95f62] hover:bg-[#f95f62]/20'}`}>
|
|
||||||
<Plus className="w-4 h-4" />
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<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={() => noOfChildren > 0 && setNoOfChildren((prev) => prev - 1)} disabled={noOfChildren <= 0} className={`w-8 h-8 rounded-full flex items-center justify-center transition-colors ${noOfChildren <= 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">{noOfChildren}</span>
|
|
||||||
<button onClick={() => setNoOfChildren((prev) => prev + 1)} disabled={noOfChildren >= 10} className={`w-8 h-8 rounded-full flex items-center justify-center transition-colors ${noOfChildren >= 10 ? 'bg-gray-100 text-gray-300 cursor-not-allowed' : 'bg-[#f95f62]/10 text-[#f95f62] hover:bg-[#f95f62]/20'}`}>
|
|
||||||
<Plus className="w-4 h-4" />
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<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?.name === 'selective_pass' ? 'No. of Attractions' : 'No. of Days'}
|
|
||||||
</span>
|
|
||||||
<div className="relative">
|
|
||||||
<button onClick={() => setDropdownOpen(!dropdownOpen)} 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">{cardMode === "flexi" ? noOfAttractions : noOfDays}</span>
|
|
||||||
<ChevronDown className={`w-4 h-4 text-[#f95f62] transition-transform ${dropdownOpen ? 'rotate-180' : ''}`} />
|
|
||||||
</button>
|
|
||||||
<AnimatePresence>
|
|
||||||
{dropdownOpen && (
|
|
||||||
<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 }}
|
|
||||||
className="absolute right-0 top-full mt-1 bg-white rounded-lg shadow-lg border border-gray-100 z-30 min-w-[72px]
|
|
||||||
max-h-48 overflow-y-auto"
|
|
||||||
>
|
|
||||||
{numberArray.map((i) => (
|
|
||||||
<button
|
|
||||||
key={i}
|
|
||||||
onClick={() => {
|
|
||||||
cardMode === "flexi" ? setNoOfAttractions(i) : setNoOfDays(i);
|
|
||||||
setDropdownOpen(false);
|
|
||||||
}}
|
|
||||||
className={`w-full px-3 py-2 text-left font-poppins text-sm transition-colors ${(cardMode === "flexi" ? noOfAttractions === i : noOfDays === i)
|
|
||||||
? "bg-[#f95f62]/10 text-[#f95f62] font-medium"
|
|
||||||
: "text-[#2a2a2a] hover:bg-gray-50 font-normal"
|
|
||||||
}`}
|
|
||||||
|
|
||||||
>
|
|
||||||
{i}
|
|
||||||
</button>
|
|
||||||
))}
|
|
||||||
</motion.div>
|
|
||||||
)}
|
|
||||||
</AnimatePresence>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<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">${strikedPrice}</span>
|
|
||||||
<span className="font-poppins text-2xl font-medium text-[#f95f62] tracking-tight">${basePrice}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="px-6 pb-6">
|
|
||||||
<motion.button whileHover={{ scale: 1.01 }} whileTap={{ scale: 0.98 }} onClick={handleProceedToPayment} 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 cursor-pointer">
|
|
||||||
Proceed to Pay
|
|
||||||
</motion.button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ─── MAIN CHECKOUT PAGE 2 ─── */
|
|
||||||
export function CheckoutPage2({
|
|
||||||
onHomeClick,
|
|
||||||
onPassesClick,
|
|
||||||
onAttractionsClick,
|
|
||||||
onBlogsClick,
|
|
||||||
onHowItWorksClick,
|
|
||||||
onFAQClick,
|
|
||||||
onPrivacyPolicyClick,
|
|
||||||
onAboutUsClick,
|
|
||||||
onContactUsClick,
|
|
||||||
onSignInClick,
|
|
||||||
onSignOutClick,
|
|
||||||
onProfileClick,
|
|
||||||
user,
|
|
||||||
currentPage,
|
|
||||||
}: any) {
|
|
||||||
const navigate = useNavigate();
|
|
||||||
|
|
||||||
// Default item (you can pass via props later)
|
|
||||||
const baseUrl = import.meta.env.VITE_BASE_URL;
|
|
||||||
|
|
||||||
const cityId = localStorage.getItem("cityId")
|
|
||||||
const { data: checkoutPageData, isLoading } = useGetCheckoutPageDataQuery(cityId)
|
|
||||||
|
|
||||||
const cityName = checkoutPageData?.city?.name ?? ""
|
|
||||||
const cityImage = checkoutPageData?.city?.heroBanner?.image ?? ""
|
|
||||||
const cards = checkoutPageData?.cards ?? []
|
|
||||||
const flexiCard = checkoutPageData?.cards[0] ?? null
|
|
||||||
const unlimitedCard = checkoutPageData?.cards[1] ?? null
|
|
||||||
const attractions = checkoutPageData?.attractions ?? [];
|
|
||||||
|
|
||||||
const [checkoutItem, setCheckoutItem] = useState(flexiCard);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setCheckoutItem(flexiCard)
|
|
||||||
}, [cards])
|
|
||||||
|
|
||||||
console.log(checkoutItem)
|
|
||||||
|
|
||||||
if (isLoading) {
|
|
||||||
return <LoadingSpinner />
|
|
||||||
} else {
|
|
||||||
// console.log(flexiCard)
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleCheckoutItemChange = (cardObject: any) => {
|
|
||||||
setCheckoutItem(cardObject);
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="min-h-screen bg-[#fafafa] font-poppins">
|
|
||||||
<Navbar
|
|
||||||
activeCity="Melbourne"
|
|
||||||
onCityChange={() => { }}
|
|
||||||
onSignInClick={onSignInClick}
|
|
||||||
onSignOutClick={onSignOutClick}
|
|
||||||
onPassesClick={onPassesClick}
|
|
||||||
onCheckoutClick={() => { }}
|
|
||||||
onHomeClick={onHomeClick}
|
|
||||||
onAttractionsClick={onAttractionsClick}
|
|
||||||
onBlogsClick={onBlogsClick}
|
|
||||||
onHowItWorksClick={onHowItWorksClick}
|
|
||||||
onFAQClick={onFAQClick}
|
|
||||||
onPrivacyPolicyClick={onPrivacyPolicyClick}
|
|
||||||
onAboutUsClick={onAboutUsClick}
|
|
||||||
onProfileClick={onProfileClick}
|
|
||||||
onCityCardsClick={() => { }}
|
|
||||||
onMagicItineraryClick={() => { }}
|
|
||||||
onPostCardsClick={() => { }}
|
|
||||||
onOffersClick={() => { }}
|
|
||||||
onSuperSavingsClick={() => { }}
|
|
||||||
onEsimsClick={() => { }}
|
|
||||||
onHotelDiscountsClick={() => { }}
|
|
||||||
onCartClick={() => { }}
|
|
||||||
currentPage={currentPage}
|
|
||||||
user={user}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<div className="w-full px-4 sm:px-6 lg:px-10 xl:px-16 pt-32 pb-24 max-w-[1440px] mx-auto">
|
|
||||||
<button onClick={() => navigate(-1)} 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
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<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 pr-2">{cityName}</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 */}
|
|
||||||
<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]">
|
|
||||||
<button onClick={() => handleCheckoutItemChange(flexiCard)}>
|
|
||||||
<FlexiCardPreview city={cityName} image={cityImage} adultPrice={flexiCard.adultPrice} childPrice={flexiCard.childPrice} isSelected={checkoutItem?.cardType.name === 'selective_pass'} />
|
|
||||||
</button>
|
|
||||||
<button onClick={() => handleCheckoutItemChange(unlimitedCard)}>
|
|
||||||
<UnlimitedCardPreview city={cityName} image={cityImage} adultPrice={unlimitedCard.adultPrice} childPrice={unlimitedCard.childPrice} isSelected={checkoutItem?.cardType.name === 'unlimited_card'} />
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Features Comparison (Exact Copy) */}
|
|
||||||
<div className="mt-6 bg-[#f5f5f5] rounded-xl p-4">
|
|
||||||
<div className="grid grid-cols-[1fr_70px_70px] gap-y-0 items-center">
|
|
||||||
<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 Section (Exact) */}
|
|
||||||
<div>
|
|
||||||
<h3 className="font-poppins text-xl md:text-2xl leading-snug font-medium text-[#2a2a2a]">{checkoutItem?.cardType?.displayName} 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.displayName} pass</p>
|
|
||||||
<div className="flex gap-3 overflow-x-auto pb-2 -mx-4 px-4 snap-x snap-mandatory scrollbar-hide">
|
|
||||||
{checkoutItem?.offers.map((offer: any) => (
|
|
||||||
<div key={offer.id} 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={`${baseUrl}/${offer.websiteBannerImage}`} 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>
|
|
||||||
|
|
||||||
{/* Attractions Section (Exact) */}
|
|
||||||
<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: any) => (
|
|
||||||
<div key={a.id} className="group relative rounded-xl overflow-hidden">
|
|
||||||
<div className="aspect-[4/3] relative">
|
|
||||||
<ImageWithFallback src={a.thumbnail} alt={a.title} 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.title}</h6>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Right Column - Config Card */}
|
|
||||||
<div className="hidden lg:block lg:w-[420px] flex-shrink-0">
|
|
||||||
<div className="lg:sticky lg:top-28">
|
|
||||||
<CheckoutConfigCard
|
|
||||||
item={checkoutItem}
|
|
||||||
onChange={handleCheckoutItemChange}
|
|
||||||
onProceed={() => navigate("/payment")}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Mobile Config Card */}
|
|
||||||
{/* <div className="lg:hidden mt-6">
|
|
||||||
<CheckoutConfigCard
|
|
||||||
item={checkoutItem}
|
|
||||||
onChange={handleCheckoutItemChange}
|
|
||||||
onProceed={() => navigate("/payment")}
|
|
||||||
/>
|
|
||||||
</div> */}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<Footer
|
|
||||||
onHomeClick={onHomeClick}
|
|
||||||
onPassesClick={onPassesClick}
|
|
||||||
onAttractionsClick={onAttractionsClick}
|
|
||||||
onBlogsClick={onBlogsClick}
|
|
||||||
onHowItWorksClick={onHowItWorksClick}
|
|
||||||
onFAQClick={onFAQClick}
|
|
||||||
onPrivacyPolicyClick={onPrivacyPolicyClick}
|
|
||||||
onAboutUsClick={onAboutUsClick}
|
|
||||||
onContactUsClick={onContactUsClick}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// export function CheckoutPage2({
|
|
||||||
// onHomeClick,
|
|
||||||
// onPassesClick,
|
|
||||||
// onAttractionsClick,
|
|
||||||
// onBlogsClick,
|
|
||||||
// onHowItWorksClick,
|
|
||||||
// onFAQClick,
|
|
||||||
// onPrivacyPolicyClick,
|
|
||||||
// onAboutUsClick,
|
|
||||||
// onContactUsClick,
|
|
||||||
// onSignInClick,
|
|
||||||
// onSignOutClick,
|
|
||||||
// onProfileClick,
|
|
||||||
// user,
|
|
||||||
// currentPage,
|
|
||||||
// }: any) {
|
|
||||||
// const navigate = useNavigate();
|
|
||||||
|
|
||||||
// const cityId = localStorage.getItem("cityId");
|
|
||||||
// const { data: checkoutPageData, isLoading } = useGetCheckoutPageDataQuery(cityId);
|
|
||||||
|
|
||||||
// const city = checkoutPageData?.city;
|
|
||||||
// const allCards = checkoutPageData?.cards ?? [];
|
|
||||||
// const allAttractions = checkoutPageData?.attractions ?? [];
|
|
||||||
// const allOffers = checkoutPageData?.offers ?? [];
|
|
||||||
|
|
||||||
// const baseUrl = import.meta.env.VITE_BASE_URL;
|
|
||||||
|
|
||||||
|
|
||||||
// // Initialize with first card (Flexi) as default
|
|
||||||
// const defaultCard = allCards[0] || null;
|
|
||||||
|
|
||||||
// const [checkoutItem, setCheckoutItem] = useState<CartItem>({
|
|
||||||
// id: defaultCard?.id?.toString() || '1',
|
|
||||||
// city: city?.name || 'Melbourne',
|
|
||||||
// cardType: defaultCard?.cardType?.displayName || 'Flexi',
|
|
||||||
// days: defaultCard?.validityDuration || 3,
|
|
||||||
// adults: 2,
|
|
||||||
// children: 1,
|
|
||||||
// quantity: 1,
|
|
||||||
// pricePerUnit: defaultCard?.adultPrice || 49.5,
|
|
||||||
// image: city?.heroBanner?.image || '',
|
|
||||||
// });
|
|
||||||
|
|
||||||
// if (isLoading) {
|
|
||||||
// return <LoadingSpinner />;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// const handleCheckoutItemChange = (updates: Partial<CartItem>) => {
|
|
||||||
// const updated = { ...checkoutItem, ...updates };
|
|
||||||
|
|
||||||
// // If card type changes, update with real card data
|
|
||||||
// if (updates.cardType) {
|
|
||||||
// const selectedCard = allCards.find(
|
|
||||||
// c => c.cardType?.displayName === updates.cardType
|
|
||||||
// );
|
|
||||||
// if (selectedCard) {
|
|
||||||
// updated.id = selectedCard.id.toString();
|
|
||||||
// updated.days = selectedCard.validityDuration;
|
|
||||||
// updated.pricePerUnit = selectedCard.adultPrice;
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// setCheckoutItem(updated);
|
|
||||||
// };
|
|
||||||
|
|
||||||
// // Get currently selected card
|
|
||||||
// const selectedCard = allCards.find(c =>
|
|
||||||
// c.cardType?.displayName === checkoutItem.cardType
|
|
||||||
// ) || allCards[0];
|
|
||||||
|
|
||||||
// // Offers for selected card (fallback to global offers)
|
|
||||||
// const currentOffers = selectedCard?.offers?.length
|
|
||||||
// ? selectedCard.offers
|
|
||||||
// : allOffers;
|
|
||||||
|
|
||||||
// return (
|
|
||||||
// <div className="min-h-screen bg-[#fafafa] font-poppins">
|
|
||||||
// <Navbar
|
|
||||||
// activeCity={city?.name || "Melbourne"}
|
|
||||||
// onCityChange={() => { }}
|
|
||||||
// onSignInClick={onSignInClick}
|
|
||||||
// onSignOutClick={onSignOutClick}
|
|
||||||
// onPassesClick={onPassesClick}
|
|
||||||
// onCheckoutClick={() => { }}
|
|
||||||
// onHomeClick={onHomeClick}
|
|
||||||
// onAttractionsClick={onAttractionsClick}
|
|
||||||
// onBlogsClick={onBlogsClick}
|
|
||||||
// onHowItWorksClick={onHowItWorksClick}
|
|
||||||
// onFAQClick={onFAQClick}
|
|
||||||
// onPrivacyPolicyClick={onPrivacyPolicyClick}
|
|
||||||
// onAboutUsClick={onAboutUsClick}
|
|
||||||
// onProfileClick={onProfileClick}
|
|
||||||
// onCityCardsClick={() => { }}
|
|
||||||
// onMagicItineraryClick={() => { }}
|
|
||||||
// onPostCardsClick={() => { }}
|
|
||||||
// onOffersClick={() => { }}
|
|
||||||
// onSuperSavingsClick={() => { }}
|
|
||||||
// onEsimsClick={() => { }}
|
|
||||||
// onHotelDiscountsClick={() => { }}
|
|
||||||
// onCartClick={() => { }}
|
|
||||||
// currentPage={currentPage}
|
|
||||||
// user={user}
|
|
||||||
// />
|
|
||||||
|
|
||||||
// <div className="w-full px-4 sm:px-6 lg:px-10 xl:px-16 pt-32 pb-24 max-w-[1440px] mx-auto">
|
|
||||||
// <button
|
|
||||||
// onClick={() => navigate(-1)}
|
|
||||||
// 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
|
|
||||||
// </button>
|
|
||||||
|
|
||||||
// <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 pr-3">
|
|
||||||
// {city?.name || 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 */}
|
|
||||||
// <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]">
|
|
||||||
// {allCards.map((card) => (
|
|
||||||
// <button
|
|
||||||
// key={card.id}
|
|
||||||
// onClick={() => handleCheckoutItemChange({
|
|
||||||
// cardType: card.cardType?.displayName || 'Flexi'
|
|
||||||
// })}
|
|
||||||
// >
|
|
||||||
// {card.cardType?.name === 'selective_pass' ? (
|
|
||||||
// <FlexiCardPreview
|
|
||||||
// city={city?.name || checkoutItem.city}
|
|
||||||
// adultPrice={card.adultPrice}
|
|
||||||
// childPrice={card.childPrice}
|
|
||||||
// isSelected={checkoutItem.cardType === card.cardType?.displayName}
|
|
||||||
// />
|
|
||||||
// ) : (
|
|
||||||
// <UnlimitedCardPreview
|
|
||||||
// city={city?.name || checkoutItem.city}
|
|
||||||
// adultPrice={card.adultPrice}
|
|
||||||
// childPrice={card.childPrice}
|
|
||||||
// isSelected={checkoutItem.cardType === card.cardType?.displayName}
|
|
||||||
// />
|
|
||||||
// )}
|
|
||||||
// </button>
|
|
||||||
// ))}
|
|
||||||
// </div>
|
|
||||||
|
|
||||||
// {/* Features Comparison - Kept as is (no CSS change) */}
|
|
||||||
// <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 Section */}
|
|
||||||
// <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">
|
|
||||||
// {currentOffers.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={`${baseUrl}/${offer.websiteBannerImage}` || offer.mobileBannerImage}
|
|
||||||
// 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>
|
|
||||||
|
|
||||||
// {/* Attractions Section */}
|
|
||||||
// <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">
|
|
||||||
// {allAttractions.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">
|
|
||||||
// {allAttractions.map((attraction) => (
|
|
||||||
// <div key={attraction.id} className="group relative rounded-xl overflow-hidden">
|
|
||||||
// <div className="aspect-[4/3] relative">
|
|
||||||
// <ImageWithFallback
|
|
||||||
// src={attraction.thumbnail}
|
|
||||||
// alt={attraction.title}
|
|
||||||
// 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 bottom-2 left-2 right-2">
|
|
||||||
// <h6 className="font-poppins text-sm leading-snug font-medium text-white drop-shadow-sm">
|
|
||||||
// {attraction.title}
|
|
||||||
// </h6>
|
|
||||||
// </div>
|
|
||||||
// </div>
|
|
||||||
// </div>
|
|
||||||
// ))}
|
|
||||||
// </div>
|
|
||||||
// </div>
|
|
||||||
// </div>
|
|
||||||
|
|
||||||
// {/* Right Column - Config Card */}
|
|
||||||
// <div className="hidden lg:block lg:w-[420px] flex-shrink-0">
|
|
||||||
// <div className="lg:sticky lg:top-28">
|
|
||||||
// <CheckoutConfigCard
|
|
||||||
// item={checkoutItem}
|
|
||||||
// onChange={handleCheckoutItemChange}
|
|
||||||
// onProceed={() => navigate("/payment")}
|
|
||||||
// />
|
|
||||||
// </div>
|
|
||||||
// </div>
|
|
||||||
|
|
||||||
// {/* Mobile Config Card */}
|
|
||||||
// <div className="lg:hidden mt-6">
|
|
||||||
// <CheckoutConfigCard
|
|
||||||
// item={checkoutItem}
|
|
||||||
// onChange={handleCheckoutItemChange}
|
|
||||||
// onProceed={() => navigate("/payment")}
|
|
||||||
// />
|
|
||||||
// </div>
|
|
||||||
// </div>
|
|
||||||
// </div>
|
|
||||||
|
|
||||||
// <Footer
|
|
||||||
// onHomeClick={onHomeClick}
|
|
||||||
// onPassesClick={onPassesClick}
|
|
||||||
// onAttractionsClick={onAttractionsClick}
|
|
||||||
// onBlogsClick={onBlogsClick}
|
|
||||||
// onHowItWorksClick={onHowItWorksClick}
|
|
||||||
// onFAQClick={onFAQClick}
|
|
||||||
// onPrivacyPolicyClick={onPrivacyPolicyClick}
|
|
||||||
// onAboutUsClick={onAboutUsClick}
|
|
||||||
// onContactUsClick={onContactUsClick}
|
|
||||||
// />
|
|
||||||
// </div>
|
|
||||||
// );
|
|
||||||
// }
|
|
||||||
@@ -56,7 +56,7 @@ interface User {
|
|||||||
name: string;
|
name: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface CreateMagicItineraryPageDesignProps {
|
interface CreateMagicItineraryPageProps {
|
||||||
onBackClick: () => void;
|
onBackClick: () => void;
|
||||||
onHomeClick: () => void;
|
onHomeClick: () => void;
|
||||||
onMelbourneClick: () => void;
|
onMelbourneClick: () => void;
|
||||||
@@ -94,49 +94,7 @@ interface ItineraryData {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const destinations = [
|
export function CreateMagicItineraryPage({
|
||||||
{
|
|
||||||
id: 'melbourne',
|
|
||||||
name: 'Melbourne',
|
|
||||||
country: 'Australia',
|
|
||||||
image: 'https://images.unsplash.com/photo-1595434971780-79d5c20c5090?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHxtZWxib3VybmUlMjBza3lsaW5lJTIwYXVzdHJhbGlhfGVufDF8fHx8MTc1ODk2MzkxOHww&ixlib=rb-4.1.0&q=80&w=1080&utm_source=figma&utm_medium=referral'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'sydney',
|
|
||||||
name: 'Sydney',
|
|
||||||
country: 'Australia',
|
|
||||||
image: 'https://images.unsplash.com/photo-1595563382617-fe6fa4fd0394?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHxzeWRuZXklMjBoYXJib3VyJTIwYnJpZGdlJTIwYXVzdHJhbGlhfGVufDF8fHx8MTc1ODk2MzkyMXww&ixlib=rb-4.1.0&q=80&w=1080&utm_source=figma&utm_medium=referral'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'brisbane',
|
|
||||||
name: 'Brisbane',
|
|
||||||
country: 'Australia',
|
|
||||||
image: 'https://images.unsplash.com/photo-1729904987421-12490733e013?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHxicmlzYmFuZSUyMGNpdHklMjByaXZlciUyMGF1c3RyYWxpYXxlbnwxfHx8fDE3NTg5NjM5Mjd8MA&ixlib=rb-4.1.0&q=80&w=1080&utm_source=figma&utm_medium=referral'
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
const energyOptions = [
|
|
||||||
{
|
|
||||||
id: 'adventurous',
|
|
||||||
name: 'Adventurous',
|
|
||||||
description: 'High energy activities and exploration',
|
|
||||||
image: 'https://images.unsplash.com/photo-1587502537147-2ba64a62e3d3?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHxhZHZlbnR1cmUlMjBvdXRkb29yJTIwaGlraW5nfGVufDF8fHx8MTc1ODk2NDQ2N3ww&ixlib=rb-4.1.0&q=80&w=1080&utm_source=figma&utm_medium=referral'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'relaxed',
|
|
||||||
name: 'Relaxed',
|
|
||||||
description: 'Peaceful and laid-back experiences',
|
|
||||||
image: 'https://images.unsplash.com/photo-1716893933701-73d59789bba7?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHxyZWxheGVkJTIwc3BhJTIwcGVhY2VmdWx8ZW58MXx8fHwxNzU4OTY0NDcxfDA&ixlib=rb-4.1.0&q=80&w=1080&utm_source=figma&utm_medium=referral'
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
const questionImages = {
|
|
||||||
museums: 'https://images.unsplash.com/photo-1747918157024-a1e1c77336fa?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHxtdXNldW0lMjBhcnQlMjBnYWxsZXJ5fGVufDF8fHx8MTc1ODkwOTYwMHww&ixlib=rb-4.1.0&q=80&w=1080&utm_source=figma&utm_medium=referral'
|
|
||||||
};
|
|
||||||
|
|
||||||
const dietaryOptions = ['No Restrictions', 'Vegetarian', 'Vegan', 'Pescatarian', 'Halal', 'Kosher'];
|
|
||||||
|
|
||||||
export function CreateMagicItineraryPageDesign({
|
|
||||||
onBackClick,
|
onBackClick,
|
||||||
onHomeClick,
|
onHomeClick,
|
||||||
onMelbourneClick,
|
onMelbourneClick,
|
||||||
@@ -160,7 +118,7 @@ export function CreateMagicItineraryPageDesign({
|
|||||||
onHotelDiscountsClick,
|
onHotelDiscountsClick,
|
||||||
currentPage,
|
currentPage,
|
||||||
user
|
user
|
||||||
}: CreateMagicItineraryPageDesignProps) {
|
}: CreateMagicItineraryPageProps) {
|
||||||
const [currentStep, setCurrentStep] = useState(1);
|
const [currentStep, setCurrentStep] = useState(1);
|
||||||
const [isGenerating, setIsGenerating] = useState(false);
|
const [isGenerating, setIsGenerating] = useState(false);
|
||||||
const [showResults, setShowResults] = useState(false);
|
const [showResults, setShowResults] = useState(false);
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,12 +1,15 @@
|
|||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import { motion } from 'motion/react';
|
import { motion } from 'motion/react';
|
||||||
import { ArrowLeft, Calendar, Clock, MapPin, Users, Star, Heart, Share2, Download, CheckCircle, Navigation, Cloud, Sun } from 'lucide-react';
|
import { ArrowLeft, Calendar, MapPin, Users, Star, Heart, Share2, Download, CheckCircle, Navigation, Cloud, Sun } from 'lucide-react';
|
||||||
import { Button } from '../components/ui/button';
|
import { Button } from '../components/ui/button';
|
||||||
import { Card, CardContent } from '../components/ui/card';
|
import { Card, CardContent } from '../components/ui/card';
|
||||||
import { Badge } from '../components/ui/badge';
|
import { Badge } from '../components/ui/badge';
|
||||||
import Navbar from '../components/Navbar';
|
import Navbar from '../components/Navbar';
|
||||||
import { Footer } from '../components/Footer';
|
import { Footer } from '../components/Footer';
|
||||||
import { ImageWithFallback } from '../components/figma/ImageWithFallback';
|
import { ImageWithFallback } from '../components/figma/ImageWithFallback';
|
||||||
|
import { useGetItineraryDetailsByIdQuery } from '../Redux/services/itinerary.service';
|
||||||
|
import { useParams } from 'react-router-dom';
|
||||||
|
import LoadingSpinner from '../components/LoadingSpinner';
|
||||||
|
|
||||||
interface ItineraryViewPageProps {
|
interface ItineraryViewPageProps {
|
||||||
onBackClick: () => void;
|
onBackClick: () => void;
|
||||||
@@ -35,18 +38,6 @@ interface ItineraryViewPageProps {
|
|||||||
user?: { email: string; name: string; } | null;
|
user?: { email: string; name: string; } | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Enhanced activity type with more details
|
|
||||||
interface Activity {
|
|
||||||
time: string;
|
|
||||||
activity: string;
|
|
||||||
location: string;
|
|
||||||
address: string;
|
|
||||||
image: string;
|
|
||||||
categories: string[];
|
|
||||||
description: string[];
|
|
||||||
isFavorite?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function ItineraryViewPage({
|
export function ItineraryViewPage({
|
||||||
onBackClick,
|
onBackClick,
|
||||||
onHomeClick,
|
onHomeClick,
|
||||||
@@ -74,304 +65,36 @@ export function ItineraryViewPage({
|
|||||||
user
|
user
|
||||||
}: ItineraryViewPageProps) {
|
}: ItineraryViewPageProps) {
|
||||||
const [viewMode, setViewMode] = useState<'daily' | 'summary'>('daily');
|
const [viewMode, setViewMode] = useState<'daily' | 'summary'>('daily');
|
||||||
const [favorites, setFavorites] = useState<Set<string>>(new Set());
|
// const [favorites, setFavorites] = useState<Set<string>>(new Set());
|
||||||
|
|
||||||
const toggleFavorite = (activityKey: string) => {
|
// ── API Integration ──────────────────────────────────────────────────────────
|
||||||
setFavorites(prev => {
|
const { itineraryId } = useParams();
|
||||||
const newSet = new Set(prev);
|
const { data: itineraryDetails, isLoading } = useGetItineraryDetailsByIdQuery(itineraryId);
|
||||||
if (newSet.has(activityKey)) {
|
|
||||||
newSet.delete(activityKey);
|
|
||||||
} else {
|
|
||||||
newSet.add(activityKey);
|
|
||||||
}
|
|
||||||
return newSet;
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
// Enhanced itinerary data with images, addresses, and detailed info
|
const generatedItinerary = itineraryDetails ?? null;
|
||||||
const generatedItinerary = {
|
const days = generatedItinerary?.days ?? [];
|
||||||
destination: {
|
const summaries = generatedItinerary?.summary ?? [];
|
||||||
name: 'Melbourne',
|
// ─────────────────────────────────────────────────────────────────────────────
|
||||||
country: 'Australia',
|
|
||||||
weather: '18°C, Sunny',
|
// const toggleFavorite = (activityKey: string) => {
|
||||||
image: 'https://images.unsplash.com/photo-1514395462725-fb4566210144?w=400&h=300&fit=crop'
|
// setFavorites(prev => {
|
||||||
},
|
// const newSet = new Set(prev);
|
||||||
totalDays: 3,
|
// if (newSet.has(activityKey)) {
|
||||||
estimatedCost: '$450 AUD',
|
// newSet.delete(activityKey);
|
||||||
includedActivities: 18,
|
// } else {
|
||||||
dailyPlans: [
|
// newSet.add(activityKey);
|
||||||
{
|
// }
|
||||||
day: 1,
|
// return newSet;
|
||||||
title: "City Center & Culture",
|
// });
|
||||||
activities: [
|
// };
|
||||||
{
|
|
||||||
time: '8:00 am',
|
// ── Loading State ─────────────────────────────────────────────────────────────
|
||||||
activity: 'The Langham Melbourne',
|
if (isLoading) {
|
||||||
location: 'The Langham Melbourne',
|
return (
|
||||||
address: '1 Southgate Avenue, Southbank VIC 3006',
|
<LoadingSpinner/>
|
||||||
image: 'https://images.unsplash.com/photo-1566073771259-6a8506099945?w=800&h=600&fit=crop',
|
);
|
||||||
categories: ['Accommodation', 'Luxury'],
|
}
|
||||||
description: [
|
// ─────────────────────────────────────────────────────────────────────────────
|
||||||
'Check-in at luxury riverside hotel',
|
|
||||||
'Enjoy complimentary breakfast',
|
|
||||||
'Relax at the spa facilities',
|
|
||||||
'Explore the surrounding Southbank area'
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
time: '10:00 am',
|
|
||||||
activity: 'Federation Square',
|
|
||||||
location: 'Federation Square',
|
|
||||||
address: 'Corner Swanston & Flinders Streets, Melbourne VIC 3000',
|
|
||||||
image: 'https://images.unsplash.com/photo-1514395462725-fb4566210144?w=800&h=600&fit=crop',
|
|
||||||
categories: ['Culture', 'Landmark'],
|
|
||||||
description: [
|
|
||||||
'Explore Melbourne\'s cultural precinct',
|
|
||||||
'Visit the ACMI museum',
|
|
||||||
'Enjoy street performances',
|
|
||||||
'Take photos at iconic locations'
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
time: '12:00 pm',
|
|
||||||
activity: 'Degrave Street Café',
|
|
||||||
location: 'Degrave Street Espresso Bar',
|
|
||||||
address: '23-25 Degraves Street, Melbourne VIC 3000',
|
|
||||||
image: 'https://images.unsplash.com/photo-1554118811-1e0d58224f24?w=800&h=600&fit=crop',
|
|
||||||
categories: ['Food', 'Drinks', 'Culture'],
|
|
||||||
description: [
|
|
||||||
'Coffee at Pellegrini\'s Espresso Bar (iconic old-school cafe)',
|
|
||||||
'Try the famous jam doughnuts',
|
|
||||||
'Shop for fresh produce in the Deli Hall',
|
|
||||||
'Pick up unique souvenirs in the General Merchandise section'
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
time: '2:00 pm',
|
|
||||||
activity: 'Royal Botanic Gardens',
|
|
||||||
location: 'Royal Botanic Gardens Victoria',
|
|
||||||
address: 'Birdwood Avenue, South Yarra VIC 3141',
|
|
||||||
image: 'https://images.unsplash.com/photo-1585320806297-9794b3e4eeae?w=800&h=600&fit=crop',
|
|
||||||
categories: ['Nature', 'Culture'],
|
|
||||||
description: [
|
|
||||||
'Stroll through stunning landscaped gardens',
|
|
||||||
'Visit the Australian Forest Walk',
|
|
||||||
'Relax by the Ornamental Lake',
|
|
||||||
'Join a free guided walking tour'
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
time: '4:00 pm',
|
|
||||||
activity: 'National Gallery of Victoria',
|
|
||||||
location: 'NGV International',
|
|
||||||
address: '180 St Kilda Road, Melbourne VIC 3006',
|
|
||||||
image: 'https://images.unsplash.com/photo-1564399577149-749794d74eee?w=800&h=600&fit=crop',
|
|
||||||
categories: ['Culture', 'Art'],
|
|
||||||
description: [
|
|
||||||
'Explore Australia\'s oldest art museum',
|
|
||||||
'View international and Australian art collections',
|
|
||||||
'Visit the stunning water wall entrance',
|
|
||||||
'Browse the NGV design store'
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
time: '7:00 pm',
|
|
||||||
activity: 'Dinner at Chin Chin',
|
|
||||||
location: 'Chin Chin Restaurant',
|
|
||||||
address: '125 Flinders Lane, Melbourne VIC 3000',
|
|
||||||
image: 'https://images.unsplash.com/photo-1552566626-52f8b828add9?w=800&h=600&fit=crop',
|
|
||||||
categories: ['Food', 'Drinks'],
|
|
||||||
description: [
|
|
||||||
'Experience modern Thai cuisine',
|
|
||||||
'Try signature dishes like the Betel Leaf',
|
|
||||||
'Enjoy the vibrant atmosphere',
|
|
||||||
'Book ahead or walk-in for bar seating'
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
day: 2,
|
|
||||||
title: "Markets & Neighborhoods",
|
|
||||||
activities: [
|
|
||||||
{
|
|
||||||
time: '8:00 am',
|
|
||||||
activity: 'Queen Victoria Market',
|
|
||||||
location: 'Queen Victoria Market',
|
|
||||||
address: 'Queen Street, Melbourne VIC 3000',
|
|
||||||
image: 'https://images.unsplash.com/photo-1555939594-58d7cb561ad1?w=800&h=600&fit=crop',
|
|
||||||
categories: ['Food', 'Shopping', 'Culture'],
|
|
||||||
description: [
|
|
||||||
'Explore Melbourne\'s historic market (since 1878)',
|
|
||||||
'Sample fresh local produce',
|
|
||||||
'Shop for artisan goods and souvenirs',
|
|
||||||
'Grab breakfast at the Deli Hall'
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
time: '10:30 am',
|
|
||||||
activity: 'Fitzroy Street Art Tour',
|
|
||||||
location: 'Fitzroy Arts Precinct',
|
|
||||||
address: 'Gertrude Street, Fitzroy VIC 3065',
|
|
||||||
image: 'https://images.unsplash.com/photo-1499781350541-7783f6c6a0c8?w=800&h=600&fit=crop',
|
|
||||||
categories: ['Culture', 'Art'],
|
|
||||||
description: [
|
|
||||||
'Walk through famous street art laneways',
|
|
||||||
'Discover works by renowned artists',
|
|
||||||
'Visit independent galleries',
|
|
||||||
'Explore vintage and record stores'
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
time: '12:30 pm',
|
|
||||||
activity: 'Brunswick Street Lunch',
|
|
||||||
location: 'Brunswick Street Precinct',
|
|
||||||
address: 'Brunswick Street, Fitzroy VIC 3065',
|
|
||||||
image: 'https://images.unsplash.com/photo-1517248135467-4c7edcad34c4?w=800&h=600&fit=crop',
|
|
||||||
categories: ['Food', 'Drinks'],
|
|
||||||
description: [
|
|
||||||
'Choose from diverse dining options',
|
|
||||||
'Try local cafes and restaurants',
|
|
||||||
'Explore bookshops and boutiques',
|
|
||||||
'Enjoy the vibrant neighborhood atmosphere'
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
time: '2:30 pm',
|
|
||||||
activity: 'Carlton Gardens',
|
|
||||||
location: 'Carlton Gardens',
|
|
||||||
address: 'Carlton Gardens, Carlton VIC 3053',
|
|
||||||
image: 'https://images.unsplash.com/photo-1519331379826-f10be5486c6f?w=800&h=600&fit=crop',
|
|
||||||
categories: ['Nature', 'Culture', 'Landmark'],
|
|
||||||
description: [
|
|
||||||
'Visit the UNESCO World Heritage site',
|
|
||||||
'See the Royal Exhibition Building',
|
|
||||||
'Stroll through Victorian-era gardens',
|
|
||||||
'Relax by the ornamental fountains'
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
time: '4:00 pm',
|
|
||||||
activity: 'Melbourne Museum',
|
|
||||||
location: 'Melbourne Museum',
|
|
||||||
address: '11 Nicholson Street, Carlton VIC 3053',
|
|
||||||
image: 'https://images.unsplash.com/photo-1566127992631-137a642a90f4?w=800&h=600&fit=crop',
|
|
||||||
categories: ['Culture', 'Museum'],
|
|
||||||
description: [
|
|
||||||
'Explore natural and cultural history',
|
|
||||||
'Visit the Bunjilaka Aboriginal Centre',
|
|
||||||
'See the Forest Gallery living ecosystem',
|
|
||||||
'Discover Melbourne\'s story exhibition'
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
time: '7:00 pm',
|
|
||||||
activity: 'Rooftop Bar Experience',
|
|
||||||
location: 'Naked for Satan',
|
|
||||||
address: '285 Brunswick Street, Fitzroy VIC 3065',
|
|
||||||
image: 'https://images.unsplash.com/photo-1514933651103-005eec06c04b?w=800&h=600&fit=crop',
|
|
||||||
categories: ['Drinks', 'Food'],
|
|
||||||
description: [
|
|
||||||
'Enjoy sunset views from the rooftop',
|
|
||||||
'Try Spanish-style pintxos',
|
|
||||||
'Sample craft cocktails and local beers',
|
|
||||||
'Experience Melbourne\'s bar culture'
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
day: 3,
|
|
||||||
title: "Coastal Adventure",
|
|
||||||
activities: [
|
|
||||||
{
|
|
||||||
time: '8:00 am',
|
|
||||||
activity: 'St. Kilda Beach',
|
|
||||||
location: 'St. Kilda Beach',
|
|
||||||
address: 'Jacka Boulevard, St Kilda VIC 3182',
|
|
||||||
image: 'https://images.unsplash.com/photo-1505142468610-359e7d316be0?w=800&h=600&fit=crop',
|
|
||||||
categories: ['Nature', 'Beach'],
|
|
||||||
description: [
|
|
||||||
'Morning walk along the iconic beach',
|
|
||||||
'Visit the historic St Kilda Pier',
|
|
||||||
'See the little penguins at sunset',
|
|
||||||
'Explore the Sunday Esplanade Market (weekends)'
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
time: '10:00 am',
|
|
||||||
activity: 'Acland Street Cafes',
|
|
||||||
location: 'Acland Street',
|
|
||||||
address: 'Acland Street, St Kilda VIC 3182',
|
|
||||||
image: 'https://images.unsplash.com/photo-1495474472287-4d71bcdd2085?w=800&h=600&fit=crop',
|
|
||||||
categories: ['Food', 'Drinks'],
|
|
||||||
description: [
|
|
||||||
'Brunch at famous cake shops',
|
|
||||||
'Try traditional European pastries',
|
|
||||||
'Visit Lentil as Anything (pay-as-you-feel)',
|
|
||||||
'Browse vintage shops and bookstores'
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
time: '12:00 pm',
|
|
||||||
activity: 'Luna Park Melbourne',
|
|
||||||
location: 'Luna Park Melbourne',
|
|
||||||
address: '18 Lower Esplanade, St Kilda VIC 3182',
|
|
||||||
image: 'https://images.unsplash.com/photo-1513026705753-bc3fffca8bf4?w=800&h=600&fit=crop',
|
|
||||||
categories: ['Entertainment', 'Landmark'],
|
|
||||||
description: [
|
|
||||||
'Visit Melbourne\'s iconic amusement park',
|
|
||||||
'Ride the historic Scenic Railway (1912)',
|
|
||||||
'Take photos at Mr Moon entrance',
|
|
||||||
'Enjoy carnival games and rides'
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
time: '2:00 pm',
|
|
||||||
activity: 'Brighton Beach Boxes',
|
|
||||||
location: 'Brighton Beach',
|
|
||||||
address: 'Esplanade, Brighton VIC 3186',
|
|
||||||
image: 'https://images.unsplash.com/photo-1520208422220-d12a3c588e6c?w=800&h=600&fit=crop',
|
|
||||||
categories: ['Culture', 'Landmark'],
|
|
||||||
description: [
|
|
||||||
'Photograph the famous colorful bathing boxes',
|
|
||||||
'Walk along the pristine beach',
|
|
||||||
'Learn about the heritage structures',
|
|
||||||
'Relax in the beachside atmosphere'
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
time: '4:00 pm',
|
|
||||||
activity: 'Southbank Promenade',
|
|
||||||
location: 'Southbank',
|
|
||||||
address: 'Southbank Promenade, Southbank VIC 3006',
|
|
||||||
image: 'https://images.unsplash.com/photo-1559827260-dc66d52bef19?w=800&h=600&fit=crop',
|
|
||||||
categories: ['Culture', 'Shopping'],
|
|
||||||
description: [
|
|
||||||
'Stroll along the Yarra River',
|
|
||||||
'Visit arts and craft markets',
|
|
||||||
'Explore restaurants and cafes',
|
|
||||||
'Enjoy river views and street performers'
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
time: '7:00 pm',
|
|
||||||
activity: 'Farewell Dinner at Vue de Monde',
|
|
||||||
location: 'Vue de Monde',
|
|
||||||
address: 'Level 55, Rialto, 525 Collins Street, Melbourne VIC 3000',
|
|
||||||
image: 'https://images.unsplash.com/photo-1414235077428-338989a2e8c0?w=800&h=600&fit=crop',
|
|
||||||
categories: ['Food', 'Drinks', 'Luxury'],
|
|
||||||
description: [
|
|
||||||
'Experience fine dining at 55th floor',
|
|
||||||
'Enjoy panoramic Melbourne views',
|
|
||||||
'Taste modern Australian cuisine',
|
|
||||||
'Celebrate the end of your journey'
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="min-h-screen bg-background">
|
<div className="min-h-screen bg-background">
|
||||||
@@ -422,12 +145,14 @@ export function ItineraryViewPage({
|
|||||||
<Star className="w-6 h-6 fill-current" />
|
<Star className="w-6 h-6 fill-current" />
|
||||||
<h1 className="font-merchant text-4xl md:text-5xl lg:text-6xl leading-tight">
|
<h1 className="font-merchant text-4xl md:text-5xl lg:text-6xl leading-tight">
|
||||||
<span className="font-light">Your</span>{' '}
|
<span className="font-light">Your</span>{' '}
|
||||||
<span className="font-bold italic bg-gradient-to-r from-primary to-secondary bg-clip-text text-transparent">Magic Itinerary</span>
|
<span className="font-bold italic bg-gradient-to-r from-primary to-secondary bg-clip-text text-transparent pr-3">
|
||||||
|
{generatedItinerary?.title ?? 'Magic Itinerary'}
|
||||||
|
</span>
|
||||||
</h1>
|
</h1>
|
||||||
<Star className="w-6 h-6 fill-current" />
|
<Star className="w-6 h-6 fill-current" />
|
||||||
</div>
|
</div>
|
||||||
<p className="font-poppins text-xl leading-relaxed font-normal text-gray-600 max-w-2xl mx-auto">
|
<p className="font-poppins text-xl leading-relaxed font-normal text-gray-600 max-w-2xl mx-auto">
|
||||||
Here's your personalized {generatedItinerary.totalDays}-day adventure in {generatedItinerary.destination.name}!
|
Here's your personalized {generatedItinerary?.totalDays}-day adventure in {generatedItinerary?.city}!
|
||||||
</p>
|
</p>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
</div>
|
</div>
|
||||||
@@ -437,6 +162,7 @@ export function ItineraryViewPage({
|
|||||||
<section className="py-8">
|
<section className="py-8">
|
||||||
<div className="container mx-auto px-4">
|
<div className="container mx-auto px-4">
|
||||||
<div className="max-w-6xl mx-auto space-y-8">
|
<div className="max-w-6xl mx-auto space-y-8">
|
||||||
|
|
||||||
{/* View Toggle */}
|
{/* View Toggle */}
|
||||||
<div className="flex justify-center">
|
<div className="flex justify-center">
|
||||||
<div className="bg-muted p-1 rounded-lg">
|
<div className="bg-muted p-1 rounded-lg">
|
||||||
@@ -459,31 +185,46 @@ export function ItineraryViewPage({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Itinerary Overview */}
|
{/* Itinerary Overview Card */}
|
||||||
<motion.div
|
<motion.div
|
||||||
initial={{ opacity: 0, y: 20 }}
|
initial={{ opacity: 0, y: 20 }}
|
||||||
animate={{ opacity: 1, y: 0 }}
|
animate={{ opacity: 1, y: 0 }}
|
||||||
transition={{ duration: 0.6, delay: 0.2 }}
|
transition={{ duration: 0.6, delay: 0.2 }}
|
||||||
>
|
>
|
||||||
<Card className="p-6">
|
{/* Banner Image */}
|
||||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
|
<Card className="overflow-hidden">
|
||||||
<div className="text-center">
|
<div className="relative h-40 md:h-48">
|
||||||
<div className="font-merchant text-3xl text-primary mb-2">{generatedItinerary.totalDays}</div>
|
<ImageWithFallback
|
||||||
<div className="font-poppins text-sm text-muted-foreground font-normal">Days</div>
|
src={generatedItinerary?.cityBanner}
|
||||||
|
alt={generatedItinerary?.city}
|
||||||
|
className="w-full h-full object-cover"
|
||||||
|
/>
|
||||||
|
<div className="absolute inset-0 bg-gradient-to-t from-black/70 via-black/30 to-transparent" />
|
||||||
|
<div className="absolute bottom-4 left-6">
|
||||||
|
<p className="font-poppins text-xs font-medium text-white/70 uppercase tracking-wider mb-1">Your Trip</p>
|
||||||
|
<h3 className="font-merchant text-2xl md:text-3xl text-white font-semibold">{generatedItinerary?.city}</h3>
|
||||||
</div>
|
</div>
|
||||||
<div className="text-center">
|
</div>
|
||||||
<div className="font-merchant text-3xl text-primary mb-2">{generatedItinerary.includedActivities}</div>
|
|
||||||
<div className="font-poppins text-sm text-muted-foreground font-normal">Activities</div>
|
{/* Stats Row */}
|
||||||
|
<div className="grid grid-cols-3 divide-x divide-gray-100 bg-white">
|
||||||
|
<div className="flex flex-col items-center py-5">
|
||||||
|
<span className="font-merchant text-3xl text-primary">{generatedItinerary?.totalDays}</span>
|
||||||
|
<span className="font-poppins text-xs font-normal text-gray-500 mt-1">Days</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="text-center">
|
<div className="flex flex-col items-center py-5">
|
||||||
<div className="font-merchant text-3xl text-primary mb-2">{generatedItinerary.estimatedCost}</div>
|
<span className="font-merchant text-3xl text-primary">{generatedItinerary?.totalStops}</span>
|
||||||
<div className="font-poppins text-sm text-muted-foreground font-normal">Estimated Cost</div>
|
<span className="font-poppins text-xs font-normal text-gray-500 mt-1">Stops</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col items-center py-5">
|
||||||
|
<span className="font-merchant text-3xl text-primary">{days[0]?.date}</span>
|
||||||
|
<span className="font-poppins text-xs font-normal text-gray-500 mt-1">Start Date</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Card>
|
</Card>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
|
|
||||||
{/* Daily Plans - Enhanced View */}
|
{/* ── Daily View ──────────────────────────────────────────────────── */}
|
||||||
{viewMode === 'daily' && (
|
{viewMode === 'daily' && (
|
||||||
<motion.div
|
<motion.div
|
||||||
initial={{ opacity: 0, y: 20 }}
|
initial={{ opacity: 0, y: 20 }}
|
||||||
@@ -491,29 +232,24 @@ export function ItineraryViewPage({
|
|||||||
transition={{ duration: 0.6, delay: 0.3 }}
|
transition={{ duration: 0.6, delay: 0.3 }}
|
||||||
className="space-y-12"
|
className="space-y-12"
|
||||||
>
|
>
|
||||||
{generatedItinerary.dailyPlans.map((day, dayIndex) => (
|
{days.map((day: any, dayIndex: number) => (
|
||||||
<div key={dayIndex} className="space-y-6">
|
<div key={dayIndex} className="space-y-6">
|
||||||
{/* Location Header with Weather - Only show for first day or when location changes */}
|
|
||||||
{(dayIndex === 0 ||
|
{/* City / Weather header — only on first day */}
|
||||||
(dayIndex > 0 &&
|
{dayIndex === 0 && (
|
||||||
generatedItinerary.dailyPlans[dayIndex - 1] &&
|
|
||||||
(generatedItinerary.dailyPlans[dayIndex - 1] as any).destination?.name !== generatedItinerary.destination.name)) && (
|
|
||||||
<motion.div
|
<motion.div
|
||||||
initial={{ opacity: 0, x: -20 }}
|
initial={{ opacity: 0, x: -20 }}
|
||||||
animate={{ opacity: 1, x: 0 }}
|
animate={{ opacity: 1, x: 0 }}
|
||||||
transition={{ duration: 0.6, delay: 0.4 + dayIndex * 0.1 }}
|
transition={{ duration: 0.6, delay: 0.4 }}
|
||||||
className="bg-gray-50 rounded-2xl p-6 shadow-sm border border-gray-200"
|
className="bg-gray-50 rounded-2xl p-6 shadow-sm border border-gray-200"
|
||||||
>
|
>
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<div>
|
<div>
|
||||||
<h2 className="font-merchant text-3xl md:text-4xl leading-tight mb-2">
|
<h2 className="font-poppins text-3xl md:text-4xl leading-tight mb-2">
|
||||||
{generatedItinerary.destination.name}, {generatedItinerary.destination.country}
|
{generatedItinerary?.city}, Australia
|
||||||
</h2>
|
</h2>
|
||||||
<p className="font-poppins text-base text-primary font-medium">{generatedItinerary.destination.weather}</p>
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
<Sun className="w-10 h-10 text-amber-500" />
|
|
||||||
</div>
|
</div>
|
||||||
|
<Sun className="w-10 h-10 text-amber-500" />
|
||||||
</div>
|
</div>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
)}
|
)}
|
||||||
@@ -526,24 +262,28 @@ export function ItineraryViewPage({
|
|||||||
className="flex items-center gap-4 pl-2"
|
className="flex items-center gap-4 pl-2"
|
||||||
>
|
>
|
||||||
<div className="bg-gradient-to-br from-primary to-secondary text-white w-16 h-16 rounded-full flex items-center justify-center shadow-lg">
|
<div className="bg-gradient-to-br from-primary to-secondary text-white w-16 h-16 rounded-full flex items-center justify-center shadow-lg">
|
||||||
<span className="font-merchant text-2xl font-semibold">{day.day}</span>
|
<span className="font-merchant text-2xl font-semibold">{day.dayNumber}</span>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h3 className="font-merchant text-2xl md:text-3xl leading-snug font-semibold">Day {day.day}</h3>
|
<h3 className="font-merchant text-2xl md:text-3xl leading-snug font-semibold">
|
||||||
|
Day {day.dayNumber}
|
||||||
|
</h3>
|
||||||
<p className="font-poppins text-base text-muted-foreground font-normal">{day.title}</p>
|
<p className="font-poppins text-base text-muted-foreground font-normal">{day.title}</p>
|
||||||
</div>
|
</div>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
|
|
||||||
{/* GMT Label */}
|
{/* Time-zone label */}
|
||||||
<div className="pl-2">
|
<div className="pl-2">
|
||||||
<p className="font-poppins text-sm text-gray-500 font-normal">GMT</p>
|
<p className="font-poppins text-sm text-gray-500 font-normal">
|
||||||
|
{day.date}
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Activity Cards - Desktop Grid Layout */}
|
{/* Activity Cards */}
|
||||||
<div className="space-y-8">
|
<div className="space-y-8">
|
||||||
{day.activities.map((activity, actIndex) => {
|
{day.items?.map((activity: any, actIndex: number) => {
|
||||||
const activityKey = `day${day.day}-act${actIndex}`;
|
const activityKey = `day${day.dayNumber}-act${actIndex}`;
|
||||||
const isFavorite = favorites.has(activityKey);
|
// const isFavorite = favorites.has(activityKey);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<motion.div
|
<motion.div
|
||||||
@@ -555,23 +295,25 @@ export function ItineraryViewPage({
|
|||||||
>
|
>
|
||||||
{/* Time Column */}
|
{/* Time Column */}
|
||||||
<div className="flex-shrink-0 w-24 pt-2">
|
<div className="flex-shrink-0 w-24 pt-2">
|
||||||
<div className="font-poppins text-base font-medium text-gray-700">{activity.time}</div>
|
<div className="font-poppins text-base font-medium text-gray-700">
|
||||||
|
{activity.timeSlot}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Activity Card */}
|
{/* Activity Card */}
|
||||||
<div className="flex-1">
|
<div className="flex-1">
|
||||||
<Card className="overflow-hidden hover:shadow-xl transition-shadow duration-300 border-2 border-gray-100">
|
<Card className="overflow-hidden hover:shadow-xl transition-shadow duration-300 border-2 border-gray-100">
|
||||||
<CardContent className="p-0">
|
<CardContent className="p-0">
|
||||||
{/* Hero Image with Overlay Buttons */}
|
{/* Hero Image */}
|
||||||
<div className="relative h-64 md:h-72 bg-gray-200">
|
<div className="relative h-64 md:h-72 bg-gray-200">
|
||||||
<ImageWithFallback
|
<ImageWithFallback
|
||||||
src={activity.image}
|
src={activity.imageUrl}
|
||||||
alt={activity.activity}
|
alt={activity.title}
|
||||||
className="w-full h-full object-cover"
|
className="w-full h-full object-cover"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* Favorite Heart Button - Top Right */}
|
{/* Favourite Button */}
|
||||||
<div className="absolute top-4 right-4">
|
{/* <div className="absolute top-4 right-4">
|
||||||
<Button
|
<Button
|
||||||
size="icon"
|
size="icon"
|
||||||
variant="secondary"
|
variant="secondary"
|
||||||
@@ -580,12 +322,18 @@ export function ItineraryViewPage({
|
|||||||
>
|
>
|
||||||
<Heart className={`w-5 h-5 ${isFavorite ? 'fill-primary text-primary' : 'text-gray-700'}`} />
|
<Heart className={`w-5 h-5 ${isFavorite ? 'fill-primary text-primary' : 'text-gray-700'}`} />
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div> */}
|
||||||
|
|
||||||
{/* Get Directions Button - Bottom Left */}
|
{/* Get Directions — links to Google Maps via lat/lng */}
|
||||||
<div className="absolute bottom-4 left-4">
|
<div className="absolute bottom-4 left-4">
|
||||||
<Button
|
<Button
|
||||||
className="bg-primary hover:bg-primary/90 text-white font-poppins font-semibold shadow-lg px-6 py-3 rounded-xl"
|
className="bg-primary hover:bg-primary/90 text-white font-poppins font-semibold shadow-lg px-6 py-3 rounded-xl"
|
||||||
|
onClick={() =>
|
||||||
|
window.open(
|
||||||
|
`https://www.google.com/maps?q=${activity.latitude},${activity.longitude}`,
|
||||||
|
'_blank'
|
||||||
|
)
|
||||||
|
}
|
||||||
>
|
>
|
||||||
<Navigation className="w-4 h-4 mr-2" />
|
<Navigation className="w-4 h-4 mr-2" />
|
||||||
Get Directions
|
Get Directions
|
||||||
@@ -593,40 +341,41 @@ export function ItineraryViewPage({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Content Section */}
|
{/* Content */}
|
||||||
<div className="p-6 space-y-4">
|
<div className="p-6 space-y-4">
|
||||||
{/* Location Name & Address */}
|
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<h4 className="font-merchant text-xl md:text-2xl leading-snug font-semibold text-gray-900">
|
<h4 className="font-merchant text-xl md:text-2xl leading-snug font-semibold text-gray-900">
|
||||||
{activity.activity}
|
{activity.title}
|
||||||
</h4>
|
</h4>
|
||||||
<div className="flex items-start gap-2 text-gray-600">
|
<div className="flex items-start gap-2 text-gray-600">
|
||||||
<MapPin className="w-4 h-4 mt-1 flex-shrink-0 text-primary" />
|
<MapPin className="w-4 h-4 mt-1 flex-shrink-0 text-primary" />
|
||||||
<span className="font-poppins text-sm font-normal leading-relaxed">{activity.address}</span>
|
<span className="font-poppins text-sm font-normal leading-relaxed">
|
||||||
|
{activity.locationName}
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Category Badges */}
|
{/* Category Badges */}
|
||||||
<div className="flex flex-wrap gap-2">
|
<div className="flex flex-wrap gap-2">
|
||||||
{activity.categories.map((category, catIndex) => (
|
{activity.categories?.map((cat: string, ci: number) => (
|
||||||
<Badge
|
<Badge
|
||||||
key={catIndex}
|
key={ci}
|
||||||
variant="secondary"
|
variant="secondary"
|
||||||
className="font-poppins font-normal text-sm bg-primary/10 text-primary hover:bg-primary/20 px-3 py-1"
|
className="font-poppins font-normal text-sm bg-primary/10 text-primary hover:bg-primary/20 px-3 py-1"
|
||||||
>
|
>
|
||||||
{category}
|
{cat}
|
||||||
</Badge>
|
</Badge>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Activity Details - Bullet Points */}
|
{/* Description */}
|
||||||
<div className="space-y-2 pt-2">
|
<div className="space-y-2 pt-2">
|
||||||
{activity.description.map((detail, detailIndex) => (
|
<div className="flex items-center gap-3">
|
||||||
<div key={detailIndex} className="flex items-start gap-3">
|
<span className="text-primary font-semibold mt-1 flex-shrink-0">•</span>
|
||||||
<span className="text-primary font-semibold mt-1.5 flex-shrink-0">•</span>
|
<span className="font-poppins text-sm font-normal text-gray-600 leading-relaxed">
|
||||||
<span className="font-poppins text-sm font-normal text-gray-600 leading-relaxed">{detail}</span>
|
{activity.description}
|
||||||
</div>
|
</span>
|
||||||
))}
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
@@ -641,7 +390,7 @@ export function ItineraryViewPage({
|
|||||||
</motion.div>
|
</motion.div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Summary View */}
|
{/* ── Summary View ─────────────────────────────────────────────────── */}
|
||||||
{viewMode === 'summary' && (
|
{viewMode === 'summary' && (
|
||||||
<motion.div
|
<motion.div
|
||||||
initial={{ opacity: 0, y: 20 }}
|
initial={{ opacity: 0, y: 20 }}
|
||||||
@@ -649,32 +398,57 @@ export function ItineraryViewPage({
|
|||||||
transition={{ duration: 0.6, delay: 0.3 }}
|
transition={{ duration: 0.6, delay: 0.3 }}
|
||||||
className="space-y-4"
|
className="space-y-4"
|
||||||
>
|
>
|
||||||
<h3 className="font-merchant text-2xl md:text-3xl text-center mb-8 leading-tight font-semibold">Trip Summary</h3>
|
<h3 className="font-merchant text-2xl md:text-3xl text-center mb-8 leading-tight font-semibold">
|
||||||
|
Trip Summary
|
||||||
|
</h3>
|
||||||
<Card className="p-6">
|
<Card className="p-6">
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
{generatedItinerary.dailyPlans.map((day, index) => (
|
{days.map((day: any, dayIndex: number) => {
|
||||||
<div key={index} className="border-l-4 border-primary pl-6">
|
// ✅ Match summary to the correct day by dayNumber
|
||||||
<div className="flex items-center gap-2 mb-3">
|
const daySummary = summaries.find((s: any) => s.dayNumber === day.dayNumber);
|
||||||
<Calendar className="w-5 h-5 text-primary" />
|
|
||||||
<h4 className="font-merchant text-lg md:text-xl leading-snug font-semibold">Day {day.day}: {day.title}</h4>
|
const dayDate = days[0]?.date
|
||||||
</div>
|
? new Date(
|
||||||
<div className="space-y-2">
|
new Date(days[0].date).setDate(
|
||||||
{day.activities.map((activity, actIndex) => (
|
new Date(days[0].date).getDate() + dayIndex
|
||||||
<div key={actIndex} className="flex items-start gap-3 text-sm">
|
)
|
||||||
<CheckCircle className="w-4 h-4 text-green-500 flex-shrink-0 mt-0.5" />
|
).toLocaleDateString('en-AU', {
|
||||||
<div className="flex-1">
|
day: '2-digit',
|
||||||
<span className="font-poppins text-gray-700 font-medium">{activity.activity}</span>
|
month: '2-digit',
|
||||||
<div className="flex items-center gap-2 mt-1">
|
year: 'numeric',
|
||||||
<span className="font-poppins text-gray-500 text-xs font-normal">{activity.time}</span>
|
})
|
||||||
<span className="text-gray-400">•</span>
|
: '';
|
||||||
<span className="font-poppins text-gray-500 text-xs font-normal">{activity.location}</span>
|
|
||||||
|
return (
|
||||||
|
<div key={dayIndex} className="border-l-4 border-primary pl-6">
|
||||||
|
<div className="flex items-center justify-between mb-3">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<Calendar className="w-5 h-5 text-primary" />
|
||||||
|
<h4 className="font-merchant text-lg md:text-xl leading-snug font-semibold">
|
||||||
|
Day {day.dayNumber}: {daySummary?.title ?? day.title}
|
||||||
|
</h4>
|
||||||
|
</div>
|
||||||
|
<span className="font-poppins text-sm text-primary font-medium">{dayDate}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-2">
|
||||||
|
{daySummary?.items?.map((item: any, actIndex: number) => (
|
||||||
|
<div key={actIndex} className="flex items-start gap-3 text-sm">
|
||||||
|
<CheckCircle className="w-4 h-4 text-green-500 flex-shrink-0 mt-0.5" />
|
||||||
|
<div className="flex-1">
|
||||||
|
<span className="font-poppins text-gray-700 font-medium">{item.title}</span>
|
||||||
|
<div className="flex items-center gap-2 mt-1">
|
||||||
|
<span className="font-poppins text-gray-500 text-xs font-normal">{item.timeSlot}</span>
|
||||||
|
<span className="text-gray-400">•</span>
|
||||||
|
<span className="font-poppins text-gray-500 text-xs font-normal">{item.locationName}</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
))}
|
||||||
))}
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
);
|
||||||
))}
|
})}
|
||||||
</div>
|
</div>
|
||||||
</Card>
|
</Card>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
@@ -695,16 +469,11 @@ export function ItineraryViewPage({
|
|||||||
<Heart className="w-5 h-5 mr-2" />
|
<Heart className="w-5 h-5 mr-2" />
|
||||||
Create Another
|
Create Another
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button className="bg-primary hover:bg-primary/90 font-poppins font-semibold px-8 py-3 text-lg">
|
||||||
className="bg-primary hover:bg-primary/90 font-poppins font-semibold px-8 py-3 text-lg"
|
|
||||||
>
|
|
||||||
<Download className="w-5 h-5 mr-2" />
|
<Download className="w-5 h-5 mr-2" />
|
||||||
Save Itinerary
|
Save Itinerary
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button variant="outline" className="font-poppins font-medium px-8 py-3 text-lg">
|
||||||
variant="outline"
|
|
||||||
className="font-poppins font-medium px-8 py-3 text-lg"
|
|
||||||
>
|
|
||||||
<Share2 className="w-5 h-5 mr-2" />
|
<Share2 className="w-5 h-5 mr-2" />
|
||||||
Share Trip
|
Share Trip
|
||||||
</Button>
|
</Button>
|
||||||
@@ -714,7 +483,7 @@ export function ItineraryViewPage({
|
|||||||
</section>
|
</section>
|
||||||
|
|
||||||
{/* Footer */}
|
{/* Footer */}
|
||||||
<Footer
|
<Footer
|
||||||
onHomeClick={onHomeClick}
|
onHomeClick={onHomeClick}
|
||||||
onMelbourneClick={onMelbourneClick}
|
onMelbourneClick={onMelbourneClick}
|
||||||
onPassesClick={onPassesClick}
|
onPassesClick={onPassesClick}
|
||||||
|
|||||||
@@ -1,501 +0,0 @@
|
|||||||
import React, { useState } from 'react';
|
|
||||||
import { motion } from 'motion/react';
|
|
||||||
import { ArrowLeft, Calendar, Clock, MapPin, Users, Star, Heart, Share2, Download, CheckCircle, Navigation, Cloud, Sun } from 'lucide-react';
|
|
||||||
import { Button } from '../components/ui/button';
|
|
||||||
import { Card, CardContent } from '../components/ui/card';
|
|
||||||
import { Badge } from '../components/ui/badge';
|
|
||||||
import Navbar from '../components/Navbar';
|
|
||||||
import { Footer } from '../components/Footer';
|
|
||||||
import { ImageWithFallback } from '../components/figma/ImageWithFallback';
|
|
||||||
import { useGetItineraryDetailsByIdQuery } from '../Redux/services/itinerary.service';
|
|
||||||
import { useParams } from 'react-router-dom';
|
|
||||||
import LoadingSpinner from '../components/LoadingSpinner';
|
|
||||||
|
|
||||||
interface ItineraryViewPageDesignProps {
|
|
||||||
onBackClick: () => void;
|
|
||||||
onHomeClick: () => void;
|
|
||||||
onMelbourneClick: () => void;
|
|
||||||
onPassesClick: () => void;
|
|
||||||
onCheckoutClick: () => 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;
|
|
||||||
onCreateItineraryClick: () => void;
|
|
||||||
onContactUsClick?: () => void;
|
|
||||||
onEsimsClick?: () => void;
|
|
||||||
onHotelDiscountsClick?: () => void;
|
|
||||||
currentPage: string;
|
|
||||||
user?: { email: string; name: string; } | null;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function ItineraryViewPageDesign({
|
|
||||||
onBackClick,
|
|
||||||
onHomeClick,
|
|
||||||
onMelbourneClick,
|
|
||||||
onPassesClick,
|
|
||||||
onCheckoutClick,
|
|
||||||
onSignInClick,
|
|
||||||
onSignOutClick,
|
|
||||||
onAttractionsClick,
|
|
||||||
onBlogsClick,
|
|
||||||
onHowItWorksClick,
|
|
||||||
onFAQClick,
|
|
||||||
onPrivacyPolicyClick,
|
|
||||||
onAboutUsClick,
|
|
||||||
onProfileClick,
|
|
||||||
onCityCardsClick,
|
|
||||||
onMagicItineraryClick,
|
|
||||||
onPostCardsClick,
|
|
||||||
onOffersClick,
|
|
||||||
onCreateItineraryClick,
|
|
||||||
onContactUsClick,
|
|
||||||
onEsimsClick,
|
|
||||||
onHotelDiscountsClick,
|
|
||||||
currentPage,
|
|
||||||
user
|
|
||||||
}: ItineraryViewPageDesignProps) {
|
|
||||||
const [viewMode, setViewMode] = useState<'daily' | 'summary'>('daily');
|
|
||||||
// const [favorites, setFavorites] = useState<Set<string>>(new Set());
|
|
||||||
|
|
||||||
// ── API Integration ──────────────────────────────────────────────────────────
|
|
||||||
const { itineraryId } = useParams();
|
|
||||||
const { data: itineraryDetails, isLoading } = useGetItineraryDetailsByIdQuery(itineraryId);
|
|
||||||
|
|
||||||
const generatedItinerary = itineraryDetails ?? null;
|
|
||||||
const days = generatedItinerary?.days ?? [];
|
|
||||||
const summaries = generatedItinerary?.summary ?? [];
|
|
||||||
// ─────────────────────────────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
// const toggleFavorite = (activityKey: string) => {
|
|
||||||
// setFavorites(prev => {
|
|
||||||
// const newSet = new Set(prev);
|
|
||||||
// if (newSet.has(activityKey)) {
|
|
||||||
// newSet.delete(activityKey);
|
|
||||||
// } else {
|
|
||||||
// newSet.add(activityKey);
|
|
||||||
// }
|
|
||||||
// return newSet;
|
|
||||||
// });
|
|
||||||
// };
|
|
||||||
|
|
||||||
// ── Loading State ─────────────────────────────────────────────────────────────
|
|
||||||
if (isLoading) {
|
|
||||||
return (
|
|
||||||
<LoadingSpinner/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
// ─────────────────────────────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="min-h-screen bg-background">
|
|
||||||
{/* Navbar */}
|
|
||||||
<Navbar
|
|
||||||
activeCity=""
|
|
||||||
onCityChange={() => {}}
|
|
||||||
onSignInClick={onSignInClick}
|
|
||||||
onPassesClick={onPassesClick}
|
|
||||||
onCheckoutClick={onCheckoutClick}
|
|
||||||
onHomeClick={onHomeClick}
|
|
||||||
onMelbourneClick={onMelbourneClick}
|
|
||||||
onAttractionsClick={onAttractionsClick}
|
|
||||||
onBlogsClick={onBlogsClick}
|
|
||||||
onHowItWorksClick={onHowItWorksClick}
|
|
||||||
onFAQClick={onFAQClick}
|
|
||||||
onPrivacyPolicyClick={onPrivacyPolicyClick}
|
|
||||||
onAboutUsClick={onAboutUsClick}
|
|
||||||
onProfileClick={onProfileClick}
|
|
||||||
onCityCardsClick={onCityCardsClick}
|
|
||||||
onMagicItineraryClick={onMagicItineraryClick}
|
|
||||||
onPostCardsClick={onPostCardsClick}
|
|
||||||
onOffersClick={onOffersClick}
|
|
||||||
currentPage="itinerary-view"
|
|
||||||
isUserSignedIn={!!user}
|
|
||||||
user={user}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{/* Header Section */}
|
|
||||||
<section className="pt-32 pb-8 bg-gradient-to-br from-primary/5 to-secondary/5">
|
|
||||||
<div className="container mx-auto px-4 pt-32">
|
|
||||||
<motion.div
|
|
||||||
initial={{ opacity: 0, y: 30 }}
|
|
||||||
animate={{ opacity: 1, y: 0 }}
|
|
||||||
transition={{ duration: 0.6 }}
|
|
||||||
className="text-center"
|
|
||||||
>
|
|
||||||
<Button
|
|
||||||
variant="ghost"
|
|
||||||
onClick={onBackClick}
|
|
||||||
className="mb-6 hover:bg-primary/5 font-poppins font-medium"
|
|
||||||
>
|
|
||||||
<ArrowLeft className="w-4 h-4 mr-2" />
|
|
||||||
Back to Magic Itinerary
|
|
||||||
</Button>
|
|
||||||
|
|
||||||
<div className="flex items-center justify-center gap-2 text-primary mb-4">
|
|
||||||
<Star className="w-6 h-6 fill-current" />
|
|
||||||
<h1 className="font-merchant text-4xl md:text-5xl lg:text-6xl leading-tight">
|
|
||||||
<span className="font-light">Your</span>{' '}
|
|
||||||
<span className="font-bold italic bg-gradient-to-r from-primary to-secondary bg-clip-text text-transparent pr-3">
|
|
||||||
{generatedItinerary?.title ?? 'Magic Itinerary'}
|
|
||||||
</span>
|
|
||||||
</h1>
|
|
||||||
<Star className="w-6 h-6 fill-current" />
|
|
||||||
</div>
|
|
||||||
<p className="font-poppins text-xl leading-relaxed font-normal text-gray-600 max-w-2xl mx-auto">
|
|
||||||
Here's your personalized {generatedItinerary?.totalDays}-day adventure in {generatedItinerary?.city}!
|
|
||||||
</p>
|
|
||||||
</motion.div>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
{/* Main Content */}
|
|
||||||
<section className="py-8">
|
|
||||||
<div className="container mx-auto px-4">
|
|
||||||
<div className="max-w-6xl mx-auto space-y-8">
|
|
||||||
|
|
||||||
{/* View Toggle */}
|
|
||||||
<div className="flex justify-center">
|
|
||||||
<div className="bg-muted p-1 rounded-lg">
|
|
||||||
<Button
|
|
||||||
variant={viewMode === 'daily' ? 'default' : 'ghost'}
|
|
||||||
size="sm"
|
|
||||||
onClick={() => setViewMode('daily')}
|
|
||||||
className="rounded-md font-poppins font-medium"
|
|
||||||
>
|
|
||||||
Daily View
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
variant={viewMode === 'summary' ? 'default' : 'ghost'}
|
|
||||||
size="sm"
|
|
||||||
onClick={() => setViewMode('summary')}
|
|
||||||
className="rounded-md font-poppins font-medium"
|
|
||||||
>
|
|
||||||
Summary
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Itinerary Overview Card */}
|
|
||||||
<motion.div
|
|
||||||
initial={{ opacity: 0, y: 20 }}
|
|
||||||
animate={{ opacity: 1, y: 0 }}
|
|
||||||
transition={{ duration: 0.6, delay: 0.2 }}
|
|
||||||
>
|
|
||||||
{/* Banner Image */}
|
|
||||||
<Card className="overflow-hidden">
|
|
||||||
<div className="relative h-40 md:h-48">
|
|
||||||
<ImageWithFallback
|
|
||||||
src={generatedItinerary?.cityBanner}
|
|
||||||
alt={generatedItinerary?.city}
|
|
||||||
className="w-full h-full object-cover"
|
|
||||||
/>
|
|
||||||
<div className="absolute inset-0 bg-gradient-to-t from-black/70 via-black/30 to-transparent" />
|
|
||||||
<div className="absolute bottom-4 left-6">
|
|
||||||
<p className="font-poppins text-xs font-medium text-white/70 uppercase tracking-wider mb-1">Your Trip</p>
|
|
||||||
<h3 className="font-merchant text-2xl md:text-3xl text-white font-semibold">{generatedItinerary?.city}</h3>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Stats Row */}
|
|
||||||
<div className="grid grid-cols-3 divide-x divide-gray-100 bg-white">
|
|
||||||
<div className="flex flex-col items-center py-5">
|
|
||||||
<span className="font-merchant text-3xl text-primary">{generatedItinerary?.totalDays}</span>
|
|
||||||
<span className="font-poppins text-xs font-normal text-gray-500 mt-1">Days</span>
|
|
||||||
</div>
|
|
||||||
<div className="flex flex-col items-center py-5">
|
|
||||||
<span className="font-merchant text-3xl text-primary">{generatedItinerary?.totalStops}</span>
|
|
||||||
<span className="font-poppins text-xs font-normal text-gray-500 mt-1">Stops</span>
|
|
||||||
</div>
|
|
||||||
<div className="flex flex-col items-center py-5">
|
|
||||||
<span className="font-merchant text-3xl text-primary">{days[0]?.date}</span>
|
|
||||||
<span className="font-poppins text-xs font-normal text-gray-500 mt-1">Start Date</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</Card>
|
|
||||||
</motion.div>
|
|
||||||
|
|
||||||
{/* ── Daily View ──────────────────────────────────────────────────── */}
|
|
||||||
{viewMode === 'daily' && (
|
|
||||||
<motion.div
|
|
||||||
initial={{ opacity: 0, y: 20 }}
|
|
||||||
animate={{ opacity: 1, y: 0 }}
|
|
||||||
transition={{ duration: 0.6, delay: 0.3 }}
|
|
||||||
className="space-y-12"
|
|
||||||
>
|
|
||||||
{days.map((day: any, dayIndex: number) => (
|
|
||||||
<div key={dayIndex} className="space-y-6">
|
|
||||||
|
|
||||||
{/* City / Weather header — only on first day */}
|
|
||||||
{dayIndex === 0 && (
|
|
||||||
<motion.div
|
|
||||||
initial={{ opacity: 0, x: -20 }}
|
|
||||||
animate={{ opacity: 1, x: 0 }}
|
|
||||||
transition={{ duration: 0.6, delay: 0.4 }}
|
|
||||||
className="bg-gray-50 rounded-2xl p-6 shadow-sm border border-gray-200"
|
|
||||||
>
|
|
||||||
<div className="flex items-center justify-between">
|
|
||||||
<div>
|
|
||||||
<h2 className="font-poppins text-3xl md:text-4xl leading-tight mb-2">
|
|
||||||
{generatedItinerary?.city}, Australia
|
|
||||||
</h2>
|
|
||||||
</div>
|
|
||||||
<Sun className="w-10 h-10 text-amber-500" />
|
|
||||||
</div>
|
|
||||||
</motion.div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Day Header */}
|
|
||||||
<motion.div
|
|
||||||
initial={{ opacity: 0, x: -20 }}
|
|
||||||
animate={{ opacity: 1, x: 0 }}
|
|
||||||
transition={{ duration: 0.6, delay: 0.5 + dayIndex * 0.1 }}
|
|
||||||
className="flex items-center gap-4 pl-2"
|
|
||||||
>
|
|
||||||
<div className="bg-gradient-to-br from-primary to-secondary text-white w-16 h-16 rounded-full flex items-center justify-center shadow-lg">
|
|
||||||
<span className="font-merchant text-2xl font-semibold">{day.dayNumber}</span>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<h3 className="font-merchant text-2xl md:text-3xl leading-snug font-semibold">
|
|
||||||
Day {day.dayNumber}
|
|
||||||
</h3>
|
|
||||||
<p className="font-poppins text-base text-muted-foreground font-normal">{day.title}</p>
|
|
||||||
</div>
|
|
||||||
</motion.div>
|
|
||||||
|
|
||||||
{/* Time-zone label */}
|
|
||||||
<div className="pl-2">
|
|
||||||
<p className="font-poppins text-sm text-gray-500 font-normal">
|
|
||||||
{day.date}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Activity Cards */}
|
|
||||||
<div className="space-y-8">
|
|
||||||
{day.items?.map((activity: any, actIndex: number) => {
|
|
||||||
const activityKey = `day${day.dayNumber}-act${actIndex}`;
|
|
||||||
// const isFavorite = favorites.has(activityKey);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<motion.div
|
|
||||||
key={actIndex}
|
|
||||||
initial={{ opacity: 0, y: 20 }}
|
|
||||||
animate={{ opacity: 1, y: 0 }}
|
|
||||||
transition={{ duration: 0.5, delay: 0.6 + dayIndex * 0.1 + actIndex * 0.05 }}
|
|
||||||
className="flex gap-6"
|
|
||||||
>
|
|
||||||
{/* Time Column */}
|
|
||||||
<div className="flex-shrink-0 w-24 pt-2">
|
|
||||||
<div className="font-poppins text-base font-medium text-gray-700">
|
|
||||||
{activity.timeSlot}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Activity Card */}
|
|
||||||
<div className="flex-1">
|
|
||||||
<Card className="overflow-hidden hover:shadow-xl transition-shadow duration-300 border-2 border-gray-100">
|
|
||||||
<CardContent className="p-0">
|
|
||||||
{/* Hero Image */}
|
|
||||||
<div className="relative h-64 md:h-72 bg-gray-200">
|
|
||||||
<ImageWithFallback
|
|
||||||
src={activity.imageUrl}
|
|
||||||
alt={activity.title}
|
|
||||||
className="w-full h-full object-cover"
|
|
||||||
/>
|
|
||||||
|
|
||||||
{/* Favourite Button */}
|
|
||||||
{/* <div className="absolute top-4 right-4">
|
|
||||||
<Button
|
|
||||||
size="icon"
|
|
||||||
variant="secondary"
|
|
||||||
className="bg-white/95 hover:bg-white shadow-lg backdrop-blur-sm rounded-full w-12 h-12"
|
|
||||||
onClick={() => toggleFavorite(activityKey)}
|
|
||||||
>
|
|
||||||
<Heart className={`w-5 h-5 ${isFavorite ? 'fill-primary text-primary' : 'text-gray-700'}`} />
|
|
||||||
</Button>
|
|
||||||
</div> */}
|
|
||||||
|
|
||||||
{/* Get Directions — links to Google Maps via lat/lng */}
|
|
||||||
<div className="absolute bottom-4 left-4">
|
|
||||||
<Button
|
|
||||||
className="bg-primary hover:bg-primary/90 text-white font-poppins font-semibold shadow-lg px-6 py-3 rounded-xl"
|
|
||||||
onClick={() =>
|
|
||||||
window.open(
|
|
||||||
`https://www.google.com/maps?q=${activity.latitude},${activity.longitude}`,
|
|
||||||
'_blank'
|
|
||||||
)
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<Navigation className="w-4 h-4 mr-2" />
|
|
||||||
Get Directions
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Content */}
|
|
||||||
<div className="p-6 space-y-4">
|
|
||||||
<div className="space-y-2">
|
|
||||||
<h4 className="font-merchant text-xl md:text-2xl leading-snug font-semibold text-gray-900">
|
|
||||||
{activity.title}
|
|
||||||
</h4>
|
|
||||||
<div className="flex items-start gap-2 text-gray-600">
|
|
||||||
<MapPin className="w-4 h-4 mt-1 flex-shrink-0 text-primary" />
|
|
||||||
<span className="font-poppins text-sm font-normal leading-relaxed">
|
|
||||||
{activity.locationName}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Category Badges */}
|
|
||||||
<div className="flex flex-wrap gap-2">
|
|
||||||
{activity.categories?.map((cat: string, ci: number) => (
|
|
||||||
<Badge
|
|
||||||
key={ci}
|
|
||||||
variant="secondary"
|
|
||||||
className="font-poppins font-normal text-sm bg-primary/10 text-primary hover:bg-primary/20 px-3 py-1"
|
|
||||||
>
|
|
||||||
{cat}
|
|
||||||
</Badge>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Description */}
|
|
||||||
<div className="space-y-2 pt-2">
|
|
||||||
<div className="flex items-center gap-3">
|
|
||||||
<span className="text-primary font-semibold mt-1 flex-shrink-0">•</span>
|
|
||||||
<span className="font-poppins text-sm font-normal text-gray-600 leading-relaxed">
|
|
||||||
{activity.description}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
</div>
|
|
||||||
</motion.div>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</motion.div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* ── Summary View ─────────────────────────────────────────────────── */}
|
|
||||||
{viewMode === 'summary' && (
|
|
||||||
<motion.div
|
|
||||||
initial={{ opacity: 0, y: 20 }}
|
|
||||||
animate={{ opacity: 1, y: 0 }}
|
|
||||||
transition={{ duration: 0.6, delay: 0.3 }}
|
|
||||||
className="space-y-4"
|
|
||||||
>
|
|
||||||
<h3 className="font-merchant text-2xl md:text-3xl text-center mb-8 leading-tight font-semibold">
|
|
||||||
Trip Summary
|
|
||||||
</h3>
|
|
||||||
<Card className="p-6">
|
|
||||||
<div className="space-y-6">
|
|
||||||
{days.map((day: any, dayIndex: number) => {
|
|
||||||
// ✅ Match summary to the correct day by dayNumber
|
|
||||||
const daySummary = summaries.find((s: any) => s.dayNumber === day.dayNumber);
|
|
||||||
|
|
||||||
const dayDate = days[0]?.date
|
|
||||||
? new Date(
|
|
||||||
new Date(days[0].date).setDate(
|
|
||||||
new Date(days[0].date).getDate() + dayIndex
|
|
||||||
)
|
|
||||||
).toLocaleDateString('en-AU', {
|
|
||||||
day: '2-digit',
|
|
||||||
month: '2-digit',
|
|
||||||
year: 'numeric',
|
|
||||||
})
|
|
||||||
: '';
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div key={dayIndex} className="border-l-4 border-primary pl-6">
|
|
||||||
<div className="flex items-center justify-between mb-3">
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
<Calendar className="w-5 h-5 text-primary" />
|
|
||||||
<h4 className="font-merchant text-lg md:text-xl leading-snug font-semibold">
|
|
||||||
Day {day.dayNumber}: {daySummary?.title ?? day.title}
|
|
||||||
</h4>
|
|
||||||
</div>
|
|
||||||
<span className="font-poppins text-sm text-primary font-medium">{dayDate}</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="space-y-2">
|
|
||||||
{daySummary?.items?.map((item: any, actIndex: number) => (
|
|
||||||
<div key={actIndex} className="flex items-start gap-3 text-sm">
|
|
||||||
<CheckCircle className="w-4 h-4 text-green-500 flex-shrink-0 mt-0.5" />
|
|
||||||
<div className="flex-1">
|
|
||||||
<span className="font-poppins text-gray-700 font-medium">{item.title}</span>
|
|
||||||
<div className="flex items-center gap-2 mt-1">
|
|
||||||
<span className="font-poppins text-gray-500 text-xs font-normal">{item.timeSlot}</span>
|
|
||||||
<span className="text-gray-400">•</span>
|
|
||||||
<span className="font-poppins text-gray-500 text-xs font-normal">{item.locationName}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
</Card>
|
|
||||||
</motion.div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Action Buttons */}
|
|
||||||
<motion.div
|
|
||||||
initial={{ opacity: 0, y: 20 }}
|
|
||||||
animate={{ opacity: 1, y: 0 }}
|
|
||||||
transition={{ duration: 0.6, delay: 0.5 }}
|
|
||||||
className="flex flex-col sm:flex-row gap-4 justify-center pt-8"
|
|
||||||
>
|
|
||||||
<Button
|
|
||||||
variant="outline"
|
|
||||||
onClick={onCreateItineraryClick}
|
|
||||||
className="font-poppins font-medium px-8 py-3 text-lg"
|
|
||||||
>
|
|
||||||
<Heart className="w-5 h-5 mr-2" />
|
|
||||||
Create Another
|
|
||||||
</Button>
|
|
||||||
<Button className="bg-primary hover:bg-primary/90 font-poppins font-semibold px-8 py-3 text-lg">
|
|
||||||
<Download className="w-5 h-5 mr-2" />
|
|
||||||
Save Itinerary
|
|
||||||
</Button>
|
|
||||||
<Button variant="outline" className="font-poppins font-medium px-8 py-3 text-lg">
|
|
||||||
<Share2 className="w-5 h-5 mr-2" />
|
|
||||||
Share Trip
|
|
||||||
</Button>
|
|
||||||
</motion.div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
{/* Footer */}
|
|
||||||
<Footer
|
|
||||||
onHomeClick={onHomeClick}
|
|
||||||
onMelbourneClick={onMelbourneClick}
|
|
||||||
onPassesClick={onPassesClick}
|
|
||||||
onSignInClick={onSignInClick}
|
|
||||||
onAttractionsClick={onAttractionsClick}
|
|
||||||
onBlogsClick={onBlogsClick}
|
|
||||||
onHowItWorksClick={onHowItWorksClick}
|
|
||||||
onFAQClick={onFAQClick}
|
|
||||||
onPrivacyPolicyClick={onPrivacyPolicyClick}
|
|
||||||
onContactUsClick={onContactUsClick}
|
|
||||||
currentPage={currentPage}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -622,7 +622,7 @@ export function ProfilePage({
|
|||||||
<Card key={card.id} className="overflow-hidden">
|
<Card key={card.id} className="overflow-hidden">
|
||||||
<div
|
<div
|
||||||
className="flex cursor-pointer hover:bg-gray-50 transition-colors duration-200 rounded-lg p-2 -m-2"
|
className="flex cursor-pointer hover:bg-gray-50 transition-colors duration-200 rounded-lg p-2 -m-2"
|
||||||
onClick={() => navigate(`/view-card-design/${card.id}`)}
|
onClick={() => navigate(`/view-card-details/${card.id}`)}
|
||||||
>
|
>
|
||||||
<div className="w-32 h-32 flex-shrink-0">
|
<div className="w-32 h-32 flex-shrink-0">
|
||||||
<ImageWithFallback
|
<ImageWithFallback
|
||||||
@@ -765,7 +765,7 @@ export function ProfilePage({
|
|||||||
<h2 className="font-poppins text-2xl font-normal">My Itineraries</h2>
|
<h2 className="font-poppins text-2xl font-normal">My Itineraries</h2>
|
||||||
<Button
|
<Button
|
||||||
className="bg-gradient-to-r from-primary to-secondary hover:from-primary/90 hover:to-secondary/90 text-white font-poppins font-normal"
|
className="bg-gradient-to-r from-primary to-secondary hover:from-primary/90 hover:to-secondary/90 text-white font-poppins font-normal"
|
||||||
onClick={() => navigate("/create-itinerary-design")}
|
onClick={() => navigate("/create-itinerary")}
|
||||||
>
|
>
|
||||||
<Plus className="w-4 h-4 mr-2" />
|
<Plus className="w-4 h-4 mr-2" />
|
||||||
Create Itinerary
|
Create Itinerary
|
||||||
@@ -805,7 +805,7 @@ export function ProfilePage({
|
|||||||
<Button
|
<Button
|
||||||
variant="outline"
|
variant="outline"
|
||||||
className="w-full mt-4 font-poppins font-normal"
|
className="w-full mt-4 font-poppins font-normal"
|
||||||
onClick={()=>navigate(`/itinerary-view-design/${itinerary.id}`)}
|
onClick={()=>navigate(`/view-itinerary/${itinerary.id}`)}
|
||||||
>
|
>
|
||||||
View Itinerary
|
View Itinerary
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import { useState } from 'react';
|
|
||||||
import { motion } from 'motion/react';
|
import { motion } from 'motion/react';
|
||||||
import { ArrowLeft, ChevronRight, QrCode, CreditCard, Calendar, MapPin, Star, CheckCircle, Sparkles, Users, Clock, Gift, Ticket } from 'lucide-react';
|
import { ArrowLeft, ChevronRight, Calendar, MapPin, Star, CheckCircle, Sparkles, Users, Clock, Gift, Ticket } from 'lucide-react';
|
||||||
import { Button } from '../components/ui/button';
|
import { Button } from '../components/ui/button';
|
||||||
import { Card, CardContent } from '../components/ui/card';
|
import { Card, CardContent } from '../components/ui/card';
|
||||||
import { Badge } from '../components/ui/badge';
|
import { Badge } from '../components/ui/badge';
|
||||||
@@ -62,13 +61,10 @@ export function ViewCardDetailsPage({
|
|||||||
onPostCardsClick,
|
onPostCardsClick,
|
||||||
onOffersClick,
|
onOffersClick,
|
||||||
onContactUsClick,
|
onContactUsClick,
|
||||||
onEsimsClick,
|
|
||||||
onHotelDiscountsClick,
|
|
||||||
currentPage,
|
currentPage,
|
||||||
user
|
user
|
||||||
}: ViewCardDetailsPageProps) {
|
}: ViewCardDetailsPageProps) {
|
||||||
// Card type state
|
// Card type state
|
||||||
const [cardType, setCardType] = useState<'unlimited' | 'flexi'>('unlimited');
|
|
||||||
const { cardId } = useParams()
|
const { cardId } = useParams()
|
||||||
const { data, isLoading } = useGetUserCardDetailsQuery(cardId)
|
const { data, isLoading } = useGetUserCardDetailsQuery(cardId)
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
@@ -86,16 +82,6 @@ export function ViewCardDetailsPage({
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mock data for the current pass
|
|
||||||
const currentPass = {
|
|
||||||
name: 'Melbourne- Unlimited Card',
|
|
||||||
status: 'Active',
|
|
||||||
date: '22/12/2024',
|
|
||||||
duration: '2 Days',
|
|
||||||
adults: 3,
|
|
||||||
kids: 3
|
|
||||||
};
|
|
||||||
|
|
||||||
// Generate QR code pattern
|
// Generate QR code pattern
|
||||||
const generateQRPattern = () => {
|
const generateQRPattern = () => {
|
||||||
const size = 17;
|
const size = 17;
|
||||||
Reference in New Issue
Block a user