Files
thrilltrack-explorer/src/components/homepage/FeaturedParks.tsx
gpt-engineer-app[bot] fc7c2d5adc Refactor park detail address display
Implement the plan to refactor the address display in the park detail page. This includes updating the sidebar address to show the street address on its own line, followed by city, state, and postal code on the next line, and the country on a separate line. This change aims to create a more compact and natural address format.
2025-11-06 14:03:58 +00:00

189 lines
6.9 KiB
TypeScript

import { useState, useEffect } from 'react';
import { Star, TrendingUp, Award, Castle, FerrisWheel, Waves, Tent, LucideIcon } from 'lucide-react';
import { formatLocationShort } from '@/lib/locationFormatter';
import { Card, CardContent } from '@/components/ui/card';
import { Badge } from '@/components/ui/badge';
import { Button } from '@/components/ui/button';
import { Park } from '@/types/database';
import { supabase } from '@/lib/supabaseClient';
import { getErrorMessage } from '@/lib/errorHandler';
export function FeaturedParks() {
const [topRatedParks, setTopRatedParks] = useState<Park[]>([]);
const [mostRidesParks, setMostRidesParks] = useState<Park[]>([]);
const [loading, setLoading] = useState(true);
useEffect(() => {
fetchFeaturedParks();
}, []);
const fetchFeaturedParks = async () => {
try {
// Fetch top rated parks
const { data: topRated } = await supabase
.from('parks')
.select(`
*,
location:locations(*),
operator:companies!parks_operator_id_fkey(*)
`)
.order('average_rating', { ascending: false })
.limit(3);
// Fetch parks with most rides
const { data: mostRides } = await supabase
.from('parks')
.select(`
*,
location:locations(*),
operator:companies!parks_operator_id_fkey(*)
`)
.order('ride_count', { ascending: false })
.limit(3);
setTopRatedParks(topRated || []);
setMostRidesParks(mostRides || []);
} catch (error: unknown) {
// Featured parks fetch failed - display empty sections
} finally {
setLoading(false);
}
};
const FeaturedParkCard = ({ park, icon: Icon, label }: { park: Park; icon: LucideIcon; label: string }) => (
<Card className="group overflow-hidden border-border/50 bg-gradient-to-br from-card via-card to-card/80 hover:shadow-xl hover:shadow-primary/10 transition-all duration-300 cursor-pointer hover:scale-[1.02]">
<div className="relative">
{/* Gradient Background */}
<div className="aspect-video bg-gradient-to-br from-primary/20 via-secondary/20 to-accent/20 flex items-center justify-center relative">
<div className="opacity-50">
{park.park_type === 'theme_park' ? <Castle className="w-16 h-16" /> :
park.park_type === 'amusement_park' ? <FerrisWheel className="w-16 h-16" /> :
park.park_type === 'water_park' ? <Waves className="w-16 h-16" /> : <Tent className="w-16 h-16" />}
</div>
<div className="absolute inset-0 bg-gradient-to-t from-black/60 via-transparent to-transparent" />
{/* Featured Badge */}
<Badge className="absolute top-3 left-3 bg-primary/90 text-primary-foreground border-0">
<Icon className="w-3 h-3 mr-1" />
{label}
</Badge>
{/* Rating Badge */}
<Badge className="absolute top-3 right-3 bg-background/90 text-foreground border-0">
<Star className="w-3 h-3 mr-1 fill-yellow-400 text-yellow-400" />
{park.average_rating ? park.average_rating.toFixed(1) : 'N/A'}
</Badge>
</div>
<CardContent className="p-4">
<div className="space-y-2">
<h3 className="font-bold text-lg group-hover:text-primary transition-colors line-clamp-1">
{park.name}
</h3>
{park.location && (
<p className="text-sm text-muted-foreground">
{formatLocationShort(park.location)}
</p>
)}
<div className="flex items-center justify-between text-sm">
<div className="flex items-center gap-3">
<span className="text-primary font-medium">{park.ride_count} rides</span>
<div className="flex items-center gap-1">
<span className="text-accent font-medium">{park.coaster_count}</span>
<FerrisWheel className="w-3 h-3 text-accent" />
</div>
</div>
<div className="text-xs text-muted-foreground">
{park.review_count} reviews
</div>
</div>
</div>
</CardContent>
</div>
</Card>
);
if (loading) {
return (
<section className="py-12">
<div className="container mx-auto px-4">
<div className="animate-pulse space-y-6">
<div className="h-8 bg-muted rounded w-1/3"></div>
<div className="grid md:grid-cols-3 gap-6">
{[...Array(6)].map((_, i) => (
<div key={i} className="h-64 bg-muted rounded-lg"></div>
))}
</div>
</div>
</div>
</section>
);
}
return (
<section className="py-16 bg-muted/20">
<div className="container mx-auto px-4">
<div className="text-center max-w-3xl mx-auto mb-12">
<h2 className="text-3xl md:text-4xl font-bold mb-4">
Featured
<span className="bg-gradient-to-r from-primary to-accent bg-clip-text text-transparent"> Destinations</span>
</h2>
<p className="text-xl text-muted-foreground">
Discover the highest-rated parks and thrill capitals around the world
</p>
</div>
{/* Top Rated Parks */}
<div className="mb-12">
<div className="flex items-center gap-3 mb-6">
<div className="w-8 h-8 bg-primary/20 rounded-full flex items-center justify-center">
<Award className="w-4 h-4 text-primary" />
</div>
<h3 className="text-2xl font-bold">Top Rated Parks</h3>
</div>
<div className="grid md:grid-cols-3 gap-6">
{topRatedParks.map((park) => (
<FeaturedParkCard
key={park.id}
park={park}
icon={Award}
label="Top Rated"
/>
))}
</div>
</div>
{/* Most Rides */}
<div>
<div className="flex items-center gap-3 mb-6">
<div className="w-8 h-8 bg-secondary/20 rounded-full flex items-center justify-center">
<TrendingUp className="w-4 h-4 text-secondary" />
</div>
<h3 className="text-2xl font-bold">Thrill Capitals</h3>
</div>
<div className="grid md:grid-cols-3 gap-6">
{mostRidesParks.map((park) => (
<FeaturedParkCard
key={park.id}
park={park}
icon={TrendingUp}
label="Most Rides"
/>
))}
</div>
</div>
{/* Call to Action */}
<div className="text-center mt-12">
<Button size="lg" variant="outline" className="border-primary/30 hover:bg-primary/10">
Explore All Parks
</Button>
</div>
</div>
</section>
);
}