mirror of
https://github.com/pacnpal/thrilltrack-explorer.git
synced 2025-12-23 18:11:12 -05:00
feat: Complete type safety for Phase 1 & 2
This commit is contained in:
@@ -3,6 +3,7 @@ import { supabase } from '@/integrations/supabase/client';
|
||||
import { createTableQuery } from '@/lib/supabaseHelpers';
|
||||
import { logger } from '@/lib/logger';
|
||||
import { MODERATION_CONSTANTS } from '@/lib/moderation/constants';
|
||||
import type { Database } from '@/integrations/supabase/types';
|
||||
|
||||
/**
|
||||
* Entity types supported by the cache
|
||||
@@ -10,12 +11,34 @@ import { MODERATION_CONSTANTS } from '@/lib/moderation/constants';
|
||||
type EntityType = 'rides' | 'parks' | 'companies';
|
||||
|
||||
/**
|
||||
* Cache structure for entities
|
||||
* Type definitions for cached entities (can be partial)
|
||||
*/
|
||||
type Ride = Database['public']['Tables']['rides']['Row'];
|
||||
type Park = Database['public']['Tables']['parks']['Row'];
|
||||
type Company = Database['public']['Tables']['companies']['Row'];
|
||||
|
||||
/**
|
||||
* Discriminated union for all cached entity types
|
||||
*/
|
||||
type CachedEntity = Ride | Park | Company;
|
||||
|
||||
/**
|
||||
* Map entity type strings to their corresponding types
|
||||
* Cache stores partial entities with at least id and name
|
||||
*/
|
||||
interface EntityTypeMap {
|
||||
rides: Partial<Ride> & { id: string; name: string };
|
||||
parks: Partial<Park> & { id: string; name: string };
|
||||
companies: Partial<Company> & { id: string; name: string };
|
||||
}
|
||||
|
||||
/**
|
||||
* Cache structure for entities with flexible typing
|
||||
*/
|
||||
interface EntityCacheStructure {
|
||||
rides: Map<string, any>;
|
||||
parks: Map<string, any>;
|
||||
companies: Map<string, any>;
|
||||
rides: Map<string, Partial<Ride> & { id: string; name: string }>;
|
||||
parks: Map<string, Partial<Park> & { id: string; name: string }>;
|
||||
companies: Map<string, Partial<Company> & { id: string; name: string }>;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -50,10 +73,13 @@ export function useEntityCache() {
|
||||
});
|
||||
|
||||
/**
|
||||
* Get a cached entity by ID
|
||||
* Get a cached entity by ID with type safety
|
||||
*/
|
||||
const getCached = useCallback((type: EntityType, id: string): any | undefined => {
|
||||
return cacheRef.current[type].get(id);
|
||||
const getCached = useCallback(<T extends EntityType>(
|
||||
type: T,
|
||||
id: string
|
||||
): EntityTypeMap[T] | undefined => {
|
||||
return cacheRef.current[type].get(id) as EntityTypeMap[T] | undefined;
|
||||
}, []);
|
||||
|
||||
/**
|
||||
@@ -64,9 +90,13 @@ export function useEntityCache() {
|
||||
}, []);
|
||||
|
||||
/**
|
||||
* Set a cached entity with LRU eviction
|
||||
* Set a cached entity with LRU eviction and type safety
|
||||
*/
|
||||
const setCached = useCallback((type: EntityType, id: string, data: any): void => {
|
||||
const setCached = useCallback(<T extends EntityType>(
|
||||
type: T,
|
||||
id: string,
|
||||
data: EntityTypeMap[T]
|
||||
): void => {
|
||||
const cache = cacheRef.current[type];
|
||||
|
||||
// LRU eviction: remove oldest entry if cache is full
|
||||
@@ -92,10 +122,10 @@ export function useEntityCache() {
|
||||
* Bulk fetch entities from the database and cache them
|
||||
* Only fetches entities that aren't already cached
|
||||
*/
|
||||
const bulkFetch = useCallback(async (
|
||||
type: EntityType,
|
||||
const bulkFetch = useCallback(async <T extends EntityType>(
|
||||
type: T,
|
||||
ids: string[]
|
||||
): Promise<any[]> => {
|
||||
): Promise<EntityTypeMap[T][]> => {
|
||||
if (ids.length === 0) return [];
|
||||
|
||||
// Filter to only uncached IDs
|
||||
@@ -148,7 +178,7 @@ export function useEntityCache() {
|
||||
// Cache the fetched entities
|
||||
if (data) {
|
||||
data.forEach((entity: any) => {
|
||||
setCached(type, entity.id, entity);
|
||||
setCached(type, entity.id, entity as EntityTypeMap[T]);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -192,7 +192,7 @@ export function useRealtimeSubscriptions(
|
||||
} else {
|
||||
const { data: ride } = await supabase
|
||||
.from('rides')
|
||||
.select('name, park_id')
|
||||
.select('id, name, park_id')
|
||||
.eq('id', content.entity_id)
|
||||
.maybeSingle();
|
||||
|
||||
@@ -203,7 +203,7 @@ export function useRealtimeSubscriptions(
|
||||
if (ride.park_id) {
|
||||
const { data: park } = await supabase
|
||||
.from('parks')
|
||||
.select('name')
|
||||
.select('id, name')
|
||||
.eq('id', ride.park_id)
|
||||
.maybeSingle();
|
||||
|
||||
@@ -221,7 +221,7 @@ export function useRealtimeSubscriptions(
|
||||
} else {
|
||||
const { data: park } = await supabase
|
||||
.from('parks')
|
||||
.select('name')
|
||||
.select('id, name')
|
||||
.eq('id', content.entity_id)
|
||||
.maybeSingle();
|
||||
|
||||
@@ -237,7 +237,7 @@ export function useRealtimeSubscriptions(
|
||||
} else {
|
||||
const { data: company } = await supabase
|
||||
.from('companies')
|
||||
.select('name')
|
||||
.select('id, name')
|
||||
.eq('id', content.entity_id)
|
||||
.maybeSingle();
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
|
||||
import { useState, useEffect } from 'react';
|
||||
import { supabase } from '@/integrations/supabase/client';
|
||||
import { getErrorMessage } from '@/lib/errorHandler';
|
||||
import type { PhotoSubmissionItem } from '@/types/photo-submissions';
|
||||
|
||||
interface UsePhotoSubmissionItemsResult {
|
||||
@@ -61,9 +62,10 @@ export function usePhotoSubmissionItems(
|
||||
if (itemsError) throw itemsError;
|
||||
|
||||
setPhotos(data || []);
|
||||
} catch (err: any) {
|
||||
console.error('Error fetching photo submission items:', err);
|
||||
setError(err.message || 'Failed to load photos');
|
||||
} catch (error) {
|
||||
const errorMsg = getErrorMessage(error);
|
||||
console.error('Error fetching photo submission items:', errorMsg);
|
||||
setError(errorMsg);
|
||||
setPhotos([]);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
|
||||
Reference in New Issue
Block a user