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