| "use client"; |
|
|
| import { useCallback, useState } from "react"; |
| import { useRouter } from "next/navigation"; |
| import { Film, Loader2, Upload } from "lucide-react"; |
| import { Button } from "@/components/ui/button"; |
| import { cn, formatFileSize } from "@/lib/utils"; |
|
|
| const MAX_SIZE = 500 * 1024 * 1024; |
| const ACCEPTED = ["video/mp4", "video/quicktime", "video/webm"]; |
|
|
| export function VideoUpload() { |
| const router = useRouter(); |
| const [dragging, setDragging] = useState(false); |
| const [uploading, setUploading] = useState(false); |
| const [error, setError] = useState<string | null>(null); |
|
|
| const uploadFile = useCallback(async (file: File) => { |
| setError(null); |
|
|
| if (!ACCEPTED.includes(file.type) && !file.name.match(/\.(mp4|mov|webm)$/i)) { |
| setError("Please upload MP4, MOV, or WebM."); |
| return; |
| } |
| if (file.size > MAX_SIZE) { |
| setError(`File too large (${formatFileSize(file.size)}). Max 500MB.`); |
| return; |
| } |
|
|
| setUploading(true); |
| try { |
| const formData = new FormData(); |
| formData.append("video", file); |
|
|
| const res = await fetch("/api/upload", { method: "POST", body: formData }); |
| const data = await res.json(); |
| if (!res.ok) throw new Error(data.error || "Upload failed"); |
|
|
| router.push(`/editor?id=${data.id}`); |
| } catch (err) { |
| setError(err instanceof Error ? err.message : "Upload failed"); |
| } finally { |
| setUploading(false); |
| } |
| }, [router]); |
|
|
| const onDrop = (e: React.DragEvent) => { |
| e.preventDefault(); |
| setDragging(false); |
| const file = e.dataTransfer.files?.[0]; |
| if (file) void uploadFile(file); |
| }; |
|
|
| return ( |
| <div className="mx-auto w-full max-w-xl space-y-4"> |
| <div |
| onDragOver={(e) => { e.preventDefault(); setDragging(true); }} |
| onDragLeave={() => setDragging(false)} |
| onDrop={onDrop} |
| className={cn( |
| "flex min-h-[320px] cursor-pointer flex-col items-center justify-center rounded-2xl border-2 border-dashed p-10 transition-colors", |
| dragging ? "border-zinc-900 bg-zinc-50" : "border-zinc-300 bg-white hover:border-zinc-400", |
| )} |
| onClick={() => document.getElementById("video-input")?.click()} |
| > |
| <input |
| id="video-input" |
| type="file" |
| accept="video/mp4,video/quicktime,video/webm,.mp4,.mov,.webm" |
| className="hidden" |
| onChange={(e) => { |
| const file = e.target.files?.[0]; |
| if (file) void uploadFile(file); |
| }} |
| /> |
| {uploading ? ( |
| <Loader2 className="h-12 w-12 animate-spin text-zinc-500" /> |
| ) : ( |
| <Upload className="h-12 w-12 text-zinc-400" /> |
| )} |
| <h2 className="mt-4 text-xl font-semibold">Drop video here</h2> |
| <p className="mt-2 text-sm text-zinc-500">or click to browse</p> |
| <p className="mt-4 text-xs text-zinc-400">MP4, MOV, WebM • Max 500MB • 5 min</p> |
| </div> |
| |
| {error && ( |
| <div className="rounded-lg border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700"> |
| {error} |
| </div> |
| )} |
| |
| <div className="flex items-center gap-2 rounded-lg bg-zinc-100 px-4 py-3 text-sm text-zinc-600"> |
| <Film className="h-4 w-4" /> |
| Upload a short vertical video to add viral-style captions |
| </div> |
| </div> |
| ); |
| } |
|
|