Skip to content
File

Blob: src/client/pages/account/index.tsx

typescript82 lines
1import { NavCard } from "@/client/components/ui/nav-card";
2import { PageHeader } from "@/client/components/ui/page-header";
3import { Skeleton } from "@/client/components/ui/skeleton";
4import { useRequireSession } from "@/client/hooks/use-auth-guard";
5import { type SessionUser } from "@/client/lib/auth-client";
6import { fallbackHandle } from "@/client/lib/handle";
7 
8const HUB_CARD_COUNT = 6;
9 
10const AccountHubSkeleton = () => (
11 <div className="mx-auto max-w-3xl" aria-busy="true">
12 <div className="mb-8">
13 <Skeleton className="mb-3 h-8 w-44" />
14 <Skeleton className="h-4 w-64" />
15 </div>
16 <div className="flex flex-col gap-3">
17 {Array.from({ length: HUB_CARD_COUNT }, (_, i) => (
18 <div
19 key={i}
20 className="flex h-[5.5rem] items-center justify-between gap-4 rounded-2xl border border-zinc-800/60 bg-zinc-900/40 p-5 sm:p-6"
21 >
22 <div className="min-w-0 flex-1 space-y-2">
23 <Skeleton className="h-5 w-32" />
24 <Skeleton className="h-3 w-56" />
25 </div>
26 <Skeleton className="h-5 w-5 shrink-0 rounded-full" />
27 </div>
28 ))}
29 </div>
30 </div>
31);
32 
33// `/account` is the settings hub. Each card is a focused subpage that
34// owns one concern; the hub never edits anything inline. Adding new
35// surfaces (2FA, email change, delete account) means appending another
36// NavCard, not rearranging this page.
37export const AccountPage = () => {
38 const session = useRequireSession("/account");
39 
40 if (session.isPending || !session.data) {
41 return <AccountHubSkeleton />;
42 }
43 
44 const sessionUser = session.data.user as SessionUser;
45 const handle = sessionUser.preferredUsername ?? fallbackHandle(sessionUser);
46 
47 return (
48 <div className="mx-auto max-w-3xl">
49 <PageHeader title="Your account" description={`Signed in as ${session.data.user.email}.`} />
50 <div className="flex flex-col gap-3">
51 <NavCard
52 to="/account/profile"
53 title="Profile"
54 description="Your name and username."
55 meta={<code className="font-mono">{handle}</code>}
56 />
57 <NavCard
58 to="/account/password"
59 title="Password"
60 description="Change the password on your email + password credential."
61 />
62 <NavCard
63 to="/account/identities"
64 title="Identities"
65 description="Link or unlink GitHub and Google for sign-in."
66 />
67 <NavCard to="/account/sessions" title="Sessions" description="Devices currently signed in to tessera." />
68 <NavCard
69 to="/account/apps"
70 title="Connected apps"
71 description="Third-party apps you've authorized to sign you in."
72 />
73 <NavCard
74 to="/account/userinfo"
75 title="User info"
76 description="The OIDC claims downstream apps see when you sign in."
77 />
78 </div>
79 </div>
80 );
81};