page.tsx100 lines · main
1import { cookies } from 'next/headers';
2import { redirect } from 'next/navigation';
3
4import { SignOutButton } from './sign-out-button';
5
6interface SessionResponse {
7 user?: {
8 id?: string;
9 email?: string;
10 name?: string | null;
11 emailVerified?: boolean | string | null;
12 };
13 session?: { expiresAt?: string };
14}
15
16const INTERNAL_API_ORIGIN = process.env.BRIVEN_API_INTERNAL_URL ?? 'http://localhost:3001';
17
18export const metadata = { title: 'auth · account' };
19export const dynamic = 'force-dynamic';
20
21/**
22 * Hosted-pages account screen. Server-side fetches the session by
23 * forwarding the browser cookie to `/v1/auth-tenant/get-session`. Falls
24 * back to `/sign-in` when there is no session. Hard-redacts the email to
25 * a domain hint per CLAUDE.md §5.1 — even the account holder sees only
26 * the domain on this screen, mirroring the dashboard convention.
27 */
28export default async function HostedAccountPage({
29 params,
30}: {
31 params: Promise<{ projectId: string }>;
32}) {
33 const { projectId } = await params;
34 const cookieStore = await cookies();
35 const cookieHeader = cookieStore.toString();
36
37 let body: SessionResponse | null = null;
38 try {
39 const res = await fetch(`${INTERNAL_API_ORIGIN}/v1/auth-tenant/get-session`, {
40 method: 'GET',
41 headers: {
42 cookie: cookieHeader,
43 'x-briven-project-id': projectId,
44 },
45 cache: 'no-store',
46 });
47 if (res.ok) body = (await res.json()) as SessionResponse;
48 } catch {
49 body = null;
50 }
51
52 const user = body?.user;
53 if (!user?.id) {
54 redirect(`/auth/${projectId}/sign-in`);
55 }
56
57 const domain = user.email?.split('@')[1] ?? 'unknown';
58 const initial = user.name ? Array.from(user.name)[0] ?? null : null;
59
60 return (
61 <article className="flex flex-col gap-5 rounded-md border border-[var(--color-border)] bg-[var(--color-surface)] p-6">
62 <header>
63 <h1 className="font-mono text-base text-[var(--color-text)]">account</h1>
64 <p className="mt-1 font-mono text-xs text-[var(--color-text-muted)]">
65 signed in
66 </p>
67 </header>
68
69 <dl className="grid grid-cols-[max-content_1fr] gap-x-4 gap-y-1 font-mono text-xs">
70 <dt className="text-[var(--color-text-muted)]">id</dt>
71 <dd className="text-[var(--color-text)]">
72 <code title={user.id}>{user.id}</code>
73 </dd>
74 <dt className="text-[var(--color-text-muted)]">domain</dt>
75 <dd className="text-[var(--color-text)]">
76 <span className="text-[var(--color-text-muted)]">•••@</span>
77 {domain}
78 </dd>
79 <dt className="text-[var(--color-text-muted)]">name</dt>
80 <dd className="text-[var(--color-text)]">
81 {initial ? `${initial}•••` : '—'}
82 </dd>
83 <dt className="text-[var(--color-text-muted)]">verified</dt>
84 <dd className="text-[var(--color-text)]">
85 {user.emailVerified ? 'yes' : 'no'}
86 </dd>
87 {body?.session?.expiresAt ? (
88 <>
89 <dt className="text-[var(--color-text-muted)]">expires</dt>
90 <dd className="text-[var(--color-text)]">
91 {body.session.expiresAt.slice(0, 16).replace('T', ' ')} utc
92 </dd>
93 </>
94 ) : null}
95 </dl>
96
97 <SignOutButton projectId={projectId} />
98 </article>
99 );
100}