"use client"; import { useEffect, useRef, useState } from "react"; import { useRouter } from "next/navigation"; const ACCEPTED = [".backerup", ".json"]; const MAX_BYTES = 500 * 1024 * 1024; function formatBytes(n: number) { if (n < 1024) return `${n} B`; if (n < 1024 * 1024) return `${(n / 1024).toFixed(1)} KB`; return `${(n / 1024 / 1024).toFixed(2)} MB`; } export default function SharePage() { const router = useRouter(); const inputRef = useRef(null); const [file, setFile] = useState(null); const [displayName, setDisplayName] = useState(""); const [description, setDescription] = useState(""); const [group, setGroup] = useState(""); const [existingGroups, setExistingGroups] = useState([]); const [dragging, setDragging] = useState(false); const [uploading, setUploading] = useState(false); const [error, setError] = useState(null); // Pre-fetch existing group names for autocomplete useEffect(() => { fetch("/api/files") .then((r) => r.json()) .then((records: Array<{ group?: string }>) => { const groups = Array.from( new Set(records.map((r) => r.group).filter(Boolean) as string[]) ).sort(); setExistingGroups(groups); }) .catch(() => { }); }, []); function pickFile(f: File) { const name = f.name.toLowerCase(); if (!ACCEPTED.some((ext) => name.endsWith(ext))) { setError("Only .backerup and .json files are accepted."); return; } if (f.size > MAX_BYTES) { setError("File exceeds the 500 MB limit."); return; } setError(null); setFile(f); } function onDrop(e: React.DragEvent) { e.preventDefault(); setDragging(false); const dropped = e.dataTransfer.files[0]; if (dropped) pickFile(dropped); } async function handleSubmit(e: React.FormEvent) { e.preventDefault(); if (!file) return; setUploading(true); setError(null); try { const formData = new FormData(); formData.append("file", file); if (displayName.trim()) formData.append("displayName", displayName.trim()); formData.append("description", description); if (group.trim()) formData.append("group", group.trim()); const res = await fetch("/api/upload", { method: "POST", body: formData }); const data = (await res.json()) as { token?: string; error?: string }; if (!res.ok || !data.token) { throw new Error(data.error ?? "Upload failed"); } router.push(`/files/${data.token}`); } catch (err) { setError(err instanceof Error ? err.message : "Unknown error"); setUploading(false); } } const isBackerup = file?.name.toLowerCase().endsWith(".backerup"); return (

Share a file

Upload a{" "} .backerup snapshot or{" "} .json container config. Anyone with the link can download it.

{/* Drop zone */}
inputRef.current?.click()} onDragOver={(e) => { e.preventDefault(); setDragging(true); }} onDragLeave={() => setDragging(false)} onDrop={onDrop} > { const f = e.target.files?.[0]; if (f) pickFile(f); }} /> {file ? ( <>
{isBackerup ? "📦" : "🗂️"}

{file.name}

{formatBytes(file.size)} ·{" "}

) : ( <>
📁

Drop a file here or click to browse

Accepts .backerup and{" "} .json — up to 500 MB

)}
{/* Display name */}
setDisplayName(e.target.value)} placeholder="e.g. Production Postgres backup" maxLength={120} className="w-full rounded-xl border border-slate-700 bg-slate-900 px-4 py-3 text-sm text-slate-200 placeholder-slate-600 outline-none focus:border-cyan-500 transition-colors" />

Shown instead of the raw filename.

{/* Group */}
setGroup(e.target.value)} placeholder="e.g. production, homelab, postgres" maxLength={60} className="w-full rounded-xl border border-slate-700 bg-slate-900 px-4 py-3 text-sm text-slate-200 placeholder-slate-600 outline-none focus:border-cyan-500 transition-colors" /> {existingGroups.length > 0 && ( {existingGroups.map((g) => ( )}

Tag this upload so others can filter by group on the browse page.

{/* Description */}