From a2e05c50806d4ce76c67be6dae6d86b51135ae85 Mon Sep 17 00:00:00 2001 From: "gpt-engineer-app[bot]" <159125892+gpt-engineer-app[bot]@users.noreply.github.com> Date: Fri, 17 Oct 2025 14:04:57 +0000 Subject: [PATCH] Refactor: Implement complete plan --- src/components/profile/RideCreditsManager.tsx | 2 +- src/hooks/moderation/useEntityCache.ts | 2 +- src/hooks/useCoasterStats.ts | 44 ++++ src/hooks/useEntityVersions.ts | 6 +- src/hooks/useTechnicalSpecifications.ts | 52 +++++ src/lib/runtimeValidation.ts | 2 +- src/pages/Profile.tsx | 194 +++++++++++++----- src/pages/RideModelDetail.tsx | 18 +- src/types/database.ts | 4 +- src/types/versioning.ts | 2 +- 10 files changed, 258 insertions(+), 68 deletions(-) create mode 100644 src/hooks/useCoasterStats.ts create mode 100644 src/hooks/useTechnicalSpecifications.ts diff --git a/src/components/profile/RideCreditsManager.tsx b/src/components/profile/RideCreditsManager.tsx index 3c08eae9..0e7a430e 100644 --- a/src/components/profile/RideCreditsManager.tsx +++ b/src/components/profile/RideCreditsManager.tsx @@ -121,7 +121,7 @@ export function RideCreditsManager({ userId }: RideCreditsManagerProps) { const { data, error } = await query; if (error) throw error; - let processedData = (data || []) as any[]; + let processedData = data || []; // Sort by name client-side if needed if (sortBy === 'name') { diff --git a/src/hooks/moderation/useEntityCache.ts b/src/hooks/moderation/useEntityCache.ts index 2601b909..a11fa0ae 100644 --- a/src/hooks/moderation/useEntityCache.ts +++ b/src/hooks/moderation/useEntityCache.ts @@ -170,7 +170,7 @@ export function useEntityCache() { // Collect all entity IDs from submissions submissions.forEach(submission => { - const content = submission.content as any; + const content = submission.content; if (content && typeof content === 'object') { if (content.ride_id) rideIds.add(content.ride_id); if (content.park_id) parkIds.add(content.park_id); diff --git a/src/hooks/useCoasterStats.ts b/src/hooks/useCoasterStats.ts new file mode 100644 index 00000000..5a0f9830 --- /dev/null +++ b/src/hooks/useCoasterStats.ts @@ -0,0 +1,44 @@ +import { useQuery } from '@tanstack/react-query'; +import { supabase } from '@/integrations/supabase/client'; + +export interface CoasterStat { + id: string; + ride_id: string; + stat_name: string; + stat_value: number; + unit?: string | null; + category?: string | null; + description?: string | null; + display_order: number; + created_at: string; +} + +export function useCoasterStats(rideId: string | undefined) { + return useQuery({ + queryKey: ['coaster-stats', rideId], + queryFn: async () => { + if (!rideId) return []; + + const { data, error } = await (supabase as any) + .from('ride_coaster_stats') + .select('*') + .eq('ride_id', rideId) + .order('display_order'); + + if (error) throw error; + + return (data || []).map((stat: any) => ({ + id: stat.id, + ride_id: stat.ride_id, + stat_name: stat.stat_name, + stat_value: stat.stat_value, + unit: stat.unit || null, + category: stat.category || null, + description: stat.description || null, + display_order: stat.display_order, + created_at: stat.created_at, + })) as CoasterStat[]; + }, + enabled: !!rideId + }); +} diff --git a/src/hooks/useEntityVersions.ts b/src/hooks/useEntityVersions.ts index 078e28bd..12bd45b6 100644 --- a/src/hooks/useEntityVersions.ts +++ b/src/hooks/useEntityVersions.ts @@ -41,8 +41,8 @@ export function useEntityVersions(entityType: EntityType, entityId: string) { const versionTable = `${entityType}_versions`; const entityIdCol = `${entityType}_id`; - const { data, error } = await supabase - .from(versionTable as any) + const { data, error } = await (supabase as any) + .from(versionTable) .select(` *, profiles:created_by(username, display_name, avatar_url) @@ -63,7 +63,7 @@ export function useEntityVersions(entityType: EntityType, entityId: string) { return; } - const versionsWithProfiles = (data as any[]).map((v: any) => ({ + const versionsWithProfiles = (data || []).map((v: any) => ({ ...v, profiles: v.profiles || { username: 'Unknown', diff --git a/src/hooks/useTechnicalSpecifications.ts b/src/hooks/useTechnicalSpecifications.ts new file mode 100644 index 00000000..ffd749ba --- /dev/null +++ b/src/hooks/useTechnicalSpecifications.ts @@ -0,0 +1,52 @@ +import { useQuery } from '@tanstack/react-query'; +import { supabase } from '@/integrations/supabase/client'; + +export interface TechnicalSpecification { + id: string; + entity_type: 'ride' | 'ride_model'; + entity_id: string; + spec_name: string; + spec_value: string; + spec_unit?: string | null; + category?: string | null; + display_order: number; + created_at: string; +} + +export function useTechnicalSpecifications( + entityType: 'ride' | 'ride_model', + entityId: string | undefined +) { + return useQuery({ + queryKey: ['technical-specifications', entityType, entityId], + queryFn: async () => { + if (!entityId) return []; + + const tableName = entityType === 'ride' + ? 'ride_technical_specifications' + : 'ride_model_technical_specifications'; + const idColumn = entityType === 'ride' ? 'ride_id' : 'ride_model_id'; + + const { data, error } = await (supabase as any) + .from(tableName) + .select('*') + .eq(idColumn, entityId) + .order('display_order'); + + if (error) throw error; + + return (data || []).map((spec: any) => ({ + id: spec.id, + entity_type: entityType, + entity_id: entityId, + spec_name: spec.spec_name, + spec_value: spec.spec_value, + spec_unit: spec.spec_unit || null, + category: spec.category || null, + display_order: spec.display_order, + created_at: spec.created_at, + })) as TechnicalSpecification[]; + }, + enabled: !!entityId + }); +} diff --git a/src/lib/runtimeValidation.ts b/src/lib/runtimeValidation.ts index 43c6bfab..06fbeceb 100644 --- a/src/lib/runtimeValidation.ts +++ b/src/lib/runtimeValidation.ts @@ -121,7 +121,7 @@ export const rideModelRuntimeSchema = z.object({ manufacturer_id: z.string().uuid().nullable().optional(), category: z.string(), description: z.string().nullable().optional(), - technical_specs: z.array(z.unknown()).nullable().optional(), + // Note: technical_specs deprecated - use ride_model_technical_specifications table created_at: z.string().optional(), updated_at: z.string().optional(), }).passthrough(); diff --git a/src/pages/Profile.tsx b/src/pages/Profile.tsx index 6eaaf57d..622790a7 100644 --- a/src/pages/Profile.tsx +++ b/src/pages/Profile.tsx @@ -19,7 +19,7 @@ import { UserListManager } from '@/components/lists/UserListManager'; import { RideCreditsManager } from '@/components/profile/RideCreditsManager'; import { useUsernameValidation } from '@/hooks/useUsernameValidation'; import { User, MapPin, Calendar, Star, Trophy, Settings, Camera, Edit3, Save, X, ArrowLeft, Check, AlertCircle, Loader2, UserX, FileText, Image } from 'lucide-react'; -import { Profile as ProfileType, ActivityEntry, ReviewActivity, SubmissionActivity, RankingActivity } from '@/types/database'; +import { Profile as ProfileType } from '@/types/database'; import { supabase } from '@/integrations/supabase/client'; import { useToast } from '@/hooks/use-toast'; import { getErrorMessage } from '@/lib/errorHandler'; @@ -30,6 +30,95 @@ import { UserBlockButton } from '@/components/profile/UserBlockButton'; import { PersonalLocationDisplay } from '@/components/profile/PersonalLocationDisplay'; import { useUserRole } from '@/hooks/useUserRole'; +// Activity type definitions +interface SubmissionActivity { + id: string; + type: 'submission'; + submission_type: 'park' | 'ride' | 'photo' | 'company' | 'ride_model'; + status: string; + created_at: string; + content?: { + action?: 'edit' | 'create'; + name?: string; + slug?: string; + entity_slug?: string; + entity_name?: string; + park_slug?: string; + park_name?: string; + company_type?: string; + manufacturer_slug?: string; + description?: string; + }; + photo_preview?: string; + photo_count?: number; + entity_type?: 'park' | 'ride' | 'company'; + entity_id?: string; +} + +interface RankingActivity { + id: string; + type: 'ranking'; + title: string; + description?: string; + list_type: string; + created_at: string; +} + +interface ReviewActivity { + id: string; + type: 'review'; + rating: number; + title?: string; + content?: string; + created_at: string; + moderation_status?: string; + park_id?: string; + ride_id?: string; + parks?: { + name: string; + slug: string; + } | null; + rides?: { + name: string; + slug: string; + parks?: { + name: string; + slug: string; + } | null; + } | null; +} + +interface CreditActivity { + id: string; + type: 'credit'; + ride_count: number; + first_ride_date?: string; + created_at: string; + rides?: { + name: string; + slug: string; + parks?: { + name: string; + slug: string; + } | null; + } | null; +} + +type ActivityEntry = SubmissionActivity | RankingActivity | ReviewActivity | CreditActivity; + +// Type guards +const isSubmissionActivity = (act: ActivityEntry): act is SubmissionActivity => + act.type === 'submission'; + +const isRankingActivity = (act: ActivityEntry): act is RankingActivity => + act.type === 'ranking'; + +const isReviewActivity = (act: ActivityEntry): act is ReviewActivity => + act.type === 'review'; + +const isCreditActivity = (act: ActivityEntry): act is CreditActivity => + act.type === 'credit'; + export default function Profile() { const { username @@ -764,7 +853,7 @@ export default function Profile() { <>
- {reviewActivity.title || reviewActivity.description || 'Left a review'} + {reviewActivity.title || reviewActivity.content || 'Left a review'}
- {(activity as any).content?.action === 'edit' ? 'Edited' : 'Submitted'}{' '} - {(activity as any).submission_type === 'photo' ? 'photos for' : (activity as any).submission_type || 'content'} - {(activity as any).content?.name && ` ${(activity as any).content.name}`} + {activity.content?.action === 'edit' ? 'Edited' : 'Submitted'}{' '} + {activity.submission_type === 'photo' ? 'photos for' : activity.submission_type || 'content'} + {activity.content?.name && ` ${activity.content.name}`}
- {(activity as any).status === 'pending' && ( + {activity.status === 'pending' && (- {(activity as any).content.description} + {activity.content.description}
)} > - ) : activity.type === 'ranking' ? ( + ) : isRankingActivity(activity) ? ( <>- {(activity as any).description} + {activity.description}
)} > - ) : ( + ) : isCreditActivity(activity) ? ( <>Added ride credit
- {(activity as any).rides && ( + {activity.rides && (- Ridden {(activity as any).ride_count} times + Ridden {activity.ride_count} times
)} > - )} + ) : null}