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