Implement photo upload enhancements

This commit is contained in:
gpt-engineer-app[bot]
2025-09-29 17:13:17 +00:00
parent 343d9c934c
commit 1542683456
4 changed files with 528 additions and 62 deletions

View File

@@ -10,6 +10,7 @@ import { Button } from '@/components/ui/button';
import { Badge } from '@/components/ui/badge';
import { Upload, X, Eye, Loader2 } from 'lucide-react';
import { cn } from '@/lib/utils';
import { DragDropZone } from './DragDropZone';
interface UppyPhotoUploadProps {
onUploadComplete?: (urls: string[]) => void;
@@ -25,6 +26,8 @@ interface UppyPhotoUploadProps {
disabled?: boolean;
showPreview?: boolean;
size?: 'default' | 'compact' | 'large';
enableDragDrop?: boolean;
showUploadModal?: boolean;
}
interface CloudflareResponse {
@@ -59,6 +62,8 @@ export function UppyPhotoUpload({
disabled = false,
showPreview = true,
size = 'default',
enableDragDrop = true,
showUploadModal = true,
}: UppyPhotoUploadProps) {
const [isModalOpen, setIsModalOpen] = useState(false);
const [uploadedImages, setUploadedImages] = useState<string[]>([]);
@@ -167,45 +172,44 @@ export function UppyPhotoUpload({
setUploadProgress(Math.round(progress));
});
// Handle upload success
uppy.on('upload-success', async (file, response) => {
try {
const cloudflareId = file?.meta?.cloudflareId as string;
if (!cloudflareId) {
throw new Error('Missing Cloudflare ID');
}
// Poll for upload completion
let attempts = 0;
const maxAttempts = 30; // 30 seconds max
let imageData: UploadSuccessResponse | null = null;
while (attempts < maxAttempts) {
const statusResponse = await supabase.functions.invoke('upload-image', {
body: null,
method: 'GET',
});
if (!statusResponse.error && statusResponse.data) {
const status: UploadSuccessResponse = statusResponse.data;
if (status.uploaded && status.urls) {
imageData = status;
break;
}
}
await new Promise(resolve => setTimeout(resolve, 1000));
attempts++;
}
if (imageData?.urls) {
setUploadedImages(prev => [...prev, imageData.urls!.public]);
}
} catch (error) {
console.error('Upload post-processing failed:', error);
onUploadError?.(error as Error);
// Handle upload success
uppy.on('upload-success', async (file, response) => {
try {
const cloudflareId = file?.meta?.cloudflareId as string;
if (!cloudflareId) {
throw new Error('Missing Cloudflare ID');
}
});
// Poll for upload completion with the correct ID
let attempts = 0;
const maxAttempts = 30; // 30 seconds max
let imageData: UploadSuccessResponse | null = null;
while (attempts < maxAttempts) {
const statusResponse = await supabase.functions.invoke('upload-image', {
body: { id: cloudflareId },
});
if (!statusResponse.error && statusResponse.data) {
const status: UploadSuccessResponse = statusResponse.data;
if (status.uploaded && status.urls) {
imageData = status;
break;
}
}
await new Promise(resolve => setTimeout(resolve, 1000));
attempts++;
}
if (imageData?.urls) {
setUploadedImages(prev => [...prev, imageData.urls!.public]);
}
} catch (error) {
console.error('Upload post-processing failed:', error);
onUploadError?.(error as Error);
}
});
// Handle upload error
uppy.on('upload-error', (file, error, response) => {
@@ -263,6 +267,20 @@ export function UppyPhotoUpload({
};
}, [maxFiles, maxSizeMB, allowedFileTypes, metadata, variant, disabled, onUploadStart, onUploadComplete, onUploadError, toast, uploadedImages, size]);
const handleDragDropFiles = async (files: File[]) => {
if (!uppyRef.current || disabled) return;
// Add files to Uppy and start upload
files.forEach((file) => {
uppyRef.current?.addFile({
source: 'drag-drop',
name: file.name,
type: file.type,
data: file,
});
});
};
const removeImage = (index: number) => {
const newUrls = uploadedImages.filter((_, i) => i !== index);
setUploadedImages(newUrls);
@@ -367,12 +385,50 @@ export function UppyPhotoUpload({
);
};
const renderContent = () => {
if (enableDragDrop && !children) {
return (
<DragDropZone
onFilesAdded={handleDragDropFiles}
maxFiles={maxFiles}
maxSizeMB={maxSizeMB}
allowedFileTypes={allowedFileTypes}
disabled={disabled}
className="min-h-[200px]"
>
<div className="space-y-4">
{renderUploadTrigger()}
{renderPreview()}
</div>
</DragDropZone>
);
}
return (
<div className="space-y-4">
{enableDragDrop && children ? (
<DragDropZone
onFilesAdded={handleDragDropFiles}
maxFiles={maxFiles}
maxSizeMB={maxSizeMB}
allowedFileTypes={allowedFileTypes}
disabled={disabled}
>
{renderUploadTrigger()}
</DragDropZone>
) : (
renderUploadTrigger()
)}
{renderPreview()}
</div>
);
};
return (
<div className={cn("space-y-4", className)}>
{renderUploadTrigger()}
{renderPreview()}
{renderContent()}
{uppyRef.current && (
{uppyRef.current && showUploadModal && (
<DashboardModal
uppy={uppyRef.current}
open={isModalOpen}