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%
4.9 KB · 101 lines tsx
Raw Blame History
1import Link from "next/link";2import { ArrowRight, FolderKanban } from "lucide-react";3import { getWorkspace } from "@/lib/session";4import { projectStatsByProject } from "@/lib/queries/account";5import { formatDateOnly, formatNumber, formatUsd, truncate } from "@/lib/format";6import { PageHeader } from "@/components/ui/page-header";7import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table";8import { Badge } from "@/components/ui/badge";9import { Button } from "@/components/ui/button";10import { EmptyState } from "@/components/ui/empty-state";11import { CreateProjectDialog } from "@/components/dashboard/projects/create-project-dialog";12import { SelectProjectButton } from "@/components/dashboard/projects/project-actions";1314export const dynamic = "force-dynamic";1516export const metadata = { title: "Projects · Fetcha" };1718export default async function ProjectsPage({ searchParams }: { searchParams: Promise<{ new?: string }> }) {19  const sp = await searchParams;20  const ws = await getWorkspace();21  const stats = await projectStatsByProject(ws.organization.id);2223  return (24    <div className="grid gap-6">25      <PageHeader26        title="Projects"27        description="Each project has its own API keys, request log, defaults and spending limits. Switch the current project from the top bar or here."28        actions={<CreateProjectDialog defaultOpen={sp.new === "1"} />}29      />3031      {ws.projects.length === 0 ? (32        <EmptyState icon={FolderKanban} title="No projects" description="Create a project to start generating API keys." action={<CreateProjectDialog />} />33      ) : (34        <div className="overflow-hidden rounded-lg border border-border bg-bg-elevated shadow-xs">35          <Table>36            <TableHeader>37              <TableRow className="hover:bg-transparent">38                <TableHead>Project</TableHead>39                <TableHead>Environment</TableHead>40                <TableHead className="text-right">Requests · month</TableHead>41                <TableHead className="text-right">Spend · month</TableHead>42                <TableHead className="text-right">Keys</TableHead>43                <TableHead>Created</TableHead>44                <TableHead className="text-right">45                  <span className="sr-only">Actions</span>46                </TableHead>47              </TableRow>48            </TableHeader>49            <TableBody>50              {ws.projects.map((p) => {51                const s = stats.get(p.id) ?? { requests: 0, spendUsd: 0, activeKeys: 0 };52                const current = p.id === ws.project.id;53                return (54                  <TableRow key={p.id}>55                    <TableCell>56                      <div className="flex min-w-0 flex-col gap-0.5">57                        <div className="flex items-center gap-2">58                          <Link href={`/dashboard/projects/${p.id}`} className="font-medium text-fg underline-offset-4 hover:underline">59                            {p.name}60                          </Link>61                          {current ? (62                            <Badge variant="accent" dot>63                              Current64                            </Badge>65                          ) : null}66                        </div>67                        {p.description ? <span className="text-[12.5px] text-fg-muted">{truncate(p.description, 80)}</span> : <span className="font-mono text-[11.5px] text-fg-subtle">{p.id}</span>}68                      </div>69                    </TableCell>70                    <TableCell>71                      <Badge variant={p.environment === "production" ? "success" : "warning"}>{p.environment}</Badge>72                    </TableCell>73                    <TableCell className="text-right font-mono tabular">{formatNumber(s.requests)}</TableCell>74                    <TableCell className="text-right font-mono tabular">{formatUsd(s.spendUsd)}</TableCell>75                    <TableCell className="text-right font-mono tabular">76                      <Link href={`/dashboard/api-keys?project=${p.id}`} className="underline-offset-4 hover:underline">77                        {s.activeKeys}78                      </Link>79                    </TableCell>80                    <TableCell className="whitespace-nowrap tabular text-fg-muted">{formatDateOnly(p.createdAt)}</TableCell>81                    <TableCell className="text-right">82                      <div className="flex items-center justify-end gap-1.5">83                        {!current ? <SelectProjectButton projectId={p.id} /> : null}84                        <Button asChild size="xs" variant="ghost">85                          <Link href={`/dashboard/projects/${p.id}`}>86                            Open <ArrowRight />87                          </Link>88                        </Button>89                      </div>90                    </TableCell>91                  </TableRow>92                );93              })}94            </TableBody>95          </Table>96        </div>97      )}98    </div>99  );100}101