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.2 KB · 77 lines tsx
Raw Blame History
1import { Flag } from "lucide-react";2import { requireAdmin } from "@/lib/session";3import { getFeatureFlags } from "@/lib/queries/admin";4import { PageHeader } from "@/components/ui/page-header";5import { Badge } from "@/components/ui/badge";6import { EmptyState } from "@/components/ui/empty-state";7import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table";8import { DateCell, Mono, Panel } from "@/components/admin/primitives";9import { DeleteFlagButton, FlagSwitch, NewFlagDialog } from "@/components/admin/flag-controls";1011export const dynamic = "force-dynamic";1213export default async function AdminFlagsPage() {14  await requireAdmin();15  const flags = await getFeatureFlags();1617  return (18    <>19      <PageHeader eyebrow="Configuration" title="Feature flags" description="Global kill-switches and staged rollouts. A flag applies to everyone unless plans or organization ids are set." actions={<NewFlagDialog />} />20      <Panel flush>21        {flags.length === 0 ? (22          <div className="p-4">23            <EmptyState icon={Flag} title="No feature flags" description="Create a flag, then read it from code with the feature_flags table. Flags start disabled." action={<NewFlagDialog />} compact />24          </div>25        ) : (26          <Table>27            <TableHeader>28              <TableRow>29                <TableHead>Key</TableHead>30                <TableHead>Description</TableHead>31                <TableHead>Plans</TableHead>32                <TableHead>Organizations</TableHead>33                <TableHead>Updated</TableHead>34                <TableHead>Enabled</TableHead>35                <TableHead></TableHead>36              </TableRow>37            </TableHeader>38            <TableBody>39              {flags.map((f) => (40                <TableRow key={f.key}>41                  <TableCell>42                    <Mono className="font-medium">{f.key}</Mono>43                  </TableCell>44                  <TableCell className="max-w-[320px] text-fg-muted">{f.description ?? "—"}</TableCell>45                  <TableCell>46                    {f.plans?.length ? (47                      <div className="flex flex-wrap gap-1">48                        {f.plans.map((p) => (49                          <Badge key={p} variant="outline" className="capitalize">50                            {p}51                          </Badge>52                        ))}53                      </div>54                    ) : (55                      <span className="text-fg-subtle">all</span>56                    )}57                  </TableCell>58                  <TableCell className="text-[12px] text-fg-muted">{f.organizationIds?.length ? `${f.organizationIds.length} org(s)` : <span className="text-fg-subtle">all</span>}</TableCell>59                  <TableCell>60                    <DateCell value={f.updatedAt} />61                  </TableCell>62                  <TableCell>63                    <FlagSwitch flagKey={f.key} enabled={f.enabled} />64                  </TableCell>65                  <TableCell className="text-right">66                    <DeleteFlagButton flagKey={f.key} />67                  </TableCell>68                </TableRow>69              ))}70            </TableBody>71          </Table>72        )}73      </Panel>74    </>75  );76}77