From af00cefc1c420690235949d94ffa93430f168b27 Mon Sep 17 00:00:00 2001 From: "gpt-engineer-app[bot]" <159125892+gpt-engineer-app[bot]@users.noreply.github.com> Date: Mon, 29 Sep 2025 22:44:23 +0000 Subject: [PATCH] Refactor RideForm image handling --- src/components/admin/RideForm.tsx | 104 ++++++++++++++---- src/components/rides/RideCard.tsx | 4 +- src/integrations/supabase/types.ts | 12 ++ src/pages/RideDetail.tsx | 4 +- src/types/database.ts | 4 + ...6_edd65296-ebed-4bf2-bd28-44c8e60ba9a1.sql | 12 ++ 6 files changed, 112 insertions(+), 28 deletions(-) create mode 100644 supabase/migrations/20250929224236_edd65296-ebed-4bf2-bd28-44c8e60ba9a1.sql diff --git a/src/components/admin/RideForm.tsx b/src/components/admin/RideForm.tsx index 8f46dff4..6fc35c6f 100644 --- a/src/components/admin/RideForm.tsx +++ b/src/components/admin/RideForm.tsx @@ -8,7 +8,7 @@ import { Input } from '@/components/ui/input'; import { Textarea } from '@/components/ui/textarea'; import { Label } from '@/components/ui/label'; import { Badge } from '@/components/ui/badge'; -import { PhotoUpload } from '@/components/upload/PhotoUpload'; +import { UppyPhotoUpload } from '@/components/upload/UppyPhotoUpload'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'; import { DatePicker } from '@/components/ui/date-picker'; import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from '@/components/ui/dialog'; @@ -65,9 +65,21 @@ const rideSchema = z.object({ type RideFormData = z.infer; interface RideFormProps { - onSubmit: (data: RideFormData & { image_url?: string }) => Promise; + onSubmit: (data: RideFormData & { + image_url?: string; + banner_image_url?: string; + banner_image_id?: string; + card_image_url?: string; + card_image_id?: string; + }) => Promise; onCancel?: () => void; - initialData?: Partial; + initialData?: Partial; isEditing?: boolean; } @@ -116,7 +128,10 @@ const intensityLevels = [ export function RideForm({ onSubmit, onCancel, initialData, isEditing = false }: RideFormProps) { const [submitting, setSubmitting] = useState(false); - const [rideImage, setRideImage] = useState(initialData?.image_url || ''); + const [bannerImageUrl, setBannerImageUrl] = useState(initialData?.banner_image_url || ''); + const [bannerImageId, setBannerImageId] = useState(initialData?.banner_image_id || ''); + const [cardImageUrl, setCardImageUrl] = useState(initialData?.card_image_url || ''); + const [cardImageId, setCardImageId] = useState(initialData?.card_image_id || ''); const { preferences } = useUnitPreferences(); const measurementSystem = preferences.measurement_system; @@ -221,7 +236,10 @@ export function RideForm({ onSubmit, onCancel, initialData, isEditing = false }: drop_height_meters: data.drop_height_meters ? convertDistanceToMetric(data.drop_height_meters, measurementSystem) : undefined, - image_url: rideImage || undefined + banner_image_url: bannerImageUrl || undefined, + banner_image_id: bannerImageId || undefined, + card_image_url: cardImageUrl || undefined, + card_image_id: cardImageId || undefined }; // Build composite submission if new entities were created @@ -715,25 +733,63 @@ export function RideForm({ onSubmit, onCancel, initialData, isEditing = false }: - {/* Image */} -
- - setRideImage(urls[0] || '')} - onError={(error) => { - toast({ - title: "Upload Error", - description: error, - variant: "destructive" - }); - }} - /> -

- High-quality image of the ride (recommended: 800x600px) -

+ {/* Images */} +
+

Images

+ +
+ + { + if (urls[0]) { + setBannerImageUrl(urls[0]); + // Extract image ID from Cloudflare URL (format: https://imagedelivery.net///) + const idMatch = urls[0].match(/\/([^/]+)\/public$/); + if (idMatch) { + setBannerImageId(idMatch[1]); + } + } + }} + showPreview={true} + /> + {bannerImageUrl && ( +
+ Banner preview +
+ )} +

+ High-resolution banner image for the ride detail page (recommended: 1200x400px) +

+
+ +
+ + { + if (urls[0]) { + setCardImageUrl(urls[0]); + // Extract image ID from Cloudflare URL + const idMatch = urls[0].match(/\/([^/]+)\/public$/); + if (idMatch) { + setCardImageId(idMatch[1]); + } + } + }} + showPreview={true} + /> + {cardImageUrl && ( +
+ Card preview +
+ )} +

+ Square or rectangular image for ride cards and listings (recommended: 400x300px) +

+
{/* Form Actions */} diff --git a/src/components/rides/RideCard.tsx b/src/components/rides/RideCard.tsx index f3acb9d2..6d0b032d 100644 --- a/src/components/rides/RideCard.tsx +++ b/src/components/rides/RideCard.tsx @@ -53,9 +53,9 @@ export function RideCard({ ride, showParkName = true, className }: RideCardProps
{/* Image/Icon Section */}
- {ride.image_url ? ( + {(ride.card_image_url || ride.image_url) ? ( {ride.name} diff --git a/src/integrations/supabase/types.ts b/src/integrations/supabase/types.ts index 4ad362a3..09342141 100644 --- a/src/integrations/supabase/types.ts +++ b/src/integrations/supabase/types.ts @@ -614,7 +614,11 @@ export type Database = { Row: { age_requirement: number | null average_rating: number | null + banner_image_id: string | null + banner_image_url: string | null capacity_per_hour: number | null + card_image_id: string | null + card_image_url: string | null category: string closing_date: string | null coaster_stats: Json | null @@ -650,7 +654,11 @@ export type Database = { Insert: { age_requirement?: number | null average_rating?: number | null + banner_image_id?: string | null + banner_image_url?: string | null capacity_per_hour?: number | null + card_image_id?: string | null + card_image_url?: string | null category: string closing_date?: string | null coaster_stats?: Json | null @@ -686,7 +694,11 @@ export type Database = { Update: { age_requirement?: number | null average_rating?: number | null + banner_image_id?: string | null + banner_image_url?: string | null capacity_per_hour?: number | null + card_image_id?: string | null + card_image_url?: string | null category?: string closing_date?: string | null coaster_stats?: Json | null diff --git a/src/pages/RideDetail.tsx b/src/pages/RideDetail.tsx index d8f22e70..b5612795 100644 --- a/src/pages/RideDetail.tsx +++ b/src/pages/RideDetail.tsx @@ -168,9 +168,9 @@ export default function RideDetail() { {/* Hero Section */}
- {ride.image_url ? ( + {(ride.banner_image_url || ride.card_image_url || ride.image_url) ? ( {ride.name} diff --git a/src/types/database.ts b/src/types/database.ts index 61fd4d92..299e16fa 100644 --- a/src/types/database.ts +++ b/src/types/database.ts @@ -88,6 +88,10 @@ export interface Ride { average_rating: number; review_count: number; image_url?: string; + banner_image_url?: string; + banner_image_id?: string; + card_image_url?: string; + card_image_id?: string; // New roller coaster specific fields coaster_type?: string; seating_type?: string; diff --git a/supabase/migrations/20250929224236_edd65296-ebed-4bf2-bd28-44c8e60ba9a1.sql b/supabase/migrations/20250929224236_edd65296-ebed-4bf2-bd28-44c8e60ba9a1.sql new file mode 100644 index 00000000..05ec9f1e --- /dev/null +++ b/supabase/migrations/20250929224236_edd65296-ebed-4bf2-bd28-44c8e60ba9a1.sql @@ -0,0 +1,12 @@ +-- Add banner and card image fields to rides table +ALTER TABLE public.rides +ADD COLUMN IF NOT EXISTS banner_image_url TEXT, +ADD COLUMN IF NOT EXISTS banner_image_id TEXT, +ADD COLUMN IF NOT EXISTS card_image_url TEXT, +ADD COLUMN IF NOT EXISTS card_image_id TEXT; + +-- Add comment to document the fields +COMMENT ON COLUMN public.rides.banner_image_url IS 'Full Cloudflare URL for banner image displayed on ride detail page'; +COMMENT ON COLUMN public.rides.banner_image_id IS 'Cloudflare image ID for banner'; +COMMENT ON COLUMN public.rides.card_image_url IS 'Full Cloudflare URL for card image displayed in listings'; +COMMENT ON COLUMN public.rides.card_image_id IS 'Cloudflare image ID for card'; \ No newline at end of file