AIWeddingFittingRoom / components /ImageUploader.tsx
Lianjx's picture
Upload 71 files
459775e verified
import React, { useCallback } from 'react';
import { Upload, X, Plus } from 'lucide-react';
import { Language } from '../types';
import { TRANSLATIONS } from '../constants/translations';
interface ImageUploaderProps {
onImagesSelect: (base64Images: string[]) => void;
currentImages: string[];
disabled: boolean;
language: Language;
}
export const ImageUploader: React.FC<ImageUploaderProps> = ({ onImagesSelect, currentImages, disabled, language }) => {
const t = TRANSLATIONS[language];
const handleFileChange = useCallback((event: React.ChangeEvent<HTMLInputElement>) => {
const files = event.target.files;
if (files && files.length > 0) {
const newImages: string[] = [];
let processed = 0;
// Limit to 5 images for performance
const maxFiles = Math.min(files.length, 5);
for (let i = 0; i < maxFiles; i++) {
const file = files[i];
if (file.size > 5 * 1024 * 1024) {
alert(`File ${file.name} is too large. Skip.`);
processed++;
if (processed === maxFiles) onImagesSelect([...currentImages, ...newImages]);
continue;
}
const reader = new FileReader();
reader.onloadend = () => {
newImages.push(reader.result as string);
processed++;
if (processed === maxFiles) {
// Append to existing images
onImagesSelect([...currentImages, ...newImages]);
}
};
reader.readAsDataURL(file);
}
}
}, [currentImages, onImagesSelect]);
const removeImage = (index: number) => {
const newImages = currentImages.filter((_, i) => i !== index);
onImagesSelect(newImages);
};
const hasImages = currentImages.length > 0;
return (
<div className="w-full">
<div className={`
relative w-full min-h-[300px] rounded-2xl border-2 border-dashed
transition-all duration-300 flex flex-col items-center justify-center p-4
${hasImages ? 'border-rose-300 bg-rose-50' : 'border-gray-300 bg-gray-50 hover:bg-gray-100 hover:border-gray-400'}
${disabled ? 'opacity-60 cursor-not-allowed' : ''}
`}>
{!hasImages ? (
// Empty State
<div className="flex flex-col items-center justify-center text-center pointer-events-none">
<div className="w-16 h-16 mb-4 rounded-full bg-white flex items-center justify-center shadow-sm">
<Upload className="w-8 h-8 text-rose-400" />
</div>
<p className="text-lg font-medium text-gray-700">{t.uploadTitle}</p>
<p className="text-sm text-gray-500 mt-2 max-w-xs">{t.uploadDesc}</p>
</div>
) : (
// Grid View for Multiple Images
<div className="w-full grid grid-cols-2 sm:grid-cols-3 gap-4">
{currentImages.map((img, idx) => (
<div key={idx} className="relative aspect-[3/4] rounded-xl overflow-hidden shadow-sm group">
<img src={img} alt={`Uploaded ${idx}`} className="w-full h-full object-cover" />
{!disabled && (
<button
onClick={() => removeImage(idx)}
className="absolute top-2 right-2 bg-black/50 text-white p-1 rounded-full hover:bg-red-500 transition-colors"
>
<X className="w-4 h-4" />
</button>
)}
</div>
))}
{/* Add More Button (if less than 5) */}
{currentImages.length < 5 && !disabled && (
<div className="relative aspect-[3/4] rounded-xl border-2 border-dashed border-rose-300 flex flex-col items-center justify-center bg-white/50 hover:bg-white transition-colors cursor-pointer group">
<Plus className="w-8 h-8 text-rose-400 mb-2 group-hover:scale-110 transition-transform" />
<span className="text-xs text-rose-500 font-bold">Add Photo</span>
<input
type="file"
multiple
accept="image/png, image/jpeg, image/jpg, image/webp"
onChange={handleFileChange}
disabled={disabled}
className="absolute inset-0 w-full h-full opacity-0 cursor-pointer"
/>
</div>
)}
</div>
)}
{/* Hidden Input for Initial Drag/Drop area */}
{!hasImages && (
<input
type="file"
multiple
accept="image/png, image/jpeg, image/jpg, image/webp"
onChange={handleFileChange}
disabled={disabled}
className="absolute inset-0 w-full h-full opacity-0 cursor-pointer disabled:cursor-not-allowed"
/>
)}
</div>
{hasImages && (
<p className="text-center text-xs text-gray-400 mt-2">
{currentImages.length} photo(s) selected. Max 5.
</p>
)}
</div>
);
};