g1t/apps/web/app/routes/settings-menu-json.ts

34 lines1,479 bytesCodeBlame

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.

status.g1t.sh with incident management, invites that land you in the workspace, settings as pages, usage without quotas1/**
2 * What the account menu shows under the avatar, fetched when it opens so
3 * no page waits on it: the name people see, the primary address, and how
4 * many invites are left when there is a limit. Each part is optional: a
5 * service that cannot answer leaves its part out.
6 */
7import type { Route } from "./+types/settings-menu-json";
8import { accounts, identity } from "../lib/services.server";
9import { getViewer } from "../lib/session.server";
10import { loadInvites } from "../lib/invites.server";
11
12export type AccountMenuData = {
13 name: string | null;
14 email: string | null;
15 /** Invites left, or null when there is no limit (or none can be made yet). */
16 invites_left: number | null;
17};
18
19export async function loader({ context }: Route.LoaderArgs) {
20 const user = getViewer(context);
21 if (!user) return Response.json(null, { status: 401, headers: { "cache-control": "private, no-store" } });
22 const [profile, emails, invites] = await Promise.all([
23 identity.profile(user.username).catch(() => null),
24 accounts.listEmails(user).catch(() => null),
25 loadInvites(user),
26 ]);
27 const primary = emails?.ok ? (emails.value.emails.find((e) => e.primary)?.email ?? null) : null;
28 const data: AccountMenuData = {
29 name: profile?.name?.trim() || null,
30 email: primary,
31 invites_left: invites?.allowance.limit != null ? (invites.allowance.remaining ?? 0) : null,
32 };
33 return Response.json(data, { headers: { "cache-control": "private, max-age=60" } });
34}