Skip to content
46 linesCodeBlameRaw
1/**
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";
11import { invitesPage } from "../lib/invites";
12
13export type AccountMenuData = {
14 name: string | null;
15 email: string | null;
16 /** Invites left, or null when there is no limit (or none can be made yet). */
17 invites_left: number | null;
18 /**
19 * Whether the menus list Settings → Invites: while sign-up takes an
20 * invite, or, once anyone can sign up, when there are invites already
21 * made to look back on (`invitesPage`).
22 */
23 invites_page: boolean;
24};
25
26export async function loader({ context }: Route.LoaderArgs) {
27 const user = getViewer(context);
28 if (!user) return Response.json(null, { status: 401, headers: { "cache-control": "private, no-store" } });
29 const [profile, emails, invites] = await Promise.all([
30 identity.profile(user.username).catch(() => null),
31 accounts.listEmails(user).catch(() => null),
32 loadInvites(user),
33 ]);
34 const primary = emails?.ok ? (emails.value.emails.find((e) => e.primary)?.email ?? null) : null;
35 const data: AccountMenuData = {
36 name: profile?.name?.trim() || null,
37 email: primary,
38 // Nothing to make once anyone can sign up, so no count either.
39 invites_left: invites?.mode !== "open" && invites?.allowance.limit != null ? (invites.allowance.remaining ?? 0) : null,
40 // An account that cannot list its invites yet (unconfirmed) keeps the page: it says why.
41 invites_page: invites ? invitesPage(invites.mode, invites.invites.length).listed : true,
42 };
43 // Not kept by the browser: the menu asks again after a name, address or
44 // invite changes, and a kept copy would show the old one for a minute.
45 return Response.json(data, { headers: { "cache-control": "private, no-store" } });
46}