TypeScript 97.5%
SQL 1.4%
Python 0.8%
1"use client";2import * as React from "react";3import { useRouter } from "next/navigation";4import { setOrganizationProviderVisibility, setOrganizationSuspended, updateOrganizationLimits, type AdminActionResult } from "@/actions/admin";5import { Button } from "@/components/ui/button";6import { Input, Textarea } from "@/components/ui/input";7import { Field, Hint, Label } from "@/components/ui/label";8import { Switch } from "@/components/ui/switch";9import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from "@/components/ui/dialog";10import { ResultMessage } from "./action-button";1112export function OrgProviderVisibilitySwitch({ orgId, enabled }: { orgId: string; enabled: boolean }) {13 const router = useRouter();14 const [pending, start] = React.useTransition();15 const [result, setResult] = React.useState<AdminActionResult | null>(null);16 return (17 <div className="flex items-start gap-3">18 <Switch19 id={`pv-${orgId}`}20 checked={enabled}21 disabled={pending}22 aria-label="Provider visibility"23 onCheckedChange={(v) =>24 start(async () => {25 const r = await setOrganizationProviderVisibility(orgId, v);26 setResult(r);27 if (r.ok) router.refresh();28 })29 }30 />31 <div className="grid gap-0.5">32 <Label htmlFor={`pv-${orgId}`}>Provider visibility (debug)</Label>33 <Hint>When on, `debug: true` responses include real upstream provider names for this customer. Off for everyone by default.</Hint>34 <ResultMessage result={result} />35 </div>36 </div>37 );38}3940export function OrgSuspendSwitch({ orgId, suspended }: { orgId: string; suspended: boolean }) {41 const router = useRouter();42 const [pending, start] = React.useTransition();43 const [open, setOpen] = React.useState(false);44 const [reason, setReason] = React.useState("");45 const [result, setResult] = React.useState<AdminActionResult | null>(null);46 const apply = (v: boolean) =>47 start(async () => {48 const r = await setOrganizationSuspended(orgId, v, reason);49 setResult(r);50 if (r.ok) {51 setOpen(false);52 setReason("");53 router.refresh();54 }55 });56 return (57 <div className="flex items-start gap-3">58 <Switch id={`sus-${orgId}`} checked={suspended} disabled={pending} aria-label="Suspended" onCheckedChange={(v) => (v ? setOpen(true) : apply(false))} />59 <div className="grid gap-0.5">60 <Label htmlFor={`sus-${orgId}`}>Suspended</Label>61 <Hint>Suspended organizations get `FORBIDDEN` on every API call and see a banner in the dashboard.</Hint>62 <ResultMessage result={result} />63 </div>64 <Dialog open={open} onOpenChange={setOpen}>65 <DialogContent size="sm">66 <DialogHeader>67 <DialogTitle>Suspend this organization?</DialogTitle>68 <DialogDescription>All API keys stop working immediately. Members can still sign in to read logs and contact support.</DialogDescription>69 </DialogHeader>70 <Field>71 <Label htmlFor="sus-reason">Reason (internal)</Label>72 <Textarea id="sus-reason" value={reason} onChange={(e) => setReason(e.target.value)} placeholder="Optional — stored in the audit log" />73 </Field>74 <DialogFooter>75 <Button variant="outline" onClick={() => setOpen(false)}>76 Cancel77 </Button>78 <Button variant="danger" loading={pending} onClick={() => apply(true)}>79 Suspend80 </Button>81 </DialogFooter>82 </DialogContent>83 </Dialog>84 </div>85 );86}8788export function OrgLimitsForm({ orgId, softLimitUsd, hardLimitUsd }: { orgId: string; softLimitUsd: number | null; hardLimitUsd: number | null }) {89 const router = useRouter();90 const [pending, start] = React.useTransition();91 const [soft, setSoft] = React.useState(softLimitUsd?.toString() ?? "");92 const [hard, setHard] = React.useState(hardLimitUsd?.toString() ?? "");93 const [result, setResult] = React.useState<AdminActionResult | null>(null);94 const toNum = (s: string) => (s.trim() === "" ? null : Number(s));95 return (96 <form97 className="grid gap-3"98 onSubmit={(e) => {99 e.preventDefault();100 start(async () => {101 const r = await updateOrganizationLimits(orgId, { softLimitUsd: toNum(soft), hardLimitUsd: toNum(hard) });102 setResult(r);103 if (r.ok) router.refresh();104 });105 }}106 >107 <div className="grid gap-3 sm:grid-cols-2">108 <Field>109 <Label htmlFor="soft">Soft limit (USD / month)</Label>110 <Input id="soft" type="number" min={0} step="0.01" inputMode="decimal" value={soft} onChange={(e) => setSoft(e.target.value)} placeholder="none" className="font-mono" />111 <Hint>Sends a warning email; requests continue.</Hint>112 </Field>113 <Field>114 <Label htmlFor="hard">Hard limit (USD / month)</Label>115 <Input id="hard" type="number" min={0} step="0.01" inputMode="decimal" value={hard} onChange={(e) => setHard(e.target.value)} placeholder="none" className="font-mono" />116 <Hint>Requests fail with `USAGE_LIMIT_REACHED` once exceeded.</Hint>117 </Field>118 </div>119 <div className="flex items-center gap-3">120 <Button type="submit" size="sm" loading={pending}>121 Save limits122 </Button>123 <ResultMessage result={result} />124 </div>125 </form>126 );127}128