Add a delete button to the library detail page

Staff and uploaders (for their own items) get a Delete action on
/detail/J-<id> with an inline confirmation; on success it returns to the
library and refreshes files, duplicates and storage.
This commit is contained in:
2026-09-17 12:57:26 -05:00
parent cd490b0a23
commit 7ec8ee974e
@@ -1,5 +1,5 @@
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import { ArrowLeft, Download } from "lucide-react";
import { ArrowLeft, Download, Trash2 } from "lucide-react";
import { useEffect, useState } from "react";
import { Link, useNavigate } from "react-router-dom";
@@ -296,8 +296,10 @@ function CustomDataCard({ item, canEdit }: { item: MediaItem; canEdit: boolean }
export function LibraryDetail({ jId }: { jId: string }) {
const navigate = useNavigate();
const queryClient = useQueryClient();
const ordering = useUi((state) => state.ordering);
const user = useAuth((state) => state.user);
const [confirmDelete, setConfirmDelete] = useState(false);
const rawUrl = `/api/files/${jId}/raw/`;
const query = useQuery({
@@ -313,6 +315,20 @@ export function LibraryDetail({ jId }: { jId: string }) {
),
});
const deleteMutation = useMutation({
mutationFn: () =>
api<{ deleted: string[]; errors: { j_id?: string; error: string }[] }>(
"/api/delete/",
{ method: "POST", json: { j_ids: [jId] } },
),
onSuccess: () => {
void queryClient.invalidateQueries({ queryKey: ["files"] });
void queryClient.invalidateQueries({ queryKey: ["duplicates-md5"] });
void queryClient.invalidateQueries({ queryKey: ["storage"] });
navigate("/");
},
});
useEffect(() => {
function handleKeyDown(event: KeyboardEvent) {
if (event.ctrlKey || event.metaKey || event.altKey) return;
@@ -361,6 +377,12 @@ export function LibraryDetail({ jId }: { jId: string }) {
user.is_staff ||
user.role === "staff"),
);
const canDelete = Boolean(
user &&
(user.is_superuser ||
user.role === "staff" ||
user.id === item.uploaded_by_id),
);
return (
<DetailShell
@@ -381,6 +403,42 @@ export function LibraryDetail({ jId }: { jId: string }) {
<a href={`${rawUrl}?download=1`} className={linkButtonClass}>
<Download className="h-4 w-4" /> Download
</a>
{canDelete ? (
confirmDelete ? (
<>
<span className="text-xs text-ctp-subtext0">
Delete this item and all its copies?
</span>
<Button
variant="danger"
disabled={deleteMutation.isPending}
onClick={() => deleteMutation.mutate()}
>
{deleteMutation.isPending ? "Deleting…" : "Confirm delete"}
</Button>
<Button
variant="ghost"
onClick={() => setConfirmDelete(false)}
disabled={deleteMutation.isPending}
>
Cancel
</Button>
</>
) : (
<Button
variant="danger"
onClick={() => setConfirmDelete(true)}
title="Delete this item from the library"
>
<Trash2 className="h-4 w-4" /> Delete
</Button>
)
) : null}
{deleteMutation.isError ? (
<p className="text-sm text-ctp-red">
{errorMessage(deleteMutation.error)}
</p>
) : null}
</div>
}
media={