import { useState, useRef } from "react"; // ============================================= // Types (DO NOT MODIFY) // ============================================= interface UploadedFile { id: string; name: string; size: number; type: string; status: "pending" | "uploading" | "done" | "error"; progress: number; } // ============================================= // Helper: format file size (DO NOT MODIFY) // ============================================= function formatSize(bytes: number): string { if (bytes < 1024) return `${bytes} B`; if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`; return `${(bytes / (1024 * 1024)).toFixed(1)} MB`; } // ============================================= // Helper: simulate upload with progress (DO NOT MODIFY) // ============================================= // Calls onProgress(0–100) periodically, then onComplete or onError. // Returns a cleanup function to cancel the upload. function simulateUpload( onProgress: (progress: number) => void, onComplete: () => void, onError: () => void, ) { let progress = 0; const interval = setInterval(() => { progress += Math.random() * 20 + 5; if (progress >= 100) { clearInterval(interval); if (Math.random() < 0.1) { onError(); } else { onProgress(100); onComplete(); } } else { onProgress(Math.min(progress, 99)); } }, 300); return () => clearInterval(interval); } // ============================================= // TODO: Implement Drag & Drop File Upload Zone // ============================================= // // Requirements: // 1. Drop zone that accepts dragged files // 2. Click the zone to open a file picker (hidden <input>) // 3. Visual feedback when dragging over (border/bg change) // 4. Show each file with name, size, progress bar, and status // 5. Simulate upload with progress using simulateUpload() // 6. Remove button for each file // 7. Handle multiple files at once // // Key drag events: // - onDragOver: e.preventDefault() — REQUIRED to allow drop // - onDragLeave: reset visual feedback // - onDrop: e.preventDefault(), read e.dataTransfer.files // // Hints: // - `isDragging` state for visual feedback // - `files` state: UploadedFile[] for the file list // - Hidden <input type="file" ref={inputRef}> for click-to-browse // - processFiles(fileList): convert FileList to UploadedFile[], // add to state, then call simulateUpload for each // - Use crypto.randomUUID() for unique file IDs // - Update file progress/status by mapping over state export default function App() { const [files, setFiles] = useState<UploadedFile[]>([]); const [isDragging, setIsDragging] = useState(false); // TODO: Create a ref for the hidden file input // const inputRef = useRef<HTMLInputElement>(null); // ============================================= // TODO: Implement processFiles // ============================================= // Takes a FileList, converts each to an UploadedFile object, // adds them to state, then starts simulateUpload for each. // // For each file in the FileList: // 1. Create an UploadedFile with id, name, size, type, // status: "uploading", progress: 0 // 2. Add all new files to state // 3. Call simulateUpload with callbacks that update // the matching file's progress/status in state const processFiles = (fileList: FileList) => { // Your code here }; // ============================================= // TODO: Implement drag event handlers // ============================================= // onDragOver — MUST call e.preventDefault() to allow dropping const handleDragOver = (e: React.DragEvent) => { // Your code here }; // onDragLeave — reset isDragging const handleDragLeave = (e: React.DragEvent) => { // Your code here }; // onDrop — prevent default, reset isDragging, process files const handleDrop = (e: React.DragEvent) => { // Your code here }; // ============================================= // TODO: Implement click-to-browse handler // ============================================= const handleFileSelect = (e: React.ChangeEvent<HTMLInputElement>) => { // Your code here // Don't forget to reset e.target.value = "" so the same file can be re-selected }; // ============================================= // TODO: Implement removeFile // ============================================= const removeFile = (id: string) => { // Your code here }; return ( <div style={{ maxWidth: 600, margin: "0 auto", padding: 24, fontFamily: "system-ui" }}> <h2 style={{ fontSize: 20, fontWeight: 700, marginBottom: 4 }}> Drag & Drop Zone </h2> <p style={{ fontSize: 14, color: "#666", marginBottom: 24 }}> Build a file upload zone with drag-and-drop, click to browse, upload progress, and file management. </p> {/* ============================================= */} {/* TODO: Drop zone */} {/* ============================================= */} {/* Attach: onDragOver, onDragLeave, onDrop */} {/* onClick → inputRef.current?.click() */} {/* Change border/bg when isDragging */} <div onDragOver={handleDragOver} onDragLeave={handleDragLeave} onDrop={handleDrop} // onClick={() => inputRef.current?.click()} style={{ border: `2px dashed ${isDragging ? "#111" : "#d1d5db"}`, borderRadius: 12, padding: "48px 24px", textAlign: "center", cursor: "pointer", background: isDragging ? "#f9fafb" : "transparent", transition: "all 0.2s", }} > <div style={{ fontSize: 36, marginBottom: 8 }}> {isDragging ? "📥" : "☁️"} </div> <p style={{ fontWeight: 600, color: isDragging ? "#111" : "#555", marginBottom: 4 }}> {isDragging ? "Drop files here" : "Drag & drop files here"} </p> <p style={{ fontSize: 13, color: "#999" }}>or click to browse</p> </div> {/* TODO: Hidden file input */} {/* <input type="file" multiple hidden ref={inputRef} onChange={handleFileSelect} /> */} {/* File list */} <div style={{ marginTop: 24 }}> {files.length === 0 ? ( <div style={{ textAlign: "center", color: "#999", fontSize: 13, padding: "24px 0" }}> Drop files above to see them here. </div> ) : ( files.map((file) => ( <div key={file.id} style={{ display: "flex", alignItems: "center", gap: 12, border: "1px solid #e5e7eb", borderRadius: 8, padding: 14, marginBottom: 10, }} > {/* File icon */} <div style={{ width: 40, height: 40, borderRadius: 8, background: "#f3f4f6", display: "flex", alignItems: "center", justifyContent: "center", fontSize: 11, fontWeight: 600, color: "#666", textTransform: "uppercase", flexShrink: 0, }}> {file.name.split(".").pop()?.slice(0, 4) || "?"} </div> {/* File info + progress */} <div style={{ flex: 1, minWidth: 0 }}> <div style={{ display: "flex", justifyContent: "space-between", marginBottom: 4 }}> <span style={{ fontSize: 13, fontWeight: 600, color: "#111", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap", }}> {file.name} </span> <span style={{ fontSize: 11, color: "#999", flexShrink: 0, marginLeft: 8 }}> {formatSize(file.size)} </span> </div> {/* Progress bar */} <div style={{ height: 6, background: "#f3f4f6", borderRadius: 3, overflow: "hidden", }}> <div style={{ height: "100%", borderRadius: 3, transition: "width 0.3s", width: `${file.progress}%`, background: file.status === "error" ? "#ef4444" : file.status === "done" ? "#22c55e" : "#111", }} /> </div> {/* Status */} <div style={{ marginTop: 4, fontSize: 11 }}> {file.status === "uploading" && ( <span style={{ color: "#666" }}>Uploading... {Math.round(file.progress)}%</span> )} {file.status === "done" && ( <span style={{ color: "#22c55e" }}>Complete</span> )} {file.status === "error" && ( <span style={{ color: "#ef4444" }}>Upload failed</span> )} </div> </div> {/* Remove button */} <button onClick={() => removeFile(file.id)} style={{ color: "#999", fontSize: 18, flexShrink: 0, padding: 4 }} aria-label={`Remove ${file.name}`} > × </button> </div> )) )} </div> {/* Hint */} <div style={{ marginTop: 24, padding: 16, background: "#f9fafb", borderLeft: "4px solid #111", borderRadius: "0 8px 8px 0", fontSize: 13, color: "#555", }}> <strong>Key events:</strong> <ul style={{ margin: "8px 0 0 16px", padding: 0, lineHeight: 1.8 }}> <li><code>onDragOver</code> — must call <code>e.preventDefault()</code> to allow drop</li> <li><code>onDrop</code> — read files from <code>e.dataTransfer.files</code></li> <li><code>onDragLeave</code> — reset visual feedback</li> <li>Hidden <code><input type="file"></code> for click-to-browse</li> <li>Use <code>simulateUpload()</code> to animate progress per file</li> </ul> </div> </div> ); }