UNPKG

clouflare-r2-file-upload

Version:

A React component for uploading files to Cloudflare R2

240 lines 20.1 kB
import React, { useCallback, useState } from "react"; import { PutObjectCommand, DeleteObjectCommand } from "@aws-sdk/client-s3"; import s3Client from "../libs/s3"; import { useDropzone } from "react-dropzone"; import { truncateFileName } from "../libs/truncate"; import { bytesToMegabytes } from "../libs/convert"; import FileIcon from "../assets/file.png"; import { formatFileName } from "../config/change_image_name"; let urls = []; const FileUpload = ({ type = "single", acceptedFileTypes, title, subtitle, message, acceptFileTypesText, style = "full_box", choseTitle, handleInputChange, changeImageName = false, default_img, classname, REGION, ACCESS_KEY_ID, SECRET_ACCESS_KEY, ENDPOINT, BUCKET_NAME, IMG_DOMAIN, }) => { const [files, setFiles] = useState([]); const [uploading, setUploading] = useState(false); const [imagesInfo, setImagesInfo] = useState([]); const [localPreviews, setLocalPreviews] = useState(new Map()); // console.log( // REGION, // ACCESS_KEY_ID, // SECRET_ACCESS_KEY, // ENDPOINT, // BUCKET_NAME, // IMG_DOMAIN // ); const onDrop = useCallback(async (acceptedFiles) => { const newFiles = type === "single" ? [acceptedFiles[0]] : [...files, ...acceptedFiles]; // Mostrar la vista previa local const newLocalPreviews = new Map(localPreviews); acceptedFiles.forEach((file) => { const reader = new FileReader(); reader.onloadend = () => { newLocalPreviews.set(file.name, reader.result); setLocalPreviews(new Map(newLocalPreviews)); }; reader.readAsDataURL(file); }); setFiles(newFiles); await handleUpload(newFiles); }, [files, localPreviews, type]); const { getRootProps, getInputProps } = useDropzone({ onDrop, multiple: type === "multiple", accept: acceptedFileTypes ? { acceptedFileTypes } : undefined, }); const handleUpload = async (files) => { setUploading(true); const uploadPromises = files.map((file) => { return new Promise((resolve, reject) => { const params = { Bucket: BUCKET_NAME, Key: changeImageName ? formatFileName(file.name) : file.name, Body: file, }; const command = new PutObjectCommand(params); s3Client(REGION, ACCESS_KEY_ID, SECRET_ACCESS_KEY, ENDPOINT) .send(command) .then(() => { const uploadedImageUrl = `${IMG_DOMAIN}/${params.Key}`; setFiles((prevFiles) => prevFiles.filter((f) => f.name !== file.name)); urls.push(uploadedImageUrl); if (type === "multiple") { handleInputChange([...urls, uploadedImageUrl]); setImagesInfo((prevUrls) => [ ...prevUrls, { name: params.Key, url: uploadedImageUrl, size: file.size, type: file.type, }, ]); } else if (type === "single") { handleInputChange([uploadedImageUrl]); setImagesInfo([ { name: params.Key, url: uploadedImageUrl, size: file.size, type: file.type, }, ]); } setLocalPreviews((prevPreviews) => { const updatedPreviews = new Map(prevPreviews); updatedPreviews.delete(file.name); return updatedPreviews; }); resolve(); }) .catch((err) => { console.error(err); reject(err); }); }); }); await Promise.all(uploadPromises); setFiles([]); setUploading(false); }; const handleDelete = async (url) => { // event.stopPropagation(); const key = url.split("/").pop(); const params = { Bucket: BUCKET_NAME, Key: key, }; try { const command = new DeleteObjectCommand(params); await s3Client(REGION, ACCESS_KEY_ID, SECRET_ACCESS_KEY, ENDPOINT).send(command); setImagesInfo(imagesInfo.filter((imageUrl) => imageUrl.url !== url)); urls = urls.filter((urll) => urll !== url); handleInputChange(urls.filter((urll) => urll !== url)); } catch (err) { console.error(err); } }; return (React.createElement(React.Fragment, null, style === "full_box" && (React.createElement("div", { className: "p-4 flex flex-wrap justify-center items-center w-full h-full" }, React.createElement("div", { className: "w-full max-w-lg shadow-lg rounded-lg p-6 relative" }, title || subtitle ? (React.createElement("div", { className: "flex items-center pb-3 border-b border-gray-200" }, React.createElement("div", { className: "flex-1" }, React.createElement("h3", { className: "text-gray-800 text-xl font-bold" }, title), React.createElement("p", { className: "text-gray-600 text-xs mt-1" }, subtitle)))) : null, React.createElement("div", { ...getRootProps(), className: "text-blue-600 cursor-pointer" }, React.createElement("div", { className: `rounded-lg border-2 border-gray-200 border-dashed hover:bg-gray-100 transition-colors duration-200 ${title || subtitle ? "mt-6" : ""}` }, React.createElement("div", { className: "p-4 min-h-[180px] flex flex-col items-center justify-center text-center cursor-pointer" }, React.createElement("svg", { xmlns: "http://www.w3.org/2000/svg", className: "w-10 mb-4 fill-gray-600 inline-block", viewBox: "0 0 32 32" }, React.createElement("path", { d: "M23.75 11.044a7.99 7.99 0 0 0-15.5-.009A8 8 0 0 0 9 27h3a1 1 0 0 0 0-2H9a6 6 0 0 1-.035-12 1.038 1.038 0 0 0 1.1-.854 5.991 5.991 0 0 1 11.862 0A1.08 1.08 0 0 0 23 13a6 6 0 0 1 0 12h-3a1 1 0 0 0 0 2h3a8 8 0 0 0 .75-15.956z", "data-original": "#000000" }), React.createElement("path", { d: "M20.293 19.707a1 1 0 0 0 1.414-1.414l-5-5a1 1 0 0 0-1.414 0l-5 5a1 1 0 0 0 1.414 1.414L15 16.414V29a1 1 0 0 0 2 0V16.414z", "data-original": "#000000" })), React.createElement("div", null, React.createElement("h4", { className: "text-md text-gray-600 font-medium" }, message || "Drag & Drop or Choose file to upload"), React.createElement("p", { className: "text-xs text-gray-500" }, acceptFileTypesText)), React.createElement("input", { ...getInputProps(), disabled: uploading })))), imagesInfo.map((image, index) => (React.createElement("div", { key: index, className: "flex flex-col bg-gray-50 p-2 rounded-lg mt-4" }, React.createElement("div", { className: "flex items-center gap-2" }, image.type.startsWith("image") ? (React.createElement("img", { src: image.url, alt: "Uploaded", className: "h-14 w-12 object-cover rounded-md" })) : (React.createElement("img", { src: FileIcon, alt: "Uploaded", className: "h-14 w-12 object-cover rounded-md" }) // <p>File</p> ), React.createElement("p", { className: "text-md text-gray-600 flex-1 font-semibold" }, truncateFileName(image.name, 12), " ", React.createElement("span", { className: "ml-2" }, bytesToMegabytes(image.size), " mb")), React.createElement("svg", { xmlns: "http://www.w3.org/2000/svg", className: "w-3 cursor-pointer shrink-0 fill-gray-400 hover:fill-red-500", viewBox: "0 0 320.591 320.591", onClick: () => handleDelete(image.url) }, React.createElement("path", { d: "M30.391 318.583a30.37 30.37 0 0 1-21.56-7.288c-11.774-11.844-11.774-30.973 0-42.817L266.643 10.665c12.246-11.459 31.462-10.822 42.921 1.424 10.362 11.074 10.966 28.095 1.414 39.875L51.647 311.295a30.366 30.366 0 0 1-21.256 7.288z", "data-original": "#000000" }), React.createElement("path", { d: "M287.9 318.583a30.37 30.37 0 0 1-21.257-8.806L8.83 51.963C-2.078 39.225-.595 20.055 12.143 9.146c11.369-9.736 28.136-9.736 39.504 0l259.331 257.813c12.243 11.462 12.876 30.679 1.414 42.922-.456.487-.927.958-1.414 1.414a30.368 30.368 0 0 1-23.078 7.288z", "data-original": "#000000" })))))), files.map((file) => (React.createElement("div", { key: file.name, className: "flex flex-col bg-gray-50 p-2 rounded-lg mt-4" }, React.createElement("div", { className: "flex relative" }, file.type.startsWith("image") ? (React.createElement("img", { src: localPreviews.get(file.name) || "", alt: file.name, style: { opacity: localPreviews.has(file.name) ? 0.5 : 1, transition: "opacity 0.3s", }, className: "h-14 w-14 object-cover rounded-md" })) : (React.createElement("img", { src: FileIcon, alt: file.name, style: { opacity: localPreviews.has(file.name) ? 0.5 : 1, transition: "opacity 0.3s", }, className: "h-14 w-12 object-cover rounded-md" }) // <p>File</p> ), localPreviews.has(file.name) && (React.createElement("div", { className: "absolute flex items-center justify-center top-0 left-3 bottom-0" }, React.createElement("div", { className: "animate-spin inline-block size-8 border-[3px] border-current border-t-transparent text-blue-600 rounded-full dark:text-blue-500", role: "status", "aria-label": "loading" }, React.createElement("span", { className: "sr-only" }, "Loading..."))))))))))), style === "simple" && (React.createElement("div", { className: "p-4 flex flex-wrap justify-center items-center w-full h-full" }, React.createElement("div", { className: "w-full max-w-lg shadow-lg rounded-lg p-6 relative" }, React.createElement("div", { className: "grid gap-1 mb-3" }, React.createElement("div", { className: "flex items-center justify-center" }, React.createElement("svg", { xmlns: "http://www.w3.org/2000/svg", width: "20", height: "20", viewBox: "0 0 20 20", fill: "none" }, React.createElement("g", { id: "Attach 01" }, React.createElement("path", { id: "Vector", d: "M13.5756 7.08335L7.97765 13.0209C7.4895 13.5386 6.69804 13.5386 6.20988 13.0209C5.72173 12.5031 5.72173 11.6636 6.20988 11.1459L11.8078 5.20835M11.2202 5.83335L12.3987 4.58335C13.375 3.54782 14.9579 3.54782 15.9342 4.58335C16.9105 5.61889 16.9105 7.29782 15.9342 8.33336L14.7557 9.58336M15.3433 8.95835L9.7454 14.8959C8.28093 16.4492 5.90657 16.4492 4.4421 14.8959C2.97763 13.3426 2.97763 10.8242 4.4421 9.27085L10.04 3.33334", stroke: "#4F46E5", "stroke-width": "1.6", "stroke-linecap": "round", "stroke-linejoin": "round" }))), React.createElement("span", { className: "text-center text-gray-400 text-sm font-medium leading-snug" }, title)), React.createElement("p", { className: "text-center text-gray-400 text-xs font-normal leading-4" }, message || "Drag & Drop or Choose file to upload")), React.createElement("div", { ...getRootProps() }, React.createElement("div", { className: "mb-5 w-full h-11 rounded-3xl border border-gray-300 justify-between items-center inline-flex" }, React.createElement("h2", { className: "text-gray-900/20 text-sm font-normal leading-snug pl-4" }, choseTitle || "Choose File"), React.createElement("input", { ...getInputProps(), disabled: uploading }), React.createElement("div", { className: "flex w-28 h-11 px-2 flex-col bg-indigo-600 rounded-r-3xl shadow text-white text-xs font-semibold leading-4 \r\n items-center justify-center cursor-pointer focus:outline-none" }, choseTitle || "Choose File"))), imagesInfo.map((image, index) => (React.createElement("div", { key: index, className: "w-full grid gap-1 mb-4" }, React.createElement("div", { className: "flex items-center gap-2" }, image.type.startsWith("image") ? (React.createElement("img", { src: image.url, alt: "Uploaded", className: "h-14 w-12 object-cover rounded-md" })) : (React.createElement("img", { src: FileIcon, alt: "Uploaded", className: "h-14 w-12 object-cover rounded-md" }) // <p>File</p> ), React.createElement("p", { className: "text-md text-gray-600 flex-1 font-semibold" }, truncateFileName(image.name, 12), " ", React.createElement("span", { className: "ml-2" }, bytesToMegabytes(image.size), " mb")), React.createElement("svg", { xmlns: "http://www.w3.org/2000/svg", className: "w-3 cursor-pointer shrink-0 fill-gray-400 hover:fill-red-500", viewBox: "0 0 320.591 320.591", onClick: () => handleDelete(image.url) }, React.createElement("path", { d: "M30.391 318.583a30.37 30.37 0 0 1-21.56-7.288c-11.774-11.844-11.774-30.973 0-42.817L266.643 10.665c12.246-11.459 31.462-10.822 42.921 1.424 10.362 11.074 10.966 28.095 1.414 39.875L51.647 311.295a30.366 30.366 0 0 1-21.256 7.288z", "data-original": "#000000" }), React.createElement("path", { d: "M287.9 318.583a30.37 30.37 0 0 1-21.257-8.806L8.83 51.963C-2.078 39.225-.595 20.055 12.143 9.146c11.369-9.736 28.136-9.736 39.504 0l259.331 257.813c12.243 11.462 12.876 30.679 1.414 42.922-.456.487-.927.958-1.414 1.414a30.368 30.368 0 0 1-23.078 7.288z", "data-original": "#000000" })))))), files.map((file) => (React.createElement("div", { key: file.name, className: "w-full grid gap-1 mb-4" }, React.createElement("div", { className: "flex relative" }, file.type.startsWith("image") ? (React.createElement("img", { src: localPreviews.get(file.name) || "", alt: file.name, style: { opacity: localPreviews.has(file.name) ? 0.5 : 1, transition: "opacity 0.3s", }, className: "h-14 w-14 object-cover rounded-md" })) : (React.createElement("img", { src: FileIcon, alt: file.name, style: { opacity: localPreviews.has(file.name) ? 0.5 : 1, transition: "opacity 0.3s", }, className: "h-14 w-12 object-cover rounded-md" }) // <p>File</p> ), localPreviews.has(file.name) && (React.createElement("div", { className: "absolute flex items-center justify-center top-0 left-3 bottom-0" }, React.createElement("div", { className: "animate-spin inline-block size-8 border-[3px] border-current border-t-transparent text-blue-600 rounded-full dark:text-blue-500", role: "status", "aria-label": "loading" }, React.createElement("span", { className: "sr-only" }, "Loading..."))))))))))), style === "logo_box" && (React.createElement("div", { className: `${classname} flex flex-col gap-4` }, React.createElement("div", { className: `relative w-full h-full flex gap-2 rounded-md` }, React.createElement("div", { ...getRootProps() }, React.createElement("input", { ...getInputProps(), disabled: uploading }), files.length > 0 && localPreviews.has(files[0].name) ? (React.createElement("div", { className: "w-full h-full relative flex items-center justify-center top-0" }, React.createElement("img", { src: localPreviews.get(files[0].name) || "", alt: files[0].name, style: { opacity: localPreviews.has(files[0].name) ? 0.5 : 1, transition: "opacity 0.3s", }, className: "h-full w-full object-fit rounded-md" }), React.createElement("div", { className: "absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2" }, React.createElement("svg", { xmlns: "http://www.w3.org/2000/svg", width: "64", height: "64", viewBox: "0 0 28 28" }, React.createElement("path", { fill: "#2563eb", d: "M10.72,19.9a8,8,0,0,1-6.5-9.79A7.77,7.77,0,0,1,10.4,4.16a8,8,0,0,1,9.49,6.52A1.54,1.54,0,0,0,21.38,12h.13a1.37,1.37,0,0,0,1.38-1.54,11,11,0,1,0-12.7,12.39A1.54,1.54,0,0,0,12,21.34h0A1.47,1.47,0,0,0,10.72,19.9Z" }, React.createElement("animateTransform", { attributeName: "transform", dur: "0.75s", repeatCount: "indefinite", type: "rotate", values: "0 12 12;360 12 12" })))))) : (React.createElement("img", { // src={configuration.image_url} src: imagesInfo.length > 0 ? imagesInfo[0].url : default_img, alt: "College logo", className: "w-full h-full rounded-md object-cover" }))), files.length === 0 && (React.createElement("div", { onClick: () => handleDelete(imagesInfo.length > 0 ? imagesInfo[0].url : ""), className: "flex items-center justify-center size-6 absolute top-1 right-2 rounded-full cursor-pointer bg-blue-500/30 text-white p-1" }, React.createElement("svg", { width: "237px", height: "237px", viewBox: "2 2 20 20", fill: "none", xmlns: "http://www.w3.org/2000/svg", transform: "rotate(90)" }, React.createElement("g", { id: "SVGRepo_bgCarrier", "stroke-width": "0" }), React.createElement("g", { id: "SVGRepo_tracerCarrier", "stroke-linecap": "round", "stroke-linejoin": "round" }), React.createElement("g", { id: "SVGRepo_iconCarrier" }, " ", React.createElement("g", { "clip-path": "url(#clip0_429_11083)" }, " ", React.createElement("path", { d: "M7 7.00006L17 17.0001M7 17.0001L17 7.00006", stroke: "#0838f7", "stroke-width": "2.4", "stroke-linecap": "round", "stroke-linejoin": "round" }), " "), " ", React.createElement("defs", null, " ", React.createElement("clipPath", { id: "clip0_429_11083" }, " ", React.createElement("rect", { width: "24", height: "24", fill: "white" }), " "), " "), " "))))))))); }; export default FileUpload; //# sourceMappingURL=FileUpload.js.map