From 67d7f977b7ac296bd369097da79dc61389c9324e Mon Sep 17 00:00:00 2001 From: aryabenade Date: Fri, 24 Apr 2026 19:02:51 +0530 Subject: [PATCH] show attractions from backend on cityHomePage --- src/Redux/services/attractions.service.ts | 16 +- src/components/MelbourneAttractions.tsx | 441 +++++++++------------- 2 files changed, 188 insertions(+), 269 deletions(-) diff --git a/src/Redux/services/attractions.service.ts b/src/Redux/services/attractions.service.ts index 01b7dee..2695346 100644 --- a/src/Redux/services/attractions.service.ts +++ b/src/Redux/services/attractions.service.ts @@ -30,6 +30,20 @@ export const attractionsApi = createApi({ return `/attractions/customer/customer-attractions?${params.toString()}`; }, }), + getAttractionsForHomePage: builder.query({ + // cityId is required, others optional + query: ({ cityId, categoryId}) => { + const params = new URLSearchParams(); + + // required + params.append('cityXid', cityId); + + // optional + if (categoryId) params.append('categoryXid', categoryId); + + return `/attractions/list/city-attractions?${params.toString()}`; + }, + }), getAttractionDetailsById: builder.query({ query: (id: number) => `/attractions/customer/${id}`, @@ -38,4 +52,4 @@ export const attractionsApi = createApi({ }), }); -export const { useGetAttractionFiltersQuery,useGetCustomerAttractionsQuery,useGetAttractionDetailsByIdQuery } = attractionsApi; \ No newline at end of file +export const { useGetAttractionFiltersQuery,useGetCustomerAttractionsQuery,useGetAttractionDetailsByIdQuery,useGetAttractionsForHomePageQuery } = attractionsApi; \ No newline at end of file diff --git a/src/components/MelbourneAttractions.tsx b/src/components/MelbourneAttractions.tsx index b76a998..f531a81 100644 --- a/src/components/MelbourneAttractions.tsx +++ b/src/components/MelbourneAttractions.tsx @@ -3,271 +3,167 @@ import { ChevronLeft, ChevronRight, Clock, Users, Star, Zap, CheckCircle, MapPin import { ImageWithFallback } from './figma/ImageWithFallback'; import { motion } from 'motion/react'; import { useNavigate } from 'react-router-dom'; - -const melbourneAttractions = [ - { - id: 1, - name: "Royal Botanic Gardens", - city: "Melbourne", - country: "Australia", - image: "https://images.unsplash.com/photo-1721272962395-a848331ce92d?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHxtZWxib3VybmUlMjByb3lhbCUyMGJvdGFuaWMlMjBnYXJkZW5zfGVufDF8fHx8MTc1NzMzNzc4OXww&ixlib=rb-4.1.0&q=80&w=1080&utm_source=figma&utm_medium=referral", - rating: 4.8, - reviews: "15,600+", - category: "Gardens", - originalPrice: "Free", - includedValue: "$25", - perks: [ - { icon: Volume2, label: "Audio garden tour", color: "text-green-600" }, - { icon: MapPin, label: "Garden maps", color: "text-blue-600" }, - { icon: Camera, label: "Photo spots guide", color: "text-purple-600" } - ] - }, - { - id: 2, - name: "Federation Square", - city: "Melbourne", - country: "Australia", - image: "https://images.unsplash.com/photo-1639655001512-e4b58d4874b8?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHxtZWxib3VybmUlMjBmZWRlcmF0aW9uJTIwc3F1YXJlfGVufDF8fHx8MTc1NzMzNzc5Mnww&ixlib=rb-4.1.0&q=80&w=1080&utm_source=figma&utm_medium=referral", - rating: 4.6, - reviews: "22,400+", - category: "Landmarks", - originalPrice: "Free", - includedValue: "$35", - perks: [ - { icon: Volume2, label: "Cultural tours", color: "text-orange-600" }, - { icon: Eye, label: "Gallery access", color: "text-blue-600" }, - { icon: Users, label: "Event access", color: "text-purple-600" } - ] - }, - { - id: 3, - name: "Queen Victoria Market", - city: "Melbourne", - country: "Australia", - image: "https://images.unsplash.com/photo-1676454953709-e0be46f62490?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHxtZWxib3VybmUlMjBxdWVlbiUyMHZpY3RvcmlhJTIwbWFya2V0fGVufDF8fHx8MTc1NzMzNzc5Nnww&ixlib=rb-4.1.0&q=80&w=1080&utm_source=figma&utm_medium=referral", - rating: 4.7, - reviews: "18,200+", - category: "Markets", - originalPrice: "$45", - includedValue: "$45", - perks: [ - { icon: Users, label: "Food tours", color: "text-orange-600" }, - { icon: Coffee, label: "Tastings", color: "text-brown-600" }, - { icon: Volume2, label: "History guide", color: "text-blue-600" } - ] - }, - { - id: 4, - name: "Eureka Skydeck", - city: "Melbourne", - country: "Australia", - image: "https://images.unsplash.com/photo-1629677713183-29248e1268d7?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHxtZWxib3VybmUlMjBldXJla2ElMjB0b3dlcnxlbnwxfHx8fDE3NTczMzc4MDB8MA&ixlib=rb-4.1.0&q=80&w=1080&utm_source=figma&utm_medium=referral", - rating: 4.9, - reviews: "11,800+", - category: "Views", - originalPrice: "$32", - includedValue: "$32", - perks: [ - { icon: Zap, label: "Skip-the-line", color: "text-green-600" }, - { icon: Eye, label: "360° views", color: "text-purple-600" }, - { icon: Camera, label: "Photo experiences", color: "text-blue-600" } - ] - }, - { - id: 5, - name: "St Kilda Beach & Pier", - city: "Melbourne", - country: "Australia", - image: "https://images.unsplash.com/photo-1674732954456-159835c0a46b?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHxtZWxib3VybmUlMjBzdCUyMGtpbGRhJTIwYmVhY2h8ZW58MXx8fHwxNzU3MzM3ODAzfDA&ixlib=rb-4.1.0&q=80&w=1080&utm_source=figma&utm_medium=referral", - rating: 4.5, - reviews: "14,300+", - category: "Beach", - originalPrice: "Free", - includedValue: "$20", - perks: [ - { icon: Users, label: "Penguin tours", color: "text-blue-600" }, - { icon: MapPin, label: "Beach activities", color: "text-green-600" }, - { icon: Camera, label: "Sunset spots", color: "text-purple-600" } - ] - }, - { - id: 6, - name: "Melbourne Laneways", - city: "Melbourne", - country: "Australia", - image: "https://images.unsplash.com/photo-1705120624704-0970afc29fea?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHxtZWxib3VybmUlMjBsYW5ld2F5cyUyMHN0cmVldCUyMGFydHxlbnwxfHx8fDE3NTczMzc4MDd8MA&ixlib=rb-4.1.0&q=80&w=1080&utm_source=figma&utm_medium=referral", - rating: 4.8, - reviews: "19,500+", - category: "Street Art", - originalPrice: "$55", - includedValue: "$55", - perks: [ - { icon: Palette, label: "Art tours", color: "text-pink-600" }, - { icon: Coffee, label: "Café stops", color: "text-brown-600" }, - { icon: Camera, label: "Photo walks", color: "text-purple-600" } - ] - }, - { - id: 7, - name: "Melbourne Zoo", - city: "Melbourne", - country: "Australia", - image: "https://images.unsplash.com/photo-1681429477985-30dc7b88dd5b?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHxtZWxib3VybmUlMjB6b28lMjBhbmltYWxzfGVufDF8fHx8MTc1NzMzNzgxMHww&ixlib=rb-4.1.0&q=80&w=1080&utm_source=figma&utm_medium=referral", - rating: 4.7, - reviews: "13,900+", - category: "Wildlife", - originalPrice: "$42", - includedValue: "$42", - perks: [ - { icon: Zap, label: "Skip-the-line", color: "text-green-600" }, - { icon: Users, label: "Animal encounters", color: "text-orange-600" }, - { icon: Volume2, label: "Keeper talks", color: "text-blue-600" } - ] - }, - { - id: 8, - name: "Royal Exhibition Building", - city: "Melbourne", - country: "Australia", - image: "https://images.unsplash.com/photo-1720523794299-c3b445d71a51?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHxtZWxib3VybmUlMjByb3lhbCUyMGV4aGliaXRpb24lMjBidWlsZGluZ3xlbnwxfHx8fDE3NTczMzc4MTR8MA&ixlib=rb-4.1.0&q=80&w=1080&utm_source=figma&utm_medium=referral", - rating: 4.6, - reviews: "8,700+", - category: "Heritage", - originalPrice: "$25", - includedValue: "$25", - perks: [ - { icon: Volume2, label: "Audio tours", color: "text-blue-600" }, - { icon: Eye, label: "Exhibitions", color: "text-purple-600" }, - { icon: MapPin, label: "Heritage walks", color: "text-green-600" } - ] - } -]; - -const categories = ["All", "Landmarks", "Gardens", "Markets", "Views", "Beach", "Street Art", "Wildlife", "Heritage"]; +import { useGetAttractionsForHomePageQuery } from '../Redux/services/attractions.service'; export function MelbourneAttractions() { - const [activeCategory, setActiveCategory] = useState("All"); + const [selectedCategoryId, setSelectedCategoryId] = useState(null); const navigate = useNavigate(); - const cityName = localStorage.getItem("cityName") + const cityName = localStorage.getItem("cityName"); + const cityId = localStorage.getItem("cityId"); - const filteredAttractions = activeCategory === "All" - ? melbourneAttractions - : melbourneAttractions.filter(attraction => attraction.category === activeCategory); + const { data: homePageAttractionsData } = useGetAttractionsForHomePageQuery({ cityId }); - const AttractionCard = ({ attraction, index }: { attraction: typeof melbourneAttractions[0], index: number }) => ( - - {/* 3D Flip Container */} -
- - {/* FRONT FACE */} -
- {/* Background Image */} - + const apiAttractions = homePageAttractionsData?.attractions || []; + const apiCategories = homePageAttractionsData?.categories || []; - {/* Rating Badge */} - {/*
-
- -
- {attraction.rating} -
*/} + // Filter attractions by selected category + const filteredAttractions = selectedCategoryId === null + ? apiAttractions + : apiAttractions.filter((attraction: any) => + attraction.categories?.some((cat: any) => cat.id === selectedCategoryId) + ); - {/* Front Content - Clean Title & Location */} -
-
-

{attraction.name}

-

- {attraction.city}, {attraction.country} -

+ const AttractionCard = ({ attraction, index }: { attraction: any; index: number }) => { + // Get cover image or first image from galleries + const coverImage = attraction.galleries?.find((g: any) => g.isCoverImage)?.filePathUrl + || attraction.galleries?.[0]?.filePathUrl + || ''; + + // Filter only inclusions (isInclusion: true) + const inclusions = attraction.inclusions?.filter((inc: any) => inc.isInclusion) || []; + + return ( + +
+ + {/* FRONT FACE */} +
+ +
+
+

{attraction.title}

+

{attraction.city?.cityName}, Australia

+
-
- {/* BACK FACE */} -
- {/* Back Content Container */} -
- - {/* Included Value Section */} -
-
- - Included Value + {/* BACK FACE */} +
+
+ + {/* Pricing Section */} +
+
+ + Included Value +
+
+ ${attraction.ticketPriceAdult} + {attraction.ticketPriceChild && ( + + / Child ${attraction.ticketPriceChild} + + )} +
+

+ {attraction.isBookingRequired ? 'Booking required' : 'No booking required'} +

-
{attraction.includedValue}
-

- {attraction.originalPrice === "Free" - ? "Premium access included" - : "Save money with CityCard"} -

-
- {/* What's Included List */} -
-

What's Included:

-
- {attraction.perks.slice(0, 3).map((perk, perkIndex) => ( -
-
- -
- {perk.label} + {/* Inclusions List */} + {inclusions.length > 0 && ( +
+

What's Included:

+
+ {inclusions.slice(0, 3).map((inc: any) => ( +
+
+ +
+ {inc.title} +
+ ))}
- ))} -
-
- - {/* Duration & Meta Info */} -
-
-
- - 2-3 hours
-
- - All ages + )} + + {/* Duration & Group Info */} +
+
+ {attraction.durations && ( +
+ + {attraction.durations} mins +
+ )} + {attraction.groupSize && ( +
+ + Max {attraction.groupSize} +
+ )} + {attraction.ageRange && ( +
+ + {attraction.ageRange} +
+ )}
-
- {/* Footer Features */} -
-
-
- - Mobile ticket + {/* Categories */} + {attraction.categories?.length > 0 && ( +
+ {attraction.categories.slice(0, 2).map((cat: any) => ( + + {cat.categoryName} + + ))}
-
- - Instant confirmation + )} + + {/* Footer */} +
+
+
+ + Mobile ticket +
+
+ + Instant confirmation +
-
- {/* Decorative Elements */} -
-
+ {/* Decorative Elements */} +
+
+
-
-
- - ); +
+ + ); + }; return (
+ {/* Header */} Experiences

- Discover {cityName}'s iconic landmarks, vibrant culture, world-class dining, and hidden gems - all included with your {cityName} CityCard + Discover {cityName}'s iconic landmarks, vibrant culture, world-class dining, and hidden gems — all included with your {cityName} CityCard

@@ -303,23 +199,41 @@ export function MelbourneAttractions() { viewport={{ once: true }} className="flex flex-wrap justify-center gap-3 mb-12" > - {categories.map((category, index) => ( + {/* "All" button */} + setSelectedCategoryId(null)} + whileHover={{ scale: 1.05 }} + whileTap={{ scale: 0.95 }} + className={`px-6 py-4 h-14 rounded-2xl font-medium transition-all duration-300 ${ + selectedCategoryId === null + ? 'bg-gradient-to-r from-primary to-secondary text-white shadow-xl shadow-primary/25 ring-2 ring-primary/20' + : 'bg-white/80 backdrop-blur-sm text-gray-700 hover:text-gray-900 hover:shadow-lg border border-gray-200/50 hover:border-primary/20 hover:bg-white' + }`} + > + All + + + {/* Dynamic category buttons from API */} + {apiCategories.map((category: any, index: number) => ( setActiveCategory(category)} + onClick={() => setSelectedCategoryId(category.id)} whileHover={{ scale: 1.05 }} whileTap={{ scale: 0.95 }} className={`px-6 py-4 h-14 rounded-2xl font-medium transition-all duration-300 ${ - activeCategory === category + selectedCategoryId === category.id ? 'bg-gradient-to-r from-primary to-secondary text-white shadow-xl shadow-primary/25 ring-2 ring-primary/20' : 'bg-white/80 backdrop-blur-sm text-gray-700 hover:text-gray-900 hover:shadow-lg border border-gray-200/50 hover:border-primary/20 hover:bg-white' }`} > - {category} + {category.categoryName} ))} @@ -327,52 +241,44 @@ export function MelbourneAttractions() { {/* Mobile Horizontal Carousel */}
- {/* Scroll Container */}
- {filteredAttractions.map((attraction, index) => ( + {filteredAttractions.map((attraction: any, index: number) => ( ))}
- - {/* Scroll Indicators */}
- {Array.from({ length: Math.ceil(filteredAttractions.length / 2) }).map((_, index) => ( -
+ {Array.from({ length: Math.ceil(filteredAttractions.length / 2) }).map((_: any, index: number) => ( +
))}
- - {/* Mobile Hint Text */}
-

- Swipe to explore more Melbourne attractions -

+

Swipe to explore more {cityName} attractions

{/* Desktop Bento Grid */}
- {/* Top Row - 3 equal cards */}
- {filteredAttractions.slice(0, 3).map((attraction, index) => ( + {filteredAttractions.slice(0, 3).map((attraction: any, index: number) => ( ))}
- - {/* Consistent Vertical Spacing */}
- - {/* Bottom Row - 2 larger cards */}
- {filteredAttractions.slice(3, 5).map((attraction, index) => ( + {filteredAttractions.slice(3, 5).map((attraction: any, index: number) => ( ))}
+ {/* Empty State */} + {filteredAttractions.length === 0 && ( +
+

No attractions found for this category.

+
+ )} + {/* Call to Action */} navigate('/passes')} + onClick={() => navigate('/passes')} > Get Your {cityName} Card - - {/* Shine animation */}
+
);