Files
CityCards-Website/src/pages/PaymentDetailsPage.tsx

640 lines
29 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import React, { useEffect, useState } from 'react';
import { motion, AnimatePresence } from 'motion/react';
import {
ArrowLeft, User, Lock, Shield, Pencil, UserCheck, Gift, AlertCircle
} from 'lucide-react';
import Navbar from '../components/Navbar';
import { Footer } from '../components/Footer';
import { Card, CardContent, CardHeader } from '../components/ui/card';
import { Separator } from '../components/ui/separator';
import { useGetUserProfileDetailsQuery } from '../Redux/services/profile.service';
import LoadingSpinner from '../components/LoadingSpinner';
import { useNavigate, useParams } from 'react-router-dom';
import {
useGetCardBookingDetailsQuery,
useStoreRecipientDetailsMutation,
usePayForCardMutation,
} from '../Redux/services/cards.service';
import { toast } from 'sonner';
import countries from 'i18n-iso-countries';
import enLocale from 'i18n-iso-countries/langs/en.json';
export interface CheckoutOrderItem {
city: string;
cardType: 'Flexi' | 'Unlimited';
days: number;
adults: number;
children: number;
quantity: number;
pricePerUnit: number;
}
interface PaymentDetailsPageProps {
checkoutOrder?: CheckoutOrderItem | null;
onBackClick: () => void;
onPaymentComplete: () => void;
onHomeClick: () => void;
onPassesClick: () => void;
onAttractionsClick?: () => void;
onBlogsClick?: () => void;
onHowItWorksClick?: () => void;
onFAQClick?: () => void;
onPrivacyPolicyClick?: () => void;
onAboutUsClick?: () => void;
onProfileClick?: () => void;
onCityCardsClick?: () => void;
onMagicItineraryClick?: () => void;
onPostCardsClick?: () => void;
onOffersClick?: () => void;
onSuperSavingsClick?: () => void;
onEsimsClick?: () => void;
onHotelDiscountsClick?: () => void;
onContactUsClick?: () => void;
onCartClick?: () => void;
onCheckoutClick?: () => void;
onSignInClick: () => void;
onSignOutClick?: () => void;
currentPage?: string;
user?: { email: string; name: string } | null;
}
// Register English locale for country codes
countries.registerLocale(enLocale);
const getCountryCode = (countryName: string): string => {
const code = countries.getAlpha2Code(countryName, 'en');
if (code) return code;
if (countryName.length === 2 && /^[A-Z]{2}$/i.test(countryName)) {
return countryName.toUpperCase();
}
console.warn(`Unknown country name: ${countryName}, defaulting to 'AU'`);
return 'AU';
};
/* ─── Editable field component ─── */
function Field({
label,
value,
onChange,
placeholder,
type = 'text',
error,
maxLength,
inputMode,
prefilled,
disabled = false,
}: {
label: React.ReactNode;
value: string;
onChange: (v: string) => void;
placeholder?: string;
type?: string;
error?: string;
maxLength?: number;
inputMode?: React.HTMLAttributes<HTMLInputElement>['inputMode'];
prefilled?: boolean;
disabled?: boolean;
}) {
const [focused, setFocused] = useState(false);
return (
<div className="flex flex-col gap-1 w-full">
<label className="font-poppins text-sm font-normal text-[#555] leading-relaxed">
{label}
</label>
<div className="relative">
<input
type={type}
value={value}
onChange={(e) => onChange(e.target.value)}
onFocus={() => setFocused(true)}
onBlur={() => setFocused(false)}
placeholder={placeholder}
maxLength={maxLength}
inputMode={inputMode}
disabled={disabled}
className={`w-full border rounded-xl px-4 py-3 pr-10 font-poppins text-base font-normal text-[#2a2a2a] outline-none transition-all duration-200 placeholder:text-[#ccc]
${disabled
? 'bg-gray-100 text-gray-500 cursor-not-allowed border-gray-300'
: error
? 'border-red-300 focus:border-red-400 bg-red-50/30'
: focused
? 'border-[#F95F62] ring-2 ring-[#F95F62]/10'
: prefilled
? 'border-[#F95F62]/25 bg-[#F95F62]/[0.02]'
: 'border-[#E4AFB1] bg-[#FFF5F5]'
}`}
/>
{prefilled && !focused && !disabled && (
<Pencil className="absolute right-3 top-1/2 -translate-y-1/2 w-3.5 h-3.5 text-[#F95F62]/40" />
)}
</div>
{error && (
<span className="font-poppins text-xs font-normal text-red-500 flex items-center gap-1">
<AlertCircle className="w-3 h-3" />{error}
</span>
)}
</div>
);
}
/* ─── Card type badge ─── */
function CardTypeBadge({ cardType }: { cardType: 'Flexi' | 'Unlimited' }) {
return (
<span className={`inline-flex items-center px-3 py-1 rounded-full font-poppins text-xs font-medium ${cardType === 'Flexi'
? 'bg-[#f95faf]/10 text-[#f95faf]'
: 'bg-[#F95F62]/10 text-[#F95F62]'
}`}>
{cardType} Card
</span>
);
}
/* ─── Main Component ─── */
export function PaymentDetailsPage({
onHomeClick,
onPassesClick,
onAttractionsClick,
onBlogsClick,
onHowItWorksClick,
onFAQClick,
onPrivacyPolicyClick,
onAboutUsClick,
onProfileClick,
onCityCardsClick,
onMagicItineraryClick,
onPostCardsClick,
onOffersClick,
onSuperSavingsClick,
onEsimsClick,
onHotelDiscountsClick,
onContactUsClick,
onCartClick,
onCheckoutClick,
onSignInClick,
onSignOutClick,
currentPage,
user,
}: PaymentDetailsPageProps) {
const [selectedTab, setSelectedTab] = useState<'myself' | 'gift'>('myself');
// Gift fields
const [giftFirstName, setGiftFirstName] = useState('');
const [giftLastName, setGiftLastName] = useState('');
const [giftEmail, setGiftEmail] = useState('');
const [giftPhone, setGiftPhone] = useState('');
const [giftCity, setGiftCity] = useState('');
const [giftCountry, setGiftCountry] = useState('');
const [giftIsd, setGiftIsd] = useState('');
const [giftMessage, setGiftMessage] = useState('');
// Profile data
const [formData, setFormData] = useState({
firstName: '',
lastName: '',
email: '',
phone: '',
country: '',
address1: '',
address2: '',
city: '',
postalCode: '',
});
const navigate = useNavigate();
const userId = localStorage.getItem('userId');
const { bookingId } = useParams();
const { data: userDetails, isLoading } = useGetUserProfileDetailsQuery(userId);
const { data } = useGetCardBookingDetailsQuery(bookingId);
const [storeRecipientDetails] = useStoreRecipientDetailsMutation();
const [payForCard, { isLoading: isCreatingPayment }] = usePayForCardMutation();
const bookingDetails = data?.bookingDetails ?? null;
useEffect(() => {
if (userDetails) {
setFormData({
firstName: userDetails?.firstName,
lastName: userDetails?.lastName,
email: userDetails?.emailAddress,
phone: userDetails?.mobileNumber,
country: userDetails?.country,
address1: userDetails?.address1,
address2: userDetails?.address2,
city: userDetails?.cityName,
postalCode: userDetails?.zipCode,
});
}
}, [userDetails]);
const [errors, setErrors] = useState<Record<string, string>>({});
const validate = () => {
const e: Record<string, string> = {};
if (selectedTab === 'gift') {
// First Name
if (!giftFirstName.trim()) e.giftFirstName = 'First name is required';
else if (/\s/.test(giftFirstName)) e.giftFirstName = 'First name must not contain spaces';
else if (!/^[A-Za-z]+$/.test(giftFirstName)) e.giftFirstName = 'First name must contain only letters (AZ)';
else if (giftFirstName.length < 2 || giftFirstName.length > 50) e.giftFirstName = 'First name must be between 2 and 50 characters';
// Last Name
if (!giftLastName.trim()) e.giftLastName = 'Last name is required';
else if (/\s/.test(giftLastName)) e.giftLastName = 'Last name must not contain spaces';
else if (!/^[A-Za-z]+$/.test(giftLastName)) e.giftLastName = 'Last name must contain only letters (AZ)';
else if (giftLastName.length < 2 || giftLastName.length > 50) e.giftLastName = 'Last name must be between 2 and 50 characters';
// ISD Code
if (!giftIsd.trim()) e.giftIsd = 'ISD code is required';
else if (/\s/.test(giftIsd)) e.giftIsd = 'ISD code must not contain spaces';
else if (!giftIsd.startsWith('+')) e.giftIsd = "ISD code must start with '+' (e.g. +91)";
else if (!/^\+\d+$/.test(giftIsd)) e.giftIsd = "ISD code must contain only digits after '+'";
// Email
if (!giftEmail.trim()) e.giftEmail = 'Email address is required';
else if (!/\S+@\S+\.\S+/.test(giftEmail)) e.giftEmail = 'Enter a valid email (e.g. name@example.com)';
// Phone
if (!giftPhone.trim()) e.giftPhone = 'Phone number is required';
else if (/\s/.test(giftPhone)) e.giftPhone = 'Phone number must not contain spaces';
else if (!/^\d+$/.test(giftPhone)) e.giftPhone = 'Phone number must contain only digits (09)';
else if (giftPhone.length < 7 || giftPhone.length > 15) e.giftPhone = 'Phone number must be between 7 and 15 digits';
// Message
if (!giftMessage.trim()) e.giftMessage = 'Message is required';
else if (giftMessage.length < 5) e.giftMessage = 'Message must be at least 5 characters long';
else if (giftMessage.length > 500) e.giftMessage = 'Message must not exceed 500 characters';
// City
if (!giftCity.trim()) e.giftCity = 'City is required';
else if (!/^[A-Za-z\s\-'À-ÿ]+$/.test(giftCity)) e.giftCity = 'City can only contain letters and spaces';
else if (/\s{2,}/.test(giftCity)) e.giftCity = 'City must not contain multiple consecutive spaces';
else if (giftCity.length < 2 || giftCity.length > 50) e.giftCity = 'City must be between 2 and 50 characters';
// Country
if (!giftCountry.trim()) e.giftCountry = 'Country is required';
else if (!/^[A-Za-z\s\-'À-ÿ]+$/.test(giftCountry)) e.giftCountry = 'Country can only contain letters and spaces';
else if (giftCountry.length < 2 || giftCountry.length > 50) e.giftCountry = 'Country must be between 2 and 50 characters';
}
return e;
};
const [isRedirecting, setIsRedirecting] = useState(false);
const handlePayment = async () => {
const validationErrors = validate();
setErrors(validationErrors);
if (Object.keys(validationErrors).length > 0) {
// toast.error('Please fill all required fields');
return;
}
if (selectedTab === 'gift') {
const recipientDetails = {
isForSelf: true,
recipientFirstName: giftFirstName,
recipientLastName: giftLastName,
recipientEmail: giftEmail,
recipientIsdCode: `+${giftIsd}`,
recipientPhone: giftPhone,
recipientCity: giftCity,
recipientCountry: giftCountry,
giftMessage: giftMessage,
};
try {
await storeRecipientDetails({ recipientDetails, bookingId }).unwrap();
toast.success('Gift details saved!');
} catch (err) {
console.error('Failed to save gift details:', err);
toast.error('Failed to save gift details. Please try again.');
return;
}
}
setIsRedirecting(true);
try {
const payResponse = await payForCard(bookingId).unwrap();
console.log('payForCard response:', payResponse);
const { checkoutPageUrl } = payResponse;
const setCookie = (name: string, value: string, days = 1) => {
const expires = new Date();
expires.setTime(expires.getTime() + days * 24 * 60 * 60 * 1000);
document.cookie = `${name}=${value};expires=${expires.toUTCString()};path=/;SameSite=Lax`;
};
setCookie('pendingBookingId', bookingId);
localStorage.setItem('pendingBookingId', bookingId);
sessionStorage.setItem('pendingBookingId', bookingId);
if (!checkoutPageUrl || typeof checkoutPageUrl !== 'string') {
throw new Error('Invalid checkout URL received from server');
}
if (!checkoutPageUrl.startsWith('http://') && !checkoutPageUrl.startsWith('https://')) {
throw new Error('Checkout URL must start with http:// or https://');
}
window.location.href = checkoutPageUrl;
} catch (err: any) {
console.error('Payment initiation error:', err);
const errorMsg = err?.data?.message || err?.message || 'Failed to initiate payment. Please try again.';
toast.error(errorMsg);
setIsRedirecting(false);
}
};
if (isLoading) {
return <LoadingSpinner />;
}
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}
/>
<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>
<motion.div initial={{ opacity: 0, y: 20 }} animate={{ opacity: 1, y: 0 }} className="mb-8">
<div className="flex items-center gap-4 mb-2">
<h1 className="font-poppins text-2xl md:text-3xl lg:text-4xl leading-tight">
<span className="font-light">Review & </span>
<span className="font-bold italic bg-gradient-to-r from-[#F95F62] to-[#F95FAF] bg-clip-text text-transparent pr-2">Pay</span>
</h1>
<div className="flex items-center gap-2 text-[#22a86b]">
<Shield className="w-4 h-4" />
<span className="font-poppins text-sm font-medium">SSL Secured</span>
</div>
</div>
<p className="font-poppins text-sm leading-relaxed font-normal text-[#8e8e8e]">
Complete your purchase securely. You will be redirected to Stripe to enter your card details.
</p>
</motion.div>
<div className="grid lg:grid-cols-3 gap-8">
<motion.div initial={{ opacity: 0, x: -20 }} animate={{ opacity: 1, x: 0 }} className="lg:col-span-2">
<Card className="shadow-lg border-0 overflow-hidden">
<CardHeader className="pb-0 pt-6 px-6 border-b border-gray-100">
<div className="flex gap-2 mb-6">
<button
onClick={() => setSelectedTab('myself')}
className={`flex-1 flex items-center justify-center gap-2 py-3 rounded-xl font-poppins text-sm font-medium transition-all duration-200 ${selectedTab === 'myself'
? 'bg-[#F95F62] text-white shadow-md shadow-[#F95F62]/20'
: 'bg-gray-100 text-[#555] hover:bg-gray-200'
}`}
>
<User className="w-4 h-4" />
<span>For myself</span>
</button>
<button
onClick={() => setSelectedTab('gift')}
className={`flex-1 flex items-center justify-center gap-2 py-3 rounded-xl font-poppins text-sm font-medium transition-all duration-200 ${selectedTab === 'gift'
? 'bg-[#F95F62] text-white shadow-md shadow-[#F95F62]/20'
: 'bg-gray-100 text-[#555] hover:bg-gray-200'
}`}
>
<Gift className="w-4 h-4" />
<span>To gift Someone</span>
</button>
</div>
</CardHeader>
<CardContent className="px-6 py-6 space-y-6">
<motion.div
initial={{ opacity: 0, y: -8 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.3 }}
className="flex items-center gap-3 bg-[#F95F62]/6 border border-[#F95F62]/20 rounded-xl px-4 py-3"
>
<div className="w-8 h-8 rounded-lg bg-[#F95F62]/10 flex items-center justify-center flex-shrink-0">
<UserCheck className="w-4 h-4 text-[#F95F62]" />
</div>
<p className="font-poppins text-sm font-normal text-[#555] leading-relaxed">
<span className="font-medium text-[#2a2a2a]">Details pre-filled from your profile.</span>{' '}
{selectedTab === 'myself' ? 'Personal & billing details are locked.' : 'Only gift recipient details are editable.'}
</p>
</motion.div>
<Separator />
{/* Personal Information */}
<motion.div initial={{ opacity: 0 }} animate={{ opacity: 1 }} transition={{ delay: 0.1 }} className="space-y-5">
<div className="flex items-center gap-3">
<div className="w-8 h-8 bg-[#F95F62] text-white rounded-full flex items-center justify-center font-poppins text-sm font-semibold flex-shrink-0">
1
</div>
<h2 className="font-poppins text-xl leading-snug font-semibold text-[#2a2a2a]">Personal Information</h2>
</div>
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
<Field label="First Name" value={formData.firstName} onChange={() => { }} prefilled disabled />
<Field label="Last Name" value={formData.lastName} onChange={() => { }} prefilled disabled />
</div>
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
<Field label="Email Address" value={formData.email} onChange={() => { }} type="email" prefilled disabled />
<Field label="Phone Number" value={formData.phone} onChange={() => { }} type="tel" prefilled disabled />
</div>
</motion.div>
{/* Gift Section */}
<AnimatePresence>
{selectedTab === 'gift' && (
<motion.div
key="gift-section"
initial={{ opacity: 0, height: 0 }}
animate={{ opacity: 1, height: 'auto' }}
exit={{ opacity: 0, height: 0 }}
transition={{ duration: 0.25 }}
className="overflow-hidden"
>
<div className="border border-[#F95F62]/15 rounded-xl px-5 py-4 space-y-4">
<div className="flex items-center gap-2">
<Gift className="w-4 h-4 text-[#F95F62]" />
<h3 className="font-poppins text-base font-semibold text-[#2a2a2a]">Gift Recipient Details</h3>
</div>
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
<Field label={<>Recipient First Name <span className="text-red-500">*</span></>} value={giftFirstName} onChange={setGiftFirstName} placeholder="Enter recipient's first name" error={errors.giftFirstName} />
<Field label={<>Recipient Last Name <span className="text-red-500">*</span></>} value={giftLastName} onChange={setGiftLastName} placeholder="Enter recipient's last name" error={errors.giftLastName} />
<Field label={<>Recipient ISD Code <span className="text-red-500">*</span></>} value={giftIsd} onChange={setGiftIsd} placeholder="e.g., +61" error={errors.giftIsd} />
<Field label={<>Recipient Phone <span className="text-red-500">*</span></>} value={giftPhone} onChange={setGiftPhone} type="tel" placeholder="Enter recipient's phone number" error={errors.giftPhone} />
<Field label={<>Recipient Email <span className="text-red-500">*</span></>} value={giftEmail} onChange={setGiftEmail} type="email" placeholder="Enter recipient's email" error={errors.giftEmail} />
<Field label={<>Recipient City <span className="text-red-500">*</span></>} value={giftCity} onChange={setGiftCity} placeholder="Enter recipient's city" error={errors.giftCity} />
<Field label={<>Recipient Country <span className="text-red-500">*</span></>} value={giftCountry} onChange={setGiftCountry} placeholder="Enter recipient's country" error={errors.giftCountry} />
<Field label={<>Gift Message <span className="text-red-500">*</span></>} value={giftMessage} onChange={setGiftMessage} placeholder="Write a heartfelt message" error={errors.giftMessage} />
</div>
</div>
</motion.div>
)}
</AnimatePresence>
<Separator />
{/* Billing Address */}
<motion.div initial={{ opacity: 0 }} animate={{ opacity: 1 }} transition={{ delay: 0.15 }} className="space-y-5">
<div className="flex items-center gap-3">
<div className="w-8 h-8 bg-[#F95F62] text-white rounded-full flex items-center justify-center font-poppins text-sm font-semibold flex-shrink-0">
2
</div>
<h2 className="font-poppins text-xl leading-snug font-semibold text-[#2a2a2a]">Billing Address</h2>
</div>
<div className="space-y-4">
<Field label="Address 1" value={formData.address1} onChange={() => { }} prefilled disabled />
<Field label="Address 2" value={formData.address2} onChange={() => { }} prefilled disabled />
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
<Field label="City / Suburb" value={formData.city} onChange={() => { }} prefilled disabled />
<Field label="State" value="Victoria" onChange={() => { }} prefilled disabled />
</div>
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
<Field label="Postcode" value={formData.postalCode} onChange={() => { }} inputMode="numeric" prefilled disabled />
<Field label="Country" value={formData.country} onChange={() => { }} prefilled disabled />
</div>
</div>
</motion.div>
</CardContent>
</Card>
</motion.div>
{/* Right Column: Order Summary & Payment Button */}
<motion.div initial={{ opacity: 0, x: 20 }} animate={{ opacity: 1, x: 0 }} className="lg:col-span-1">
<div className="lg:sticky lg:top-28 space-y-4">
<div className="bg-white rounded-2xl shadow-[0px_2px_16px_0px_rgba(0,0,0,0.06)] overflow-hidden">
<div className="px-6 py-4 border-b border-gray-100">
<h3 className="font-poppins text-lg leading-snug font-semibold text-[#2a2a2a]">Order Summary</h3>
</div>
<div className="px-6 py-5 border-b border-gray-100">
<div className="flex items-start gap-4">
<div
className={`w-16 h-10 rounded-lg flex-shrink-0 flex items-center justify-center ${bookingDetails?.cardMode?.toLowerCase() === 'flexi'
? 'bg-gradient-to-br from-[#f95faf] to-[#F95F62]'
: 'bg-gradient-to-br from-[#F95F62] to-[#c94245]'
}`}
>
<span className="font-poppins text-[10px] font-semibold text-white">{bookingDetails?.cardMode}</span>
</div>
<div className="flex-1 min-w-0">
<div className="flex items-center gap-2 flex-wrap">
<h4 className="font-poppins text-base font-semibold text-[#2a2a2a]">{bookingDetails?.name}</h4>
<CardTypeBadge cardType={bookingDetails?.cardMode} />
</div>
<p className="font-poppins text-sm font-normal text-[#8e8e8e] mt-0.5">
{bookingDetails?.cardMode?.toLowerCase() === 'flexi'
? `${bookingDetails?.noOfAttractions} Attractions`
: `${bookingDetails?.noOfDays} Days`}
</p>
</div>
</div>
<div className="mt-4 space-y-2">
<div className="flex items-center justify-between">
<span className="font-poppins text-sm font-normal text-[#8e8e8e]">Adults</span>
<span className="font-poppins text-sm font-medium text-[#2a2a2a]">{bookingDetails?.totalAdult}</span>
</div>
<div className="flex items-center justify-between">
<span className="font-poppins text-sm font-normal text-[#8e8e8e]">Children</span>
<span className="font-poppins text-sm font-medium text-[#2a2a2a]">{bookingDetails?.totalChild}</span>
</div>
</div>
</div>
<div className="px-6 py-5">
<div className="space-y-3">
<div className="flex items-center justify-between">
<span className="font-poppins text-sm font-normal text-[#555]">Subtotal</span>
<span className="font-poppins text-sm font-normal text-[#2a2a2a]">${bookingDetails?.baseAmount}</span>
</div>
<div className="flex items-center justify-between">
<span className="font-poppins text-sm font-normal text-[#555]">GST (10%)</span>
<span className="font-poppins text-sm font-normal text-[#2a2a2a]">${bookingDetails?.totalTaxAmount}</span>
</div>
<div className="flex items-center justify-between">
<span className="font-poppins text-sm font-normal text-[#555]">Booking fee</span>
<span className="font-poppins text-sm font-normal text-[#22a86b]">Free</span>
</div>
<div className="pt-3 border-t border-gray-100 flex items-center justify-between">
<span className="font-poppins text-base font-semibold text-[#2a2a2a]">Total</span>
<span className="font-poppins text-2xl font-semibold text-[#F95F62]">${bookingDetails?.totalAmount}</span>
</div>
</div>
</div>
</div>
<motion.button
whileHover={{ scale: 1.01 }}
whileTap={{ scale: 0.98 }}
onClick={handlePayment}
disabled={isRedirecting || isCreatingPayment}
className="w-full py-4 rounded-2xl bg-[#F95F62] text-white font-poppins text-base font-semibold hover:bg-[#e8545a] transition-colors shadow-lg shadow-[#F95F62]/20 disabled:opacity-70 flex items-center justify-center gap-2"
>
{isRedirecting ? (
<>
<motion.div
animate={{ rotate: 360 }}
transition={{ duration: 1, repeat: Infinity, ease: 'linear' }}
className="w-5 h-5 border-2 border-white/30 border-t-white rounded-full"
/>
Redirecting to Stripe...
</>
) : (
<>
<Lock className="w-4 h-4" />
Proceed to Payment · ${bookingDetails?.totalAmount}
</>
)}
</motion.button>
<p className="font-poppins text-xs font-normal text-[#aaa] text-center">
You will be redirected to Stripes secure checkout page to enter your card details.
By completing your purchase you agree to our Terms of Service and Privacy Policy.
</p>
</div>
</motion.div>
</div>
</div>
<Footer
onHomeClick={onHomeClick}
onPassesClick={onPassesClick}
onAttractionsClick={onAttractionsClick}
onBlogsClick={onBlogsClick}
onHowItWorksClick={onHowItWorksClick}
onFAQClick={onFAQClick}
onPrivacyPolicyClick={onPrivacyPolicyClick}
onAboutUsClick={onAboutUsClick}
onContactUsClick={onContactUsClick}
/>
</div>
);
}