import { useEffect, useMemo, useState } from "react"; import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; import { Pencil, Send } from "lucide-react"; import type { ToolMcpGatewayTokenCreated } from "@paperclipai/shared"; import { Link, Navigate, useNavigate, useParams } from "@/lib/router"; import { useCompany } from "@/context/CompanyContext"; import { useBreadcrumbs } from "@/context/BreadcrumbContext"; import { useToast } from "@/context/ToastContext"; import { queryKeys } from "@/lib/queryKeys"; import { toolsApi } from "@/api/tools"; import { agentsApi } from "@/api/agents"; import { projectsApi } from "@/api/projects"; import { Button } from "@/components/ui/button"; import { Skeleton } from "@/components/ui/skeleton"; import { cn } from "@/lib/utils"; import { ErrorState } from "@/pages/tools/shared"; import { GATEWAY_TABS, gatewayTabHref, isGatewayTabKey, type GatewayTabKey } from "./gateway-tabs"; import { gatewaysQueryKey } from "./NewGatewayDialog"; import { ConnectClientDialog } from "./ConnectClientDialog"; import { EditGatewayDialog } from "./EditGatewayDialog"; import { deriveGatewayApps, isGatewayOn } from "./gateway-helpers"; import { OverviewPanel } from "./panels/OverviewPanel"; import { AppsToolsPanel } from "./panels/AppsToolsPanel"; import { TokensPanel } from "./panels/TokensPanel"; import { GatewayActivityPanel } from "./panels/GatewayActivityPanel"; import { GatewayAdvancedPanel } from "./panels/GatewayAdvancedPanel"; import { CopyableGatewayUrl } from "./CopyableGatewayUrl"; export function GatewayDetail() { const { gatewayId = "", tab } = useParams<{ gatewayId: string; tab?: string }>(); const navigate = useNavigate(); const queryClient = useQueryClient(); const { pushToast } = useToast(); const { selectedCompanyId } = useCompany(); const { setBreadcrumbs } = useBreadcrumbs(); const [snippetOpen, setSnippetOpen] = useState(false); const [editing, setEditing] = useState(false); const [createdTokens, setCreatedTokens] = useState([]); const activeTab: GatewayTabKey | null = isGatewayTabKey(tab) ? tab : null; const gatewaysQuery = useQuery({ queryKey: gatewaysQueryKey(selectedCompanyId ?? "__none__"), queryFn: () => toolsApi.listGateways(selectedCompanyId!), enabled: !!selectedCompanyId, }); const profilesQuery = useQuery({ queryKey: queryKeys.tools.profiles(selectedCompanyId ?? "__none__"), queryFn: () => toolsApi.listProfiles(selectedCompanyId!), enabled: !!selectedCompanyId, }); const connectionsQuery = useQuery({ queryKey: queryKeys.tools.connections(selectedCompanyId ?? "__none__"), queryFn: () => toolsApi.listConnections(selectedCompanyId!), enabled: !!selectedCompanyId, }); const applicationsQuery = useQuery({ queryKey: queryKeys.tools.applications(selectedCompanyId ?? "__none__"), queryFn: () => toolsApi.listApplications(selectedCompanyId!), enabled: !!selectedCompanyId, }); const agentsQuery = useQuery({ queryKey: queryKeys.agents.list(selectedCompanyId ?? "__none__"), queryFn: () => agentsApi.list(selectedCompanyId!), enabled: !!selectedCompanyId, }); const projectsQuery = useQuery({ queryKey: queryKeys.projects.list(selectedCompanyId ?? "__none__", { includeArchived: true }), queryFn: () => projectsApi.list(selectedCompanyId!, { includeArchived: true }), enabled: !!selectedCompanyId, }); const gateway = useMemo( () => (gatewaysQuery.data?.gateways ?? []).find((g) => g.id === gatewayId), [gatewaysQuery.data, gatewayId], ); const profile = useMemo( () => (profilesQuery.data?.profiles ?? []).find((p) => p.id === gateway?.profileId), [profilesQuery.data, gateway?.profileId], ); const apps = useMemo( () => deriveGatewayApps( profile, applicationsQuery.data?.applications ?? [], connectionsQuery.data?.connections ?? [], ), [profile, applicationsQuery.data, connectionsQuery.data], ); const agentNames = useMemo( () => new Map((agentsQuery.data ?? []).map((agent) => [agent.id, agent.name])), [agentsQuery.data], ); const projectNames = useMemo( () => new Map((projectsQuery.data ?? []).map((project) => [project.id, project.name])), [projectsQuery.data], ); function rememberCreatedToken(token: ToolMcpGatewayTokenCreated) { setCreatedTokens((current) => [token, ...current.filter((item) => item.id !== token.id)]); } useEffect(() => { if (!gateway) return; setBreadcrumbs([ { label: "Connectors", href: "/apps" }, { label: "Gateways", href: "/apps/gateways" }, { label: gateway.name }, ]); return () => setBreadcrumbs([]); }, [setBreadcrumbs, gateway]); const toggleMutation = useMutation({ mutationFn: () => toolsApi.updateGateway(selectedCompanyId!, gatewayId, { status: gateway && isGatewayOn(gateway) ? "disabled" : "active", }), onSuccess: async (updated) => { pushToast({ title: updated.status === "active" ? "Gateway on" : "Gateway off", body: updated.status === "active" ? `${updated.name} is exposing its tools again.` : `${updated.name} is off — every client goes silent.`, tone: "success", }); await queryClient.invalidateQueries({ queryKey: gatewaysQueryKey(selectedCompanyId!) }); }, onError: (error) => pushToast({ title: "Couldn't update the gateway", body: error instanceof Error ? error.message : String(error), tone: "error", }), }); if (!selectedCompanyId) { return
Select an organization to manage gateways.
; } if (!activeTab) { return ; } if (gatewaysQuery.isLoading) { return (
); } if (gatewaysQuery.isError) { return gatewaysQuery.refetch()} />; } if (!gateway) { return (

We couldn’t find that gateway.

); } return (
Apps · Gateways

{gateway.name}

{activeTab === "overview" && ( toggleMutation.mutate()} /> )} {activeTab === "apps" && } {activeTab === "tokens" && ( )} {activeTab === "activity" && ( )} {activeTab === "advanced" && ( )}
); }