show AttractionDetails from backend on AttractionDetails page

This commit is contained in:
aryabenade
2026-03-19 19:21:39 +05:30
parent 3b920c2461
commit b3e1c0faf4
3 changed files with 111 additions and 126 deletions

View File

@@ -117,7 +117,7 @@ export function AppRouter({
<Route path="/attractions/:attractionId" element={
<motion.div key="attraction-details" {...pageTransition}>
<AttractionDetailsPage
attractionId={attractionId || ''}
// attractionId={attractionId || ''}
{...commonNavHandlers}
onBackClick={() => navigate(-1)}
onCheckoutClick={() => navigate('/checkout')}

View File

@@ -6,6 +6,8 @@ import { Badge } from './ui/badge';
import { Card, } from './ui/card';
import { ImageWithFallback } from './figma/ImageWithFallback';
import { Layout } from '../Layout';
import { useParams } from 'react-router-dom';
import { useGetAttractionDetailsByIdQuery } from '../Redux/services/attractions.service';
interface AttractionDetailsPageProps {
onBackClick: () => void;
@@ -13,7 +15,7 @@ interface AttractionDetailsPageProps {
onSignInClick: () => void;
onSignOutClick?: () => void;
user?: { email: string; name: string } | null;
attractionId: string;
// attractionId: string;
}
export function AttractionDetailsPage({
@@ -23,74 +25,33 @@ export function AttractionDetailsPage({
onSignOutClick,
user,
}: AttractionDetailsPageProps) {
const [date, setDate] = useState<Date | undefined>(new Date());
// Featured attraction for the main display
const featuredAttraction = {
id: 'phi-phi',
name: 'Phi Phi Islands Adventure Day Trip with Seaview Lunch by V. Marine Tour',
badges: ['Bestseller', 'Free cancellation', 'Reservation Required'],
images: {
main: 'https://images.unsplash.com/photo-1559827260-dc66d52bef19?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHxzbm9ya2VsaW5nJTIwdHVydGxlJTIwYWR2ZW50dXJlfGVufDF8fHx8MTc1ODEwNDkwMHww&ixlib=rb-4.1.0&q=80&w=1080&utm_source=figma&utm_medium=referral',
gallery: [
'https://images.unsplash.com/photo-1559827260-dc66d52bef19?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHxzbm9ya2VsaW5nJTIwdHVydGxlJTIwYWR2ZW50dXJlfGVufDF8fHx8MTc1ODEwNDkwMHww&ixlib=rb-4.1.0&q=80&w=1080&utm_source=figma&utm_medium=referral',
'https://images.unsplash.com/photo-1571019613454-1cb2f99b2d8b?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHxpc2xhbmQlMjB0b3VyJTIwYWRvJTIwdHJvcGljYWx8ZW58MXx8fHwxNzU4MTA0OTEwfDA&ixlib=rb-4.1.0&q=80&w=1080&utm_source=figma&utm_medium=referral',
'https://images.unsplash.com/photo-1506905925346-21bda4d32df4?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHxncmVhdCUyMG9jZWFuJTIwcm9hZCUyMGF1c3RyYWxpYXxlbnwxfHx8fDE3NTgxMDQ5Mzd8MA&ixlib=rb-4.1.0&q=80&w=1080&utm_source=figma&utm_medium=referral',
'https://images.unsplash.com/photo-1682687220742-aba13b6e50ba?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHxhdHYlMjBkZXNlcnQlMjB0b3VyfGVufDF8fHx8MTc1ODEwNDg5Nnww&ixlib=rb-4.1.0&q=80&w=1080&utm_source=figma&utm_medium=referral'
]
},
overview: {
duration: '3 days',
groupSize: '10 people',
ages: '18-99 yrs',
languages: 'English, Japanese'
},
description: 'The Phi Phi archipelago is a must-visit while in Phuket, and this speedboat trip whisks you around the islands in one day. Swim over the coral reefs of Pileh Lagoon, have lunch at Phi Phi Leh, snorkel at Bamboo Island, and visit Monkey Beach and Maya Bay, immortalized in "The Beach." Boat transfers, snacks, buffet lunch, snorkeling equipment, and Phuket hotel pickup and drop-off all included.',
highlights: [
'Experience the thrill of a speedboat to the stunning Phi Phi Islands',
'Be amazed by the variety of marine life in the archepelago',
'Enjoy relaxing in paradise with white sand beaches and azure turquoise water',
'Feel the comfort of a tour limited to 35 passengers',
'Catch a glimpse of the wild monkeys around Monkey Beach'
],
included: [
'Beverages, drinking water, morning tea and buffet lunch',
'Local taxes',
'Hotel pickup and drop-off by air-conditioned minivan',
'Insurance Transfer to a private pier',
'Soft drinks',
'Tour Guide'
],
notIncluded: [
'Towel',
'Tips',
'Alcoholic Beverages'
],
bookingOptions: [
'By Calling on 022 2645675',
'Email your details at islands.booking@mail.com',
'Via CityCards Portal'
]
};
const { attractionId } = useParams()
const { data: attraction, isLoading } = useGetAttractionDetailsByIdQuery(Number(attractionId));
if (isLoading) {
return <div>loading...</div>
}
return (
<Layout
activeCity=""
onSignInClick={onSignInClick}
onSignOutClick={onSignOutClick}
user={user}
showCitySubmenu={false}
>
activeCity=""
onSignInClick={onSignInClick}
onSignOutClick={onSignOutClick}
user={user}
// showCitySubmenu={false}
>
<div className="container mx-auto px-4 pt-40 pb-16 max-w-6xl">
{/* Back Button */}
<motion.div
<motion.div
className="mb-8"
initial={{ opacity: 0, x: -20 }}
animate={{ opacity: 1, x: 0 }}
transition={{ duration: 0.5 }}
>
<Button
variant="ghost"
<Button
variant="ghost"
onClick={onBackClick}
className="font-poppins font-medium text-base text-gray-600 hover:text-primary transition-colors duration-200"
>
@@ -102,27 +63,26 @@ export function AttractionDetailsPage({
{/* Title and Badges Section */}
<div className="mb-8">
<div className="flex flex-wrap gap-3 mb-6">
{featuredAttraction.badges.map((badge, index) => (
<Badge
key={index}
{attraction.attractionBadges.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'
}`}
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.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">
Phi Phi Islands Adventure
{attraction.title}
</span>{' '}
<span className="text-[#2d3134]">
Day Trip with Seaview Lunch by V. Marine Tour
Day Trip by {attraction.partner.businessName}
</span>
</h1>
</div>
@@ -132,18 +92,18 @@ export function AttractionDetailsPage({
{/* Main large image */}
<div className="col-span-2 row-span-2">
<ImageWithFallback
src={featuredAttraction.images.main}
src={attraction.attractionGalleries[0].filePathUrl}
alt="Main attraction image"
className="w-full h-full object-cover rounded-lg"
/>
</div>
{/* Gallery images */}
{featuredAttraction.images.gallery.slice(0, 4).map((image, index) => (
<div key={index} className="col-span-1 row-span-1">
{attraction.attractionGalleries.slice().map((image:any) => (
<div key={image.id} className="col-span-1 row-span-1">
<ImageWithFallback
src={image}
alt={`Gallery image ${index + 1}`}
src={image.filePathUrl}
alt={`Gallery image ${image.id}`}
className="w-full h-full object-cover rounded-lg"
/>
</div>
@@ -156,20 +116,43 @@ export function AttractionDetailsPage({
<div className="lg:col-span-2 space-y-12">
{/* Overview Cards */}
<div className="grid grid-cols-2 md:grid-cols-4 gap-6">
{Object.entries(featuredAttraction.overview).map(([key, value]) => (
<Card key={key} 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">
{key === 'duration' && <Clock className="w-6 h-6 text-primary" />}
{key === 'groupSize' && <Users className="w-6 h-6 text-primary" />}
{key === 'ages' && <Users className="w-6 h-6 text-primary" />}
{key === 'languages' && <MapPin className="w-6 h-6 text-primary" />}
</div>
<h3 className="font-normal text-primary capitalize mb-1">
{key === 'groupSize' ? 'Group Size' : key}
</h3>
<p className="text-sm text-[#717171] font-light">{value}</p>
</Card>
))}
{/* 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">{attraction.durations} mins</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">{attraction.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">{attraction.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">
{attraction.attractionLanguages.map((lang: any) => lang.language.name).join(", ")}
</p>
</Card>
</div>
{/* Tour Overview */}
@@ -181,7 +164,7 @@ export function AttractionDetailsPage({
</h2>
</div>
<p className="text-[#2d3134] leading-relaxed text-lg font-light">
{featuredAttraction.description}
{attraction.description}
</p>
</div>
@@ -194,12 +177,12 @@ export function AttractionDetailsPage({
</h3>
</div>
<ul className="space-y-4">
{featuredAttraction.highlights.map((highlight, index) => (
<li key={index} className="flex items-start gap-3 group">
{attraction.attractionHighlights.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}</span>
<span className="text-[#2d3134] leading-relaxed font-light">{highlight.title}</span>
</li>
))}
</ul>
@@ -220,30 +203,32 @@ export function AttractionDetailsPage({
<Check className="w-5 h-5" />
Included
</h4>
{featuredAttraction.included.map((item, index) => (
<div key={index} 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" />
{attraction.attractionInclusions.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>
<span className="text-[#2d3134] font-light">{item}</span>
</div>
))}
))}
</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>
{featuredAttraction.notIncluded.map((item, index) => (
<div key={index} 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" />
{attraction.attractionInclusions.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>
<span className="text-[#2d3134] font-light">{item}</span>
</div>
))}
))}
</div>
</div>
</div>
@@ -262,7 +247,8 @@ export function AttractionDetailsPage({
<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">Phi Phi Islands, Thailand</p>
<p className="text-sm text-gray-600 font-light">{attraction.title}</p>
<p className="text-sm text-gray-600 font-light">{attraction.address} </p>
</div>
</div>
</div>
@@ -276,7 +262,7 @@ export function AttractionDetailsPage({
<h3 className="text-xl font-bold text-primary mb-1">Select Date</h3>
<p className="text-sm text-gray-600">Choose your preferred visit date</p>
</div>
{/* Custom Calendar Design */}
<div className="space-y-4">
{/* Calendar Header */}
@@ -305,7 +291,7 @@ export function AttractionDetailsPage({
<div className="grid grid-cols-7 gap-1">
{/* Previous month */}
<button className="h-10 w-10 text-sm text-gray-300 hover:bg-gray-50 rounded">31</button>
{/* Current month */}
{Array.from({ length: 30 }, (_, i) => {
const day = i + 1;
@@ -314,13 +300,12 @@ export function AttractionDetailsPage({
return (
<button
key={day}
className={`h-10 w-10 text-sm rounded font-medium transition-all duration-200 ${
isSelected
? 'bg-primary text-white shadow-lg scale-105'
: isToday
className={`h-10 w-10 text-sm rounded font-medium transition-all duration-200 ${isSelected
? 'bg-primary text-white shadow-lg scale-105'
: isToday
? 'bg-primary/10 text-primary border border-primary/20'
: 'text-gray-700 hover:bg-primary/5 hover:text-primary'
}`}
}`}
>
{day}
</button>
@@ -356,7 +341,7 @@ export function AttractionDetailsPage({
<div className="space-y-4">
<div className="flex items-center justify-between">
<span className="text-gray-600">Adult Ticket</span>
<span className="font-bold text-xl text-primary">$89</span>
<span className="font-bold text-xl text-primary">{attraction.ticketPriceAdult}</span>
</div>
<div className="flex items-center justify-between">
<span className="text-gray-600">Service Fee</span>
@@ -365,14 +350,14 @@ export function AttractionDetailsPage({
<div className="border-t border-primary/20 pt-4">
<div className="flex items-center justify-between">
<span className="font-semibold text-gray-900">Total</span>
<span className="font-bold text-2xl text-primary">$94</span>
<span className="font-bold text-2xl text-primary">${attraction.ticketPriceAdult + 5}</span>
</div>
</div>
</div>
</Card>
{/* Confirm Booking Button */}
<Button
<Button
className="w-full bg-primary text-white hover:bg-primary/90 py-6 text-lg rounded-xl font-semibold transition-all duration-200 shadow-lg hover:shadow-xl hover:scale-[1.02] relative overflow-hidden group"
onClick={() => onCheckoutClick()}
>
@@ -398,7 +383,7 @@ export function AttractionDetailsPage({
</div>
</div>
</Layout>
);
}

View File

@@ -403,7 +403,7 @@ export function AttractionsPage({
htmlFor={key}
className="font-poppins text-sm text-[#414141] cursor-pointer flex-1 font-normal"
>
{key} ({count as number})
{key==="selective_pass" ?"Selective":"Unlimited"} ({count as number})
</label>
</div>
))}
@@ -460,9 +460,9 @@ export function AttractionsPage({
</div>
</div>
<CardContent className="p-4 flex-1 flex flex-col">
<div className="text-sm text-muted-foreground mb-2 font-medium font-poppins">
{/* {attraction.location} */}
</div>
{/* <div className="text-sm text-muted-foreground mb-2 font-medium font-poppins">
{attraction.location}
</div> */}
<h3 className="font-semibold text-foreground mb-3 line-clamp-2 leading-tight min-h-[2.5rem] font-poppins">
{attraction.title}
</h3>