170 lines
		
	
	
		
			5.4 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
			
		
		
	
	
			170 lines
		
	
	
		
			5.4 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
| "use client";
 | |
| 
 | |
| import { useState, useRef } from "react";
 | |
| import toast, { Toaster } from "react-hot-toast";
 | |
| 
 | |
| export default function UploadForm() {
 | |
|   const [file, setFile] = useState<File | null>(null);
 | |
|   const [uploading, setUploading] = useState(false);
 | |
|   const [uploadedFileUrl, setUploadedFileUrl] = useState<string | null>(null);
 | |
|   const [progress, setProgress] = useState<number>(0); // Track upload progress
 | |
|   const fileInputRef = useRef<HTMLInputElement | null>(null); // Ref for the file input
 | |
| 
 | |
|   const handleFileChange = (e: React.ChangeEvent<HTMLInputElement>) => {
 | |
|     if (e.target.files) {
 | |
|       setFile(e.target.files[0] ?? null);
 | |
|       setUploadedFileUrl(null); // Reset the uploaded file URL when a new file is selected
 | |
|       setProgress(0); // Reset progress
 | |
|       setUploading(false); // Reset uploading state
 | |
|     }
 | |
|   };
 | |
| 
 | |
|   const handleUpload = async () => {
 | |
|     if (!file) return toast.error("Please select a file to upload.");
 | |
|     setUploading(true);
 | |
| 
 | |
|     try {
 | |
|       const formData = new FormData();
 | |
|       formData.append("file", file);
 | |
| 
 | |
|       const xhr = new XMLHttpRequest();
 | |
|       xhr.open("POST", "/api/upload", true);
 | |
| 
 | |
|       // Track upload progress
 | |
|       xhr.upload.onprogress = (event) => {
 | |
|         if (event.lengthComputable && event.total > 0) {
 | |
|           const percentComplete = Math.round((event.loaded / event.total) * 100);
 | |
|           setProgress(percentComplete);
 | |
|         }
 | |
|       };
 | |
| 
 | |
|       xhr.onload = () => {
 | |
|         if (xhr.status === 200) {
 | |
|           const response = JSON.parse(xhr.responseText);
 | |
|           setUploadedFileUrl(response.url); // Assume the API returns the uploaded file URL
 | |
|           toast.success("File uploaded successfully!");
 | |
| 
 | |
|           // Clear the file input and reset state
 | |
|           setFile(null);
 | |
|           if (fileInputRef.current) {
 | |
|             fileInputRef.current.value = ""; // Clear the file input
 | |
|           }
 | |
|         } else {
 | |
|           console.error("Upload failed:", xhr.responseText);
 | |
|           toast.error("Failed to upload file.");
 | |
|         }
 | |
|         setUploading(false);
 | |
|       };
 | |
| 
 | |
|       xhr.onerror = () => {
 | |
|         console.error("Upload failed");
 | |
|         toast.error("Failed to upload file.");
 | |
|         setUploading(false);
 | |
|       };
 | |
| 
 | |
|       xhr.send(formData);
 | |
|     } catch (error) {
 | |
|       console.error(error);
 | |
|       toast.error("Failed to upload file.");
 | |
|       setUploading(false);
 | |
|     }
 | |
|   };
 | |
| 
 | |
|   const handleCopyUrl = () => {
 | |
|     if (uploadedFileUrl) {
 | |
|       navigator.clipboard
 | |
|         .writeText(uploadedFileUrl)
 | |
|         .then(() => toast.success("File URL copied to clipboard!"))
 | |
|         .catch(() => toast.error("Failed to copy URL."));
 | |
|     }
 | |
|   };
 | |
| 
 | |
|   return (
 | |
|     <div className="flex flex-col items-center gap-4">
 | |
|       {/* Toast container */}
 | |
|       <Toaster position="top-right" reverseOrder={false} />
 | |
| 
 | |
|       <div className="flex flex-row items-center gap-4">
 | |
|         {/* Custom file input */}
 | |
|         <label
 | |
|           htmlFor="file-upload"
 | |
|           className="cursor-pointer flex items-center gap-2 rounded-full bg-white/10 px-10 py-3 font-semibold no-underline transition hover:bg-white/20"
 | |
|         >
 | |
|           {file ? (
 | |
|             <>
 | |
|               File Selected
 | |
|               {/* SVG Icon */}
 | |
|               <svg
 | |
|                 xmlns="http://www.w3.org/2000/svg"
 | |
|                 fill="none"
 | |
|                 viewBox="0 0 24 24"
 | |
|                 strokeWidth={2}
 | |
|                 stroke="currentColor"
 | |
|                 className="h-5 w-5 text-green-500"
 | |
|               >
 | |
|                 <path
 | |
|                   strokeLinecap="round"
 | |
|                   strokeLinejoin="round"
 | |
|                   d="M5 13l4 4L19 7"
 | |
|                 />
 | |
|               </svg>
 | |
|             </>
 | |
|           ) : (
 | |
|             "Select File"
 | |
|           )}
 | |
|         </label>
 | |
|         <input
 | |
|           id="file-upload"
 | |
|           ref={fileInputRef} // Attach the ref to the file input
 | |
|           type="file"
 | |
|           onChange={handleFileChange}
 | |
|           className="hidden" // Hide the default file input
 | |
|         />
 | |
|         <button
 | |
|           onClick={handleUpload}
 | |
|           disabled={uploading || !file}
 | |
|           className="rounded-full bg-white/10 px-10 py-3 font-semibold no-underline transition hover:bg-white/20"
 | |
|         >
 | |
|           {uploading ? "Uploading..." : "Upload"}
 | |
|         </button>
 | |
|       </div>
 | |
| 
 | |
|       {file && uploading && (
 | |
|         <div className="w-full max-w-md flex items-center gap-2">
 | |
|           <div className="relative h-5 flex-1 rounded-full bg-gray-200">
 | |
|             <div
 | |
|               className="absolute h-5 rounded-full bg-blue-500"
 | |
|               style={{ width: `${progress}%` }}
 | |
|             ></div>
 | |
|           </div>
 | |
|         </div>
 | |
|       )}
 | |
| 
 | |
|       {uploadedFileUrl && (
 | |
|         <div className="flex flex-row items-center gap-4">
 | |
|           <p className="text-white">{uploadedFileUrl}</p>
 | |
|           <button
 | |
|             onClick={handleCopyUrl}
 | |
|             className="flex items-center justify-center rounded-full bg-blue-500 p-2 hover:bg-blue-600"
 | |
|           >
 | |
|             {/* Copy Icon */}
 | |
|             <svg
 | |
|               xmlns="http://www.w3.org/2000/svg"
 | |
|               fill="none"
 | |
|               viewBox="0 0 24 24"
 | |
|               strokeWidth={2}
 | |
|               stroke="currentColor"
 | |
|               className="h-5 w-5 text-white"
 | |
|             >
 | |
|               <path
 | |
|                 strokeLinecap="round"
 | |
|                 strokeLinejoin="round"
 | |
|                 d="M8 16h8M8 12h8m-7 8h6a2 2 0 002-2V6a2 2 0 00-2-2H9a2 2 0 00-2 2v12a2 2 0 002 2z"
 | |
|               />
 | |
|             </svg>
 | |
|           </button>
 | |
|         </div>
 | |
|       )}
 | |
|     </div>
 | |
|   );
 | |
| } |