import { useState, useMemo } from "react"; import { useNavigate } from "react-router-dom"; import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query"; import { getDownloads, deleteDownload, deleteAllDownloads, restoreDownload } from "../api"; import SortPicker from "../components/SortPicker"; const HISTORY_SORTS = [ { value: "newest", label: "Newest" }, { value: "oldest", label: "Oldest" }, { value: "title", label: "Title A–Z" }, { value: "status", label: "Status" }, ]; function sortHistory(items, sort) { const arr = [...items]; if (sort === "oldest") return arr.sort((a, b) => new Date(a.created_at) - new Date(b.created_at)); if (sort === "title") return arr.sort((a, b) => (a.video_title ?? "").localeCompare(b.video_title ?? "")); if (sort === "status") return arr.sort((a, b) => a.status.localeCompare(b.status)); return arr.sort((a, b) => new Date(b.created_at) - new Date(a.created_at)); } const STATUS_COLORS = { pending: "text-zinc-400", downloading: "text-accent", complete: "text-green-400", failed: "text-red-400", }; const STATUS_LABELS = { pending: "Pending", downloading: "Downloading", complete: "Complete", failed: "Failed", }; function ProgressBar({ pct }) { return (
); } function daysRemaining(pendingDeleteAt) { const diff = new Date(pendingDeleteAt) - Date.now(); const days = Math.ceil(diff / (1000 * 60 * 60 * 24)); return Math.max(0, days); } export default function DownloadsPage() { const [historySort, setHistorySort] = useState("newest"); const [confirmClear, setConfirmClear] = useState(false); const qc = useQueryClient(); const { data: downloads, isLoading } = useQuery({ queryKey: ["downloads"], queryFn: () => getDownloads().then((r) => r.data), refetchInterval: (query) => { const active = query.state.data?.some( (d) => d.status === "pending" || d.status === "downloading" ); return active ? 2000 : false; }, }); const clearAllMut = useMutation({ mutationFn: deleteAllDownloads, onSuccess: () => { qc.invalidateQueries({ queryKey: ["downloads"] }); setConfirmClear(false); }, }); if (isLoading) { return (
); } const active = downloads?.filter((d) => ["pending", "downloading"].includes(d.status)) ?? []; const trash = downloads?.filter((d) => d.pending_delete_at) ?? []; const history = useMemo( () => sortHistory( downloads?.filter((d) => !["pending", "downloading"].includes(d.status) && !d.pending_delete_at) ?? [], historySort, ), [downloads, historySort], ); const hasRemovable = history.length > 0 || trash.length > 0; return (

Downloads

{hasRemovable && ( confirmClear ? (
Remove all history and trash?
) : ( ) )}
{active.length > 0 && (

Active

{active.map((d) => ( ))}
)} {trash.length > 0 && (

Trash

Watched downloads — auto-deleted when the timer runs out.

{trash.map((d) => ( ))}
)} {history.length > 0 && (

History

{history.map((d) => ( ))}
)} {!downloads?.length && (

No downloads yet. Find a video and hit Download.

)}
); } function TrashRow({ download: d }) { const navigate = useNavigate(); const qc = useQueryClient(); const restoreMut = useMutation({ mutationFn: () => restoreDownload(d.id), onSuccess: () => qc.invalidateQueries({ queryKey: ["downloads"] }), }); const deleteMut = useMutation({ mutationFn: () => deleteDownload(d.id), onSuccess: () => { qc.invalidateQueries({ queryKey: ["downloads"] }); qc.invalidateQueries({ queryKey: ["video-play", d.youtube_video_id] }); }, }); const days = daysRemaining(d.pending_delete_at); const urgentColor = days <= 1 ? "text-red-400" : days <= 3 ? "text-amber-400" : "text-zinc-500"; return (
navigate(`/watch/${d.youtube_video_id}`)} > {d.video_thumbnail_url ? ( ) : (
)}

navigate(`/watch/${d.youtube_video_id}`)} > {d.video_title}

{days === 0 ? "Deletes today" : `Deletes in ${days} day${days !== 1 ? "s" : ""}`}

); } function DownloadRow({ download: d }) { const navigate = useNavigate(); const qc = useQueryClient(); const deleteMut = useMutation({ mutationFn: () => deleteDownload(d.id), onSuccess: () => { qc.invalidateQueries({ queryKey: ["downloads"] }); qc.invalidateQueries({ queryKey: ["video-play", d.youtube_video_id] }); }, }); const isActive = d.status === "pending" || d.status === "downloading"; const canWatch = d.status === "complete" && d.youtube_video_id; return (
navigate(`/watch/${d.youtube_video_id}`) : undefined} > {d.video_thumbnail_url ? ( ) : (
)}

navigate(`/watch/${d.youtube_video_id}`) : undefined} > {d.video_title}

{STATUS_LABELS[d.status]} {d.status === "downloading" && ( {d.progress_percent.toFixed(0)}% )}
{d.status === "downloading" && } {d.error_message && (

{d.error_message}

)}
); }