diff --git a/src/components/admin/ParkForm.tsx b/src/components/admin/ParkForm.tsx index 7f98c1a4..052cfd5f 100644 --- a/src/components/admin/ParkForm.tsx +++ b/src/components/admin/ParkForm.tsx @@ -200,6 +200,19 @@ export function ParkForm({ onSubmit, onCancel, initialData, isEditing = false }: const handleFormSubmit = async (data: ParkFormData) => { try { + // CRITICAL: Block new photo uploads on edits + if (isEditing && data.images?.uploaded) { + const hasNewPhotos = data.images.uploaded.some(img => img.isLocal); + if (hasNewPhotos) { + toast({ + variant: 'destructive', + title: 'Validation Error', + description: 'New photos cannot be added during edits. Please remove new photos or use the photo gallery.' + }); + return; + } + } + // Build composite submission if new entities were created const submissionContent: any = { park: data, diff --git a/src/components/admin/RideForm.tsx b/src/components/admin/RideForm.tsx index 16daecf9..083792dc 100644 --- a/src/components/admin/RideForm.tsx +++ b/src/components/admin/RideForm.tsx @@ -264,6 +264,19 @@ export function RideForm({ onSubmit, onCancel, initialData, isEditing = false }: const handleFormSubmit = async (data: RideFormData) => { try { + // CRITICAL: Block new photo uploads on edits + if (isEditing && data.images?.uploaded) { + const hasNewPhotos = data.images.uploaded.some(img => img.isLocal); + if (hasNewPhotos) { + toast({ + variant: 'destructive', + title: 'Validation Error', + description: 'New photos cannot be added during edits. Please remove new photos or use the photo gallery.' + }); + return; + } + } + // Validate coaster stats if (coasterStats && coasterStats.length > 0) { const statsValidation = validateCoasterStats(coasterStats); diff --git a/src/components/upload/EntityMultiImageUploader.tsx b/src/components/upload/EntityMultiImageUploader.tsx index 120b8a75..ceee256e 100644 --- a/src/components/upload/EntityMultiImageUploader.tsx +++ b/src/components/upload/EntityMultiImageUploader.tsx @@ -13,6 +13,7 @@ import { DragDropZone } from './DragDropZone'; import { supabase } from '@/integrations/supabase/client'; import { toast } from '@/hooks/use-toast'; import { Skeleton } from '@/components/ui/skeleton'; +import { Alert, AlertDescription } from '@/components/ui/alert'; import { getErrorMessage } from '@/lib/errorHandler'; import { logger } from '@/lib/logger'; @@ -49,7 +50,8 @@ export function EntityMultiImageUploader({ currentBannerUrl, currentCardUrl }: EntityMultiImageUploaderProps) { - const maxImages = mode === 'create' ? 5 : 3; + const maxImages = mode === 'create' ? 5 : 0; // No uploads allowed in edit mode + const canUpload = mode === 'create'; const [loadingPhotos, setLoadingPhotos] = useState(false); // Fetch existing photos when in edit mode @@ -119,6 +121,16 @@ export function EntityMultiImageUploader({ }; const handleFilesAdded = (files: File[]) => { + // Block uploads entirely in edit mode + if (!canUpload) { + toast({ + title: 'Upload Not Allowed', + description: 'Photos cannot be added during edits. Use the photo gallery to submit additional photos.', + variant: 'destructive', + }); + return; + } + const currentCount = value.uploaded.length; const availableSlots = maxImages - currentCount; @@ -331,8 +343,18 @@ export function EntityMultiImageUploader({ ); } - // Empty state: show large drag & drop zone + // Empty state: show large drag & drop zone (create only) or message (edit) if (value.uploaded.length === 0) { + if (mode === 'edit') { + return ( + + + No existing photos found. Photos can only be added during entity creation. Use the photo gallery to submit additional photos after creation. + + + ); + } + return (

• Right-click images to set as banner or card

• Images will be uploaded when you submit the form

- {mode === 'edit' &&

• No existing photos found for this entity

}
); } - // With images: show grid + compact upload area + // With images: show grid + compact upload area (create only) or read-only (edit) return (
+ {/* Edit mode notice */} + {mode === 'edit' && ( + + + Photos cannot be added during edits. You can reassign banner/card roles below. Use the photo gallery to submit additional photos. + + + )} + {/* Image Grid */}
{value.uploaded.map((image, index) => renderImageCard(image, index))}
- {/* Compact Upload Area */} - {value.uploaded.length < maxImages && ( + {/* Compact Upload Area - Create mode only */} + {canUpload && value.uploaded.length < maxImages && (
-

Upload Photos for {entityName}

+
+

Submit Additional Photos for {entityName}

+

Photos submitted here go through a separate review process

+
@@ -155,7 +158,7 @@ export function EntityPhotoGallery({ {user ? ( <> - Upload Photos + Submit Additional Photos ) : ( <> diff --git a/src/lib/entitySubmissionHelpers.ts b/src/lib/entitySubmissionHelpers.ts index e1fd0de9..89203759 100644 --- a/src/lib/entitySubmissionHelpers.ts +++ b/src/lib/entitySubmissionHelpers.ts @@ -531,26 +531,15 @@ export async function submitParkUpdate( if (fetchError) throw new Error(`Failed to fetch park: ${fetchError.message}`); if (!existingPark) throw new Error('Park not found'); - // Upload any pending local images first - let processedImages = data.images; - if (data.images?.uploaded && data.images.uploaded.length > 0) { - try { - const uploadedImages = await uploadPendingImages(data.images.uploaded); - processedImages = { - ...data.images, - uploaded: uploadedImages - }; - } catch (error: unknown) { - const errorMessage = getErrorMessage(error); - logger.error('Park image upload failed', { - action: 'park_update', - parkId, - error: errorMessage - }); - throw new Error(`Failed to upload images: ${errorMessage}`); - } + // CRITICAL: Block new photo uploads on edits + // Photos can only be submitted during creation or via the photo gallery + if (data.images?.uploaded && data.images.uploaded.some(img => img.isLocal)) { + throw new Error('Photo uploads are not allowed during edits. Please use the photo gallery to submit additional photos.'); } + // Only allow banner/card reassignments from existing photos + let processedImages = data.images; + // Create the main submission record const { data: submissionData, error: submissionError } = await supabase .from('content_submissions') @@ -825,26 +814,15 @@ export async function submitRideUpdate( if (fetchError) throw new Error(`Failed to fetch ride: ${fetchError.message}`); if (!existingRide) throw new Error('Ride not found'); - // Upload any pending local images first - let processedImages = data.images; - if (data.images?.uploaded && data.images.uploaded.length > 0) { - try { - const uploadedImages = await uploadPendingImages(data.images.uploaded); - processedImages = { - ...data.images, - uploaded: uploadedImages - }; - } catch (error: unknown) { - const errorMessage = getErrorMessage(error); - logger.error('Ride image upload failed', { - action: 'ride_update', - rideId, - error: errorMessage - }); - throw new Error(`Failed to upload images: ${errorMessage}`); - } + // CRITICAL: Block new photo uploads on edits + // Photos can only be submitted during creation or via the photo gallery + if (data.images?.uploaded && data.images.uploaded.some(img => img.isLocal)) { + throw new Error('Photo uploads are not allowed during edits. Please use the photo gallery to submit additional photos.'); } + // Only allow banner/card reassignments from existing photos + let processedImages = data.images; + // Create the main submission record const { data: submissionData, error: submissionError } = await supabase .from('content_submissions') @@ -1002,26 +980,13 @@ export async function submitRideModelUpdate( if (fetchError) throw new Error(`Failed to fetch ride model: ${fetchError.message}`); if (!existingModel) throw new Error('Ride model not found'); - // Upload any pending local images first - let processedImages = data.images; - if (data.images?.uploaded && data.images.uploaded.length > 0) { - try { - const uploadedImages = await uploadPendingImages(data.images.uploaded); - processedImages = { - ...data.images, - uploaded: uploadedImages - }; - } catch (error: unknown) { - const errorMessage = getErrorMessage(error); - logger.error('Ride model image upload failed', { - action: 'ride_model_update', - rideModelId, - error: errorMessage - }); - throw new Error(`Failed to upload images: ${errorMessage}`); - } + // CRITICAL: Block new photo uploads on edits + if (data.images?.uploaded && data.images.uploaded.some(img => img.isLocal)) { + throw new Error('Photo uploads are not allowed during edits. Please use the photo gallery to submit additional photos.'); } + let processedImages = data.images; + // Create the main submission record const { data: submissionData, error: submissionError } = await supabase .from('content_submissions') @@ -1132,22 +1097,13 @@ export async function submitManufacturerUpdate( if (fetchError) throw new Error(`Failed to fetch manufacturer: ${fetchError.message}`); if (!existingCompany) throw new Error('Manufacturer not found'); - let processedImages = data.images; - if (data.images?.uploaded && data.images.uploaded.length > 0) { - try { - const uploadedImages = await uploadPendingImages(data.images.uploaded); - processedImages = { ...data.images, uploaded: uploadedImages }; - } catch (error: unknown) { - const errorMessage = getErrorMessage(error); - logger.error('Company image upload failed', { - action: 'manufacturer_update', - companyId, - error: errorMessage - }); - throw new Error(`Failed to upload images: ${errorMessage}`); - } + // CRITICAL: Block new photo uploads on edits + if (data.images?.uploaded && data.images.uploaded.some(img => img.isLocal)) { + throw new Error('Photo uploads are not allowed during edits. Please use the photo gallery to submit additional photos.'); } + let processedImages = data.images; + const { data: submissionData, error: submissionError } = await supabase .from('content_submissions') .insert({ @@ -1249,22 +1205,13 @@ export async function submitDesignerUpdate( if (fetchError) throw new Error(`Failed to fetch designer: ${fetchError.message}`); if (!existingCompany) throw new Error('Designer not found'); - let processedImages = data.images; - if (data.images?.uploaded && data.images.uploaded.length > 0) { - try { - const uploadedImages = await uploadPendingImages(data.images.uploaded); - processedImages = { ...data.images, uploaded: uploadedImages }; - } catch (error: unknown) { - const errorMessage = getErrorMessage(error); - logger.error('Company image upload failed', { - action: 'designer_update', - companyId, - error: errorMessage - }); - throw new Error(`Failed to upload images: ${errorMessage}`); - } + // CRITICAL: Block new photo uploads on edits + if (data.images?.uploaded && data.images.uploaded.some(img => img.isLocal)) { + throw new Error('Photo uploads are not allowed during edits. Please use the photo gallery to submit additional photos.'); } + let processedImages = data.images; + const { data: submissionData, error: submissionError } = await supabase .from('content_submissions') .insert({ @@ -1366,22 +1313,13 @@ export async function submitOperatorUpdate( if (fetchError) throw new Error(`Failed to fetch operator: ${fetchError.message}`); if (!existingCompany) throw new Error('Operator not found'); - let processedImages = data.images; - if (data.images?.uploaded && data.images.uploaded.length > 0) { - try { - const uploadedImages = await uploadPendingImages(data.images.uploaded); - processedImages = { ...data.images, uploaded: uploadedImages }; - } catch (error: unknown) { - const errorMessage = getErrorMessage(error); - logger.error('Company image upload failed', { - action: 'operator_update', - companyId, - error: errorMessage - }); - throw new Error(`Failed to upload images: ${errorMessage}`); - } + // CRITICAL: Block new photo uploads on edits + if (data.images?.uploaded && data.images.uploaded.some(img => img.isLocal)) { + throw new Error('Photo uploads are not allowed during edits. Please use the photo gallery to submit additional photos.'); } + let processedImages = data.images; + const { data: submissionData, error: submissionError } = await supabase .from('content_submissions') .insert({ @@ -1483,22 +1421,13 @@ export async function submitPropertyOwnerUpdate( if (fetchError) throw new Error(`Failed to fetch property owner: ${fetchError.message}`); if (!existingCompany) throw new Error('Property owner not found'); - let processedImages = data.images; - if (data.images?.uploaded && data.images.uploaded.length > 0) { - try { - const uploadedImages = await uploadPendingImages(data.images.uploaded); - processedImages = { ...data.images, uploaded: uploadedImages }; - } catch (error: unknown) { - const errorMessage = getErrorMessage(error); - logger.error('Company image upload failed', { - action: 'property_owner_update', - companyId, - error: errorMessage - }); - throw new Error(`Failed to upload images: ${errorMessage}`); - } + // CRITICAL: Block new photo uploads on edits + if (data.images?.uploaded && data.images.uploaded.some(img => img.isLocal)) { + throw new Error('Photo uploads are not allowed during edits. Please use the photo gallery to submit additional photos.'); } + let processedImages = data.images; + const { data: submissionData, error: submissionError } = await supabase .from('content_submissions') .insert({