222 lines · 8.9 KB
import { createFileRoute, notFound } from "@tanstack/react-router";
import { getAdminOverview } from "~/fns/admin";
import { ABUSE_CATEGORY_LABELS } from "~/lib/abuse";
import type { AbuseCategory } from "~/lib/abuse";
import { Badge } from "~/ui/Badge";
import { EmptyState } from "~/ui/EmptyState";
import { Heading } from "~/ui/Heading";
import { PageContainer } from "~/ui/PageContainer";
import { Panel } from "~/ui/Panel";
import { RelativeTime } from "~/ui/RelativeTime";
import { TextLink } from "~/ui/TextLink";
import { DisabledAccounts } from "~/components/admin/DisabledAccounts";
import { HostingControls } from "~/components/admin/HostingControls";
import { SignupCapControls } from "~/components/admin/SignupCapControls";
import { SiteAdmins } from "~/components/admin/SiteAdmins";

const ADMIN_LINKS = [
  { to: "/admin/abuse", label: "Abuse queue" },
  { to: "/admin/audit", label: "Audit log" },
  { to: "/admin/billing", label: "Usage and billing" },
  { to: "/admin/artifacts", label: "Artifacts health" },
] as const;

export const Route = createFileRoute("/admin/")({
  head: () => ({ meta: [{ title: "Site admin · gitfor" }] }),
  beforeLoad: ({ context }) => {
    if (!context.viewer?.isAdmin) throw notFound();
  },
  loader: () => getAdminOverview(),
  component: AdminDashboard,
});

type Stat = { label: string; value: number; hint?: string; tone?: "danger" | "warning" };

function StatGrid({ stats }: { stats: Stat[] }) {
  return (
    <dl className="grid grid-cols-2 gap-px overflow-hidden rounded-b-lg bg-kumo-fill sm:grid-cols-3 lg:grid-cols-5">
      {stats.map((stat) => (
        <div key={stat.label} className="bg-kumo-canvas px-4 py-3">
          <dt className="text-xs text-kumo-subtle">{stat.label}</dt>