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

387 lines
18 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 { ArrowLeft, Check, Clock, MapPin, Users, X } from 'lucide-react';
import { motion } from 'motion/react';
import { useParams } from 'react-router-dom';
import { ImageWithFallback } from '../components/figma/ImageWithFallback';
import LoadingSpinner from '../components/LoadingSpinner';
import { Badge } from '../components/ui/badge';
import { Button } from '../components/ui/button';
import { Card } from '../components/ui/card';
import { Layout } from '../Layout';
import { useGetOfferDetailsByIdQuery } from '../Redux/services/cities.service';
interface SuperSavingsDetailsPageProps {
onBackClick: () => void;
onCheckoutClick: () => void;
onSignInClick: () => void;
onSignOutClick?: () => void;
user?: { email: string; name: string } | null;
}
export function SuperSavingsDetailsPage({
onBackClick,
onCheckoutClick,
onSignInClick,
onSignOutClick,
user,
}: SuperSavingsDetailsPageProps) {
const { id } = useParams();
const { data: offer, isLoading } = useGetOfferDetailsByIdQuery(Number(id));
const baseUrl = import.meta.env.VITE_BASE_URL;
if (isLoading) {
return <LoadingSpinner />;
}
// Guard against missing data but keep all UI elements
const safeOffer = offer || {
id: 0,
title: 'Offer Details',
description: 'No description available.',
cityXid: 0,
cardXid: 0,
cardTypeXid: 0,
categoryXid: 0,
partnerName: '',
offerCode: '',
websiteBannerImage: '',
mobileBannerImage: '',
redemptionLink: '',
passType: '',
startDateTime: null,
endDateTime: null,
applyToPasses: false,
stepsForBooking: null,
offerStatus: '',
isActive: true,
createdAt: '',
updatedAt: '',
city: { id: 0, cityName: 'Unknown City' },
card: { id: 0, title: 'Unknown Card' },
cardType: { id: 0, cardTypeDisplayName: 'Standard' },
category: { id: 0, categoryName: 'General' },
};
// Build badges from available API data (preserves the badge UI section)
const superSavingsBadges = [
safeOffer.category && { badgeXid: safeOffer.category.id, badge: { badgeName: safeOffer.category.categoryName } },
safeOffer.cardType && { badgeXid: safeOffer.cardType.id, badge: { badgeName: safeOffer.cardType.cardTypeDisplayName } },
safeOffer.offerCode && { badgeXid: -1, badge: { badgeName: `Code: ${safeOffer.offerCode}` } },
safeOffer.offerStatus && { badgeXid: -2, badge: { badgeName: safeOffer.offerStatus.toUpperCase() } },
].filter(Boolean);
// Build gallery array from banner images (original expected superSavingsGalleries)
const superSavingsGalleries = [];
if (safeOffer.websiteBannerImage) {
superSavingsGalleries.push({ id: 1, filePathUrl: safeOffer.websiteBannerImage });
}
if (safeOffer.mobileBannerImage) {
superSavingsGalleries.push({ id: 2, filePathUrl: safeOffer.mobileBannerImage });
}
// If no images, add a placeholder
if (superSavingsGalleries.length === 0) {
superSavingsGalleries.push({ id: 0, filePathUrl: 'https://placehold.co/1200x800?text=No+Image' });
}
// Mock data for sections not present in API (preserve structure but show empty/fallback)
const durations = safeOffer.startDateTime && safeOffer.endDateTime
? Math.round((new Date(safeOffer.endDateTime).getTime() - new Date(safeOffer.startDateTime).getTime()) / (1000 * 60))
: 'Not specified';
const groupSize = 'Not specified';
const ageRange = 'All ages';
const superSavingsLanguages: any[] = []; // API has no language data
const superSavingsHighlights: any[] = []; // API has no highlights
// Inclusions: API has none, so show empty state (or we could derive from redemptionLink etc.)
const superSavingsInclusions: any[] = [];
const address = safeOffer.city?.cityName || 'Location not specified';
return (
<Layout
activeCity=""
onSignInClick={onSignInClick}
onSignOutClick={onSignOutClick}
user={user}
>
<div className="container mx-auto px-4 pt-40 pb-16 max-w-6xl">
{/* Back Button */}
<motion.div
className="mb-8"
initial={{ opacity: 0, x: -20 }}
animate={{ opacity: 1, x: 0 }}
transition={{ duration: 0.5 }}
>
<Button
variant="ghost"
onClick={onBackClick}
className="font-poppins font-medium text-base text-gray-600 hover:text-primary transition-colors duration-200"
>
<ArrowLeft className="w-4 h-4 mr-2" />
Back to Super-Savings Page
</Button>
</motion.div>
{/* Title and Badges Section */}
<div className="mb-8">
<div className="flex flex-wrap gap-3 mb-6">
{superSavingsBadges.map((badge: any, index: number) => (
<Badge
key={badge.badgeXid}
variant={index === 0 ? 'default' : 'secondary'}
className={`px-6 py-2 rounded-full text-sm transition-all duration-200 ${
index === 0
? 'bg-primary text-white shadow-lg'
: 'bg-primary/10 text-primary border border-primary/20'
}`}
>
{badge.badge.badgeName}
</Badge>
))}
</div>
<h1 className="text-4xl font-bold text-[#2d3134] leading-tight">
<span className="bg-gradient-to-r from-primary to-primary/80 bg-clip-text text-transparent">
{safeOffer.title}
</span>{' '}
<span className="text-[#2d3134]">
Day Trip by {safeOffer.partnerName || safeOffer.card?.title || 'Partner'}
</span>
</h1>
</div>
{/* Image Gallery Section - preserved exactly as original */}
<div className="grid grid-cols-4 grid-rows-2 gap-4 h-[510px] mb-12">
{/* Main large image */}
<div className="col-span-2 row-span-2">
<ImageWithFallback
src={ `${baseUrl}/${superSavingsGalleries[0]?.filePathUrl}` }
alt="Main attraction image"
className="w-full h-full object-cover rounded-lg"
/>
</div>
{/* Gallery images - use remaining images or repeat first if needed */}
{superSavingsGalleries.slice(1, 5).map((image: any) => (
<div key={image.id} className="col-span-1 row-span-1">
<ImageWithFallback
src={ `${baseUrl}/${image.filePathUrl}` }
alt={`Gallery image ${image.id}`}
className="w-full h-full object-cover rounded-lg"
/>
</div>
))}
{/* If less than 4 extra images, fill with placeholders to maintain grid */}
{superSavingsGalleries.slice(1, 5).length < 4 &&
Array(4 - superSavingsGalleries.slice(1, 5).length)
.fill(null)
.map((_, idx) => (
<div key={`placeholder-${idx}`} className="col-span-1 row-span-1">
<div className="w-full h-full bg-gray-100 rounded-lg flex items-center justify-center text-gray-400">
No Image
</div>
</div>
))}
</div>
{/* Main Content Grid */}
<div className="grid grid-cols-1 lg:grid-cols-3 gap-12">
{/* Left Content - Tour Details */}
<div className="lg:col-span-2 space-y-12">
{/* Overview Cards - preserved */}
<div className="grid grid-cols-2 md:grid-cols-4 gap-6">
{/* Duration */}
<Card className="p-4 text-center bg-white border border-primary/10 hover:border-primary/20 transition-all duration-200 hover:shadow-lg group">
<div className="w-12 h-12 bg-primary/10 rounded-xl mx-auto mb-3 flex items-center justify-center group-hover:bg-primary/20 transition-colors duration-200">
<Clock className="w-6 h-6 text-primary" />
</div>
<h3 className="font-normal text-primary capitalize mb-1">Duration</h3>
<p className="text-sm text-[#717171] font-light">
{typeof durations === 'number' ? `${durations} mins` : durations}
</p>
</Card>
{/* Group Size */}
<Card className="p-4 text-center bg-white border border-primary/10 hover:border-primary/20 transition-all duration-200 hover:shadow-lg group">
<div className="w-12 h-12 bg-primary/10 rounded-xl mx-auto mb-3 flex items-center justify-center group-hover:bg-primary/20 transition-colors duration-200">
<Users className="w-6 h-6 text-primary" />
</div>
<h3 className="font-normal text-primary capitalize mb-1">Group Size</h3>
<p className="text-sm text-[#717171] font-light">{groupSize}</p>
</Card>
{/* Age Range */}
<Card className="p-4 text-center bg-white border border-primary/10 hover:border-primary/20 transition-all duration-200 hover:shadow-lg group">
<div className="w-12 h-12 bg-primary/10 rounded-xl mx-auto mb-3 flex items-center justify-center group-hover:bg-primary/20 transition-colors duration-200">
<Users className="w-6 h-6 text-primary" />
</div>
<h3 className="font-normal text-primary capitalize mb-1">Age Range</h3>
<p className="text-sm text-[#717171] font-light">{ageRange}</p>
</Card>
{/* Languages */}
<Card className="p-4 text-center bg-white border border-primary/10 hover:border-primary/20 transition-all duration-200 hover:shadow-lg group">
<div className="w-12 h-12 bg-primary/10 rounded-xl mx-auto mb-3 flex items-center justify-center group-hover:bg-primary/20 transition-colors duration-200">
<MapPin className="w-6 h-6 text-primary" />
</div>
<h3 className="font-normal text-primary capitalize mb-1">Languages</h3>
<p className="text-sm text-[#717171] font-light">
{superSavingsLanguages?.length > 0
? superSavingsLanguages?.map((lang: any) => lang.language.name).join(', ')
: 'English (default)'}
</p>
</Card>
</div>
{/* Tour Overview */}
<div>
<div className="flex items-center gap-4 mb-6">
<div className="h-1 w-12 bg-primary rounded-full"></div>
<h2 className="text-3xl font-semibold text-[#2d3134]">
Tour <span className="text-primary">Overview</span>
</h2>
</div>
<p className="text-[#2d3134] leading-relaxed text-lg font-light">
{safeOffer.description}
</p>
</div>
{/* Tour Highlights - preserved even if empty */}
<div>
<div className="flex items-center gap-4 mb-6">
<div className="h-1 w-12 bg-primary rounded-full"></div>
<h3 className="text-2xl font-medium text-[#2d3134]">
Tour <span className="text-primary">Highlights</span>
</h3>
</div>
{superSavingsHighlights.length > 0 ? (
<ul className="space-y-4">
{superSavingsHighlights.map((highlight: any) => (
<li key={highlight.id} className="flex items-start gap-3 group">
<div className="w-6 h-6 bg-primary/10 rounded-full mt-1 flex items-center justify-center flex-shrink-0 group-hover:bg-primary/20 transition-colors duration-200">
<div className="w-2 h-2 bg-primary rounded-full"></div>
</div>
<span className="text-[#2d3134] leading-relaxed font-light">{highlight.title}</span>
</li>
))}
</ul>
) : (
<p className="text-gray-500 italic">No highlights listed for this offer.</p>
)}
</div>
{/* What's Included/Not Included - preserved */}
<div>
<div className="flex items-center gap-4 mb-8">
<div className="h-1 w-12 bg-primary rounded-full"></div>
<h3 className="text-3xl font-semibold text-[#2d3134]">
What's <span className="text-primary">included</span>
</h3>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-8">
{/* Included */}
<div className="space-y-4">
<h4 className="font-medium text-primary mb-4 flex items-center gap-2">
<Check className="w-5 h-5" />
Included
</h4>
{superSavingsInclusions.filter((inc: any) => inc.isInclusion === true).length > 0 ? (
superSavingsInclusions
.filter((inclusion: any) => inclusion.isInclusion === true)
.map((inclusion: any) => (
<div key={inclusion.id} className="flex items-start gap-3 group">
<div className="w-6 h-6 bg-primary/10 rounded-full flex items-center justify-center flex-shrink-0 mt-1 group-hover:bg-primary/20 transition-colors duration-200">
<Check className="w-3 h-3 text-primary" />
</div>
<span className="text-[#2d3134] font-light">{inclusion.title}</span>
</div>
))
) : (
<p className="text-gray-500 italic">No included items specified.</p>
)}
</div>
{/* Not Included */}
<div className="space-y-4">
<h4 className="font-medium text-gray-600 mb-4 flex items-center gap-2">
<X className="w-5 h-5" />
Not Included
</h4>
{superSavingsInclusions.filter((inc: any) => inc.isInclusion === false).length > 0 ? (
superSavingsInclusions
.filter((inclusion: any) => inclusion.isInclusion === false)
.map((inclusion: any) => (
<div key={inclusion.id} className="flex items-start gap-3 group">
<div className="w-6 h-6 bg-gray-100 rounded-full flex items-center justify-center flex-shrink-0 mt-1 group-hover:bg-gray-200 transition-colors duration-200">
<X className="w-3 h-3 text-gray-500" />
</div>
<span className="text-[#2d3134] font-light">{inclusion.title}</span>
</div>
))
) : (
<p className="text-gray-500 italic">No excluded items specified.</p>
)}
</div>
</div>
</div>
{/* Location on map - preserved */}
<div>
<div className="flex items-center gap-4 mb-8">
<div className="h-1 w-12 bg-primary rounded-full"></div>
<h3 className="text-3xl font-semibold text-[#2d3134]">
Location on <span className="text-primary">map</span>
</h3>
</div>
<div className="h-80 bg-gradient-to-br from-primary/5 to-primary/10 rounded-lg flex items-center justify-center border border-primary/10 hover:border-primary/20 transition-colors duration-200">
<div className="text-center">
<div className="w-16 h-16 bg-primary/10 rounded-full flex items-center justify-center mx-auto mb-4">
<MapPin className="w-8 h-8 text-primary" />
</div>
<p className="text-lg font-medium text-primary mb-2">Interactive Map</p>
<p className="text-sm text-gray-600 font-light">{safeOffer.title}</p>
<p className="text-sm text-gray-600 font-light">{address}</p>
</div>
</div>
</div>
</div>
{/* Right Sidebar - Calendar and Booking (preserved, but you can add a real calendar if needed) */}
{/* <div className="lg:col-span-1">
<Card className="sticky top-32 p-6 bg-white border border-primary/20 shadow-xl rounded-2xl">
<h3 className="text-2xl font-bold text-[#2d3134] mb-4">Book This Offer</h3>
<div className="space-y-4 mb-6">
<div className="flex justify-between items-center border-b pb-2">
<span className="text-gray-600">Availability</span>
<span className="font-medium text-green-600">
{safeOffer.offerStatus === 'active' ? 'Available' : 'Unavailable'}
</span>
</div>
{safeOffer.startDateTime && (
<div className="flex justify-between items-center border-b pb-2">
<span className="text-gray-600">Valid from</span>
<span className="font-medium">
{new Date(safeOffer.startDateTime).toLocaleDateString()}
</span>
</div>
)}
{safeOffer.endDateTime && (
<div className="flex justify-between items-center border-b pb-2">
<span className="text-gray-600">Valid until</span>
<span className="font-medium">
{new Date(safeOffer.endDateTime).toLocaleDateString()}
</span>
</div>
)}
</div>
<Button
onClick={onCheckoutClick}
className="w-full bg-primary hover:bg-primary/90 text-white font-semibold py-6 text-lg rounded-xl transition-all duration-200"
>
Proceed to Checkout
</Button>
</Card>
</div> */}
</div>
</div>
</Layout>
);
}