mirror of
https://github.com/pacnpal/thrilltrack-explorer.git
synced 2025-12-22 16:51:13 -05:00
Fix: Block photo uploads on entity edits
This commit is contained in:
@@ -200,6 +200,19 @@ export function ParkForm({ onSubmit, onCancel, initialData, isEditing = false }:
|
|||||||
|
|
||||||
const handleFormSubmit = async (data: ParkFormData) => {
|
const handleFormSubmit = async (data: ParkFormData) => {
|
||||||
try {
|
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
|
// Build composite submission if new entities were created
|
||||||
const submissionContent: any = {
|
const submissionContent: any = {
|
||||||
park: data,
|
park: data,
|
||||||
|
|||||||
@@ -264,6 +264,19 @@ export function RideForm({ onSubmit, onCancel, initialData, isEditing = false }:
|
|||||||
|
|
||||||
const handleFormSubmit = async (data: RideFormData) => {
|
const handleFormSubmit = async (data: RideFormData) => {
|
||||||
try {
|
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
|
// Validate coaster stats
|
||||||
if (coasterStats && coasterStats.length > 0) {
|
if (coasterStats && coasterStats.length > 0) {
|
||||||
const statsValidation = validateCoasterStats(coasterStats);
|
const statsValidation = validateCoasterStats(coasterStats);
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import { DragDropZone } from './DragDropZone';
|
|||||||
import { supabase } from '@/integrations/supabase/client';
|
import { supabase } from '@/integrations/supabase/client';
|
||||||
import { toast } from '@/hooks/use-toast';
|
import { toast } from '@/hooks/use-toast';
|
||||||
import { Skeleton } from '@/components/ui/skeleton';
|
import { Skeleton } from '@/components/ui/skeleton';
|
||||||
|
import { Alert, AlertDescription } from '@/components/ui/alert';
|
||||||
import { getErrorMessage } from '@/lib/errorHandler';
|
import { getErrorMessage } from '@/lib/errorHandler';
|
||||||
import { logger } from '@/lib/logger';
|
import { logger } from '@/lib/logger';
|
||||||
|
|
||||||
@@ -49,7 +50,8 @@ export function EntityMultiImageUploader({
|
|||||||
currentBannerUrl,
|
currentBannerUrl,
|
||||||
currentCardUrl
|
currentCardUrl
|
||||||
}: EntityMultiImageUploaderProps) {
|
}: 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);
|
const [loadingPhotos, setLoadingPhotos] = useState(false);
|
||||||
|
|
||||||
// Fetch existing photos when in edit mode
|
// Fetch existing photos when in edit mode
|
||||||
@@ -119,6 +121,16 @@ export function EntityMultiImageUploader({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleFilesAdded = (files: File[]) => {
|
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 currentCount = value.uploaded.length;
|
||||||
const availableSlots = maxImages - currentCount;
|
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 (value.uploaded.length === 0) {
|
||||||
|
if (mode === 'edit') {
|
||||||
|
return (
|
||||||
|
<Alert>
|
||||||
|
<AlertDescription>
|
||||||
|
No existing photos found. Photos can only be added during entity creation. Use the photo gallery to submit additional photos after creation.
|
||||||
|
</AlertDescription>
|
||||||
|
</Alert>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<DragDropZone
|
<DragDropZone
|
||||||
@@ -344,22 +366,30 @@ export function EntityMultiImageUploader({
|
|||||||
<div className="text-sm text-muted-foreground space-y-1">
|
<div className="text-sm text-muted-foreground space-y-1">
|
||||||
<p>• Right-click images to set as banner or card</p>
|
<p>• Right-click images to set as banner or card</p>
|
||||||
<p>• Images will be uploaded when you submit the form</p>
|
<p>• Images will be uploaded when you submit the form</p>
|
||||||
{mode === 'edit' && <p>• No existing photos found for this entity</p>}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// With images: show grid + compact upload area
|
// With images: show grid + compact upload area (create only) or read-only (edit)
|
||||||
return (
|
return (
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
|
{/* Edit mode notice */}
|
||||||
|
{mode === 'edit' && (
|
||||||
|
<Alert>
|
||||||
|
<AlertDescription>
|
||||||
|
Photos cannot be added during edits. You can reassign banner/card roles below. Use the photo gallery to submit additional photos.
|
||||||
|
</AlertDescription>
|
||||||
|
</Alert>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* Image Grid */}
|
{/* Image Grid */}
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||||
{value.uploaded.map((image, index) => renderImageCard(image, index))}
|
{value.uploaded.map((image, index) => renderImageCard(image, index))}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Compact Upload Area */}
|
{/* Compact Upload Area - Create mode only */}
|
||||||
{value.uploaded.length < maxImages && (
|
{canUpload && value.uploaded.length < maxImages && (
|
||||||
<DragDropZone
|
<DragDropZone
|
||||||
onFilesAdded={handleFilesAdded}
|
onFilesAdded={handleFilesAdded}
|
||||||
maxFiles={maxImages - value.uploaded.length}
|
maxFiles={maxImages - value.uploaded.length}
|
||||||
|
|||||||
@@ -107,7 +107,10 @@ export function EntityPhotoGallery({
|
|||||||
return (
|
return (
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<div className="flex flex-col sm:flex-row items-start sm:items-center justify-between gap-2 sm:gap-0">
|
<div className="flex flex-col sm:flex-row items-start sm:items-center justify-between gap-2 sm:gap-0">
|
||||||
<h3 className="text-base sm:text-lg font-semibold">Upload Photos for {entityName}</h3>
|
<div>
|
||||||
|
<h3 className="text-base sm:text-lg font-semibold">Submit Additional Photos for {entityName}</h3>
|
||||||
|
<p className="text-sm text-muted-foreground">Photos submitted here go through a separate review process</p>
|
||||||
|
</div>
|
||||||
<Button variant="ghost" onClick={() => setShowUpload(false)} className="w-full sm:w-auto">
|
<Button variant="ghost" onClick={() => setShowUpload(false)} className="w-full sm:w-auto">
|
||||||
Back to Gallery
|
Back to Gallery
|
||||||
</Button>
|
</Button>
|
||||||
@@ -155,7 +158,7 @@ export function EntityPhotoGallery({
|
|||||||
{user ? (
|
{user ? (
|
||||||
<>
|
<>
|
||||||
<Upload className="w-4 h-4" />
|
<Upload className="w-4 h-4" />
|
||||||
<span className="sm:inline">Upload Photos</span>
|
<span className="sm:inline">Submit Additional Photos</span>
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
|
|||||||
@@ -531,25 +531,14 @@ export async function submitParkUpdate(
|
|||||||
if (fetchError) throw new Error(`Failed to fetch park: ${fetchError.message}`);
|
if (fetchError) throw new Error(`Failed to fetch park: ${fetchError.message}`);
|
||||||
if (!existingPark) throw new Error('Park not found');
|
if (!existingPark) throw new Error('Park not found');
|
||||||
|
|
||||||
// Upload any pending local images first
|
// 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;
|
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}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create the main submission record
|
// Create the main submission record
|
||||||
const { data: submissionData, error: submissionError } = await supabase
|
const { data: submissionData, error: submissionError } = await supabase
|
||||||
@@ -825,25 +814,14 @@ export async function submitRideUpdate(
|
|||||||
if (fetchError) throw new Error(`Failed to fetch ride: ${fetchError.message}`);
|
if (fetchError) throw new Error(`Failed to fetch ride: ${fetchError.message}`);
|
||||||
if (!existingRide) throw new Error('Ride not found');
|
if (!existingRide) throw new Error('Ride not found');
|
||||||
|
|
||||||
// Upload any pending local images first
|
// 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;
|
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}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create the main submission record
|
// Create the main submission record
|
||||||
const { data: submissionData, error: submissionError } = await supabase
|
const { data: submissionData, error: submissionError } = await supabase
|
||||||
@@ -1002,25 +980,12 @@ export async function submitRideModelUpdate(
|
|||||||
if (fetchError) throw new Error(`Failed to fetch ride model: ${fetchError.message}`);
|
if (fetchError) throw new Error(`Failed to fetch ride model: ${fetchError.message}`);
|
||||||
if (!existingModel) throw new Error('Ride model not found');
|
if (!existingModel) throw new Error('Ride model not found');
|
||||||
|
|
||||||
// Upload any pending local images first
|
// 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;
|
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}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create the main submission record
|
// Create the main submission record
|
||||||
const { data: submissionData, error: submissionError } = await supabase
|
const { data: submissionData, error: submissionError } = await supabase
|
||||||
@@ -1132,21 +1097,12 @@ export async function submitManufacturerUpdate(
|
|||||||
if (fetchError) throw new Error(`Failed to fetch manufacturer: ${fetchError.message}`);
|
if (fetchError) throw new Error(`Failed to fetch manufacturer: ${fetchError.message}`);
|
||||||
if (!existingCompany) throw new Error('Manufacturer not found');
|
if (!existingCompany) throw new Error('Manufacturer not found');
|
||||||
|
|
||||||
|
// 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;
|
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}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const { data: submissionData, error: submissionError } = await supabase
|
const { data: submissionData, error: submissionError } = await supabase
|
||||||
.from('content_submissions')
|
.from('content_submissions')
|
||||||
@@ -1249,21 +1205,12 @@ export async function submitDesignerUpdate(
|
|||||||
if (fetchError) throw new Error(`Failed to fetch designer: ${fetchError.message}`);
|
if (fetchError) throw new Error(`Failed to fetch designer: ${fetchError.message}`);
|
||||||
if (!existingCompany) throw new Error('Designer not found');
|
if (!existingCompany) throw new Error('Designer not found');
|
||||||
|
|
||||||
|
// 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;
|
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}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const { data: submissionData, error: submissionError } = await supabase
|
const { data: submissionData, error: submissionError } = await supabase
|
||||||
.from('content_submissions')
|
.from('content_submissions')
|
||||||
@@ -1366,21 +1313,12 @@ export async function submitOperatorUpdate(
|
|||||||
if (fetchError) throw new Error(`Failed to fetch operator: ${fetchError.message}`);
|
if (fetchError) throw new Error(`Failed to fetch operator: ${fetchError.message}`);
|
||||||
if (!existingCompany) throw new Error('Operator not found');
|
if (!existingCompany) throw new Error('Operator not found');
|
||||||
|
|
||||||
|
// 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;
|
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}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const { data: submissionData, error: submissionError } = await supabase
|
const { data: submissionData, error: submissionError } = await supabase
|
||||||
.from('content_submissions')
|
.from('content_submissions')
|
||||||
@@ -1483,21 +1421,12 @@ export async function submitPropertyOwnerUpdate(
|
|||||||
if (fetchError) throw new Error(`Failed to fetch property owner: ${fetchError.message}`);
|
if (fetchError) throw new Error(`Failed to fetch property owner: ${fetchError.message}`);
|
||||||
if (!existingCompany) throw new Error('Property owner not found');
|
if (!existingCompany) throw new Error('Property owner not found');
|
||||||
|
|
||||||
|
// 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;
|
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}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const { data: submissionData, error: submissionError } = await supabase
|
const { data: submissionData, error: submissionError } = await supabase
|
||||||
.from('content_submissions')
|
.from('content_submissions')
|
||||||
|
|||||||
Reference in New Issue
Block a user