page.tsx100 lines · main
| 1 | import { apiFetch } from '@/lib/api'; |
| 2 | |
| 3 | export const metadata = { title: 'Auth · sessions' }; |
| 4 | export const dynamic = 'force-dynamic'; |
| 5 | |
| 6 | type SessionRow = { |
| 7 | handle: string; |
| 8 | userId: string; |
| 9 | tenantId: string; |
| 10 | expiresAt: string; |
| 11 | createdAt: string; |
| 12 | }; |
| 13 | |
| 14 | export default async function AuthProjectSessionsPage({ |
| 15 | params, |
| 16 | }: { |
| 17 | params: Promise<{ projectId: string }>; |
| 18 | }) { |
| 19 | const { projectId } = await params; |
| 20 | let sessions: SessionRow[] = []; |
| 21 | let err: string | null = null; |
| 22 | let status = 0; |
| 23 | |
| 24 | try { |
| 25 | const res = await apiFetch( |
| 26 | `/v1/auth-core/session/recent?limit=50&projectId=${encodeURIComponent(projectId)}`, |
| 27 | ); |
| 28 | status = res.status; |
| 29 | if (res.status === 401) { |
| 30 | err = 'sign in to briven.tech to see sessions'; |
| 31 | } else if (!res.ok) { |
| 32 | err = (await res.text().catch(() => '')) || res.statusText; |
| 33 | } else { |
| 34 | const body = (await res.json()) as { sessions?: SessionRow[] }; |
| 35 | sessions = body.sessions ?? []; |
| 36 | } |
| 37 | } catch (e) { |
| 38 | err = e instanceof Error ? e.message : String(e); |
| 39 | } |
| 40 | |
| 41 | return ( |
| 42 | <section> |
| 43 | <header className="mb-6"> |
| 44 | <h2 className="font-mono text-lg tracking-tight text-[var(--color-text)]"> |
| 45 | sessions |
| 46 | </h2> |
| 47 | <p className="mt-1 font-mono text-sm text-[var(--color-text-muted)]"> |
| 48 | who is signed in for this project |
| 49 | </p> |
| 50 | </header> |
| 51 | |
| 52 | {err ? ( |
| 53 | <div className="rounded-md border border-dashed border-[var(--color-border)] p-8 font-mono text-sm text-[var(--color-text-muted)]"> |
| 54 | {status === 401 ? err : err || 'could not load sessions'} |
| 55 | </div> |
| 56 | ) : sessions.length === 0 ? ( |
| 57 | <div className="rounded-md border border-dashed border-[var(--color-border)] p-8 font-mono text-sm text-[var(--color-text-muted)]"> |
| 58 | no active sessions for this project. |
| 59 | </div> |
| 60 | ) : ( |
| 61 | <div className="overflow-x-auto rounded-md border border-[var(--color-border-subtle)]"> |
| 62 | <table className="w-full min-w-[640px] text-left font-mono text-xs"> |
| 63 | <thead className="border-b border-[var(--color-border-subtle)] bg-[var(--color-surface)] text-[var(--color-text-muted)]"> |
| 64 | <tr> |
| 65 | <th className="px-3 py-2 font-normal">user</th> |
| 66 | <th className="px-3 py-2 font-normal">created</th> |
| 67 | <th className="px-3 py-2 font-normal">expires</th> |
| 68 | <th className="px-3 py-2 font-normal">handle</th> |
| 69 | </tr> |
| 70 | </thead> |
| 71 | <tbody> |
| 72 | {sessions.map((s) => ( |
| 73 | <tr |
| 74 | key={s.handle} |
| 75 | className="border-b border-[var(--color-border-subtle)] last:border-0" |
| 76 | > |
| 77 | <td className="max-w-[10rem] truncate px-3 py-2 text-[var(--color-text)]"> |
| 78 | {s.userId} |
| 79 | </td> |
| 80 | <td className="px-3 py-2 text-[var(--color-text-muted)]"> |
| 81 | {new Date(s.createdAt).toLocaleString()} |
| 82 | </td> |
| 83 | <td className="px-3 py-2 text-[var(--color-text-muted)]"> |
| 84 | {new Date(s.expiresAt).toLocaleString()} |
| 85 | </td> |
| 86 | <td className="max-w-[8rem] truncate px-3 py-2 text-[var(--color-text-muted)]"> |
| 87 | {s.handle} |
| 88 | </td> |
| 89 | </tr> |
| 90 | ))} |
| 91 | </tbody> |
| 92 | </table> |
| 93 | <p className="border-t border-[var(--color-border-subtle)] px-3 py-2 font-mono text-[10px] text-[var(--color-text-muted)]"> |
| 94 | {sessions.length} sessions · this project |
| 95 | </p> |
| 96 | </div> |
| 97 | )} |
| 98 | </section> |
| 99 | ); |
| 100 | } |