import { Navigate, createFileRoute } from "@tanstack/react-router"; import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; import { useEffect, useState } from "react"; import type { ModelOption, TeamSettings, UserMapping } from "@/lib/api"; import { AppShell, SettingsRow, SettingsSection } from "@/components/AppShell"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@/components/ui/select"; import { Skeleton } from "@/components/ui/skeleton"; import { api } from "@/lib/api"; import { useSession } from "@/lib/session"; export const Route = createFileRoute("/admin")({ component: AdminPage }); function AdminPage() { const session = useSession(); const options = useQuery({ queryKey: ["options"], queryFn: api.options, enabled: !!session.data?.is_admin, }); if (session.isLoading) { return (
); } if (!session.data) return ; if (!session.data.is_admin) return ; return ( ); } const PAGE_SIZE = 20; function UserMappingsSection({ enabled }: { enabled: boolean }) { const [error, setError] = useState(null); const [page, setPage] = useState(1); const mappings = useQuery({ queryKey: ["adminUserMappings", page], queryFn: () => api.adminListUserMappings(page, PAGE_SIZE), enabled, }); const total = mappings.data?.total ?? 0; const pageCount = Math.max(1, Math.ceil(total / PAGE_SIZE)); useEffect(() => { if (!mappings.isFetching && page > pageCount) { setPage(pageCount); } }, [mappings.isFetching, page, pageCount]); const remove = useMutation({ mutationFn: (gh: string) => api.adminDeleteUserMapping(gh), onSuccess: () => void mappings.refetch(), onError: (e: Error) => setError(e.message), }); const items = mappings.data?.items ?? []; return (
{error && {error}}
{mappings.isLoading ? ( ) : !items.length ? (

No mappings yet.

) : ( items.map((m: UserMapping) => (
{m.github_login} {m.work_email} {m.slack_user_id ? ` · ${m.slack_user_id}` : ""} {m.source ? ` · ${m.source}` : ""}
)) )}
{total > PAGE_SIZE && (
{total} mapping{total === 1 ? "" : "s"} · page {page} of {pageCount}
)}
); } function GlobalDefaultsSection({ models }: { models: Array }) { const qc = useQueryClient(); const settings = useQuery({ queryKey: ["teamSettings"], queryFn: api.getTeamSettings, }); const [error, setError] = useState(null); const [defaultRepoDraft, setDefaultRepoDraft] = useState(""); useEffect(() => { setDefaultRepoDraft(settings.data?.default_repo ?? ""); }, [settings.data?.default_repo]); const save = useMutation({ mutationFn: (body: TeamSettings) => api.saveTeamSettings(body), onSuccess: (saved) => { qc.setQueryData(["teamSettings"], saved); setError(null); }, onError: (e: Error) => setError(e.message), }); return (
settings.data && save.mutate({ ...settings.data, default_agent_model: model, default_agent_reasoning_effort: effort, }) } disabled={!settings.data || save.isPending} /> settings.data && save.mutate({ ...settings.data, default_agent_subagent_model: model, default_agent_subagent_reasoning_effort: effort, }) } disabled={!settings.data || save.isPending} /> setDefaultRepoDraft(e.target.value)} onBlur={() => settings.data && save.mutate({ ...settings.data, default_repo: defaultRepoDraft.trim() || null, }) } disabled={!settings.data || save.isPending} /> } /> settings.data && save.mutate({ ...settings.data, default_reviewer_model: model, default_reviewer_reasoning_effort: effort, }) } disabled={!settings.data || save.isPending} /> settings.data && save.mutate({ ...settings.data, default_reviewer_subagent_model: model, default_reviewer_subagent_reasoning_effort: effort, }) } disabled={!settings.data || save.isPending} />
{error &&

{error}

}
); } interface RolePickerProps { label: string; description: string; models: Array; model: string | null; effort: string | null; onChange: (model: string, effort: string) => void; disabled: boolean; } function RolePicker({ label, description, models, model, effort, onChange, disabled, }: RolePickerProps) { const [localModel, setLocalModel] = useState(model ?? ""); const [localEffort, setLocalEffort] = useState(effort ?? ""); useEffect(() => { setLocalModel(model ?? ""); setLocalEffort(effort ?? ""); }, [model, effort]); const selectedModel = models.find((m) => m.id === localModel); const availableEfforts = selectedModel?.efforts ?? []; const handleModelChange = (value: string | null) => { if (!value) return; const nextModel = models.find((m) => m.id === value); if (!nextModel) return; const nextEffort = nextModel.efforts.includes(localEffort) ? localEffort : nextModel.default_effort; setLocalModel(value); setLocalEffort(nextEffort); onChange(value, nextEffort); }; const handleEffortChange = (value: string | null) => { if (!value || !localModel) return; setLocalEffort(value); onChange(localModel, value); }; return ( } /> ); }