'use client'; import React, { useCallback, useMemo, useRef, useState } from 'react'; import classNames from 'classnames'; import { useDropzone } from 'react-dropzone'; import { FaUpload, FaImage, FaTimes } from 'react-icons/fa'; import { apiClient } from '@/utils/api'; import type { AxiosProgressEvent } from 'axios'; interface Props { src: string | null | undefined; className?: string; instruction?: string; onNewImageSelected: (imagePath: string | null) => void; } export default function SampleControlImage({ src, className, instruction = 'Add Control Image', onNewImageSelected, }: Props) { const [isUploading, setIsUploading] = useState(false); const [uploadProgress, setUploadProgress] = useState(0); const [localPreview, setLocalPreview] = useState(null); const fileInputRef = useRef(null); const backgroundUrl = useMemo(() => { if (localPreview) return localPreview; if (src) return `/api/img/${encodeURIComponent(src)}`; return null; }, [src, localPreview]); const handleUpload = useCallback( async (file: File) => { if (!file) return; setIsUploading(true); setUploadProgress(0); const objectUrl = URL.createObjectURL(file); setLocalPreview(objectUrl); const formData = new FormData(); formData.append('files', file); try { const resp = await apiClient.post(`/api/img/upload`, formData, { headers: { 'Content-Type': 'multipart/form-data' }, onUploadProgress: (evt: AxiosProgressEvent) => { const total = evt.total ?? 100; const loaded = evt.loaded ?? 0; setUploadProgress(Math.round((loaded * 100) / total)); }, timeout: 0, }); const uploaded = resp?.data?.files?.[0] ?? null; onNewImageSelected(uploaded); } catch (err) { console.error('Upload failed:', err); setLocalPreview(null); } finally { setIsUploading(false); setUploadProgress(0); URL.revokeObjectURL(objectUrl); if (fileInputRef.current) fileInputRef.current.value = ''; } }, [onNewImageSelected], ); const onDrop = useCallback( (acceptedFiles: File[]) => { if (acceptedFiles.length === 0) return; handleUpload(acceptedFiles[0]); }, [handleUpload], ); const clearImage = useCallback( (e?: React.MouseEvent) => { console.log('clearImage'); if (e) { e.stopPropagation(); e.preventDefault(); } setLocalPreview(null); onNewImageSelected(null); if (fileInputRef.current) fileInputRef.current.value = ''; }, [onNewImageSelected], ); // Drag & drop only; click handled via our own hidden input const { getRootProps, isDragActive } = useDropzone({ onDrop, accept: { 'image/*': ['.png', '.jpg', '.jpeg', '.gif', '.bmp', '.webp'] }, multiple: false, noClick: true, noKeyboard: true, }); const rootProps = getRootProps(); return (
!isUploading && fileInputRef.current?.click()} > {/* Hidden input for click-to-open */} { const file = e.currentTarget.files?.[0]; if (file) handleUpload(file); }} /> {/* Empty state — centered */} {!backgroundUrl && (
{instruction}
Click or drop
)} {/* Existing image overlays */} {backgroundUrl && !isUploading && ( <>
Replace
{/* Clear (X) button */} )} {/* Uploading overlay */} {isUploading && (
Uploading… {uploadProgress}%
)}
); }