mirror of
https://github.com/pacnpal/thrilltrack-explorer.git
synced 2025-12-24 20:51:13 -05:00
feat: Implement Phase 3 plan
This commit is contained in:
@@ -1,54 +1,12 @@
|
|||||||
import { useState, useEffect } from 'react';
|
|
||||||
import { Star, TrendingUp, Award, Castle, FerrisWheel, Waves, Tent } from 'lucide-react';
|
import { Star, TrendingUp, Award, Castle, FerrisWheel, Waves, Tent } from 'lucide-react';
|
||||||
import { Card, CardContent } from '@/components/ui/card';
|
import { Card, CardContent } from '@/components/ui/card';
|
||||||
import { Badge } from '@/components/ui/badge';
|
import { Badge } from '@/components/ui/badge';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { Park } from '@/types/database';
|
import { Park } from '@/types/database';
|
||||||
import { supabase } from '@/integrations/supabase/client';
|
import { useFeaturedParks } from '@/hooks/homepage/useFeaturedParks';
|
||||||
import { getErrorMessage } from '@/lib/errorHandler';
|
|
||||||
import { logger } from '@/lib/logger';
|
|
||||||
|
|
||||||
export function FeaturedParks() {
|
export function FeaturedParks() {
|
||||||
const [topRatedParks, setTopRatedParks] = useState<Park[]>([]);
|
const { topRated, mostRides } = useFeaturedParks();
|
||||||
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) {
|
|
||||||
logger.error('Failed to fetch featured parks', { error: getErrorMessage(error) });
|
|
||||||
} finally {
|
|
||||||
setLoading(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const FeaturedParkCard = ({ park, icon: Icon, label }: { park: Park; icon: any; label: string }) => (
|
const FeaturedParkCard = ({ park, icon: Icon, label }: { park: Park; icon: any; 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]">
|
<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]">
|
||||||
@@ -105,7 +63,7 @@ export function FeaturedParks() {
|
|||||||
</Card>
|
</Card>
|
||||||
);
|
);
|
||||||
|
|
||||||
if (loading) {
|
if (topRated.isLoading || mostRides.isLoading) {
|
||||||
return (
|
return (
|
||||||
<section className="py-12">
|
<section className="py-12">
|
||||||
<div className="container mx-auto px-4">
|
<div className="container mx-auto px-4">
|
||||||
|
|||||||
@@ -1,78 +1,16 @@
|
|||||||
import { useState, useEffect } from "react";
|
import { UserTopList, Park, Ride, Company } from "@/types/database";
|
||||||
import { UserTopList, UserTopListItem, Park, Ride, Company } from "@/types/database";
|
|
||||||
import { supabase } from "@/integrations/supabase/client";
|
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
import { Badge } from "@/components/ui/badge";
|
import { Badge } from "@/components/ui/badge";
|
||||||
|
import { useListItems } from "@/hooks/lists/useListItems";
|
||||||
|
|
||||||
interface ListDisplayProps {
|
interface ListDisplayProps {
|
||||||
list: UserTopList;
|
list: UserTopList;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface EnrichedListItem extends UserTopListItem {
|
|
||||||
entity?: Park | Ride | Company;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function ListDisplay({ list }: ListDisplayProps) {
|
export function ListDisplay({ list }: ListDisplayProps) {
|
||||||
const [items, setItems] = useState<EnrichedListItem[]>([]);
|
const { data: items, isLoading } = useListItems(list.id);
|
||||||
const [loading, setLoading] = useState(true);
|
|
||||||
|
|
||||||
useEffect(() => {
|
const getEntityUrl = (item: NonNullable<typeof items>[0]) => {
|
||||||
fetchItemsWithEntities();
|
|
||||||
}, [list.id]);
|
|
||||||
|
|
||||||
const fetchItemsWithEntities = async () => {
|
|
||||||
setLoading(true);
|
|
||||||
|
|
||||||
// First, get the list items
|
|
||||||
const { data: itemsData, error: itemsError } = await supabase
|
|
||||||
.from("user_top_list_items")
|
|
||||||
.select("*")
|
|
||||||
.eq("list_id", list.id)
|
|
||||||
.order("position", { ascending: true });
|
|
||||||
|
|
||||||
if (itemsError) {
|
|
||||||
console.error("Error fetching items:", itemsError);
|
|
||||||
setLoading(false);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Then, fetch the entities for each item
|
|
||||||
const enrichedItems = await Promise.all(
|
|
||||||
(itemsData as UserTopListItem[]).map(async (item) => {
|
|
||||||
let entity = null;
|
|
||||||
|
|
||||||
if (item.entity_type === "park") {
|
|
||||||
const { data } = await supabase
|
|
||||||
.from("parks")
|
|
||||||
.select("id, name, slug, park_type, location_id")
|
|
||||||
.eq("id", item.entity_id)
|
|
||||||
.single();
|
|
||||||
entity = data;
|
|
||||||
} else if (item.entity_type === "ride") {
|
|
||||||
const { data } = await supabase
|
|
||||||
.from("rides")
|
|
||||||
.select("id, name, slug, category, park_id")
|
|
||||||
.eq("id", item.entity_id)
|
|
||||||
.single();
|
|
||||||
entity = data;
|
|
||||||
} else if (item.entity_type === "company") {
|
|
||||||
const { data } = await supabase
|
|
||||||
.from("companies")
|
|
||||||
.select("id, name, slug, company_type")
|
|
||||||
.eq("id", item.entity_id)
|
|
||||||
.single();
|
|
||||||
entity = data;
|
|
||||||
}
|
|
||||||
|
|
||||||
return { ...item, entity };
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
setItems(enrichedItems);
|
|
||||||
setLoading(false);
|
|
||||||
};
|
|
||||||
|
|
||||||
const getEntityUrl = (item: EnrichedListItem) => {
|
|
||||||
if (!item.entity) return "#";
|
if (!item.entity) return "#";
|
||||||
|
|
||||||
const entity = item.entity as { slug?: string };
|
const entity = item.entity as { slug?: string };
|
||||||
@@ -89,11 +27,11 @@ export function ListDisplay({ list }: ListDisplayProps) {
|
|||||||
return "#";
|
return "#";
|
||||||
};
|
};
|
||||||
|
|
||||||
if (loading) {
|
if (isLoading) {
|
||||||
return <div className="text-center py-4 text-muted-foreground">Loading...</div>;
|
return <div className="text-center py-4 text-muted-foreground">Loading...</div>;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (items.length === 0) {
|
if (!items || items.length === 0) {
|
||||||
return (
|
return (
|
||||||
<div className="text-center py-8 text-muted-foreground">
|
<div className="text-center py-8 text-muted-foreground">
|
||||||
This list is empty. Click "Edit" to add items.
|
This list is empty. Click "Edit" to add items.
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
import { useEffect, useState } from 'react';
|
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
import { supabase } from '@/integrations/supabase/client';
|
|
||||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { RideCard } from '@/components/rides/RideCard';
|
import { RideCard } from '@/components/rides/RideCard';
|
||||||
|
import { useSimilarRides } from '@/hooks/rides/useSimilarRides';
|
||||||
|
|
||||||
interface SimilarRidesProps {
|
interface SimilarRidesProps {
|
||||||
currentRideId: string;
|
currentRideId: string;
|
||||||
@@ -32,44 +31,9 @@ interface SimilarRide {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function SimilarRides({ currentRideId, parkId, parkSlug, category }: SimilarRidesProps) {
|
export function SimilarRides({ currentRideId, parkId, parkSlug, category }: SimilarRidesProps) {
|
||||||
const [rides, setRides] = useState<SimilarRide[]>([]);
|
const { data: rides, isLoading } = useSimilarRides(currentRideId, parkId, category);
|
||||||
const [loading, setLoading] = useState(true);
|
|
||||||
|
|
||||||
useEffect(() => {
|
if (isLoading || !rides || rides.length === 0) {
|
||||||
async function fetchSimilarRides() {
|
|
||||||
const { data, error } = await supabase
|
|
||||||
.from('rides')
|
|
||||||
.select(`
|
|
||||||
id,
|
|
||||||
name,
|
|
||||||
slug,
|
|
||||||
image_url,
|
|
||||||
average_rating,
|
|
||||||
status,
|
|
||||||
category,
|
|
||||||
description,
|
|
||||||
max_speed_kmh,
|
|
||||||
max_height_meters,
|
|
||||||
duration_seconds,
|
|
||||||
review_count,
|
|
||||||
park:parks!inner(name, slug)
|
|
||||||
`)
|
|
||||||
.eq('park_id', parkId)
|
|
||||||
.eq('category', category)
|
|
||||||
.neq('id', currentRideId)
|
|
||||||
.order('average_rating', { ascending: false })
|
|
||||||
.limit(4);
|
|
||||||
|
|
||||||
if (!error && data) {
|
|
||||||
setRides(data);
|
|
||||||
}
|
|
||||||
setLoading(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
fetchSimilarRides();
|
|
||||||
}, [currentRideId, parkId, category]);
|
|
||||||
|
|
||||||
if (loading || rides.length === 0) {
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user