Skip to content
89 linesCodeBlameRaw

Pick any line to see why it is the way it is: the commit, the pull request and issue it came from, and what the agent was thinking.

Docs: a workspace knowledge base people and agents write together1import type { DocPage } 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 { useDocsAction } from "../../../components/docs/actions";
8import { PageIcon } from "../../../components/docs/sidebar";
9import { EmptyState, ErrorText, TimeAgo } from "../../../components/ui";
10import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle } from "../../../components/ui/alert-dialog";
11import { page } from "../../../lib/meta";
12import { docs } from "../../../lib/services.server";
13import { requireUser, roleIn } from "../../../lib/session.server";
14
15export function meta({ params, ...args }: Route.MetaArgs) {
16 return page(args, { title: `Trash · Docs · ${params.owner} · g1t` });
17}
18
19export async function loader({ params, context, request }: Route.LoaderArgs): Promise<{ pages: DocPage[] | null }> {
20 const viewer = requireUser(context, request);
21 if (!roleIn(viewer, params.owner)) throw data(null, { status: 404 });
22 const found = await docs.trash(params.owner.toLowerCase(), viewer).catch(() => null);
23 return { pages: found?.ok ? found.value : null };
24}
25
26/** Pages moved to the trash, in spaces the viewer can edit: restore them, or delete them for good. */
27export default function DocsTrash({ loaderData, params }: Route.ComponentProps) {
28 const slug = params.owner.toLowerCase();
29 const { send, error } = useDocsAction(slug);
30 const [forever, setForever] = useState<DocPage | null>(null);
31 const pages = loaderData.pages;
32 return (
33 <div className="mx-auto max-w-3xl">
34 <h1 className="text-xl font-semibold tracking-tight">Trash</h1>
35 <p className="mt-1 text-sm text-muted">Pages come back with what was under them. Deleting for good can&apos;t be undone and needs full access to the space.</p>
36 {error && (
37 <div className="mt-3">
38 <ErrorText>{error}</ErrorText>
39 </div>
40 )}
41 <div className="mt-6">
42 {pages === null ? (
43 <EmptyState title="Docs didn't answer">Try again in a moment.</EmptyState>
44 ) : pages.length === 0 ? (
45 <EmptyState title="The trash is empty" />
46 ) : (
47 <ul className="divide-y divide-line overflow-hidden rounded-xl border border-line bg-surface">
48 {pages.map((p) => (
49 <li key={p.id} className="flex items-center gap-3 px-4 py-2.5">
50 <PageIcon icon={p.icon} />
51 <span className="min-w-0 grow">
52 <span className="block truncate text-sm">{p.title || "Untitled"}</span>
53 <span className="block text-xs text-faint">
54 {p.space_slug} · trashed {p.archived_at ? <TimeAgo at={p.archived_at} /> : null}
55 </span>
56 </span>
57 <button type="button" onClick={() => send("restore_page", { page_id: p.id })} className="inline-flex h-8 items-center gap-1.5 rounded-md border border-line px-2.5 text-xs text-fg hover:bg-raised">
58 <RotateCcw size={13} /> Restore
59 </button>
60 <button type="button" onClick={() => setForever(p)} className="inline-flex h-8 items-center gap-1.5 rounded-md border border-danger/40 px-2.5 text-xs text-danger hover:bg-danger/10">
61 <Trash2 size={13} /> Delete
62 </button>
63 </li>
64 ))}
65 </ul>
66 )}
67 </div>
68 <AlertDialog open={!!forever} onOpenChange={(open) => !open && setForever(null)}>
69 <AlertDialogContent>
70 <AlertDialogHeader>
71 <AlertDialogTitle>Delete &ldquo;{forever?.title || "Untitled"}&rdquo; for good?</AlertDialogTitle>
72 <AlertDialogDescription>It goes with its history, comments and every page under it. This can&apos;t be undone.</AlertDialogDescription>
73 </AlertDialogHeader>
74 <AlertDialogFooter>
75 <AlertDialogCancel>Keep it</AlertDialogCancel>
76 <AlertDialogAction
77 onClick={() => {
78 if (forever) void send("delete_page", { page_id: forever.id });
79 setForever(null);
80 }}
81 >
82 Delete for good
83 </AlertDialogAction>
84 </AlertDialogFooter>
85 </AlertDialogContent>
86 </AlertDialog>
87 </div>
88 );
89}