UNPKG

@pagamio/frontend-commons-lib

Version:

Pagamio library for Frontend reusable components like the form engine and table container

98 lines (97 loc) 5.68 kB
'use client'; import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime"; import { Image as ImageIcon, Loader2, Upload, X } from 'lucide-react'; import { useDropzone } from 'react-dropzone'; import { useCallback, useState } from 'react'; import { Button, cn, useToast } from '../..'; import { useImageUpload } from '../../shared/hooks/useImageUpload'; import { Progress } from './Progress'; const ImageUploader = ({ project, env, onUploadSuccess, onError, className, disabled = false, maxFileSize = 5 * 1024 * 1024, // 5MB default acceptedFileTypes = ['image/jpeg', 'image/png', 'image/gif', 'image/webp'], placeholder = 'Click to upload or drag and drop an image', showPreview = true, value, onChange, }) => { const [previewUrl, setPreviewUrl] = useState(value || null); const [isUploading, setIsUploading] = useState(false); const [uploadProgress, setUploadProgress] = useState(0); const [error, setError] = useState(null); const { addToast } = useToast(); const { uploadFile } = useImageUpload({ project, env }); const handleFileUpload = async (file) => { if (disabled) return; setError(null); setIsUploading(true); setUploadProgress(0); try { // Validate file type if (!acceptedFileTypes.includes(file.type)) { throw new Error(`File type not supported. Accepted types: ${acceptedFileTypes.join(', ')}`); } // Validate file size if (file.size > maxFileSize) { throw new Error(`File size exceeds ${Math.round(maxFileSize / 1024 / 1024)}MB limit`); } // Show preview immediately if (showPreview) { const reader = new FileReader(); reader.onload = (e) => { setPreviewUrl(e.target?.result); }; reader.readAsDataURL(file); } // Simulate progress (since we can't track actual upload progress with fetch) const progressInterval = setInterval(() => { setUploadProgress((prev) => { if (prev < 90) return prev + 10; return prev; }); }, 200); const publicURL = await uploadFile(file); clearInterval(progressInterval); setUploadProgress(100); // Update state onChange?.(publicURL); onUploadSuccess?.(publicURL); addToast({ message: 'Image uploaded successfully!', variant: 'success', }); } catch (error) { const errorMessage = error instanceof Error ? error.message : 'Upload failed'; setError(errorMessage); setPreviewUrl(null); onChange?.(null); onError?.(error instanceof Error ? error : new Error(errorMessage)); addToast({ message: errorMessage, variant: 'error', }); } finally { setIsUploading(false); setUploadProgress(0); } }; const onDrop = useCallback((acceptedFiles) => { const file = acceptedFiles[0]; if (file) { handleFileUpload(file); } }, [disabled, acceptedFileTypes, maxFileSize]); const { getRootProps, getInputProps, isDragActive } = useDropzone({ onDrop, accept: acceptedFileTypes.reduce((acc, type) => { acc[type] = []; return acc; }, {}), maxFiles: 1, disabled: disabled || isUploading, }); const handleRemove = () => { setPreviewUrl(null); setError(null); onChange?.(null); }; return (_jsxs("div", { className: cn('w-full', className), children: [previewUrl && showPreview ? (_jsxs("div", { className: "relative mb-4", children: [_jsx("img", { src: previewUrl, alt: "Preview", className: "max-h-64 w-full rounded-lg object-contain border border-gray-200" }), !isUploading && (_jsx(Button, { type: "button", variant: "destructive", size: "sm", className: "absolute top-2 right-2", onClick: handleRemove, children: _jsx(X, { className: "h-4 w-4" }) }))] })) : (_jsxs("div", { ...getRootProps(), className: cn('border-2 border-dashed border-gray-300 rounded-lg p-8 text-center cursor-pointer transition-colors hover:border-gray-400', isDragActive && 'border-blue-500 bg-blue-50', disabled && 'cursor-not-allowed opacity-50', error && 'border-red-500 bg-red-50'), children: [_jsx("input", { ...getInputProps() }), isUploading ? (_jsxs("div", { className: "flex flex-col items-center space-y-2", children: [_jsx(Loader2, { className: "h-8 w-8 animate-spin text-blue-500" }), _jsx("p", { className: "text-sm text-gray-600", children: "Uploading image..." }), _jsx(Progress, { value: uploadProgress, className: "w-full max-w-xs" })] })) : (_jsxs("div", { className: "flex flex-col items-center space-y-2", children: [isDragActive ? (_jsx(Upload, { className: "h-8 w-8 text-blue-500" })) : (_jsx(ImageIcon, { className: "h-8 w-8 text-gray-400" })), _jsx("p", { className: "text-sm text-gray-600", children: isDragActive ? 'Drop the image here' : placeholder }), _jsxs("p", { className: "text-xs text-gray-500", children: ["Supported formats: ", acceptedFileTypes.map((type) => type.split('/')[1]).join(', ')] }), _jsxs("p", { className: "text-xs text-gray-500", children: ["Max size: ", Math.round(maxFileSize / 1024 / 1024), "MB"] })] }))] })), error && _jsx("p", { className: "mt-2 text-sm text-red-600", children: error })] })); }; export default ImageUploader;