TypeScript 97.5%
SQL 1.4%
Python 0.8%
1import Link from "next/link";2import { Users } from "lucide-react";3import { requireAdmin } from "@/lib/session";4import { listUsers, pageNum, str } from "@/lib/queries/admin";5import { formatNumber } from "@/lib/format";6import { PageHeader } from "@/components/ui/page-header";7import { Badge } from "@/components/ui/badge";8import { EmptyState } from "@/components/ui/empty-state";9import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table";10import { DateCell, Pagination, Panel, SearchForm, numCell } from "@/components/admin/primitives";1112export const dynamic = "force-dynamic";1314export default async function AdminUsersPage({ searchParams }: { searchParams: Promise<Record<string, string | string[] | undefined>> }) {15 await requireAdmin();16 const sp = await searchParams;17 const q = str(sp.q);18 const page = pageNum(sp.page);19 const data = await listUsers({ q: q || undefined, page });2021 return (22 <>23 <PageHeader eyebrow="Accounts" title="Users" description={`${formatNumber(data.total)} accounts. Search by email, name or id.`} actions={<SearchForm action="/admin/users" q={q} placeholder="email, name or user id" />} />24 <Panel flush>25 {data.total === 0 ? (26 <div className="p-4">27 <EmptyState icon={Users} title={q ? "No users match" : "No users yet"} description={q ? "Try a partial email or a different spelling." : "Users appear here as soon as they sign up."} compact />28 </div>29 ) : (30 <>31 <Table>32 <TableHeader>33 <TableRow>34 <TableHead>Email</TableHead>35 <TableHead>Name</TableHead>36 <TableHead>Verified</TableHead>37 <TableHead>Role</TableHead>38 <TableHead>Banned</TableHead>39 <TableHead className="text-right">Orgs</TableHead>40 <TableHead className="text-right">Created</TableHead>41 <TableHead className="text-right">Last login</TableHead>42 </TableRow>43 </TableHeader>44 <TableBody>45 {data.rows.map((u) => (46 <TableRow key={u.id}>47 <TableCell>48 <Link href={`/admin/users/${u.id}`} className="font-medium hover:underline">49 {u.email}50 </Link>51 </TableCell>52 <TableCell className="max-w-[200px] truncate text-fg-muted">{u.name || "—"}</TableCell>53 <TableCell>{u.emailVerified ? <Badge variant="success" dot>verified</Badge> : <Badge variant="outline">unverified</Badge>}</TableCell>54 <TableCell>{u.role === "admin" ? <Badge variant="danger">admin</Badge> : <span className="text-fg-muted">user</span>}</TableCell>55 <TableCell>{u.banned ? <Badge variant="danger" dot>banned</Badge> : <span className="text-fg-subtle">—</span>}</TableCell>56 <TableCell className={numCell}>{u.orgs}</TableCell>57 <TableCell className="text-right">58 <DateCell value={u.createdAt} />59 </TableCell>60 <TableCell className="text-right">61 <DateCell value={u.lastLogin} />62 </TableCell>63 </TableRow>64 ))}65 </TableBody>66 </Table>67 <Pagination page={data.page} pages={data.pages} total={data.total} pageSize={data.pageSize} basePath="/admin/users" params={{ q }} />68 </>69 )}70 </Panel>71 </>72 );73}74