import { useEffect, useState } from "react"; import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query"; import { approvalsApi } from "../api/approvals"; import { agentsApi } from "../api/agents"; import { useCompany } from "../context/CompanyContext"; import { useBreadcrumbs } from "../context/BreadcrumbContext"; import { queryKeys } from "../lib/queryKeys"; import { timeAgo } from "../lib/timeAgo"; import { cn } from "../lib/utils"; import { Button } from "@/components/ui/button"; import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { ShieldCheck, UserPlus, Lightbulb, CheckCircle2, XCircle, Clock } from "lucide-react"; import type { Approval } from "@paperclip/shared"; type StatusFilter = "pending" | "all"; const typeLabel: Record = { hire_agent: "Hire Agent", approve_ceo_strategy: "CEO Strategy", }; const typeIcon: Record = { hire_agent: UserPlus, approve_ceo_strategy: Lightbulb, }; function statusIcon(status: string) { if (status === "approved") return ; if (status === "rejected") return ; if (status === "pending") return ; return null; } function PayloadField({ label, value }: { label: string; value: unknown }) { if (!value) return null; return (
{label} {String(value)}
); } function HireAgentPayload({ payload }: { payload: Record }) { return (
Name {String(payload.name ?? "—")}
{!!payload.capabilities && (
Capabilities {String(payload.capabilities)}
)} {!!payload.adapterType && (
Adapter {String(payload.adapterType)}
)}
); } function CeoStrategyPayload({ payload }: { payload: Record }) { const plan = payload.plan ?? payload.description ?? payload.strategy ?? payload.text; return (
{!!plan && (
{String(plan)}
)} {!plan && (
          {JSON.stringify(payload, null, 2)}
        
)}
); } function ApprovalCard({ approval, requesterName, onApprove, onReject, isPending, }: { approval: Approval; requesterName: string | null; onApprove: () => void; onReject: () => void; isPending: boolean; }) { const Icon = typeIcon[approval.type] ?? ShieldCheck; const label = typeLabel[approval.type] ?? approval.type; return (
{/* Header */}
{label} {requesterName && ( requested by {requesterName} )}
{statusIcon(approval.status)} {approval.status} · {timeAgo(approval.createdAt)}
{/* Payload */} {approval.type === "hire_agent" ? ( ) : ( )} {/* Decision note */} {approval.decisionNote && (
Note: {approval.decisionNote}
)} {/* Actions */} {approval.status === "pending" && (
)}
); } export function Approvals() { const { selectedCompanyId } = useCompany(); const { setBreadcrumbs } = useBreadcrumbs(); const queryClient = useQueryClient(); const [statusFilter, setStatusFilter] = useState("pending"); const [actionError, setActionError] = useState(null); useEffect(() => { setBreadcrumbs([{ label: "Approvals" }]); }, [setBreadcrumbs]); const { data, isLoading, error } = useQuery({ queryKey: queryKeys.approvals.list(selectedCompanyId!), queryFn: () => approvalsApi.list(selectedCompanyId!), enabled: !!selectedCompanyId, }); const { data: agents } = useQuery({ queryKey: queryKeys.agents.list(selectedCompanyId!), queryFn: () => agentsApi.list(selectedCompanyId!), enabled: !!selectedCompanyId, }); const approveMutation = useMutation({ mutationFn: (id: string) => approvalsApi.approve(id), onSuccess: () => { setActionError(null); queryClient.invalidateQueries({ queryKey: queryKeys.approvals.list(selectedCompanyId!) }); }, onError: (err) => { setActionError(err instanceof Error ? err.message : "Failed to approve"); }, }); const rejectMutation = useMutation({ mutationFn: (id: string) => approvalsApi.reject(id), onSuccess: () => { setActionError(null); queryClient.invalidateQueries({ queryKey: queryKeys.approvals.list(selectedCompanyId!) }); }, onError: (err) => { setActionError(err instanceof Error ? err.message : "Failed to reject"); }, }); const agentName = (id: string | null) => { if (!id || !agents) return null; return agents.find((a) => a.id === id)?.name ?? null; }; const filtered = (data ?? []).filter( (a) => statusFilter === "all" || a.status === "pending", ); const pendingCount = (data ?? []).filter((a) => a.status === "pending").length; if (!selectedCompanyId) { return

Select a company first.

; } return (
setStatusFilter(v as StatusFilter)}> Pending {pendingCount > 0 && ( {pendingCount} )} All
{isLoading &&

Loading...

} {error &&

{error.message}

} {actionError &&

{actionError}

} {!isLoading && filtered.length === 0 && (

{statusFilter === "pending" ? "No pending approvals." : "No approvals yet."}

)} {filtered.length > 0 && (
{filtered.map((approval) => ( approveMutation.mutate(approval.id)} onReject={() => rejectMutation.mutate(approval.id)} isPending={approveMutation.isPending || rejectMutation.isPending} /> ))}
)}
); }