| 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 and the primary address. Each |
| 4 | * part is optional: a service that cannot answer leaves its part out. |
| 5 | */ |
| 6 | import type { Route } from "./+types/settings-menu-json"; |
| 7 | import { accounts, identity } from "../lib/services.server"; |
| 8 | import { getViewer } from "../lib/session.server"; |
| 9 | |
| 10 | export type AccountMenuData = { |
| 11 | name: string | null; |
| 12 | email: string | null; |
| 13 | }; |
| 14 | |
| 15 | export async function loader({ context }: Route.LoaderArgs) { |
| 16 | const user = getViewer(context); |
| 17 | if (!user) return Response.json(null, { status: 401, headers: { "cache-control": "private, no-store" } }); |
| 18 | const [profile, emails] = await Promise.all([ |
| 19 | identity.profile(user.username).catch(() => null), |
| 20 | accounts.listEmails(user).catch(() => null), |
| 21 | ]); |
| 22 | const primary = emails?.ok ? (emails.value.emails.find((e) => e.primary)?.email ?? null) : null; |
| 23 | const data: AccountMenuData = { |
| 24 | name: profile?.name?.trim() || null, |
| 25 | email: primary, |
| 26 | }; |
| 27 | // Not kept by the browser: the menu asks again after a name or address |
| 28 | // changes, and a kept copy would show the old one for a minute. |
| 29 | return Response.json(data, { headers: { "cache-control": "private, no-store" } }); |
| 30 | } |