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}
)}
);
}