SPB Git forge

spb/fetcha

Public
11commits 1branches 0releases
1.5 MBsize
maindefault branch
16 days agolast push
TypeScript 97.5% SQL 1.4% Python 0.8%
3.6 KB · 74 lines tsx
Raw Blame History
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