From 27a5202458d211c145c5ca106de9bca873dc143e Mon Sep 17 00:00:00 2001 From: "gpt-engineer-app[bot]" <159125892+gpt-engineer-app[bot]@users.noreply.github.com> Date: Sat, 20 Sep 2025 00:47:51 +0000 Subject: [PATCH] Implement remaining homepage features --- src/App.tsx | 6 + src/components/layout/Header.tsx | 30 ++- src/pages/ParkDetail.tsx | 448 +++++++++++++++++++++++++++++++ src/pages/Parks.tsx | 207 ++++++++++++++ src/pages/Rides.tsx | 334 +++++++++++++++++++++++ 5 files changed, 1019 insertions(+), 6 deletions(-) create mode 100644 src/pages/ParkDetail.tsx create mode 100644 src/pages/Parks.tsx create mode 100644 src/pages/Rides.tsx diff --git a/src/App.tsx b/src/App.tsx index 18daf2e9..2eb50950 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -4,6 +4,9 @@ import { TooltipProvider } from "@/components/ui/tooltip"; import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; import { BrowserRouter, Routes, Route } from "react-router-dom"; import Index from "./pages/Index"; +import Parks from "./pages/Parks"; +import ParkDetail from "./pages/ParkDetail"; +import Rides from "./pages/Rides"; import NotFound from "./pages/NotFound"; const queryClient = new QueryClient(); @@ -16,6 +19,9 @@ const App = () => ( } /> + } /> + } /> + } /> {/* ADD ALL CUSTOM ROUTES ABOVE THE CATCH-ALL "*" ROUTE */} } /> diff --git a/src/components/layout/Header.tsx b/src/components/layout/Header.tsx index 1c1e03c9..46d96899 100644 --- a/src/components/layout/Header.tsx +++ b/src/components/layout/Header.tsx @@ -4,16 +4,18 @@ import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; import { Sheet, SheetContent, SheetTrigger } from '@/components/ui/sheet'; import { Badge } from '@/components/ui/badge'; +import { Link, useNavigate } from 'react-router-dom'; export function Header() { const [isSearchFocused, setIsSearchFocused] = useState(false); + const navigate = useNavigate(); return (
{/* Logo and Brand */}
-
+
@@ -24,15 +26,23 @@ export function Header() { Theme Park Database
-
+ {/* Desktop Navigation */}
+
+ + ); + } + + return ( +
+
+ +
+ {/* Back Button */} + + + {/* Hero Section */} +
+
+ {park.banner_image_url ? ( + {park.name} + ) : ( +
+
+ {getParkTypeIcon(park.park_type)} +
+
+ )} +
+ + {/* Park Title Overlay */} +
+
+
+
+ + {park.status.replace('_', ' ').toUpperCase()} + + + {formatParkType(park.park_type)} + +
+

+ {park.name} +

+ {park.location && ( +
+ + {park.location.city && `${park.location.city}, `}{park.location.country} +
+ )} +
+ + {park.average_rating > 0 && ( +
+
+ + {park.average_rating.toFixed(1)} +
+
+ {park.review_count} reviews +
+
+ )} +
+
+
+
+ + {/* Quick Stats */} +
+ + +
{park.ride_count}
+
Total Rides
+
+
+ + +
{park.coaster_count}
+
Roller Coasters
+
+
+ + +
{park.review_count}
+
Reviews
+
+
+ + +
{getParkTypeIcon(park.park_type)}
+
{formatParkType(park.park_type)}
+
+
+
+ + {/* Main Content */} + + + Overview + Rides ({rides.length}) + Reviews + Photos + + + +
+
+ {/* Description */} + {park.description && ( + + + About {park.name} + + +

+ {park.description} +

+
+
+ )} + + {/* Top Rides Preview */} + + + Featured Rides + + +
+ {rides.slice(0, 4).map((ride) => ( +
+
{getRideIcon(ride.category)}
+
+

{ride.name}

+

+ {ride.category.replace('_', ' ')} +

+
+ {ride.average_rating > 0 && ( +
+ + {ride.average_rating.toFixed(1)} +
+ )} +
+ ))} +
+
+
+
+ +
+ {/* Park Information */} + + + Park Information + + + {park.opening_date && ( +
+ +
+
Opened
+
+ {new Date(park.opening_date).getFullYear()} +
+
+
+ )} + + {park.operator && ( +
+ +
+
Operator
+
+ {park.operator.name} +
+
+
+ )} + + {park.website_url && ( +
+ +
+
Website
+ + Visit Website + +
+
+ )} + + {park.phone && ( +
+ +
+
Phone
+
+ {park.phone} +
+
+
+ )} + + + +
+
Location
+ {park.location && ( +
+ {park.location.city &&
{park.location.city}
} + {park.location.state_province &&
{park.location.state_province}
} +
{park.location.country}
+
+ )} +
+
+
+
+
+
+ + +
+ {rides.map((ride) => ( + + +
+
{getRideIcon(ride.category)}
+
+

{ride.name}

+

+ {ride.category.replace('_', ' ')} +

+
+ {ride.average_rating > 0 && ( +
+ + {ride.average_rating.toFixed(1)} +
+ )} +
+ + {ride.description && ( +

+ {ride.description} +

+ )} + +
+
+ {ride.max_speed_kmh && ( + + {ride.max_speed_kmh} km/h + + )} + {ride.max_height_meters && ( + + {ride.max_height_meters}m + + )} +
+ + {ride.status} + +
+
+
+ ))} +
+
+ + +
+ +

Reviews Coming Soon

+

+ User reviews and ratings will be available soon +

+
+
+ + +
+ +

Photo Gallery Coming Soon

+

+ Photo galleries and media uploads will be available soon +

+
+
+
+
+
+ ); +} \ No newline at end of file diff --git a/src/pages/Parks.tsx b/src/pages/Parks.tsx new file mode 100644 index 00000000..65c4549f --- /dev/null +++ b/src/pages/Parks.tsx @@ -0,0 +1,207 @@ +import { useState, useEffect } from 'react'; +import { Header } from '@/components/layout/Header'; +import { ParkCard } from '@/components/parks/ParkCard'; +import { Input } from '@/components/ui/input'; +import { Button } from '@/components/ui/button'; +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'; +import { Badge } from '@/components/ui/badge'; +import { MapPin, Search, Filter, SlidersHorizontal } from 'lucide-react'; +import { Park } from '@/types/database'; +import { supabase } from '@/integrations/supabase/client'; +import { useNavigate } from 'react-router-dom'; + +export default function Parks() { + const [parks, setParks] = useState([]); + const [loading, setLoading] = useState(true); + const [searchQuery, setSearchQuery] = useState(''); + const [sortBy, setSortBy] = useState('name'); + const [filterType, setFilterType] = useState('all'); + const [filterStatus, setFilterStatus] = useState('all'); + const navigate = useNavigate(); + + useEffect(() => { + fetchParks(); + }, [sortBy, filterType, filterStatus]); + + const fetchParks = async () => { + try { + let query = supabase + .from('parks') + .select(`*, location:locations(*), operator:companies!parks_operator_id_fkey(*)`); + + // Apply filters + if (filterType !== 'all') { + query = query.eq('park_type', filterType); + } + if (filterStatus !== 'all') { + query = query.eq('status', filterStatus); + } + + // Apply sorting + switch (sortBy) { + case 'rating': + query = query.order('average_rating', { ascending: false }); + break; + case 'rides': + query = query.order('ride_count', { ascending: false }); + break; + case 'reviews': + query = query.order('review_count', { ascending: false }); + break; + default: + query = query.order('name'); + } + + const { data } = await query; + setParks(data || []); + } catch (error) { + console.error('Error fetching parks:', error); + } finally { + setLoading(false); + } + }; + + const filteredParks = parks.filter(park => + park.name.toLowerCase().includes(searchQuery.toLowerCase()) || + park.location?.city?.toLowerCase().includes(searchQuery.toLowerCase()) || + park.location?.country?.toLowerCase().includes(searchQuery.toLowerCase()) + ); + + const handleParkClick = (park: Park) => { + navigate(`/parks/${park.slug}`); + }; + + const parkTypes = [ + { value: 'all', label: 'All Types' }, + { value: 'theme_park', label: 'Theme Parks' }, + { value: 'amusement_park', label: 'Amusement Parks' }, + { value: 'water_park', label: 'Water Parks' }, + { value: 'family_entertainment', label: 'Family Entertainment' } + ]; + + const statusOptions = [ + { value: 'all', label: 'All Status' }, + { value: 'operating', label: 'Operating' }, + { value: 'seasonal', label: 'Seasonal' }, + { value: 'under_construction', label: 'Under Construction' }, + { value: 'closed', label: 'Closed' } + ]; + + if (loading) { + return ( +
+
+
+
+
+
+ {[...Array(8)].map((_, i) => ( +
+ ))} +
+
+
+
+ ); + } + + return ( +
+
+ +
+ {/* Page Header */} +
+
+ +

Theme Parks

+
+

+ Discover amazing theme parks, amusement parks, and attractions worldwide +

+
+ {filteredParks.length} parks found + {parks.reduce((sum, park) => sum + park.ride_count, 0)} total rides +
+
+ + {/* Search and Filters */} +
+
+
+ + setSearchQuery(e.target.value)} + className="pl-10" + /> +
+
+ + + + + +
+
+
+ + {/* Parks Grid */} + {filteredParks.length > 0 ? ( +
+ {filteredParks.map((park) => ( + handleParkClick(park)} + /> + ))} +
+ ) : ( +
+ +

No parks found

+

+ Try adjusting your search criteria or filters +

+
+ )} +
+
+ ); +} \ No newline at end of file diff --git a/src/pages/Rides.tsx b/src/pages/Rides.tsx new file mode 100644 index 00000000..054cf4e5 --- /dev/null +++ b/src/pages/Rides.tsx @@ -0,0 +1,334 @@ +import { useState, useEffect } from 'react'; +import { Header } from '@/components/layout/Header'; +import { Card, CardContent } from '@/components/ui/card'; +import { Input } from '@/components/ui/input'; +import { Button } from '@/components/ui/button'; +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'; +import { Badge } from '@/components/ui/badge'; +import { Search, Filter, SlidersHorizontal, Zap, Clock, Star } from 'lucide-react'; +import { Ride } from '@/types/database'; +import { supabase } from '@/integrations/supabase/client'; +import { useNavigate } from 'react-router-dom'; + +export default function Rides() { + const [rides, setRides] = useState([]); + const [loading, setLoading] = useState(true); + const [searchQuery, setSearchQuery] = useState(''); + const [sortBy, setSortBy] = useState('name'); + const [filterCategory, setFilterCategory] = useState('all'); + const [filterStatus, setFilterStatus] = useState('all'); + const navigate = useNavigate(); + + useEffect(() => { + fetchRides(); + }, [sortBy, filterCategory, filterStatus]); + + const fetchRides = async () => { + try { + let query = supabase + .from('rides') + .select(` + *, + park:parks!inner(name, slug, location:locations(*)), + manufacturer:companies!rides_manufacturer_id_fkey(*) + `); + + // Apply filters + if (filterCategory !== 'all') { + query = query.eq('category', filterCategory); + } + if (filterStatus !== 'all') { + query = query.eq('status', filterStatus); + } + + // Apply sorting + switch (sortBy) { + case 'rating': + query = query.order('average_rating', { ascending: false }); + break; + case 'speed': + query = query.order('max_speed_kmh', { ascending: false, nullsFirst: false }); + break; + case 'height': + query = query.order('max_height_meters', { ascending: false, nullsFirst: false }); + break; + case 'reviews': + query = query.order('review_count', { ascending: false }); + break; + default: + query = query.order('name'); + } + + const { data } = await query; + setRides(data || []); + } catch (error) { + console.error('Error fetching rides:', error); + } finally { + setLoading(false); + } + }; + + const filteredRides = rides.filter(ride => + ride.name.toLowerCase().includes(searchQuery.toLowerCase()) || + ride.park?.name?.toLowerCase().includes(searchQuery.toLowerCase()) || + ride.manufacturer?.name?.toLowerCase().includes(searchQuery.toLowerCase()) + ); + + const getRideIcon = (category: string) => { + switch (category) { + case 'roller_coaster': return '🎢'; + case 'water_ride': return '🌊'; + case 'dark_ride': return '🎭'; + case 'flat_ride': return '🎡'; + case 'kiddie_ride': return '🎠'; + case 'transportation': return '🚂'; + default: return '🎢'; + } + }; + + const getStatusColor = (status: string) => { + switch (status) { + case 'operating': return 'bg-green-500/20 text-green-400 border-green-500/30'; + case 'seasonal': return 'bg-yellow-500/20 text-yellow-400 border-yellow-500/30'; + case 'under_construction': return 'bg-blue-500/20 text-blue-400 border-blue-500/30'; + default: return 'bg-red-500/20 text-red-400 border-red-500/30'; + } + }; + + const formatCategory = (category: string) => { + return category.split('_').map(word => + word.charAt(0).toUpperCase() + word.slice(1) + ).join(' '); + }; + + const categories = [ + { value: 'all', label: 'All Categories' }, + { value: 'roller_coaster', label: 'Roller Coasters' }, + { value: 'flat_ride', label: 'Flat Rides' }, + { value: 'water_ride', label: 'Water Rides' }, + { value: 'dark_ride', label: 'Dark Rides' }, + { value: 'kiddie_ride', label: 'Kiddie Rides' }, + { value: 'transportation', label: 'Transportation' } + ]; + + const statusOptions = [ + { value: 'all', label: 'All Status' }, + { value: 'operating', label: 'Operating' }, + { value: 'seasonal', label: 'Seasonal' }, + { value: 'under_construction', label: 'Under Construction' }, + { value: 'closed', label: 'Closed' } + ]; + + if (loading) { + return ( +
+
+
+
+
+
+ {[...Array(8)].map((_, i) => ( +
+ ))} +
+
+
+
+ ); + } + + return ( +
+
+ +
+ {/* Page Header */} +
+
+
🎢
+

Rides & Attractions

+
+

+ Explore amazing rides and attractions from theme parks worldwide +

+
+ {filteredRides.length} rides found + {rides.filter(r => r.category === 'roller_coaster').length} roller coasters +
+
+ + {/* Search and Filters */} +
+
+
+ + setSearchQuery(e.target.value)} + className="pl-10" + /> +
+
+ + + + + +
+
+
+ + {/* Rides Grid */} + {filteredRides.length > 0 ? ( +
+ {filteredRides.map((ride) => ( + +
+ {/* Image/Icon Section */} +
+ {ride.image_url ? ( + {ride.name} + ) : ( +
+ {getRideIcon(ride.category)} +
+ )} + + {/* Gradient Overlay */} +
+ + {/* Status Badge */} + + {ride.status.replace('_', ' ').toUpperCase()} + +
+ + + {/* Header */} +
+
+

+ {ride.name} +

+ {getRideIcon(ride.category)} +
+ + {ride.park?.name && ( +

+ at {ride.park.name} +

+ )} +
+ + {/* Description */} + {ride.description && ( +

+ {ride.description} +

+ )} + + {/* Category Badge */} + + {formatCategory(ride.category)} + + + {/* Stats */} +
+
+ {ride.max_speed_kmh && ( +
+ + {ride.max_speed_kmh}km/h +
+ )} + {ride.max_height_meters && ( +
+ {ride.max_height_meters}m +
+ )} + {ride.duration_seconds && ( +
+ + {Math.floor(ride.duration_seconds / 60)}min +
+ )} +
+ + {ride.average_rating > 0 && ( +
+ + {ride.average_rating.toFixed(1)} +
+ )} +
+ + {/* Action Button */} + +
+
+ + ))} +
+ ) : ( +
+
🎢
+

No rides found

+

+ Try adjusting your search criteria or filters +

+
+ )} +
+
+ ); +} \ No newline at end of file