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.8 KB · 84 lines tsx
Raw Blame History
1import Link from "next/link";2import { Table, TableBody, TableCell, TableEmpty, TableHead, TableHeader, TableRow } from "@/components/ui/table";3import { StatusBadge } from "@/components/ui/badge";4import { CopyButton } from "@/components/ui/copy-button";5import { formatDate, formatNumber, timeAgo } from "@/lib/format";6import type { SessionRow } from "@/lib/queries/dashboard";7import { CloseSessionButton } from "./close-session-button";89function expiresIn(d: Date): string {10  const diff = d.getTime() - Date.now();11  if (diff <= 0) return "expired";12  const s = Math.round(diff / 1000);13  if (s < 60) return `in ${s}s`;14  const m = Math.round(s / 60);15  if (m < 60) return `in ${m}m`;16  return `in ${Math.round(m / 60)}h`;17}1819export function SessionsTable({ rows }: { rows: SessionRow[] }) {20  return (21    <Table>22      <TableHeader>23        <TableRow className="hover:bg-transparent">24          <TableHead>Session</TableHead>25          <TableHead>Label</TableHead>26          <TableHead>Project</TableHead>27          <TableHead>Created</TableHead>28          <TableHead>Last used</TableHead>29          <TableHead>Country</TableHead>30          <TableHead>Network</TableHead>31          <TableHead className="text-right">Requests</TableHead>32          <TableHead>Status</TableHead>33          <TableHead>Expires</TableHead>34          <TableHead className="text-right"> </TableHead>35        </TableRow>36      </TableHeader>37      <TableBody>38        {rows.length === 0 ? (39          <TableEmpty colSpan={11}>No sessions yet.</TableEmpty>40        ) : (41          rows.map((s) => (42            <TableRow key={s.id}>43              <TableCell>44                <span className="inline-flex items-center gap-1">45                  <code className="font-mono text-[12.5px]">{s.id}</code>46                  <CopyButton value={s.id} className="size-6" />47                </span>48              </TableCell>49              <TableCell className="max-w-[160px] truncate text-fg-muted" title={s.label ?? undefined}>50                {s.label ?? <span className="text-fg-subtle">—</span>}51              </TableCell>52              <TableCell className="text-fg-muted">{s.projectName}</TableCell>53              <TableCell className="whitespace-nowrap text-fg-muted" title={formatDate(s.createdAt, { timeStyle: "medium" })}>54                {timeAgo(s.createdAt)}55              </TableCell>56              <TableCell className="whitespace-nowrap text-fg-muted" title={s.lastUsedAt ? formatDate(s.lastUsedAt, { timeStyle: "medium" }) : undefined}>57                {s.lastUsedAt ? timeAgo(s.lastUsedAt) : <span className="text-fg-subtle">never</span>}58              </TableCell>59              <TableCell className="font-mono tabular text-fg-muted">{[s.country, s.region, s.city].filter(Boolean).join(" · ") || "any"}</TableCell>60              <TableCell className="text-fg-muted">{s.network === "isp" ? "ISP" : s.network}</TableCell>61              <TableCell className="text-right font-mono tabular">62                {s.requestCount > 0 ? (63                  <Link href={`/dashboard/requests?range=30d`} className="underline-offset-4 hover:underline">64                    {formatNumber(s.requestCount)}65                  </Link>66                ) : (67                  "0"68                )}69              </TableCell>70              <TableCell>71                <StatusBadge status={s.status} />72              </TableCell>73              <TableCell className="whitespace-nowrap font-mono tabular text-fg-muted" title={formatDate(s.expiresAt, { timeStyle: "medium" })}>74                {s.status === "active" ? expiresIn(s.expiresAt) : formatDate(s.expiresAt, { dateStyle: "short", timeStyle: "short" })}75              </TableCell>76              <TableCell className="text-right">{s.status === "active" ? <CloseSessionButton id={s.id} /> : null}</TableCell>77            </TableRow>78          ))79        )}80      </TableBody>81    </Table>82  );83}84