735 lines
32 KiB
TypeScript
735 lines
32 KiB
TypeScript
import { useState, useEffect } from 'react';
|
|
import { motion, AnimatePresence } from 'motion/react';
|
|
import { ArrowLeft, CreditCard, Users, Calendar, MapPin, Shield, Truck, Clock, ChevronRight, Check, ChevronDown, X, Mail, Smartphone } from 'lucide-react';
|
|
import { Button } from './ui/button';
|
|
import { Input } from './ui/input';
|
|
import { Label } from './ui/label';
|
|
import { Card, CardContent, CardHeader, CardTitle } from './ui/card';
|
|
import { Separator } from './ui/separator';
|
|
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription } from './ui/dialog';
|
|
import { RadioGroup, RadioGroupItem } from './ui/radio-group';
|
|
import { Checkbox } from './ui/checkbox';
|
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from './ui/select';
|
|
import { Badge } from './ui/badge';
|
|
import { Textarea } from './ui/textarea';
|
|
import Navbar from './Navbar';
|
|
import { Footer } from './Footer';
|
|
import { ImageWithFallback } from './figma/ImageWithFallback';
|
|
import { Layout } from '../Layout';
|
|
|
|
interface CheckoutPageProps {
|
|
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;
|
|
onSecureCheckoutClick?: () => void;
|
|
onContactUsClick?: () => void;
|
|
onEsimsClick?: () => void;
|
|
onHotelDiscountsClick?: () => void;
|
|
currentPage?: string;
|
|
user?: { email: string; name: string } | null;
|
|
}
|
|
|
|
|
|
// Mock cart data
|
|
const mockCartItems = [
|
|
{
|
|
id: '1',
|
|
name: 'Paris Unlimited Pass',
|
|
type: '7-Day Pass',
|
|
price: 79,
|
|
originalPrice: 149,
|
|
discount: 47,
|
|
attractions: 45,
|
|
validity: '7 days',
|
|
image: 'https://images.unsplash.com/photo-1502602898536-47ad22581b52?w=400',
|
|
features: ['Skip-the-line access', 'Mobile voucher', 'Free cancellation']
|
|
}
|
|
];
|
|
|
|
export function CheckoutPage({
|
|
onBackClick,
|
|
onHomeClick,
|
|
onMelbourneClick,
|
|
onPassesClick,
|
|
onCheckoutClick,
|
|
onSignInClick,
|
|
onSignOutClick,
|
|
onAttractionsClick,
|
|
onBlogsClick,
|
|
onHowItWorksClick,
|
|
onFAQClick,
|
|
onPrivacyPolicyClick,
|
|
onAboutUsClick,
|
|
onProfileClick,
|
|
onCityCardsClick,
|
|
onMagicItineraryClick,
|
|
onPostCardsClick,
|
|
onOffersClick,
|
|
onSecureCheckoutClick,
|
|
onContactUsClick,
|
|
onEsimsClick,
|
|
onHotelDiscountsClick,
|
|
currentPage,
|
|
user,
|
|
}: CheckoutPageProps) {
|
|
const [purchaseType, setPurchaseType] = useState<'self' | 'gift'>('self');
|
|
const [selectedPayment, setSelectedPayment] = useState('credit-card');
|
|
const [showEmailVerification, setShowEmailVerification] = useState(false);
|
|
const [verificationCode, setVerificationCode] = useState('');
|
|
const [isEmailVerified, setIsEmailVerified] = useState(false);
|
|
const [formData, setFormData] = useState({
|
|
email: '',
|
|
firstName: '',
|
|
lastName: '',
|
|
phone: '',
|
|
country: '',
|
|
address: '',
|
|
city: '',
|
|
postalCode: '',
|
|
cardNumber: '',
|
|
expiry: '',
|
|
cvv: '',
|
|
cardName: '',
|
|
agreeTerms: false,
|
|
subscribeNewsletter: false
|
|
});
|
|
const [giftData, setGiftData] = useState({
|
|
recipientName: '',
|
|
recipientPhone: '',
|
|
recipientEmail: '',
|
|
personalizedMessage: ''
|
|
});
|
|
|
|
const subtotal = mockCartItems.reduce((sum, item) => sum + item.price, 0);
|
|
const tax = Math.round(subtotal * 0.1);
|
|
const total = subtotal + tax;
|
|
const totalSavings = mockCartItems.reduce((sum, item) => sum + (item.originalPrice - item.price), 0);
|
|
|
|
const handleInputChange = (field: string, value: string | boolean) => {
|
|
setFormData(prev => ({ ...prev, [field]: value }));
|
|
|
|
// Trigger email verification when email is complete
|
|
if (field === 'email' && typeof value === 'string' && value.includes('@') && value.includes('.') && !isEmailVerified) {
|
|
setTimeout(() => {
|
|
setShowEmailVerification(true);
|
|
}, 1000);
|
|
}
|
|
};
|
|
|
|
const handleGiftInputChange = (field: string, value: string) => {
|
|
setGiftData(prev => ({ ...prev, [field]: value }));
|
|
};
|
|
|
|
const handleEmailVerification = () => {
|
|
if (verificationCode === '123456') {
|
|
setIsEmailVerified(true);
|
|
setShowEmailVerification(false);
|
|
}
|
|
};
|
|
|
|
const handleSubmit = (e: React.FormEvent) => {
|
|
e.preventDefault();
|
|
if (!isEmailVerified) {
|
|
setShowEmailVerification(true);
|
|
return;
|
|
}
|
|
const checkoutData = {
|
|
purchaseType,
|
|
formData,
|
|
...(purchaseType === 'gift' && { giftData }),
|
|
selectedPayment,
|
|
cartItems: mockCartItems
|
|
};
|
|
console.log('Processing checkout...', checkoutData);
|
|
};
|
|
|
|
const paymentMethods = [
|
|
{
|
|
id: 'credit-card',
|
|
name: 'Credit Card',
|
|
icon: <CreditCard className="w-5 h-5" />,
|
|
description: 'Visa, Mastercard, American Express'
|
|
},
|
|
{
|
|
id: 'paypal',
|
|
name: 'PayPal',
|
|
icon: <div className="w-5 h-5 bg-blue-600 rounded-sm flex items-center justify-center text-xs font-bold text-white">P</div>,
|
|
description: 'Pay with your PayPal account'
|
|
},
|
|
{
|
|
id: 'google-pay',
|
|
name: 'Google Pay',
|
|
icon: <div className="w-5 h-5 bg-green-600 rounded-sm flex items-center justify-center text-xs font-bold text-white">G</div>,
|
|
description: 'Pay with Google Pay'
|
|
}
|
|
];
|
|
|
|
return (
|
|
<div className="min-h-screen bg-background">
|
|
<Layout
|
|
activeCity="Melbourne"
|
|
onSignInClick={onSignInClick}
|
|
onSignOutClick={onSignOutClick}
|
|
user={user}
|
|
>
|
|
|
|
{/* Header Section */}
|
|
<section className="pt-32 pb-8 bg-gradient-to-br from-muted/30 to-background">
|
|
<div className="container mx-auto px-4 pt-8">
|
|
{/* Back Button */}
|
|
<motion.button
|
|
onClick={onBackClick}
|
|
className="flex items-center gap-2 text-gray-600 hover:text-gray-900 mb-6 transition-colors duration-200"
|
|
initial={{ opacity: 0, x: -20 }}
|
|
animate={{ opacity: 1, x: 0 }}
|
|
transition={{ duration: 0.5 }}
|
|
>
|
|
<ArrowLeft className="w-5 h-5" />
|
|
<span className="font-medium">Back to Cart</span>
|
|
</motion.button>
|
|
|
|
{/* Page Title */}
|
|
<motion.div
|
|
initial={{ opacity: 0, y: 30 }}
|
|
animate={{ opacity: 1, y: 0 }}
|
|
transition={{ duration: 0.6, delay: 0.1 }}
|
|
>
|
|
<h1 className="font-merchant text-3xl md:text-4xl lg:text-5xl mb-4">
|
|
<span className="font-light">Secure</span>{' '}
|
|
<span className="font-bold italic bg-gradient-to-r from-primary to-secondary bg-clip-text text-transparent">Checkout</span>
|
|
</h1>
|
|
<p className="font-poppins text-lg text-gray-600">
|
|
Complete your purchase and start exploring Paris
|
|
</p>
|
|
</motion.div>
|
|
</div>
|
|
</section>
|
|
|
|
{/* Main Checkout Content */}
|
|
<section className="py-12">
|
|
<div className="container mx-auto px-4">
|
|
<form onSubmit={handleSubmit} className="grid grid-cols-1 lg:grid-cols-5 gap-8">
|
|
{/* Left Column - Form Inputs (3/5 width) */}
|
|
<div className="lg:col-span-3 space-y-8">
|
|
{/* Purchase Type Selection */}
|
|
<motion.div
|
|
initial={{ opacity: 0, y: 20 }}
|
|
animate={{ opacity: 1, y: 0 }}
|
|
transition={{ duration: 0.5, delay: 0.1 }}
|
|
>
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle className="font-merchant">Purchase Type</CardTitle>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<RadioGroup
|
|
value={purchaseType}
|
|
onValueChange={(value) => setPurchaseType(value as 'self' | 'gift')}
|
|
className="grid grid-cols-1 md:grid-cols-2 gap-4"
|
|
>
|
|
<div className="relative">
|
|
<RadioGroupItem value="self" id="self" className="peer sr-only" />
|
|
<Label
|
|
htmlFor="self"
|
|
className="flex flex-col items-center justify-between rounded-lg border-2 border-muted bg-white p-4 hover:bg-accent hover:text-accent-foreground peer-data-[state=checked]:border-primary [&:has([data-state=checked])]:border-primary cursor-pointer transition-all min-h-[44px]"
|
|
>
|
|
<div className="flex items-center gap-3 w-full">
|
|
<Users className="w-5 h-5" />
|
|
<div className="flex-1">
|
|
<p className="font-poppins font-medium">Buy for Myself</p>
|
|
<p className="text-sm text-muted-foreground font-poppins">Purchase a pass for your own use</p>
|
|
</div>
|
|
</div>
|
|
</Label>
|
|
</div>
|
|
<div className="relative">
|
|
<RadioGroupItem value="gift" id="gift" className="peer sr-only" />
|
|
<Label
|
|
htmlFor="gift"
|
|
className="flex flex-col items-center justify-between rounded-lg border-2 border-muted bg-white p-4 hover:bg-accent hover:text-accent-foreground peer-data-[state=checked]:border-primary [&:has([data-state=checked])]:border-primary cursor-pointer transition-all min-h-[44px]"
|
|
>
|
|
<div className="flex items-center gap-3 w-full">
|
|
<Mail className="w-5 h-5" />
|
|
<div className="flex-1">
|
|
<p className="font-poppins font-medium">Gift a Pass</p>
|
|
<p className="text-sm text-muted-foreground font-poppins">Send as a gift to someone special</p>
|
|
</div>
|
|
</div>
|
|
</Label>
|
|
</div>
|
|
</RadioGroup>
|
|
</CardContent>
|
|
</Card>
|
|
</motion.div>
|
|
|
|
{/* Gift Recipient Information - Only shown when gift is selected */}
|
|
{purchaseType === 'gift' && (
|
|
<motion.div
|
|
initial={{ opacity: 0, y: 20 }}
|
|
animate={{ opacity: 1, y: 0 }}
|
|
transition={{ duration: 0.5, delay: 0.2 }}
|
|
>
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle className="flex items-center gap-2 font-merchant">
|
|
<Mail className="w-5 h-5" />
|
|
Gift Recipient Details
|
|
</CardTitle>
|
|
</CardHeader>
|
|
<CardContent className="space-y-4">
|
|
<div>
|
|
<Label htmlFor="recipientName" className="font-poppins">Recipient Name *</Label>
|
|
<Input
|
|
id="recipientName"
|
|
value={giftData.recipientName}
|
|
onChange={(e) => handleGiftInputChange('recipientName', e.target.value)}
|
|
placeholder="Jane Smith"
|
|
required={purchaseType === 'gift'}
|
|
className="mt-1 font-poppins"
|
|
/>
|
|
</div>
|
|
<div>
|
|
<Label htmlFor="recipientEmail" className="font-poppins">Recipient Email Address *</Label>
|
|
<Input
|
|
id="recipientEmail"
|
|
type="email"
|
|
value={giftData.recipientEmail}
|
|
onChange={(e) => handleGiftInputChange('recipientEmail', e.target.value)}
|
|
placeholder="recipient@email.com"
|
|
required={purchaseType === 'gift'}
|
|
className="mt-1 font-poppins"
|
|
/>
|
|
</div>
|
|
<div>
|
|
<Label htmlFor="recipientPhone" className="font-poppins">Recipient Phone Number *</Label>
|
|
<Input
|
|
id="recipientPhone"
|
|
type="tel"
|
|
value={giftData.recipientPhone}
|
|
onChange={(e) => handleGiftInputChange('recipientPhone', e.target.value)}
|
|
placeholder="+1 (555) 123-4567"
|
|
required={purchaseType === 'gift'}
|
|
className="mt-1 font-poppins"
|
|
/>
|
|
</div>
|
|
<div>
|
|
<Label htmlFor="personalizedMessage" className="font-poppins">Personalized Message</Label>
|
|
<Textarea
|
|
id="personalizedMessage"
|
|
value={giftData.personalizedMessage}
|
|
onChange={(e) => handleGiftInputChange('personalizedMessage', e.target.value)}
|
|
placeholder="Write a special message for your gift recipient..."
|
|
rows={4}
|
|
className="mt-1 font-poppins"
|
|
/>
|
|
<p className="text-xs text-muted-foreground mt-1 font-poppins">Optional: Add a personal touch to your gift</p>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
</motion.div>
|
|
)}
|
|
|
|
{/* Contact Information */}
|
|
<motion.div
|
|
initial={{ opacity: 0, y: 20 }}
|
|
animate={{ opacity: 1, y: 0 }}
|
|
transition={{ duration: 0.5, delay: purchaseType === 'gift' ? 0.3 : 0.2 }}
|
|
>
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle className="flex items-center gap-2 font-merchant">
|
|
<Users className="w-5 h-5" />
|
|
{purchaseType === 'gift' ? 'Your Information (Purchaser)' : 'Contact Information'}
|
|
{isEmailVerified && <Check className="w-5 h-5 text-green-600" />}
|
|
</CardTitle>
|
|
</CardHeader>
|
|
<CardContent className="space-y-4">
|
|
<div>
|
|
<Label htmlFor="email" className="font-poppins">Email Address *</Label>
|
|
<div className="relative">
|
|
<Input
|
|
id="email"
|
|
type="email"
|
|
value={formData.email}
|
|
onChange={(e) => handleInputChange('email', e.target.value)}
|
|
placeholder="your@email.com"
|
|
required
|
|
className={`mt-1 font-poppins ${isEmailVerified ? 'border-green-600 bg-green-50' : ''}`}
|
|
/>
|
|
{isEmailVerified && (
|
|
<Check className="absolute right-3 top-1/2 transform -translate-y-1/2 w-5 h-5 text-green-600" />
|
|
)}
|
|
</div>
|
|
{isEmailVerified && (
|
|
<p className="text-sm text-green-600 mt-1 font-poppins">Email verified ✓</p>
|
|
)}
|
|
</div>
|
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
<div>
|
|
<Label htmlFor="firstName" className="font-poppins">First Name *</Label>
|
|
<Input
|
|
id="firstName"
|
|
value={formData.firstName}
|
|
onChange={(e) => handleInputChange('firstName', e.target.value)}
|
|
placeholder="John"
|
|
required
|
|
className="mt-1 font-poppins"
|
|
/>
|
|
</div>
|
|
<div>
|
|
<Label htmlFor="lastName" className="font-poppins">Last Name *</Label>
|
|
<Input
|
|
id="lastName"
|
|
value={formData.lastName}
|
|
onChange={(e) => handleInputChange('lastName', e.target.value)}
|
|
placeholder="Doe"
|
|
required
|
|
className="mt-1 font-poppins"
|
|
/>
|
|
</div>
|
|
</div>
|
|
<div>
|
|
<Label htmlFor="phone" className="font-poppins">Phone Number</Label>
|
|
<Input
|
|
id="phone"
|
|
type="tel"
|
|
value={formData.phone}
|
|
onChange={(e) => handleInputChange('phone', e.target.value)}
|
|
placeholder="+1 (555) 123-4567"
|
|
className="mt-1 font-poppins"
|
|
/>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
</motion.div>
|
|
|
|
{/* Payment Method */}
|
|
<motion.div
|
|
initial={{ opacity: 0, y: 20 }}
|
|
animate={{ opacity: 1, y: 0 }}
|
|
transition={{ duration: 0.5, delay: 0.3 }}
|
|
>
|
|
|
|
</motion.div>
|
|
|
|
{/* Billing Address */}
|
|
<motion.div
|
|
initial={{ opacity: 0, y: 20 }}
|
|
animate={{ opacity: 1, y: 0 }}
|
|
transition={{ duration: 0.5, delay: purchaseType === 'gift' ? 0.4 : 0.3 }}
|
|
>
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle className="flex items-center gap-2 font-merchant">
|
|
<MapPin className="w-5 h-5" />
|
|
Billing Address
|
|
</CardTitle>
|
|
</CardHeader>
|
|
<CardContent className="space-y-4">
|
|
<div>
|
|
<Label htmlFor="country" className="font-poppins">Country *</Label>
|
|
<Select value={formData.country} onValueChange={(value) => handleInputChange('country', value)}>
|
|
<SelectTrigger className="mt-1">
|
|
<SelectValue placeholder="Select country" />
|
|
</SelectTrigger>
|
|
<SelectContent>
|
|
<SelectItem value="us">United States</SelectItem>
|
|
<SelectItem value="au">Australia</SelectItem>
|
|
<SelectItem value="uk">United Kingdom</SelectItem>
|
|
<SelectItem value="ca">Canada</SelectItem>
|
|
<SelectItem value="de">Germany</SelectItem>
|
|
<SelectItem value="fr">France</SelectItem>
|
|
<SelectItem value="in">India</SelectItem>
|
|
<SelectItem value="jp">Japan</SelectItem>
|
|
</SelectContent>
|
|
</Select>
|
|
</div>
|
|
<div>
|
|
<Label htmlFor="address" className="font-poppins">Street Address *</Label>
|
|
<Input
|
|
id="address"
|
|
value={formData.address}
|
|
onChange={(e) => handleInputChange('address', e.target.value)}
|
|
placeholder="123 Main Street"
|
|
required
|
|
className="mt-1 font-poppins"
|
|
/>
|
|
</div>
|
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
<div>
|
|
<Label htmlFor="city" className="font-poppins">City *</Label>
|
|
<Input
|
|
id="city"
|
|
value={formData.city}
|
|
onChange={(e) => handleInputChange('city', e.target.value)}
|
|
placeholder="New York"
|
|
required
|
|
className="mt-1 font-poppins"
|
|
/>
|
|
</div>
|
|
<div>
|
|
<Label htmlFor="postalCode" className="font-poppins">Postal Code *</Label>
|
|
<Input
|
|
id="postalCode"
|
|
value={formData.postalCode}
|
|
onChange={(e) => handleInputChange('postalCode', e.target.value)}
|
|
placeholder="10001"
|
|
required
|
|
className="mt-1 font-poppins"
|
|
/>
|
|
</div>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
</motion.div>
|
|
|
|
{/* Terms and Newsletter */}
|
|
<motion.div
|
|
initial={{ opacity: 0, y: 20 }}
|
|
animate={{ opacity: 1, y: 0 }}
|
|
transition={{ duration: 0.5, delay: purchaseType === 'gift' ? 0.5 : 0.4 }}
|
|
className="space-y-4"
|
|
>
|
|
<div className="flex items-start space-x-2">
|
|
<Checkbox
|
|
id="terms"
|
|
checked={formData.agreeTerms}
|
|
onCheckedChange={(checked) => handleInputChange('agreeTerms', checked as boolean)}
|
|
/>
|
|
<div className="grid gap-1.5 leading-none">
|
|
<label
|
|
htmlFor="terms"
|
|
className="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70 font-poppins"
|
|
>
|
|
I agree to the Terms of Service and Privacy Policy *
|
|
</label>
|
|
</div>
|
|
</div>
|
|
<div className="flex items-start space-x-2">
|
|
<Checkbox
|
|
id="newsletter"
|
|
checked={formData.subscribeNewsletter}
|
|
onCheckedChange={(checked) => handleInputChange('subscribeNewsletter', checked as boolean)}
|
|
/>
|
|
<div className="grid gap-1.5 leading-none">
|
|
<label
|
|
htmlFor="newsletter"
|
|
className="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70 font-poppins"
|
|
>
|
|
Subscribe to our newsletter for travel tips and special offers
|
|
</label>
|
|
</div>
|
|
</div>
|
|
</motion.div>
|
|
</div>
|
|
|
|
{/* Right Column - Order Summary (2/5 width) */}
|
|
<div className="lg:col-span-2">
|
|
<motion.div
|
|
initial={{ opacity: 0, y: 20 }}
|
|
animate={{ opacity: 1, y: 0 }}
|
|
transition={{ duration: 0.5, delay: purchaseType === 'gift' ? 0.4 : 0.3 }}
|
|
className="sticky top-24"
|
|
>
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle className="font-merchant flex items-center gap-2">
|
|
Order Summary
|
|
{purchaseType === 'gift' && (
|
|
<Badge variant="secondary" className="font-poppins">
|
|
<Mail className="w-3 h-3 mr-1" />
|
|
Gift Purchase
|
|
</Badge>
|
|
)}
|
|
</CardTitle>
|
|
</CardHeader>
|
|
<CardContent className="space-y-6">
|
|
{/* Gift Indicator */}
|
|
{purchaseType === 'gift' && giftData.recipientName && (
|
|
<div className="bg-gradient-to-r from-primary/10 to-secondary/10 border border-primary/20 rounded-lg p-4">
|
|
<div className="flex items-start gap-3">
|
|
<Mail className="w-5 h-5 text-primary mt-0.5" />
|
|
<div className="flex-1">
|
|
<p className="font-medium text-sm font-poppins mb-1">Gift Recipient</p>
|
|
<p className="text-sm text-gray-700 font-poppins">{giftData.recipientName}</p>
|
|
<p className="text-xs text-gray-500 font-poppins mt-1">{giftData.recipientEmail}</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{/* Cart Items */}
|
|
<div className="space-y-4">
|
|
{mockCartItems.map((item) => (
|
|
<div key={item.id} className="flex gap-4">
|
|
<ImageWithFallback
|
|
src={item.image}
|
|
alt={item.name}
|
|
className="w-20 h-20 rounded-lg object-cover"
|
|
/>
|
|
<div className="flex-1 min-w-0">
|
|
<h4 className="font-medium text-base mb-1 font-poppins">{item.name}</h4>
|
|
<p className="text-sm text-gray-500 mb-2 font-poppins">{item.type}</p>
|
|
<div className="flex items-center gap-2 mb-2">
|
|
<span className="font-bold text-lg font-poppins">€{item.price}</span>
|
|
<span className="text-sm text-gray-500 line-through font-poppins">€{item.originalPrice}</span>
|
|
<Badge variant="outline" className="text-xs">
|
|
-{item.discount}%
|
|
</Badge>
|
|
</div>
|
|
<div className="text-sm text-gray-600 font-poppins">
|
|
{item.attractions} attractions • Valid for {item.validity}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
|
|
<Separator />
|
|
|
|
{/* Features Summary */}
|
|
<div className="space-y-3">
|
|
<h4 className="font-medium font-merchant">What's Included:</h4>
|
|
<div className="space-y-2">
|
|
<div className="flex items-center gap-2 text-sm font-poppins">
|
|
<Check className="w-4 h-4 text-green-600" />
|
|
<span>Skip-the-line access to {mockCartItems[0].attractions} attractions</span>
|
|
</div>
|
|
<div className="flex items-center gap-2 text-sm font-poppins">
|
|
<Check className="w-4 h-4 text-green-600" />
|
|
<span>Valid for {mockCartItems[0].validity}</span>
|
|
</div>
|
|
<div className="flex items-center gap-2 text-sm font-poppins">
|
|
<Check className="w-4 h-4 text-green-600" />
|
|
<span>Instant mobile delivery</span>
|
|
</div>
|
|
<div className="flex items-center gap-2 text-sm font-poppins">
|
|
<Check className="w-4 h-4 text-green-600" />
|
|
<span>Free cancellation</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<Separator />
|
|
|
|
{/* Pricing Breakdown */}
|
|
<div className="space-y-3">
|
|
<div className="flex justify-between text-sm font-poppins">
|
|
<span>Subtotal</span>
|
|
<span>€{subtotal}</span>
|
|
</div>
|
|
<div className="flex justify-between text-sm font-poppins">
|
|
<span>Taxes & Fees</span>
|
|
<span>€{tax}</span>
|
|
</div>
|
|
<div className="flex justify-between text-sm text-green-600 font-poppins">
|
|
<span>Total Savings</span>
|
|
<span>-€{totalSavings}</span>
|
|
</div>
|
|
<Separator />
|
|
<div className="flex justify-between font-bold text-xl font-poppins">
|
|
<span>Total</span>
|
|
<span>€{total}</span>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Checkout Button */}
|
|
<Button
|
|
type="button"
|
|
onClick={() => onSecureCheckoutClick?.()}
|
|
className="w-full bg-gradient-to-r from-primary to-secondary hover:from-primary/90 hover:to-secondary/90 text-white font-medium py-4 text-lg font-poppins"
|
|
disabled={
|
|
!formData.agreeTerms ||
|
|
!formData.email ||
|
|
!formData.firstName ||
|
|
!formData.lastName ||
|
|
!isEmailVerified ||
|
|
(purchaseType === 'gift' && (!giftData.recipientName || !giftData.recipientEmail || !giftData.recipientPhone))
|
|
}
|
|
>
|
|
{purchaseType === 'gift' ? 'Send Gift & Proceed to Checkout' : 'Proceed to Secure Checkout'}
|
|
<ChevronRight className="w-5 h-5 ml-2" />
|
|
</Button>
|
|
|
|
{/* Security Notice */}
|
|
<div className="flex items-center gap-2 text-xs text-gray-500 justify-center font-poppins">
|
|
<Shield className="w-4 h-4" />
|
|
<span>Secure 256-bit SSL encryption</span>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
</motion.div>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</section>
|
|
|
|
{/* Email Verification Popup */}
|
|
<Dialog open={showEmailVerification} onOpenChange={setShowEmailVerification}>
|
|
<DialogContent className="sm:max-w-md">
|
|
<DialogHeader>
|
|
<DialogTitle className="flex items-center gap-2 font-merchant">
|
|
<Mail className="w-5 h-5" />
|
|
Verify Your Email
|
|
</DialogTitle>
|
|
<DialogDescription className="font-poppins">
|
|
Enter the verification code sent to your email to continue with your booking.
|
|
</DialogDescription>
|
|
</DialogHeader>
|
|
<div className="space-y-4">
|
|
<p className="text-sm text-gray-600 font-poppins">
|
|
We've sent a verification code to <strong>{formData.email}</strong>
|
|
</p>
|
|
<div>
|
|
<Label htmlFor="verification" className="font-poppins">Verification Code</Label>
|
|
<Input
|
|
id="verification"
|
|
value={verificationCode}
|
|
onChange={(e) => setVerificationCode(e.target.value)}
|
|
placeholder="Enter 6-digit code"
|
|
className="mt-1 font-poppins text-center text-lg letter-spacing-2"
|
|
maxLength={6}
|
|
/>
|
|
</div>
|
|
<div className="flex gap-3">
|
|
<Button
|
|
onClick={handleEmailVerification}
|
|
className="flex-1 bg-gradient-to-r from-primary to-secondary hover:from-primary/90 hover:to-secondary/90 text-white font-poppins"
|
|
disabled={verificationCode.length !== 6}
|
|
>
|
|
Verify Email
|
|
</Button>
|
|
<Button
|
|
variant="outline"
|
|
onClick={() => setShowEmailVerification(false)}
|
|
className="font-poppins"
|
|
>
|
|
Skip for now
|
|
</Button>
|
|
</div>
|
|
<p className="text-xs text-gray-500 text-center font-poppins">
|
|
Demo code: <strong>123456</strong>
|
|
</p>
|
|
</div>
|
|
</DialogContent>
|
|
</Dialog>
|
|
|
|
</Layout>
|
|
</div>
|
|
);
|
|
} |