515 lines
30 KiB
TypeScript
515 lines
30 KiB
TypeScript
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;
|
||
}
|
||
|
||
/* ─── 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 {
|
||
const response = await addCardToCart(cardBookingDetails);
|
||
const bookingId = response?.data?.id
|
||
if (bookingId) {
|
||
navigate(`/payment/${bookingId}`)
|
||
} else {
|
||
throw new Error(response?.error?.data?.message)
|
||
}
|
||
} catch (error:any) {
|
||
toast.error(error.message);
|
||
}
|
||
}
|
||
|
||
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 CheckoutPage({
|
||
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])
|
||
|
||
if (isLoading) {
|
||
return <LoadingSpinner />
|
||
}
|
||
|
||
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>
|
||
);
|
||
}
|