page.tsx113 lines · main
1import Link from 'next/link';
2
3import { fetchAuthUsers } from '../../lib/auth-api';
4
5export const metadata = { title: 'Auth · users' };
6export const dynamic = 'force-dynamic';
7
8function statusLabel(status?: string): string {
9 if (status === 'held') return 'on hold';
10 if (status === 'archived') return 'archived';
11 return 'active';
12}
13
14function statusColor(status?: string): string {
15 if (status === 'held') return 'var(--auth-accent, #FFFD74)';
16 if (status === 'archived') return 'var(--color-text-muted)';
17 return 'var(--color-text-muted)';
18}
19
20export default async function AuthProjectUsersPage({
21 params,
22}: {
23 params: Promise<{ projectId: string }>;
24}) {
25 const { projectId } = await params;
26 const result = await fetchAuthUsers(100, projectId);
27
28 return (
29 <section>
30 <header className="mb-6">
31 <h2 className="font-mono text-lg tracking-tight text-[var(--color-text)]">
32 users
33 </h2>
34 <p className="mt-1 font-mono text-sm text-[var(--color-text-muted)]">
35 app end-users for this project · click a row to manage access
36 </p>
37 </header>
38
39 {!result.ok ? (
40 <div className="rounded-md border border-dashed border-[var(--color-border)] p-8 font-mono text-sm text-[var(--color-text-muted)]">
41 {result.status === 401
42 ? 'sign in to briven.tech to see users'
43 : result.message || 'could not load users'}
44 </div>
45 ) : result.users.length === 0 ? (
46 <div className="rounded-md border border-dashed border-[var(--color-border)] p-8 font-mono text-sm text-[var(--color-text-muted)]">
47 no users for this project yet.
48 </div>
49 ) : (
50 <div className="overflow-x-auto rounded-md border border-[var(--color-border-subtle)]">
51 <table className="w-full min-w-[640px] text-left font-mono text-xs">
52 <thead className="border-b border-[var(--color-border-subtle)] bg-[var(--color-surface)] text-[var(--color-text-muted)]">
53 <tr>
54 <th className="px-3 py-2 font-normal">email / phone</th>
55 <th className="px-3 py-2 font-normal">status</th>
56 <th className="px-3 py-2 font-normal">joined</th>
57 <th className="px-3 py-2 font-normal" />
58 </tr>
59 </thead>
60 <tbody>
61 {result.users.map((u) => {
62 const status =
63 'status' in u && typeof u.status === 'string'
64 ? u.status
65 : 'active';
66 return (
67 <tr
68 key={u.id}
69 className="border-b border-[var(--color-border-subtle)] last:border-0 hover:bg-[var(--color-surface)]"
70 >
71 <td className="px-3 py-2 text-[var(--color-text)]">
72 <Link
73 href={`/dashboard/auth/${encodeURIComponent(projectId)}/users/${encodeURIComponent(u.id)}`}
74 className="hover:underline"
75 >
76 {u.emails[0] ?? u.phoneNumbers[0] ?? '—'}
77 </Link>
78 <span className="mt-0.5 block max-w-[14rem] truncate text-[10px] text-[var(--color-text-subtle)]">
79 {u.id}
80 </span>
81 </td>
82 <td
83 className="px-3 py-2"
84 style={{ color: statusColor(status) }}
85 >
86 {statusLabel(status)}
87 </td>
88 <td className="px-3 py-2 text-[var(--color-text-muted)]">
89 {u.timeJoined
90 ? new Date(u.timeJoined).toLocaleString()
91 : '—'}
92 </td>
93 <td className="px-3 py-2 text-right">
94 <Link
95 href={`/dashboard/auth/${encodeURIComponent(projectId)}/users/${encodeURIComponent(u.id)}`}
96 className="text-[10px] text-[var(--color-text-muted)] hover:text-[var(--color-text)]"
97 >
98 manage →
99 </Link>
100 </td>
101 </tr>
102 );
103 })}
104 </tbody>
105 </table>
106 <p className="border-t border-[var(--color-border-subtle)] px-3 py-2 font-mono text-[10px] text-[var(--color-text-muted)]">
107 {result.users.length} users · this project
108 </p>
109 </div>
110 )}
111 </section>
112 );
113}