431 lines
17 KiB
TypeScript
431 lines
17 KiB
TypeScript
import { useState } from 'react';
|
|
import { motion } from 'motion/react';
|
|
import { ArrowLeft, ChevronRight, QrCode, CreditCard, Calendar, MapPin, Star, CheckCircle, Sparkles, Users, Clock, Gift, Ticket } 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 { useGetUserCardDetailsQuery } from '../Redux/services/profile.service';
|
|
import { useNavigate, useParams } from 'react-router-dom';
|
|
import LoadingSpinner from '../components/LoadingSpinner';
|
|
|
|
interface User {
|
|
email: string;
|
|
name: string;
|
|
}
|
|
|
|
interface ViewCardDetailsPageProps {
|
|
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;
|
|
onContactUsClick?: () => void;
|
|
onEsimsClick?: () => void;
|
|
onHotelDiscountsClick?: () => void;
|
|
currentPage: string;
|
|
user: User | null;
|
|
}
|
|
|
|
export function ViewCardDetailsPage({
|
|
onBackClick,
|
|
onHomeClick,
|
|
onMelbourneClick,
|
|
onPassesClick,
|
|
onCheckoutClick,
|
|
onSignInClick,
|
|
onSignOutClick,
|
|
onAttractionsClick,
|
|
onBlogsClick,
|
|
onHowItWorksClick,
|
|
onFAQClick,
|
|
onPrivacyPolicyClick,
|
|
onAboutUsClick,
|
|
onProfileClick,
|
|
onCityCardsClick,
|
|
onMagicItineraryClick,
|
|
onPostCardsClick,
|
|
onOffersClick,
|
|
onContactUsClick,
|
|
onEsimsClick,
|
|
onHotelDiscountsClick,
|
|
currentPage,
|
|
user
|
|
}: ViewCardDetailsPageProps) {
|
|
// Card type state
|
|
const [cardType, setCardType] = useState<'unlimited' | 'flexi'>('unlimited');
|
|
const { cardId } = useParams()
|
|
const { data, isLoading } = useGetUserCardDetailsQuery(cardId)
|
|
const navigate = useNavigate()
|
|
|
|
const baseUrl = import.meta.env.VITE_BASE_URL;
|
|
|
|
const cardDetails = data?.bookingDetails ?? {}
|
|
|
|
const attractions = data?.attractions ?? []
|
|
const offers = data?.offers ?? []
|
|
|
|
if (isLoading) {
|
|
return (
|
|
<LoadingSpinner />
|
|
)
|
|
}
|
|
|
|
// 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
|
|
const generateQRPattern = () => {
|
|
const size = 17;
|
|
const pattern = [];
|
|
|
|
for (let i = 0; i < size * size; i++) {
|
|
const row = Math.floor(i / size);
|
|
const col = i % size;
|
|
|
|
// Corner squares (5x5 for smaller QR)
|
|
const isCornerSquare =
|
|
(row < 5 && col < 5) || // Top-left
|
|
(row < 5 && col >= 12) || // Top-right
|
|
(row >= 12 && col < 5); // Bottom-left
|
|
|
|
// Finder patterns within corner squares
|
|
const isFinderPattern = isCornerSquare && (
|
|
(row === 0 || row === 4 || col === 0 || col === 4) ||
|
|
(row >= 2 && row <= 2 && col >= 2 && col <= 2)
|
|
);
|
|
|
|
// Timing patterns
|
|
const isTimingPattern = (row === 4 && col >= 6 && col <= 10) || (col === 4 && row >= 6 && row <= 10);
|
|
|
|
// Random data pattern for other areas
|
|
const isDataPattern = !isCornerSquare && !isTimingPattern && Math.random() > 0.4;
|
|
|
|
pattern.push(isFinderPattern || isTimingPattern || isDataPattern);
|
|
}
|
|
|
|
return pattern;
|
|
};
|
|
|
|
const qrPattern = generateQRPattern();
|
|
|
|
return (
|
|
<div className="min-h-screen bg-[#f8f8f8]">
|
|
<Navbar
|
|
onHomeClick={onHomeClick}
|
|
onMelbourneClick={onMelbourneClick}
|
|
onPassesClick={onPassesClick}
|
|
onCheckoutClick={onCheckoutClick}
|
|
onSignInClick={onSignInClick}
|
|
onAttractionsClick={onAttractionsClick}
|
|
onBlogsClick={onBlogsClick}
|
|
onHowItWorksClick={onHowItWorksClick}
|
|
onFAQClick={onFAQClick}
|
|
onPrivacyPolicyClick={onPrivacyPolicyClick}
|
|
onAboutUsClick={onAboutUsClick}
|
|
onProfileClick={onProfileClick}
|
|
onCityCardsClick={onCityCardsClick}
|
|
onMagicItineraryClick={onMagicItineraryClick}
|
|
onPostCardsClick={onPostCardsClick}
|
|
onOffersClick={onOffersClick}
|
|
currentPage={currentPage}
|
|
isUserSignedIn={!!user}
|
|
user={user}
|
|
/>
|
|
|
|
<div className="container mx-auto px-4 pt-40 pb-6">
|
|
{/* Back Button */}
|
|
<motion.div
|
|
initial={{ opacity: 0, x: -20 }}
|
|
animate={{ opacity: 1, x: 0 }}
|
|
className="mb-6"
|
|
>
|
|
<Button
|
|
variant="ghost"
|
|
onClick={() => navigate(-1)}
|
|
className="p-0 hover:bg-transparent"
|
|
>
|
|
<div className="text-gray-600 hover:text-primary transition-colors duration-200 flex items-center gap-2">
|
|
<ArrowLeft className="w-4 h-4" />
|
|
<span>Back</span>
|
|
</div>
|
|
</Button>
|
|
</motion.div>
|
|
|
|
{/* Pass Card - Redesigned */}
|
|
<motion.div
|
|
initial={{ opacity: 0, y: 20 }}
|
|
animate={{ opacity: 1, y: 0 }}
|
|
className="mb-8"
|
|
>
|
|
<Card className="border-0 shadow-lg overflow-hidden">
|
|
{/* Gradient Header Bar */}
|
|
<div className="h-2 bg-gradient-to-r from-primary via-secondary to-primary"></div>
|
|
|
|
<CardContent className="p-6">
|
|
{/* Card Header */}
|
|
<div className="flex items-start justify-between mb-6">
|
|
<div className="flex items-start gap-4">
|
|
{/* Icon */}
|
|
<div className="w-14 h-14 rounded-xl bg-gradient-to-br from-primary/10 to-secondary/10 flex items-center justify-center flex-shrink-0">
|
|
<Ticket className="w-7 h-7 text-primary" />
|
|
</div>
|
|
|
|
{/* Title and Status */}
|
|
<div>
|
|
<h2 className="font-merchant text-xl font-semibold text-gray-900 mb-2">
|
|
{cardDetails.name}
|
|
</h2>
|
|
<Badge className="bg-gradient-to-r from-green-500 to-green-600 text-white px-3 py-1 rounded-full">
|
|
<div className="w-2 h-2 bg-white rounded-full mr-2 animate-pulse"></div>
|
|
{cardDetails.isActive ? "Active" : "Inactive"}
|
|
</Badge>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Date Badge */}
|
|
<div className="bg-gradient-to-br from-primary/5 to-secondary/5 px-4 py-2 rounded-lg">
|
|
<div className="flex items-center gap-2 text-primary">
|
|
<Calendar className="w-4 h-4" />
|
|
<span className="font-poppins font-medium text-sm">{cardDetails.validUpto}</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Stats Grid */}
|
|
<div className="grid grid-cols-3 gap-4">
|
|
{/* Duration */}
|
|
<div className="bg-gray-50 rounded-lg p-4 text-center">
|
|
<div className="w-10 h-10 rounded-full bg-white shadow-sm flex items-center justify-center mx-auto mb-2">
|
|
<Clock className="w-5 h-5 text-primary" />
|
|
</div>
|
|
<div className="font-poppins text-xs text-gray-500 mb-1">Duration</div>
|
|
<div className="font-poppins font-semibold text-gray-900">{cardDetails.noOfDays}</div>
|
|
</div>
|
|
|
|
{/* Adults */}
|
|
<div className="bg-gray-50 rounded-lg p-4 text-center">
|
|
<div className="w-10 h-10 rounded-full bg-white shadow-sm flex items-center justify-center mx-auto mb-2">
|
|
<Users className="w-5 h-5 text-primary" />
|
|
</div>
|
|
<div className="font-poppins text-xs text-gray-500 mb-1">Adults</div>
|
|
<div className="font-poppins font-semibold text-gray-900">{cardDetails.totalAdult}</div>
|
|
</div>
|
|
|
|
{/* Kids */}
|
|
<div className="bg-gray-50 rounded-lg p-4 text-center">
|
|
<div className="w-10 h-10 rounded-full bg-white shadow-sm flex items-center justify-center mx-auto mb-2">
|
|
<Star className="w-5 h-5 text-primary" />
|
|
</div>
|
|
<div className="font-poppins text-xs text-gray-500 mb-1">Kids</div>
|
|
<div className="font-poppins font-semibold text-gray-900">{cardDetails.totalChild}</div>
|
|
</div>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
</motion.div>
|
|
|
|
{/* Attractions Section - Redesigned */}
|
|
<motion.div
|
|
initial={{ opacity: 0, y: 20 }}
|
|
animate={{ opacity: 1, y: 0 }}
|
|
transition={{ delay: 0.2 }}
|
|
className="mb-8"
|
|
>
|
|
<Card className="border-0 shadow-lg">
|
|
<CardContent className="p-6">
|
|
{/* Section Header */}
|
|
<div className="flex items-center justify-between mb-5">
|
|
<div className="flex items-center gap-3">
|
|
<div className="w-10 h-10 rounded-lg bg-gradient-to-br from-primary to-secondary flex items-center justify-center">
|
|
<MapPin className="w-5 h-5 text-white" />
|
|
</div>
|
|
<div>
|
|
<h3 className="font-merchant text-lg font-semibold text-gray-900">
|
|
Included Attractions
|
|
</h3>
|
|
<p className="font-poppins text-xs text-gray-500">
|
|
Explore amazing places
|
|
</p>
|
|
</div>
|
|
</div>
|
|
<Badge className="bg-primary/10 text-primary border-0 px-3 py-1">
|
|
<span className="font-poppins font-medium">{attractions.length} {attractions.length > 1 ? "Places" : "Place"}</span>
|
|
</Badge>
|
|
</div>
|
|
|
|
{/* Attractions Scroll */}
|
|
<div className="flex items-center gap-4 overflow-x-auto pb-2 scrollbar-hide mb-4">
|
|
{attractions.map((attraction, index) => (
|
|
<motion.div
|
|
key={index}
|
|
initial={{ opacity: 0, scale: 0.9 }}
|
|
animate={{ opacity: 1, scale: 1 }}
|
|
transition={{ delay: 0.1 * index }}
|
|
className="flex-shrink-0"
|
|
>
|
|
<Card className="w-40 hover:shadow-lg transition-all duration-300 border-0 bg-gradient-to-br from-gray-50 to-white cursor-pointer group">
|
|
<CardContent className="p-3">
|
|
{/* Image */}
|
|
<div className="w-full h-24 bg-gray-200 rounded-lg mb-3 overflow-hidden">
|
|
<ImageWithFallback
|
|
src={attraction.image}
|
|
alt={attraction.title}
|
|
className="w-full h-full object-cover group-hover:scale-110 transition-transform duration-300"
|
|
/>
|
|
</div>
|
|
|
|
{/* Info */}
|
|
<div>
|
|
<h4 className="font-poppins font-semibold text-sm text-gray-900 mb-1 truncate">
|
|
{attraction.title}
|
|
</h4>
|
|
<div className="flex items-center gap-1 text-xs text-primary">
|
|
<Ticket className="w-3 h-3" />
|
|
{/* <span className="font-poppins font-medium">{attraction.tours}</span> */}
|
|
</div>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
</motion.div>
|
|
))}
|
|
</div>
|
|
|
|
{/* View All Button */}
|
|
<Button
|
|
variant="outline"
|
|
className="w-full border-2 border-dashed border-primary/30 hover:border-primary hover:bg-primary/5 text-primary font-poppins font-medium transition-all duration-300"
|
|
onClick={() => navigate("/attractions")}
|
|
>
|
|
<span>View All Attractions</span>
|
|
<ChevronRight className="w-4 h-4 ml-2" />
|
|
</Button>
|
|
</CardContent>
|
|
</Card>
|
|
</motion.div>
|
|
|
|
{/* Special Benefits & Offers - Redesigned */}
|
|
<motion.div
|
|
initial={{ opacity: 0, y: 20 }}
|
|
animate={{ opacity: 1, y: 0 }}
|
|
transition={{ delay: 0.3 }}
|
|
className="mb-8"
|
|
>
|
|
<Card className="border-0 shadow-lg overflow-hidden bg-gradient-to-br from-primary/5 via-white to-secondary/5">
|
|
<CardContent className="p-6">
|
|
{/* Header */}
|
|
<div className="flex items-center justify-between mb-6">
|
|
<div className="flex items-center gap-3">
|
|
<div className="w-12 h-12 rounded-xl bg-gradient-to-br from-primary to-secondary flex items-center justify-center">
|
|
<Gift className="w-6 h-6 text-white" />
|
|
</div>
|
|
<div>
|
|
<h3 className="font-merchant text-xl font-semibold text-gray-900">
|
|
Exclusive Offers
|
|
</h3>
|
|
<p className="font-poppins text-sm text-gray-600 font-normal">
|
|
Special deals for cardholders
|
|
</p>
|
|
</div>
|
|
</div>
|
|
<Badge className="bg-gradient-to-r from-green-500 to-green-600 text-white border-0 px-3 py-1">
|
|
<Sparkles className="w-3 h-3 mr-1" />
|
|
<span className="font-poppins font-medium">Limited Time</span>
|
|
</Badge>
|
|
</div>
|
|
|
|
{/* Featured Offers Grid */}
|
|
<div className="space-y-4 mb-5">
|
|
{
|
|
offers.map((offer: any) => (
|
|
<motion.div
|
|
initial={{ opacity: 0, y: 10 }}
|
|
animate={{ opacity: 1, y: 0 }}
|
|
transition={{ delay: 0.1 }}
|
|
className="bg-gradient-to-br from-orange-50 to-red-50 rounded-xl p-4 border border-orange-200 hover:border-orange-300 transition-all duration-300 cursor-pointer group hover:shadow-lg"
|
|
onClick={() => navigate(`/super-savings/${offer.id}`)}
|
|
>
|
|
<div className="flex items-center justify-between mb-3">
|
|
<div className="flex items-center gap-3">
|
|
<div className="w-10 h-10 rounded-lg bg-gradient-to-br from-orange-400 to-red-500 flex items-center justify-center">
|
|
<Gift className="w-5 h-5 text-white" />
|
|
</div>
|
|
<div>
|
|
<h4 className="font-poppins font-semibold text-base text-gray-900">
|
|
{offer.title}
|
|
</h4>
|
|
{/* <p className="font-poppins text-xs text-gray-600 font-normal">
|
|
{offer.description}
|
|
</p> */}
|
|
</div>
|
|
</div>
|
|
{/* <div className="bg-white rounded-lg px-3 py-2 shadow-sm">
|
|
<span className="font-merchant text-2xl font-semibold text-orange-600">25%</span>
|
|
<span className="font-poppins text-xs text-gray-600 ml-1">OFF</span>
|
|
</div> */}
|
|
</div>
|
|
<div className="flex items-center justify-between">
|
|
<p className="font-poppins text-xs text-gray-700 font-normal">
|
|
{offer.description}
|
|
</p>
|
|
<ChevronRight className="w-4 h-4 text-orange-600 group-hover:translate-x-1 transition-transform" />
|
|
</div>
|
|
</motion.div>
|
|
))
|
|
}
|
|
|
|
</div>
|
|
|
|
|
|
</CardContent>
|
|
</Card>
|
|
</motion.div>
|
|
|
|
|
|
</div>
|
|
|
|
<Footer
|
|
onHomeClick={onHomeClick}
|
|
onMelbourneClick={onMelbourneClick}
|
|
onPassesClick={onPassesClick}
|
|
onCheckoutClick={onCheckoutClick}
|
|
onSignInClick={onSignInClick}
|
|
onAttractionsClick={onAttractionsClick}
|
|
onBlogsClick={onBlogsClick}
|
|
onHowItWorksClick={onHowItWorksClick}
|
|
onFAQClick={onFAQClick}
|
|
onPrivacyPolicyClick={onPrivacyPolicyClick}
|
|
onAboutUsClick={onAboutUsClick}
|
|
onProfileClick={onProfileClick}
|
|
onCityCardsClick={onCityCardsClick}
|
|
onMagicItineraryClick={onMagicItineraryClick}
|
|
onPostCardsClick={onPostCardsClick}
|
|
onOffersClick={onOffersClick}
|
|
onContactUsClick={onContactUsClick}
|
|
currentPage={currentPage}
|
|
/>
|
|
</div>
|
|
);
|
|
} |