mirror of
https://github.com/pacnpal/thrilltrack-explorer.git
synced 2025-12-28 12:06:58 -05:00
Compare commits
3 Commits
5a2c72ecd6
...
07420a67bf
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
07420a67bf | ||
|
|
9e8e8719b4 | ||
|
|
f6c409fac4 |
@@ -62,7 +62,7 @@ export function ParkCard({ park }: ParkCardProps) {
|
|||||||
<div className="aspect-[3/2] bg-gradient-to-br from-primary/20 via-secondary/20 to-accent/20 flex items-center justify-center relative">
|
<div className="aspect-[3/2] bg-gradient-to-br from-primary/20 via-secondary/20 to-accent/20 flex items-center justify-center relative">
|
||||||
{(park.card_image_url || park.card_image_id) ? (
|
{(park.card_image_url || park.card_image_id) ? (
|
||||||
<img
|
<img
|
||||||
src={park.card_image_url || getCloudflareImageUrl(park.card_image_id, 'card')}
|
src={park.card_image_url || getCloudflareImageUrl(park.card_image_id || '', 'card')}
|
||||||
srcSet={park.card_image_id ? `
|
srcSet={park.card_image_id ? `
|
||||||
${getCloudflareImageUrl(park.card_image_id, 'cardthumb')} 600w,
|
${getCloudflareImageUrl(park.card_image_id, 'cardthumb')} 600w,
|
||||||
${getCloudflareImageUrl(park.card_image_id, 'card')} 1200w
|
${getCloudflareImageUrl(park.card_image_id, 'card')} 1200w
|
||||||
@@ -111,7 +111,7 @@ export function ParkCard({ park }: ParkCardProps) {
|
|||||||
{/* Stats & Rating */}
|
{/* Stats & Rating */}
|
||||||
<div className="flex items-center justify-between text-sm">
|
<div className="flex items-center justify-between text-sm">
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
{park.ride_count > 0 && (
|
{(park.ride_count != null && park.ride_count > 0) && (
|
||||||
<div className="flex items-center gap-1">
|
<div className="flex items-center gap-1">
|
||||||
<FerrisWheel className="w-4 h-4 text-primary/70 flex-shrink-0" />
|
<FerrisWheel className="w-4 h-4 text-primary/70 flex-shrink-0" />
|
||||||
<span className="font-medium">{park.ride_count}</span>
|
<span className="font-medium">{park.ride_count}</span>
|
||||||
@@ -120,11 +120,11 @@ export function ParkCard({ park }: ParkCardProps) {
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{park.average_rating > 0 && (
|
{(park.average_rating != null && park.average_rating > 0) && (
|
||||||
<div className="inline-flex items-center gap-1">
|
<div className="inline-flex items-center gap-1">
|
||||||
<Star className="w-4 h-4 fill-yellow-500 text-yellow-500" />
|
<Star className="w-4 h-4 fill-yellow-500 text-yellow-500" />
|
||||||
<span className="font-semibold">{park.average_rating.toFixed(1)}</span>
|
<span className="font-semibold">{park.average_rating.toFixed(1)}</span>
|
||||||
{park.review_count > 0 && (
|
{(park.review_count != null && park.review_count > 0) && (
|
||||||
<span className="text-muted-foreground">({park.review_count})</span>
|
<span className="text-muted-foreground">({park.review_count})</span>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -65,12 +65,12 @@ export function ParkFilters({ filters, onFiltersChange, parks }: ParkFiltersProp
|
|||||||
}, [locations]);
|
}, [locations]);
|
||||||
|
|
||||||
const stateOptions: MultiSelectOption[] = useMemo(() => {
|
const stateOptions: MultiSelectOption[] = useMemo(() => {
|
||||||
const states = new Set(locations?.map(l => l.state_province).filter(Boolean) || []);
|
const states = new Set(locations?.map(l => l.state_province).filter((s): s is string => s != null) || []);
|
||||||
return Array.from(states).sort().map(s => ({ label: s, value: s }));
|
return Array.from(states).sort().map(s => ({ label: s, value: s }));
|
||||||
}, [locations]);
|
}, [locations]);
|
||||||
|
|
||||||
const cityOptions: MultiSelectOption[] = useMemo(() => {
|
const cityOptions: MultiSelectOption[] = useMemo(() => {
|
||||||
const cities = new Set(locations?.map(l => l.city).filter(Boolean) || []);
|
const cities = new Set(locations?.map(l => l.city).filter((c): c is string => c != null) || []);
|
||||||
return Array.from(cities).sort().map(c => ({ label: c, value: c }));
|
return Array.from(cities).sort().map(c => ({ label: c, value: c }));
|
||||||
}, [locations]);
|
}, [locations]);
|
||||||
|
|
||||||
|
|||||||
@@ -106,7 +106,7 @@ export function ParkListView({ parks, onParkClick }: ParkListViewProps) {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Rating */}
|
{/* Rating */}
|
||||||
{park.average_rating > 0 && (
|
{(park.average_rating != null && park.average_rating > 0) && (
|
||||||
<div className="flex items-center gap-1.5 ml-4 flex-shrink-0 bg-yellow-400/10 px-3 py-1.5 rounded-full group-hover:bg-yellow-400/20 transition-colors duration-300">
|
<div className="flex items-center gap-1.5 ml-4 flex-shrink-0 bg-yellow-400/10 px-3 py-1.5 rounded-full group-hover:bg-yellow-400/20 transition-colors duration-300">
|
||||||
<Star className="w-4 h-4 fill-yellow-400 text-yellow-400" />
|
<Star className="w-4 h-4 fill-yellow-400 text-yellow-400" />
|
||||||
<span className="font-semibold text-foreground">{park.average_rating.toFixed(1)}</span>
|
<span className="font-semibold text-foreground">{park.average_rating.toFixed(1)}</span>
|
||||||
@@ -149,7 +149,7 @@ export function ParkListView({ parks, onParkClick }: ParkListViewProps) {
|
|||||||
<span className="text-accent font-semibold">{park.coaster_count || 0}</span>
|
<span className="text-accent font-semibold">{park.coaster_count || 0}</span>
|
||||||
<span className="text-muted-foreground text-xs">coasters</span>
|
<span className="text-muted-foreground text-xs">coasters</span>
|
||||||
</div>
|
</div>
|
||||||
{park.review_count > 0 && (
|
{(park.review_count != null && park.review_count > 0) && (
|
||||||
<div className="flex items-center gap-1.5 group/stat">
|
<div className="flex items-center gap-1.5 group/stat">
|
||||||
<Users className="w-3.5 h-3.5 text-muted-foreground group-hover/stat:text-foreground transition-colors duration-300" />
|
<Users className="w-3.5 h-3.5 text-muted-foreground group-hover/stat:text-foreground transition-colors duration-300" />
|
||||||
<span className="text-muted-foreground text-xs">{park.review_count} reviews</span>
|
<span className="text-muted-foreground text-xs">{park.review_count} reviews</span>
|
||||||
|
|||||||
@@ -67,10 +67,11 @@ export function BlockedUsers() {
|
|||||||
const blockedUsersWithProfiles = blocks.map(block => ({
|
const blockedUsersWithProfiles = blocks.map(block => ({
|
||||||
...block,
|
...block,
|
||||||
blocker_id: user.id,
|
blocker_id: user.id,
|
||||||
blocked_profile: profiles?.find(p => p.user_id === block.blocked_id)
|
blocked_profile: profiles?.find(p => p.user_id === block.blocked_id),
|
||||||
|
reason: block.reason || undefined
|
||||||
}));
|
}));
|
||||||
|
|
||||||
setBlockedUsers(blockedUsersWithProfiles);
|
setBlockedUsers(blockedUsersWithProfiles as UserBlock[]);
|
||||||
|
|
||||||
logger.info('Blocked users fetched successfully', {
|
logger.info('Blocked users fetched successfully', {
|
||||||
userId: user.id,
|
userId: user.id,
|
||||||
|
|||||||
@@ -134,7 +134,7 @@ export function RideCreditsManager({ userId }: RideCreditsManagerProps) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
setCredits(processedData);
|
setCredits(processedData as UserRideCredit[]);
|
||||||
} catch (error: unknown) {
|
} catch (error: unknown) {
|
||||||
handleError(error, {
|
handleError(error, {
|
||||||
action: 'Fetch Ride Credits',
|
action: 'Fetch Ride Credits',
|
||||||
|
|||||||
@@ -176,8 +176,8 @@ export function ReviewForm({
|
|||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label>Visit Date</Label>
|
<Label>Visit Date</Label>
|
||||||
<DatePicker
|
<DatePicker
|
||||||
date={watch('visit_date') ? parseDateOnly(watch('visit_date')) : undefined}
|
date={watch('visit_date') ? parseDateOnly(watch('visit_date') || '') : undefined}
|
||||||
onSelect={(date) => setValue('visit_date', date ? toDateOnly(date) : undefined)}
|
onSelect={(date) => setValue('visit_date', date ? toDateOnly(date) : '')}
|
||||||
placeholder="When did you visit?"
|
placeholder="When did you visit?"
|
||||||
disableFuture={true}
|
disableFuture={true}
|
||||||
fromYear={1950}
|
fromYear={1950}
|
||||||
|
|||||||
@@ -67,7 +67,7 @@ export function RideCard({ ride, showParkName = true, className, parkSlug }: Rid
|
|||||||
<div className="aspect-[3/2] bg-gradient-to-br from-primary/20 via-secondary/20 to-accent/20 flex items-center justify-center relative">
|
<div className="aspect-[3/2] bg-gradient-to-br from-primary/20 via-secondary/20 to-accent/20 flex items-center justify-center relative">
|
||||||
{(ride.card_image_url || ride.card_image_id || ride.image_url) ? (
|
{(ride.card_image_url || ride.card_image_id || ride.image_url) ? (
|
||||||
<img
|
<img
|
||||||
src={ride.card_image_url || getCloudflareImageUrl(ride.card_image_id, 'card') || ride.image_url}
|
src={ride.card_image_url || getCloudflareImageUrl(ride.card_image_id || '', 'card') || ride.image_url || ''}
|
||||||
srcSet={ride.card_image_id ? `
|
srcSet={ride.card_image_id ? `
|
||||||
${getCloudflareImageUrl(ride.card_image_id, 'cardthumb')} 600w,
|
${getCloudflareImageUrl(ride.card_image_id, 'cardthumb')} 600w,
|
||||||
${getCloudflareImageUrl(ride.card_image_id, 'card')} 1200w
|
${getCloudflareImageUrl(ride.card_image_id, 'card')} 1200w
|
||||||
@@ -122,29 +122,29 @@ export function RideCard({ ride, showParkName = true, className, parkSlug }: Rid
|
|||||||
{/* Stats & Rating */}
|
{/* Stats & Rating */}
|
||||||
<div className="flex flex-col gap-1.5">
|
<div className="flex flex-col gap-1.5">
|
||||||
{/* Stats Row */}
|
{/* Stats Row */}
|
||||||
{(Number(ride.max_speed_kmh) > 0 || Number(ride.max_height_meters) > 0 || Number(ride.duration_seconds) > 0) && (
|
{(Number(ride.max_speed_kmh || 0) > 0 || Number(ride.max_height_meters || 0) > 0 || Number(ride.duration_seconds || 0) > 0) && (
|
||||||
<div className="flex items-center gap-3 flex-wrap text-sm">
|
<div className="flex items-center gap-3 flex-wrap text-sm">
|
||||||
{Number(ride.max_speed_kmh) > 0 && (
|
{Number(ride.max_speed_kmh || 0) > 0 && (
|
||||||
<div className="flex items-center gap-1 min-w-0">
|
<div className="flex items-center gap-1 min-w-0">
|
||||||
<Zap className="w-4 h-4 text-primary/70 flex-shrink-0" />
|
<Zap className="w-4 h-4 text-primary/70 flex-shrink-0" />
|
||||||
<span className="font-medium whitespace-nowrap">
|
<span className="font-medium whitespace-nowrap">
|
||||||
<MeasurementDisplay value={ride.max_speed_kmh} type="speed" />
|
<MeasurementDisplay value={ride.max_speed_kmh || 0} type="speed" />
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{Number(ride.max_height_meters) > 0 && (
|
{Number(ride.max_height_meters || 0) > 0 && (
|
||||||
<div className="flex items-center gap-1 min-w-0">
|
<div className="flex items-center gap-1 min-w-0">
|
||||||
<ArrowUp className="w-4 h-4 text-accent/70 flex-shrink-0" />
|
<ArrowUp className="w-4 h-4 text-accent/70 flex-shrink-0" />
|
||||||
<span className="font-medium whitespace-nowrap">
|
<span className="font-medium whitespace-nowrap">
|
||||||
<MeasurementDisplay value={ride.max_height_meters} type="distance" />
|
<MeasurementDisplay value={ride.max_height_meters || 0} type="distance" />
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{Number(ride.duration_seconds) > 0 && (
|
{Number(ride.duration_seconds || 0) > 0 && (
|
||||||
<div className="flex items-center gap-1 min-w-0">
|
<div className="flex items-center gap-1 min-w-0">
|
||||||
<Clock className="w-4 h-4 text-secondary/70 flex-shrink-0" />
|
<Clock className="w-4 h-4 text-secondary/70 flex-shrink-0" />
|
||||||
<span className="font-medium whitespace-nowrap">
|
<span className="font-medium whitespace-nowrap">
|
||||||
{Math.floor(ride.duration_seconds / 60)}<span className="text-muted-foreground text-xs ml-0.5">min</span>
|
{Math.floor((ride.duration_seconds || 0) / 60)}<span className="text-muted-foreground text-xs ml-0.5">min</span>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@@ -152,11 +152,11 @@ export function RideCard({ ride, showParkName = true, className, parkSlug }: Rid
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Rating */}
|
{/* Rating */}
|
||||||
{ride.average_rating > 0 && (
|
{(ride.average_rating != null && ride.average_rating > 0) && (
|
||||||
<div className="inline-flex items-center gap-1 text-sm">
|
<div className="inline-flex items-center gap-1 text-sm">
|
||||||
<Star className="w-4 h-4 fill-yellow-500 text-yellow-500" />
|
<Star className="w-4 h-4 fill-yellow-500 text-yellow-500" />
|
||||||
<span className="font-semibold text-foreground">{ride.average_rating.toFixed(1)}</span>
|
<span className="font-semibold text-foreground">{ride.average_rating.toFixed(1)}</span>
|
||||||
{ride.review_count > 0 && (
|
{(ride.review_count != null && ride.review_count > 0) && (
|
||||||
<span className="text-muted-foreground">({ride.review_count})</span>
|
<span className="text-muted-foreground">({ride.review_count})</span>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -145,12 +145,12 @@ export function RideFilters({ filters, onFiltersChange, rides }: RideFiltersProp
|
|||||||
}, [locations]);
|
}, [locations]);
|
||||||
|
|
||||||
const stateOptions: MultiSelectOption[] = useMemo(() => {
|
const stateOptions: MultiSelectOption[] = useMemo(() => {
|
||||||
const states = new Set(locations?.map(l => l.state_province).filter(Boolean) || []);
|
const states = new Set(locations?.map(l => l.state_province).filter((s): s is string => s != null) || []);
|
||||||
return Array.from(states).sort().map(s => ({ label: s, value: s }));
|
return Array.from(states).sort().map(s => ({ label: s, value: s }));
|
||||||
}, [locations]);
|
}, [locations]);
|
||||||
|
|
||||||
const cityOptions: MultiSelectOption[] = useMemo(() => {
|
const cityOptions: MultiSelectOption[] = useMemo(() => {
|
||||||
const cities = new Set(locations?.map(l => l.city).filter(Boolean) || []);
|
const cities = new Set(locations?.map(l => l.city).filter((c): c is string => c != null) || []);
|
||||||
return Array.from(cities).sort().map(c => ({ label: c, value: c }));
|
return Array.from(cities).sort().map(c => ({ label: c, value: c }));
|
||||||
}, [locations]);
|
}, [locations]);
|
||||||
|
|
||||||
|
|||||||
@@ -101,7 +101,7 @@ export function RideListView({ rides, onRideClick }: RideListViewProps) {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Rating */}
|
{/* Rating */}
|
||||||
{ride.average_rating > 0 && (
|
{(ride.average_rating != null && ride.average_rating > 0) && (
|
||||||
<div className="flex items-center gap-1.5 ml-4 flex-shrink-0 bg-yellow-400/10 px-3 py-1.5 rounded-full group-hover:bg-yellow-400/20 transition-colors duration-300">
|
<div className="flex items-center gap-1.5 ml-4 flex-shrink-0 bg-yellow-400/10 px-3 py-1.5 rounded-full group-hover:bg-yellow-400/20 transition-colors duration-300">
|
||||||
<Star className="w-4 h-4 fill-yellow-400 text-yellow-400" />
|
<Star className="w-4 h-4 fill-yellow-400 text-yellow-400" />
|
||||||
<span className="font-semibold text-foreground">{ride.average_rating.toFixed(1)}</span>
|
<span className="font-semibold text-foreground">{ride.average_rating.toFixed(1)}</span>
|
||||||
|
|||||||
@@ -17,14 +17,14 @@ interface SimilarRide {
|
|||||||
name: string;
|
name: string;
|
||||||
slug: string;
|
slug: string;
|
||||||
image_url: string | null;
|
image_url: string | null;
|
||||||
average_rating: number;
|
average_rating: number | null;
|
||||||
status: string;
|
status: string;
|
||||||
category: string;
|
category: string;
|
||||||
description: string | null;
|
description: string | null;
|
||||||
max_speed_kmh: number | null;
|
max_speed_kmh: number | null;
|
||||||
max_height_meters: number | null;
|
max_height_meters: number | null;
|
||||||
duration_seconds: number | null;
|
duration_seconds: number | null;
|
||||||
review_count: number;
|
review_count: number | null;
|
||||||
park: {
|
park: {
|
||||||
name: string;
|
name: string;
|
||||||
slug: string;
|
slug: string;
|
||||||
@@ -61,7 +61,7 @@ export function SimilarRides({ currentRideId, parkId, parkSlug, category }: Simi
|
|||||||
.limit(4);
|
.limit(4);
|
||||||
|
|
||||||
if (!error && data) {
|
if (!error && data) {
|
||||||
setRides(data);
|
setRides(data as SimilarRide[]);
|
||||||
}
|
}
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -102,7 +102,7 @@ export function SearchResults({ query, onClose }: SearchResultsProps) {
|
|||||||
const getResultRating = (result: SearchResult) => {
|
const getResultRating = (result: SearchResult) => {
|
||||||
if (result.type === 'park' || result.type === 'ride') {
|
if (result.type === 'park' || result.type === 'ride') {
|
||||||
const data = result.data as Park | Ride;
|
const data = result.data as Park | Ride;
|
||||||
return data.average_rating > 0 ? data.average_rating : null;
|
return (data.average_rating != null && data.average_rating > 0) ? data.average_rating : null;
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -97,7 +97,7 @@ export function AccountProfileTab() {
|
|||||||
.maybeSingle();
|
.maybeSingle();
|
||||||
|
|
||||||
if (!error && data) {
|
if (!error && data) {
|
||||||
setDeletionRequest(data);
|
setDeletionRequest(data as AccountDeletionRequest);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -112,8 +112,8 @@ export function AccountProfileTab() {
|
|||||||
// Use the update_profile RPC function with server-side validation
|
// Use the update_profile RPC function with server-side validation
|
||||||
const { data: result, error } = await supabase.rpc('update_profile', {
|
const { data: result, error } = await supabase.rpc('update_profile', {
|
||||||
p_username: data.username,
|
p_username: data.username,
|
||||||
p_display_name: data.display_name || null,
|
p_display_name: data.display_name || '',
|
||||||
p_bio: data.bio || null
|
p_bio: data.bio || ''
|
||||||
});
|
});
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
@@ -219,7 +219,7 @@ export function AccountProfileTab() {
|
|||||||
.maybeSingle();
|
.maybeSingle();
|
||||||
|
|
||||||
if (!error && data) {
|
if (!error && data) {
|
||||||
setDeletionRequest(data);
|
setDeletionRequest(data as AccountDeletionRequest);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -253,7 +253,7 @@ export function EmailChangeDialog({ open, onOpenChange, currentEmail, userId }:
|
|||||||
metadata: {
|
metadata: {
|
||||||
currentEmail,
|
currentEmail,
|
||||||
newEmail: data.newEmail,
|
newEmail: data.newEmail,
|
||||||
errorType: error.constructor.name
|
errorType: error instanceof Error ? error.constructor.name : 'Unknown'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -78,7 +78,7 @@ const SidebarProvider = React.forwardRef<
|
|||||||
state,
|
state,
|
||||||
open,
|
open,
|
||||||
setOpen,
|
setOpen,
|
||||||
isMobile,
|
isMobile: isMobile ?? false,
|
||||||
openMobile,
|
openMobile,
|
||||||
setOpenMobile,
|
setOpenMobile,
|
||||||
toggleSidebar,
|
toggleSidebar,
|
||||||
|
|||||||
@@ -86,7 +86,7 @@ export function EntityMultiImageUploader({
|
|||||||
.from('photos')
|
.from('photos')
|
||||||
.select('id, cloudflare_image_url, cloudflare_image_id, caption, title')
|
.select('id, cloudflare_image_url, cloudflare_image_id, caption, title')
|
||||||
.eq('entity_type', entityType)
|
.eq('entity_type', entityType)
|
||||||
.eq('entity_id', entityId)
|
.eq('entity_id', entityId || '')
|
||||||
.order('created_at', { ascending: false });
|
.order('created_at', { ascending: false });
|
||||||
|
|
||||||
if (error) throw error;
|
if (error) throw error;
|
||||||
@@ -314,7 +314,7 @@ export function EntityMultiImageUploader({
|
|||||||
|
|
||||||
const existingCount = value.uploaded.filter(img => !img.isLocal).length;
|
const existingCount = value.uploaded.filter(img => !img.isLocal).length;
|
||||||
const newCount = value.uploaded.filter(img => img.isLocal).length;
|
const newCount = value.uploaded.filter(img => img.isLocal).length;
|
||||||
const parts = [];
|
const parts: string[] = [];
|
||||||
|
|
||||||
if (mode === 'edit' && existingCount > 0) {
|
if (mode === 'edit' && existingCount > 0) {
|
||||||
parts.push(`${existingCount} existing photo${existingCount !== 1 ? 's' : ''}`);
|
parts.push(`${existingCount} existing photo${existingCount !== 1 ? 's' : ''}`);
|
||||||
|
|||||||
@@ -68,7 +68,7 @@ export function EntityPhotoGallery({
|
|||||||
url: photo.cloudflare_image_url,
|
url: photo.cloudflare_image_url,
|
||||||
caption: photo.caption || undefined,
|
caption: photo.caption || undefined,
|
||||||
title: photo.title || undefined,
|
title: photo.title || undefined,
|
||||||
user_id: photo.submitted_by,
|
user_id: photo.submitted_by || '',
|
||||||
created_at: photo.created_at,
|
created_at: photo.created_at,
|
||||||
})) || [];
|
})) || [];
|
||||||
|
|
||||||
@@ -148,7 +148,7 @@ export function EntityPhotoGallery({
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col sm:flex-row gap-2 w-full sm:w-auto">
|
<div className="flex flex-col sm:flex-row gap-2 w-full sm:w-auto">
|
||||||
{isModerator && photos.length > 0 && (
|
{isModerator() && photos.length > 0 && (
|
||||||
<Button onClick={() => setShowManagement(true)} variant="outline" className="gap-2 w-full sm:w-auto">
|
<Button onClick={() => setShowManagement(true)} variant="outline" className="gap-2 w-full sm:w-auto">
|
||||||
<Settings className="w-4 h-4" />
|
<Settings className="w-4 h-4" />
|
||||||
<span className="sm:inline">Manage</span>
|
<span className="sm:inline">Manage</span>
|
||||||
|
|||||||
@@ -78,7 +78,11 @@ export function PhotoManagementDialog({
|
|||||||
.order('order_index', { ascending: true });
|
.order('order_index', { ascending: true });
|
||||||
|
|
||||||
if (error) throw error;
|
if (error) throw error;
|
||||||
setPhotos(data || []);
|
setPhotos((data || []).map(p => ({
|
||||||
|
...p,
|
||||||
|
order_index: p.order_index ?? 0,
|
||||||
|
is_featured: p.is_featured ?? false
|
||||||
|
})) as Photo[]);
|
||||||
} catch (error: unknown) {
|
} catch (error: unknown) {
|
||||||
toast({
|
toast({
|
||||||
title: 'Error',
|
title: 'Error',
|
||||||
|
|||||||
@@ -110,6 +110,9 @@ export function UppyPhotoSubmissionUpload({
|
|||||||
const { uploadURL, id: cloudflareId } = uploadData;
|
const { uploadURL, id: cloudflareId } = uploadData;
|
||||||
|
|
||||||
// Upload file to Cloudflare
|
// Upload file to Cloudflare
|
||||||
|
if (!photo.file) {
|
||||||
|
throw new Error('Photo file is missing');
|
||||||
|
}
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
formData.append('file', photo.file);
|
formData.append('file', photo.file);
|
||||||
|
|
||||||
|
|||||||
@@ -133,7 +133,7 @@ export function useEntityCache() {
|
|||||||
const uncachedIds = getUncachedIds(type, ids);
|
const uncachedIds = getUncachedIds(type, ids);
|
||||||
if (uncachedIds.length === 0) {
|
if (uncachedIds.length === 0) {
|
||||||
// All entities are cached, return them
|
// All entities are cached, return them
|
||||||
return ids.map(id => getCached(type, id)).filter(Boolean);
|
return ids.map(id => getCached(type, id)).filter((item): item is EntityTypeMap[T] => item !== undefined);
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -316,10 +316,12 @@ export function useModerationActions(config: ModerationActionsConfig): Moderatio
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
onSuccess: (data) => {
|
onSuccess: (data) => {
|
||||||
|
if (data) {
|
||||||
toast({
|
toast({
|
||||||
title: `Content ${data.action}`,
|
title: `Content ${data.action}`,
|
||||||
description: `The ${data.item.type} has been ${data.action}`,
|
description: `The ${data.item.type} has been ${data.action}`,
|
||||||
});
|
});
|
||||||
|
}
|
||||||
},
|
},
|
||||||
onSettled: () => {
|
onSettled: () => {
|
||||||
// Always refetch to ensure consistency
|
// Always refetch to ensure consistency
|
||||||
|
|||||||
@@ -113,12 +113,21 @@ export function useProfileCache() {
|
|||||||
|
|
||||||
// Cache the fetched profiles
|
// Cache the fetched profiles
|
||||||
if (data) {
|
if (data) {
|
||||||
data.forEach((profile: CachedProfile) => {
|
data.forEach((profile) => {
|
||||||
setCached(profile.user_id, profile);
|
const cachedProfile: CachedProfile = {
|
||||||
|
...profile,
|
||||||
|
display_name: profile.display_name || undefined,
|
||||||
|
avatar_url: profile.avatar_url || undefined
|
||||||
|
};
|
||||||
|
setCached(profile.user_id, cachedProfile);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return data || [];
|
return (data || []).map(profile => ({
|
||||||
|
...profile,
|
||||||
|
display_name: profile.display_name || undefined,
|
||||||
|
avatar_url: profile.avatar_url || undefined
|
||||||
|
}));
|
||||||
} catch (error: unknown) {
|
} catch (error: unknown) {
|
||||||
logger.error('Failed to bulk fetch profiles:', { error: getErrorMessage(error) });
|
logger.error('Failed to bulk fetch profiles:', { error: getErrorMessage(error) });
|
||||||
return [];
|
return [];
|
||||||
|
|||||||
@@ -64,13 +64,13 @@ export function useStatesProvinces(country?: string) {
|
|||||||
const { data, error } = await supabase
|
const { data, error } = await supabase
|
||||||
.from('locations')
|
.from('locations')
|
||||||
.select('state_province')
|
.select('state_province')
|
||||||
.eq('country', country)
|
.eq('country', country || '')
|
||||||
.not('state_province', 'is', null);
|
.not('state_province', 'is', null);
|
||||||
|
|
||||||
if (error) throw error;
|
if (error) throw error;
|
||||||
|
|
||||||
const uniqueStates = Array.from(
|
const uniqueStates = Array.from(
|
||||||
new Set(data?.map(item => item.state_province) || [])
|
new Set(data?.map(item => item.state_province).filter((s): s is string => s != null) || [])
|
||||||
).sort();
|
).sort();
|
||||||
|
|
||||||
setStatesProvinces(
|
setStatesProvinces(
|
||||||
@@ -153,7 +153,7 @@ export function useRideModels(manufacturerId?: string) {
|
|||||||
const { data, error } = await supabase
|
const { data, error } = await supabase
|
||||||
.from('ride_models')
|
.from('ride_models')
|
||||||
.select('id, name')
|
.select('id, name')
|
||||||
.eq('manufacturer_id', manufacturerId)
|
.eq('manufacturer_id', manufacturerId || '')
|
||||||
.order('name');
|
.order('name');
|
||||||
|
|
||||||
if (error) throw error;
|
if (error) throw error;
|
||||||
@@ -198,7 +198,7 @@ export function useCompanyHeadquarters() {
|
|||||||
if (error) throw error;
|
if (error) throw error;
|
||||||
|
|
||||||
const uniqueHeadquarters = Array.from(
|
const uniqueHeadquarters = Array.from(
|
||||||
new Set(data?.map(item => item.headquarters_location) || [])
|
new Set(data?.map(item => item.headquarters_location).filter((hq): hq is string => hq != null) || [])
|
||||||
).sort();
|
).sort();
|
||||||
|
|
||||||
setHeadquarters(
|
setHeadquarters(
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ export function useProfile(userId: string | undefined) {
|
|||||||
// Use get_filtered_profile RPC for privacy-aware field filtering
|
// Use get_filtered_profile RPC for privacy-aware field filtering
|
||||||
const { data, error } = await supabase.rpc('get_filtered_profile', {
|
const { data, error } = await supabase.rpc('get_filtered_profile', {
|
||||||
_profile_user_id: userId,
|
_profile_user_id: userId,
|
||||||
_viewer_id: viewerId
|
_viewer_id: viewerId || ''
|
||||||
});
|
});
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
|
|||||||
@@ -99,8 +99,8 @@ export function useSearch(options: UseSearchOptions = {}) {
|
|||||||
type: 'park',
|
type: 'park',
|
||||||
title: park.name,
|
title: park.name,
|
||||||
subtitle: [park.location?.city, park.location?.state_province, park.location?.country].filter(Boolean).join(', '),
|
subtitle: [park.location?.city, park.location?.state_province, park.location?.country].filter(Boolean).join(', '),
|
||||||
image: park.banner_image_url || park.card_image_url,
|
image: park.banner_image_url || park.card_image_url || undefined,
|
||||||
rating: park.average_rating,
|
rating: park.average_rating ?? undefined,
|
||||||
slug: park.slug,
|
slug: park.slug,
|
||||||
data: park
|
data: park
|
||||||
});
|
});
|
||||||
@@ -125,8 +125,8 @@ export function useSearch(options: UseSearchOptions = {}) {
|
|||||||
type: 'ride',
|
type: 'ride',
|
||||||
title: ride.name,
|
title: ride.name,
|
||||||
subtitle: `at ${ride.park?.name || 'Unknown Park'}`,
|
subtitle: `at ${ride.park?.name || 'Unknown Park'}`,
|
||||||
image: ride.image_url,
|
image: ride.image_url || undefined,
|
||||||
rating: ride.average_rating,
|
rating: ride.average_rating ?? undefined,
|
||||||
slug: ride.slug,
|
slug: ride.slug,
|
||||||
data: ride
|
data: ride
|
||||||
});
|
});
|
||||||
@@ -147,7 +147,7 @@ export function useSearch(options: UseSearchOptions = {}) {
|
|||||||
type: 'company',
|
type: 'company',
|
||||||
title: company.name,
|
title: company.name,
|
||||||
subtitle: company.company_type?.replace('_', ' ').replace(/\b\w/g, l => l.toUpperCase()) || 'Company',
|
subtitle: company.company_type?.replace('_', ' ').replace(/\b\w/g, l => l.toUpperCase()) || 'Company',
|
||||||
image: company.logo_url,
|
image: company.logo_url || undefined,
|
||||||
slug: company.slug,
|
slug: company.slug,
|
||||||
data: company
|
data: company
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -29,8 +29,8 @@ export function useVersionComparison(
|
|||||||
// Use the database function to get diff
|
// Use the database function to get diff
|
||||||
const { data, error: rpcError } = await supabase.rpc('get_version_diff', {
|
const { data, error: rpcError } = await supabase.rpc('get_version_diff', {
|
||||||
p_entity_type: entityType,
|
p_entity_type: entityType,
|
||||||
p_from_version_id: fromVersionId,
|
p_from_version_id: fromVersionId || '',
|
||||||
p_to_version_id: toVersionId
|
p_to_version_id: toVersionId || ''
|
||||||
});
|
});
|
||||||
|
|
||||||
if (rpcError) throw rpcError;
|
if (rpcError) throw rpcError;
|
||||||
|
|||||||
@@ -564,11 +564,11 @@ export async function submitParkUpdate(
|
|||||||
submission_id: submissionData.id,
|
submission_id: submissionData.id,
|
||||||
item_type: 'park',
|
item_type: 'park',
|
||||||
action_type: 'edit',
|
action_type: 'edit',
|
||||||
item_data: {
|
item_data: JSON.parse(JSON.stringify({
|
||||||
...extractChangedFields(data, existingPark),
|
...extractChangedFields(data, existingPark as any),
|
||||||
park_id: parkId, // Always include for relational integrity
|
park_id: parkId, // Always include for relational integrity
|
||||||
images: processedImages as unknown as Json
|
images: processedImages
|
||||||
},
|
})) as Json,
|
||||||
original_data: JSON.parse(JSON.stringify(existingPark)),
|
original_data: JSON.parse(JSON.stringify(existingPark)),
|
||||||
status: 'pending' as const,
|
status: 'pending' as const,
|
||||||
order_index: 0
|
order_index: 0
|
||||||
@@ -848,7 +848,7 @@ export async function submitRideUpdate(
|
|||||||
item_type: 'ride',
|
item_type: 'ride',
|
||||||
action_type: 'edit',
|
action_type: 'edit',
|
||||||
item_data: {
|
item_data: {
|
||||||
...extractChangedFields(data, existingRide),
|
...extractChangedFields(data, existingRide as any),
|
||||||
ride_id: rideId, // Always include for relational integrity
|
ride_id: rideId, // Always include for relational integrity
|
||||||
images: processedImages as unknown as Json
|
images: processedImages as unknown as Json
|
||||||
},
|
},
|
||||||
@@ -1012,7 +1012,7 @@ export async function submitRideModelUpdate(
|
|||||||
item_type: 'ride_model',
|
item_type: 'ride_model',
|
||||||
action_type: 'edit',
|
action_type: 'edit',
|
||||||
item_data: {
|
item_data: {
|
||||||
...extractChangedFields(data, existingModel),
|
...extractChangedFields(data, existingModel as any),
|
||||||
ride_model_id: rideModelId, // Always include for relational integrity
|
ride_model_id: rideModelId, // Always include for relational integrity
|
||||||
images: processedImages as unknown as Json
|
images: processedImages as unknown as Json
|
||||||
},
|
},
|
||||||
@@ -1124,7 +1124,7 @@ export async function submitManufacturerUpdate(
|
|||||||
item_type: 'manufacturer',
|
item_type: 'manufacturer',
|
||||||
action_type: 'edit',
|
action_type: 'edit',
|
||||||
item_data: {
|
item_data: {
|
||||||
...extractChangedFields(data, existingCompany as Partial<CompanyDatabaseRecord>),
|
...extractChangedFields(data, existingCompany as any),
|
||||||
company_id: companyId, // Always include for relational integrity
|
company_id: companyId, // Always include for relational integrity
|
||||||
company_type: 'manufacturer', // Always include for entity type discrimination
|
company_type: 'manufacturer', // Always include for entity type discrimination
|
||||||
images: processedImages as unknown as Json
|
images: processedImages as unknown as Json
|
||||||
@@ -1232,7 +1232,7 @@ export async function submitDesignerUpdate(
|
|||||||
item_type: 'designer',
|
item_type: 'designer',
|
||||||
action_type: 'edit',
|
action_type: 'edit',
|
||||||
item_data: {
|
item_data: {
|
||||||
...extractChangedFields(data, existingCompany as Partial<CompanyDatabaseRecord>),
|
...extractChangedFields(data, existingCompany as any),
|
||||||
company_id: companyId, // Always include for relational integrity
|
company_id: companyId, // Always include for relational integrity
|
||||||
company_type: 'designer', // Always include for entity type discrimination
|
company_type: 'designer', // Always include for entity type discrimination
|
||||||
images: processedImages as unknown as Json
|
images: processedImages as unknown as Json
|
||||||
@@ -1340,7 +1340,7 @@ export async function submitOperatorUpdate(
|
|||||||
item_type: 'operator',
|
item_type: 'operator',
|
||||||
action_type: 'edit',
|
action_type: 'edit',
|
||||||
item_data: {
|
item_data: {
|
||||||
...extractChangedFields(data, existingCompany as Partial<CompanyDatabaseRecord>),
|
...extractChangedFields(data, existingCompany as any),
|
||||||
company_id: companyId, // Always include for relational integrity
|
company_id: companyId, // Always include for relational integrity
|
||||||
company_type: 'operator', // Always include for entity type discrimination
|
company_type: 'operator', // Always include for entity type discrimination
|
||||||
images: processedImages as unknown as Json
|
images: processedImages as unknown as Json
|
||||||
@@ -1448,7 +1448,7 @@ export async function submitPropertyOwnerUpdate(
|
|||||||
item_type: 'property_owner',
|
item_type: 'property_owner',
|
||||||
action_type: 'edit',
|
action_type: 'edit',
|
||||||
item_data: {
|
item_data: {
|
||||||
...extractChangedFields(data, existingCompany as Partial<CompanyDatabaseRecord>),
|
...extractChangedFields(data, existingCompany as any),
|
||||||
company_id: companyId, // Always include for relational integrity
|
company_id: companyId, // Always include for relational integrity
|
||||||
company_type: 'property_owner', // Always include for entity type discrimination
|
company_type: 'property_owner', // Always include for entity type discrimination
|
||||||
images: processedImages as unknown as Json
|
images: processedImages as unknown as Json
|
||||||
|
|||||||
@@ -164,7 +164,7 @@ export function transformRideModelData(submissionData: RideModelSubmissionData):
|
|||||||
slug: submissionData.slug,
|
slug: submissionData.slug,
|
||||||
manufacturer_id: submissionData.manufacturer_id,
|
manufacturer_id: submissionData.manufacturer_id,
|
||||||
category: submissionData.category,
|
category: submissionData.category,
|
||||||
ride_type: submissionData.ride_type || null,
|
ride_type: (submissionData.ride_type || null) as string,
|
||||||
description: submissionData.description || null,
|
description: submissionData.description || null,
|
||||||
banner_image_url: submissionData.banner_image_url || null,
|
banner_image_url: submissionData.banner_image_url || null,
|
||||||
banner_image_id: submissionData.banner_image_id || null,
|
banner_image_id: submissionData.banner_image_id || null,
|
||||||
|
|||||||
@@ -344,7 +344,12 @@ class NotificationService {
|
|||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
|
|
||||||
return data || [];
|
return (data || []).map(t => ({
|
||||||
|
...t,
|
||||||
|
is_active: t.is_active ?? true,
|
||||||
|
description: t.description || undefined,
|
||||||
|
novu_workflow_id: t.novu_workflow_id || undefined
|
||||||
|
}));
|
||||||
} catch (error: unknown) {
|
} catch (error: unknown) {
|
||||||
logger.error('Error fetching notification templates', {
|
logger.error('Error fetching notification templates', {
|
||||||
action: 'fetch_notification_templates',
|
action: 'fetch_notification_templates',
|
||||||
|
|||||||
@@ -96,9 +96,9 @@ export function normalizePhotoSubmissionItems(
|
|||||||
id: item.id,
|
id: item.id,
|
||||||
url: item.cloudflare_image_url,
|
url: item.cloudflare_image_url,
|
||||||
filename: item.filename || `Photo ${item.order_index + 1}`,
|
filename: item.filename || `Photo ${item.order_index + 1}`,
|
||||||
caption: item.caption,
|
caption: item.caption || undefined,
|
||||||
title: item.title,
|
title: item.title || undefined,
|
||||||
date_taken: item.date_taken,
|
date_taken: item.date_taken || undefined,
|
||||||
order_index: item.order_index,
|
order_index: item.order_index,
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -115,12 +115,12 @@ async function logRequestMetadata(metadata: RequestMetadata): Promise<void> {
|
|||||||
p_method: metadata.method,
|
p_method: metadata.method,
|
||||||
p_status_code: metadata.statusCode,
|
p_status_code: metadata.statusCode,
|
||||||
p_duration_ms: metadata.duration,
|
p_duration_ms: metadata.duration,
|
||||||
p_error_type: metadata.errorType || null,
|
p_error_type: metadata.errorType ?? undefined,
|
||||||
p_error_message: metadata.errorMessage || null,
|
p_error_message: metadata.errorMessage ?? undefined,
|
||||||
p_user_agent: metadata.userAgent || null,
|
p_user_agent: metadata.userAgent ?? undefined,
|
||||||
p_client_version: metadata.clientVersion || null,
|
p_client_version: metadata.clientVersion ?? undefined,
|
||||||
p_parent_request_id: metadata.parentRequestId || null,
|
p_parent_request_id: metadata.parentRequestId ?? undefined,
|
||||||
p_trace_id: metadata.traceId || null,
|
p_trace_id: metadata.traceId ?? undefined,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
|
|||||||
@@ -74,7 +74,7 @@ export async function fetchSubmissionItems(submissionId: string): Promise<Submis
|
|||||||
* Build dependency tree for submission items
|
* Build dependency tree for submission items
|
||||||
*/
|
*/
|
||||||
export function buildDependencyTree(items: SubmissionItemWithDeps[]): SubmissionItemWithDeps[] {
|
export function buildDependencyTree(items: SubmissionItemWithDeps[]): SubmissionItemWithDeps[] {
|
||||||
const itemMap = new Map(items.map(item => [item.id, { ...item, dependencies: [], dependents: [] }]));
|
const itemMap = new Map(items.map(item => [item.id, { ...item, dependencies: [] as SubmissionItemWithDeps[], dependents: [] as SubmissionItemWithDeps[] }]));
|
||||||
|
|
||||||
// Build relationships
|
// Build relationships
|
||||||
items.forEach(item => {
|
items.forEach(item => {
|
||||||
|
|||||||
Reference in New Issue
Block a user