feat: Implement enhanced multi-image upload

This commit is contained in:
gpt-engineer-app[bot]
2025-10-01 18:51:48 +00:00
parent 69ce1a8132
commit 37b70111c6
7 changed files with 350 additions and 132 deletions

View File

@@ -12,7 +12,7 @@ import { Ruler, Save, X } from 'lucide-react';
import { Combobox } from '@/components/ui/combobox';
import { useCompanyHeadquarters } from '@/hooks/useAutocompleteData';
import { useUserRole } from '@/hooks/useUserRole';
import { EntityImageUploader } from '@/components/upload/EntityImageUploader';
import { EntityMultiImageUploader, ImageAssignments } from '@/components/upload/EntityMultiImageUploader';
const designerSchema = z.object({
name: z.string().min(1, 'Name is required'),
@@ -22,11 +22,15 @@ const designerSchema = z.object({
website_url: z.string().url().optional().or(z.literal('')),
founded_year: z.number().min(1800).max(new Date().getFullYear()).optional(),
headquarters_location: z.string().optional(),
logo_url: z.string().optional(),
banner_image_id: z.string().optional(),
banner_image_url: z.string().optional(),
card_image_id: z.string().optional(),
card_image_url: z.string().optional()
images: z.object({
uploaded: z.array(z.object({
url: z.string(),
cloudflare_id: z.string(),
caption: z.string().optional()
})),
banner_assignment: z.number().optional(),
card_assignment: z.number().optional()
}).optional()
});
type DesignerFormData = z.infer<typeof designerSchema>;
@@ -57,11 +61,7 @@ export function DesignerForm({ onSubmit, onCancel, initialData }: DesignerFormPr
website_url: initialData?.website_url || '',
founded_year: initialData?.founded_year || undefined,
headquarters_location: initialData?.headquarters_location || '',
logo_url: initialData?.logo_url || '',
banner_image_id: initialData?.banner_image_id || '',
banner_image_url: initialData?.banner_image_url || '',
card_image_id: initialData?.card_image_id || '',
card_image_url: initialData?.card_image_url || ''
images: initialData?.images || { uploaded: [] }
}
});
@@ -181,20 +181,10 @@ export function DesignerForm({ onSubmit, onCancel, initialData }: DesignerFormPr
</div>
{/* Images */}
<EntityImageUploader
images={{
logo: { url: watch('logo_url') },
banner: { url: watch('banner_image_url'), id: watch('banner_image_id') },
card: { url: watch('card_image_url'), id: watch('card_image_id') }
}}
onImagesChange={(images) => {
if (images.logo_url !== undefined) setValue('logo_url', images.logo_url);
if (images.banner_image_id !== undefined) setValue('banner_image_id', images.banner_image_id);
if (images.banner_image_url !== undefined) setValue('banner_image_url', images.banner_image_url);
if (images.card_image_id !== undefined) setValue('card_image_id', images.card_image_id);
if (images.card_image_url !== undefined) setValue('card_image_url', images.card_image_url);
}}
showLogo={true}
<EntityMultiImageUploader
mode={initialData ? 'edit' : 'create'}
value={watch('images') || { uploaded: [] }}
onChange={(images) => setValue('images', images)}
entityType="designer"
/>

View File

@@ -12,7 +12,7 @@ import { Building2, Save, X } from 'lucide-react';
import { Combobox } from '@/components/ui/combobox';
import { useCompanyHeadquarters } from '@/hooks/useAutocompleteData';
import { useUserRole } from '@/hooks/useUserRole';
import { EntityImageUploader } from '@/components/upload/EntityImageUploader';
import { EntityMultiImageUploader, ImageAssignments } from '@/components/upload/EntityMultiImageUploader';
const manufacturerSchema = z.object({
name: z.string().min(1, 'Name is required'),
@@ -22,11 +22,15 @@ const manufacturerSchema = z.object({
website_url: z.string().url().optional().or(z.literal('')),
founded_year: z.number().min(1800).max(new Date().getFullYear()).optional(),
headquarters_location: z.string().optional(),
logo_url: z.string().optional(),
banner_image_id: z.string().optional(),
banner_image_url: z.string().optional(),
card_image_id: z.string().optional(),
card_image_url: z.string().optional()
images: z.object({
uploaded: z.array(z.object({
url: z.string(),
cloudflare_id: z.string(),
caption: z.string().optional()
})),
banner_assignment: z.number().optional(),
card_assignment: z.number().optional()
}).optional()
});
type ManufacturerFormData = z.infer<typeof manufacturerSchema>;
@@ -57,11 +61,7 @@ export function ManufacturerForm({ onSubmit, onCancel, initialData }: Manufactur
website_url: initialData?.website_url || '',
founded_year: initialData?.founded_year || undefined,
headquarters_location: initialData?.headquarters_location || '',
logo_url: initialData?.logo_url || '',
banner_image_id: initialData?.banner_image_id || '',
banner_image_url: initialData?.banner_image_url || '',
card_image_id: initialData?.card_image_id || '',
card_image_url: initialData?.card_image_url || ''
images: initialData?.images || { uploaded: [] }
}
});
@@ -181,20 +181,10 @@ export function ManufacturerForm({ onSubmit, onCancel, initialData }: Manufactur
</div>
{/* Images */}
<EntityImageUploader
images={{
logo: { url: watch('logo_url') },
banner: { url: watch('banner_image_url'), id: watch('banner_image_id') },
card: { url: watch('card_image_url'), id: watch('card_image_id') }
}}
onImagesChange={(images) => {
if (images.logo_url !== undefined) setValue('logo_url', images.logo_url);
if (images.banner_image_id !== undefined) setValue('banner_image_id', images.banner_image_id);
if (images.banner_image_url !== undefined) setValue('banner_image_url', images.banner_image_url);
if (images.card_image_id !== undefined) setValue('card_image_id', images.card_image_id);
if (images.card_image_url !== undefined) setValue('card_image_url', images.card_image_url);
}}
showLogo={true}
<EntityMultiImageUploader
mode={initialData ? 'edit' : 'create'}
value={watch('images') || { uploaded: [] }}
onChange={(images) => setValue('images', images)}
entityType="manufacturer"
/>

View File

@@ -12,7 +12,7 @@ import { FerrisWheel, Save, X } from 'lucide-react';
import { Combobox } from '@/components/ui/combobox';
import { useCompanyHeadquarters } from '@/hooks/useAutocompleteData';
import { useUserRole } from '@/hooks/useUserRole';
import { EntityImageUploader } from '@/components/upload/EntityImageUploader';
import { EntityMultiImageUploader, ImageAssignments } from '@/components/upload/EntityMultiImageUploader';
const operatorSchema = z.object({
name: z.string().min(1, 'Name is required'),
@@ -22,11 +22,15 @@ const operatorSchema = z.object({
website_url: z.string().url().optional().or(z.literal('')),
founded_year: z.number().min(1800).max(new Date().getFullYear()).optional(),
headquarters_location: z.string().optional(),
logo_url: z.string().optional(),
banner_image_id: z.string().optional(),
banner_image_url: z.string().optional(),
card_image_id: z.string().optional(),
card_image_url: z.string().optional()
images: z.object({
uploaded: z.array(z.object({
url: z.string(),
cloudflare_id: z.string(),
caption: z.string().optional()
})),
banner_assignment: z.number().optional(),
card_assignment: z.number().optional()
}).optional()
});
type OperatorFormData = z.infer<typeof operatorSchema>;
@@ -57,11 +61,7 @@ export function OperatorForm({ onSubmit, onCancel, initialData }: OperatorFormPr
website_url: initialData?.website_url || '',
founded_year: initialData?.founded_year || undefined,
headquarters_location: initialData?.headquarters_location || '',
logo_url: initialData?.logo_url || '',
banner_image_id: initialData?.banner_image_id || '',
banner_image_url: initialData?.banner_image_url || '',
card_image_id: initialData?.card_image_id || '',
card_image_url: initialData?.card_image_url || ''
images: initialData?.images || { uploaded: [] }
}
});
@@ -181,20 +181,10 @@ export function OperatorForm({ onSubmit, onCancel, initialData }: OperatorFormPr
</div>
{/* Images */}
<EntityImageUploader
images={{
logo: { url: watch('logo_url') },
banner: { url: watch('banner_image_url'), id: watch('banner_image_id') },
card: { url: watch('card_image_url'), id: watch('card_image_id') }
}}
onImagesChange={(images) => {
if (images.logo_url !== undefined) setValue('logo_url', images.logo_url);
if (images.banner_image_id !== undefined) setValue('banner_image_id', images.banner_image_id);
if (images.banner_image_url !== undefined) setValue('banner_image_url', images.banner_image_url);
if (images.card_image_id !== undefined) setValue('card_image_id', images.card_image_id);
if (images.card_image_url !== undefined) setValue('card_image_url', images.card_image_url);
}}
showLogo={true}
<EntityMultiImageUploader
mode={initialData ? 'edit' : 'create'}
value={watch('images') || { uploaded: [] }}
onChange={(images) => setValue('images', images)}
entityType="operator"
/>

View File

@@ -12,7 +12,7 @@ import { Building2, Save, X } from 'lucide-react';
import { Combobox } from '@/components/ui/combobox';
import { useCompanyHeadquarters } from '@/hooks/useAutocompleteData';
import { useUserRole } from '@/hooks/useUserRole';
import { EntityImageUploader } from '@/components/upload/EntityImageUploader';
import { EntityMultiImageUploader, ImageAssignments } from '@/components/upload/EntityMultiImageUploader';
const propertyOwnerSchema = z.object({
name: z.string().min(1, 'Name is required'),
@@ -22,11 +22,15 @@ const propertyOwnerSchema = z.object({
website_url: z.string().url().optional().or(z.literal('')),
founded_year: z.number().min(1800).max(new Date().getFullYear()).optional(),
headquarters_location: z.string().optional(),
logo_url: z.string().optional(),
banner_image_id: z.string().optional(),
banner_image_url: z.string().optional(),
card_image_id: z.string().optional(),
card_image_url: z.string().optional()
images: z.object({
uploaded: z.array(z.object({
url: z.string(),
cloudflare_id: z.string(),
caption: z.string().optional()
})),
banner_assignment: z.number().optional(),
card_assignment: z.number().optional()
}).optional()
});
type PropertyOwnerFormData = z.infer<typeof propertyOwnerSchema>;
@@ -57,11 +61,7 @@ export function PropertyOwnerForm({ onSubmit, onCancel, initialData }: PropertyO
website_url: initialData?.website_url || '',
founded_year: initialData?.founded_year || undefined,
headquarters_location: initialData?.headquarters_location || '',
logo_url: initialData?.logo_url || '',
banner_image_id: initialData?.banner_image_id || '',
banner_image_url: initialData?.banner_image_url || '',
card_image_id: initialData?.card_image_id || '',
card_image_url: initialData?.card_image_url || ''
images: initialData?.images || { uploaded: [] }
}
});
@@ -181,20 +181,10 @@ export function PropertyOwnerForm({ onSubmit, onCancel, initialData }: PropertyO
</div>
{/* Images */}
<EntityImageUploader
images={{
logo: { url: watch('logo_url') },
banner: { url: watch('banner_image_url'), id: watch('banner_image_id') },
card: { url: watch('card_image_url'), id: watch('card_image_id') }
}}
onImagesChange={(images) => {
if (images.logo_url !== undefined) setValue('logo_url', images.logo_url);
if (images.banner_image_id !== undefined) setValue('banner_image_id', images.banner_image_id);
if (images.banner_image_url !== undefined) setValue('banner_image_url', images.banner_image_url);
if (images.card_image_id !== undefined) setValue('card_image_id', images.card_image_id);
if (images.card_image_url !== undefined) setValue('card_image_url', images.card_image_url);
}}
showLogo={true}
<EntityMultiImageUploader
mode={initialData ? 'edit' : 'create'}
value={watch('images') || { uploaded: [] }}
onChange={(images) => setValue('images', images)}
entityType="property_owner"
/>

View File

@@ -11,7 +11,7 @@ import { Badge } from '@/components/ui/badge';
import { SlugField } from '@/components/ui/slug-field';
import { Layers, Save, X } from 'lucide-react';
import { useUserRole } from '@/hooks/useUserRole';
import { EntityImageUploader } from '@/components/upload/EntityImageUploader';
import { EntityMultiImageUploader, ImageAssignments } from '@/components/upload/EntityMultiImageUploader';
const rideModelSchema = z.object({
name: z.string().min(1, 'Name is required'),
@@ -20,10 +20,15 @@ const rideModelSchema = z.object({
ride_type: z.string().min(1, 'Ride type is required'),
description: z.string().optional(),
technical_specs: z.string().optional(),
banner_image_id: z.string().optional(),
banner_image_url: z.string().optional(),
card_image_id: z.string().optional(),
card_image_url: z.string().optional()
images: z.object({
uploaded: z.array(z.object({
url: z.string(),
cloudflare_id: z.string(),
caption: z.string().optional()
})),
banner_assignment: z.number().optional(),
card_assignment: z.number().optional()
}).optional()
});
type RideModelFormData = z.infer<typeof rideModelSchema>;
@@ -69,10 +74,7 @@ export function RideModelForm({
ride_type: initialData?.ride_type || '',
description: initialData?.description || '',
technical_specs: initialData?.technical_specs || '',
banner_image_id: initialData?.banner_image_id || '',
banner_image_url: initialData?.banner_image_url || '',
card_image_id: initialData?.card_image_id || '',
card_image_url: initialData?.card_image_url || ''
images: initialData?.images || { uploaded: [] }
}
});
@@ -176,18 +178,10 @@ export function RideModelForm({
</div>
{/* Images */}
<EntityImageUploader
images={{
banner: { url: watch('banner_image_url'), id: watch('banner_image_id') },
card: { url: watch('card_image_url'), id: watch('card_image_id') }
}}
onImagesChange={(images) => {
if (images.banner_image_id !== undefined) setValue('banner_image_id', images.banner_image_id);
if (images.banner_image_url !== undefined) setValue('banner_image_url', images.banner_image_url);
if (images.card_image_id !== undefined) setValue('card_image_id', images.card_image_id);
if (images.card_image_url !== undefined) setValue('card_image_url', images.card_image_url);
}}
showLogo={false}
<EntityMultiImageUploader
mode={initialData ? 'edit' : 'create'}
value={watch('images') || { uploaded: [] }}
onChange={(images) => setValue('images', images)}
entityType="ride_model"
/>