Skip to content
92 linesCodeBlameRaw
1import type { Folio } from "@g1t/contracts";
2import { RotateCcw, Trash2 } from "lucide-react";
3import { useState } from "react";
4import { data } from "react-router";
5
6import type { Route } from "./+types/trash";
7import { useFoliosAction } from "../../../components/folios/actions";
8import { KindIcon } from "../../../components/folios/kinds";
9import { EmptyState, ErrorText, TimeAgo } from "../../../components/ui";
10import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle } from "../../../components/ui/alert-dialog";
11import { canDo } from "../../../lib/folios";
12import { page } from "../../../lib/meta";
13import { folios } from "../../../lib/services.server";
14import { requireUser, roleIn } from "../../../lib/session.server";
15
16export function meta({ params, ...args }: Route.MetaArgs) {
17 return page(args, { title: `Trash · Artifacts · ${params.owner} · g1t` });
18}
19
20export async function loader({ params, context, request }: Route.LoaderArgs): Promise<{ items: Folio[] | null }> {
21 const viewer = requireUser(context, request);
22 if (!roleIn(viewer, params.owner)) throw data(null, { status: 404 });
23 const found = await folios.trashed(params.owner.toLowerCase(), viewer).catch(() => null);
24 return { items: found?.ok ? found.value : null };
25}
26
27/** Artifacts moved to the trash that the viewer can change: restore them, or delete them for good. After 30 days they go on their own. */
28export default function FoliosTrash({ loaderData, params }: Route.ComponentProps) {
29 const slug = params.owner.toLowerCase();
30 const { send, error } = useFoliosAction(slug);
31 const [forever, setForever] = useState<Folio | null>(null);
32 const items = loaderData.items;
33 return (
34 <div className="mx-auto max-w-3xl">
35 <h1 className="text-xl font-semibold tracking-tight">Trash</h1>
36 <p className="mt-1 text-sm text-muted">Artifacts come back with what was inside them. After 30 days in the trash they are deleted for good.</p>
37 {error && (
38 <div className="mt-3">
39 <ErrorText>{error}</ErrorText>
40 </div>
41 )}
42 <div className="mt-6">
43 {items === null ? (
44 <EmptyState title="Artifacts didn't answer">Try again in a moment.</EmptyState>
45 ) : items.length === 0 ? (
46 <EmptyState title="The trash is empty" />
47 ) : (
48 <ul className="divide-y divide-line overflow-hidden rounded-xl border border-line bg-surface">
49 {items.map((f) => (
50 <li key={f.id} className="flex items-center gap-3 px-3 py-2.5 sm:px-4">
51 <KindIcon kind={f.kind} />
52 <span className="min-w-0 grow">
53 <span className="block truncate text-sm">{f.title || "Untitled"}</span>
54 <span className="block truncate text-xs text-faint">
55 {f.space?.name ?? "Private"} · trashed {f.trashed_at ? <TimeAgo at={f.trashed_at} /> : null}
56 </span>
57 </span>
58 <button type="button" onClick={() => send("restore", { folio_id: f.id })} className="inline-flex h-8 shrink-0 items-center gap-1.5 rounded-md border border-line px-2.5 text-xs text-fg hover:bg-raised">
59 <RotateCcw size={13} /> Restore
60 </button>
61 {canDo(f.viewer_role, "manage") && (
62 <button type="button" onClick={() => setForever(f)} className="inline-flex h-8 shrink-0 items-center gap-1.5 rounded-md border border-danger/40 px-2.5 text-xs text-danger hover:bg-danger/10">
63 <Trash2 size={13} /> <span className="max-sm:hidden">Delete</span>
64 </button>
65 )}
66 </li>
67 ))}
68 </ul>
69 )}
70 </div>
71 <AlertDialog open={!!forever} onOpenChange={(open) => !open && setForever(null)}>
72 <AlertDialogContent>
73 <AlertDialogHeader>
74 <AlertDialogTitle>Delete &ldquo;{forever?.title || "Untitled"}&rdquo; for good?</AlertDialogTitle>
75 <AlertDialogDescription>It goes with its history, comments and everything inside it. This can&apos;t be undone.</AlertDialogDescription>
76 </AlertDialogHeader>
77 <AlertDialogFooter>
78 <AlertDialogCancel>Keep it</AlertDialogCancel>
79 <AlertDialogAction
80 onClick={() => {
81 if (forever) void send("delete", { folio_id: forever.id });
82 setForever(null);
83 }}
84 >
85 Delete for good
86 </AlertDialogAction>
87 </AlertDialogFooter>
88 </AlertDialogContent>
89 </AlertDialog>
90 </div>
91 );
92}