import { useActionMutation } from "@agent-native/core/client/hooks"; import { useT } from "@agent-native/core/client/i18n"; import { IconAlertTriangle, IconArrowBackUp, IconTrash, } from "@tabler/icons-react"; import { useMemo, useState } from "react"; import { toast } from "sonner"; import { EmptyState } from "@/components/library/empty-state"; import { PageHeader } from "@/components/library/page-header"; import { RecordingCard } from "@/components/library/recording-card"; import { SortMenu, type SortKey } from "@/components/library/sort-menu"; import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, } from "@/components/ui/alert-dialog"; import { Button } from "@/components/ui/button"; import { useRecordings, type RecordingSummary } from "@/hooks/use-library"; import enMessages from "@/i18n/en-US"; export function meta() { return [{ title: enMessages.clipsFinalRaw.trashPageTitle }]; } function Skeleton() { return (
); } export default function TrashRoute() { const t = useT(); const [sort, setSort] = useState("recent"); const [selected, setSelected] = useState>(new Set()); const [lastSelectedId, setLastSelectedId] = useState(null); const [confirmPurge, setConfirmPurge] = useState(false); const [singlePurgeId, setSinglePurgeId] = useState(null); const [isBulkPending, setIsBulkPending] = useState(false); const args = useMemo(() => ({ view: "trash" as const, sort }), [sort]); const { data, isLoading, isError, isFetching, refetch } = useRecordings(args); const recordings = (data?.recordings ?? []) as RecordingSummary[]; // These actions are owned by other teams and ship with the template. const restore = useActionMutation("restore-recording"); const purge = useActionMutation( "delete-recording-permanent", ); const toggleSelect = (id: string, shiftKey = false) => { setSelected((prev) => { if (shiftKey && lastSelectedId && lastSelectedId !== id) { const ids = recordings.map((r) => r.id); const fromIndex = ids.indexOf(lastSelectedId); const toIndex = ids.indexOf(id); if (fromIndex !== -1 && toIndex !== -1) { const [start, end] = fromIndex < toIndex ? [fromIndex, toIndex] : [toIndex, fromIndex]; const next = new Set(prev); for (let i = start; i <= end; i++) next.add(ids[i]); return next; } } const next = new Set(prev); if (next.has(id)) next.delete(id); else next.add(id); return next; }); setLastSelectedId(id); }; const restoreAll = async (ids: string[]) => { if (ids.length === 0) return; setIsBulkPending(true); try { if (ids.length === 1) { try { await restore.mutateAsync({ id: ids[0] }); toast.success(t("trashRoute.restored")); setSelected((prev) => { const next = new Set(prev); next.delete(ids[0]); return next; }); } catch (err: any) { toast.error(err?.message ?? t("trashRoute.restoreFailed")); } return; } const results = await Promise.allSettled( ids.map((id) => restore.mutateAsync({ id })), ); const succeededIds = ids.filter( (_, i) => results[i].status === "fulfilled", ); const failed = ids.length - succeededIds.length; if (succeededIds.length > 0) { toast.success( t("trashRoute.clipsRestored", { count: succeededIds.length }), ); setSelected((prev) => { const next = new Set(prev); succeededIds.forEach((id) => next.delete(id)); return next; }); } if (failed > 0) { toast.error(t("trashRoute.clipsRestoreFailed", { count: failed })); } } finally { setIsBulkPending(false); } }; const purgeAll = async (ids: string[]) => { if (ids.length === 0) return; setIsBulkPending(true); try { if (ids.length === 1) { try { await purge.mutateAsync({ id: ids[0] }); toast.success(t("trashRoute.permanentlyDeleted")); setSelected((prev) => { const next = new Set(prev); next.delete(ids[0]); return next; }); } catch (err: any) { toast.error(err?.message ?? t("trashRoute.deleteFailed")); } return; } const results = await Promise.allSettled( ids.map((id) => purge.mutateAsync({ id })), ); const succeededIds = ids.filter( (_, i) => results[i].status === "fulfilled", ); const failed = ids.length - succeededIds.length; if (succeededIds.length > 0) { toast.success( t("trashRoute.clipsPermanentlyDeleted", { count: succeededIds.length, }), ); setSelected((prev) => { const next = new Set(prev); succeededIds.forEach((id) => next.delete(id)); return next; }); } if (failed > 0) { toast.error(t("trashRoute.clipsDeleteFailed", { count: failed })); } } finally { setIsBulkPending(false); setConfirmPurge(false); } }; const selectedIds = Array.from(selected); const allSelected = recordings.length > 0 && selected.size === recordings.length; const toggleSelectAll = () => { setSelected((prev) => prev.size === recordings.length ? new Set() : new Set(recordings.map((r) => r.id)), ); setLastSelectedId(null); }; return (

{t("trashRoute.title")}

{selectedIds.length > 0 && ( <> {t("trashRoute.selected", { count: selectedIds.length })} )}
{isLoading ? (
{Array.from({ length: 6 }).map((_, i) => ( ))}
) : isError ? (

{t("libraryGrid.loadFailedTitle")}

{t("libraryGrid.loadFailedBody")}

) : recordings.length === 0 ? ( ) : (
{recordings.map((r) => ( restoreAll([r.id])} onTrash={() => setSinglePurgeId(r.id)} /> ))}
)}
{t("trashRoute.deleteForeverTitle")} {t("trashRoute.bulkDeleteDescription", { count: selectedIds.length, })} {t("common.cancel")} purgeAll(selectedIds)} > {t("trashRoute.deleteForever")} { if (!open) setSinglePurgeId(null); }} > {t("trashRoute.deleteForeverTitle")} {t("trashRoute.singleDeleteDescription")} {t("common.cancel")} { if (singlePurgeId) purgeAll([singlePurgeId]); setSinglePurgeId(null); }} > {t("trashRoute.deleteForever")}
); }