Files
jerapah-flow/packages/web/src/pages/WorkflowTrashPage.jsx
T
nsrbandCursor db884808f2 refactor: split oversized modules and start server src/stores layout
Consolidate format helpers under lib/format, split React Query hooks by domain, extract AuthPicker/ConfigRefHint and HTTP trigger routing, demote file-private exports, and move KV/secrets/variables/profiles/http-auths stores under src/stores with root re-exports.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-22 06:16:38 +07:00

146 lines
5.1 KiB
React

import { useState } from "react";
import { Link, useNavigate } from "react-router-dom";
import { LuArrowLeft, LuRotateCcw, LuTrash2 } from "react-icons/lu";
import { errorMessage } from "../api/client.js";
import {
usePurgeWorkflowTrash,
useRestoreWorkflowTrash,
useWorkflowTrash,
} from "../api/hooks.js";
import { ConfirmDialog } from "../components/ConfirmDialog.jsx";
import { formatTime } from "../lib/format";
import { useNotifications } from "../notifications.jsx";
function formatAge(ms) {
if (ms == null || ms < 0) return "—";
const mins = Math.floor(ms / 60_000);
if (mins < 60) return `${mins}m ago`;
const hours = Math.floor(mins / 60);
if (hours < 48) return `${hours}h ago`;
const days = Math.floor(hours / 24);
return `${days}d ago`;
}
export function WorkflowTrashPage() {
const navigate = useNavigate();
const { notify } = useNotifications();
const { data: items = [], isLoading } = useWorkflowTrash();
const restore = useRestoreWorkflowTrash();
const purge = usePurgeWorkflowTrash();
const [confirmPurge, setConfirmPurge] = useState(null);
return (
<div className="space-y-4">
<div className="flex items-center gap-2">
<Link to="/workflows" className="btn btn-ghost btn-sm btn-square" aria-label="Back">
<LuArrowLeft className="size-4" />
</Link>
<h1 className="text-xl font-semibold">Workflow trash</h1>
</div>
<p className="text-sm opacity-70">
Deleted workflows are kept for 7 days. Restore to bring them back, or delete permanently
to remove the file and revision history.
</p>
{restore.isError ? (
<p className="text-error text-sm">{errorMessage(restore.error)}</p>
) : null}
{purge.isError ? (
<p className="text-error text-sm">{errorMessage(purge.error)}</p>
) : null}
{isLoading ? (
<span className="loading loading-spinner" />
) : !items.length ? (
<p className="opacity-50">Trash is empty.</p>
) : (
<div className="overflow-x-auto">
<table className="table table-sm">
<thead>
<tr>
<th>Name</th>
<th>File</th>
<th>Owner</th>
<th>Deleted</th>
<th>Age</th>
<th>Purge in</th>
<th />
</tr>
</thead>
<tbody>
{items.map((item) => (
<tr key={item.id} className="hover">
<td>{item.name ?? "—"}</td>
<td className="font-mono text-xs">{item.file}</td>
<td className="font-mono text-xs">{item.owner}</td>
<td className="whitespace-nowrap">{formatTime(item.deleted_at)}</td>
<td>{formatAge(item.age_ms)}</td>
<td>
{item.days_until_purge <= 0 ? (
<span className="text-warning">soon</span>
) : (
`${item.days_until_purge}d`
)}
</td>
<td className="text-right whitespace-nowrap">
<button
type="button"
className="btn btn-ghost btn-xs"
title="Restore"
aria-label="Restore"
disabled={restore.isPending}
onClick={() =>
restore.mutate(item.id, {
onSuccess: (data) => {
notify.success("Workflow restored");
navigate(
`/workflows/${encodeURIComponent(data.owner)}/${encodeURIComponent(data.file)}/edit`,
);
},
})
}
>
<LuRotateCcw className="size-4" />
</button>
<button
type="button"
className="btn btn-ghost btn-xs text-error"
title="Delete permanently"
aria-label="Delete permanently"
onClick={() => setConfirmPurge(item)}
>
<LuTrash2 className="size-4" />
</button>
</td>
</tr>
))}
</tbody>
</table>
</div>
)}
<ConfirmDialog
open={Boolean(confirmPurge)}
title="Delete permanently?"
message={
confirmPurge
? `${confirmPurge.name ?? confirmPurge.file} (${confirmPurge.owner}/${confirmPurge.file}) will be removed forever, including revision history.`
: ""
}
confirmLabel="Delete forever"
loading={purge.isPending}
onCancel={() => setConfirmPurge(null)}
onConfirm={() =>
purge.mutate(confirmPurge.id, {
onSuccess: () => {
notify.success("Permanently deleted");
setConfirmPurge(null);
},
})
}
/>
</div>
);
}