Spaces:
Paused
Paused
| 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> | |
| ); | |
| }; | |