Skip to content

g1t/apps/web/app/routes/inbox-json.ts

39 lines1,589 bytesCodeBlame
1/**
2 * What the inbox panel in the top bar shows (components/inbox.tsx), fetched
3 * when it opens and for each tab: the newest items under the tab, and the
4 * unread counts. `?only=counts` is the bell's count alone, asked for every
5 * minute while a page is in view.
6 */
7import type { InboxCounts, InboxItem } from "@g1t/contracts";
8
9import type { Route } from "./+types/inbox-json";
10import { type InboxTab, inboxTab, severityOf } from "../lib/inbox";
11import { inbox } from "../lib/services.server";
12import { getViewer } from "../lib/session.server";
13
14/** As many items as the panel shows; the rest are on /inbox. */
15const PANEL_ITEMS = 30;
16
17export type InboxPanelData = {
18 tab: InboxTab;
19 /** Null when only the counts were asked for, or the list could not be read. */
20 items: InboxItem[] | null;
21 counts: InboxCounts | null;
22};
23
24export async function loader({ request, context }: Route.LoaderArgs) {
25 // Never kept: it changes as people work through it.
26 const headers = { "cache-control": "private, no-store" };
27 const user = getViewer(context);
28 if (!user) return Response.json(null, { status: 401, headers });
29 const url = new URL(request.url);
30 const tab = inboxTab(url.searchParams.get("tab"));
31 const [page, counts] = await Promise.all([
32 url.searchParams.get("only") === "counts"
33 ? null
34 : inbox.list(user, { severity: severityOf(tab), limit: PANEL_ITEMS }).catch(() => null),
35 inbox.counts(user.username).catch(() => null),
36 ]);
37 const data: InboxPanelData = { tab, items: page?.items ?? null, counts };
38 return Response.json(data, { headers });
39}