@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
JavaScript
'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;