import { useEffect, useState } from "react"; import { disconnectConnector, getCloudStatus, getConnectors, getMcpServers, getSlackStatus, type CloudStatus, type Connector, type McpServer, type SlackStatus, } from "../../api"; import { McpServerDetail } from "./CustomMcp"; import { ConnectorBadge } from "../../connectors/ConnectorIcon"; import { AllowlistBlock, ConnectorTools, ListeningSessionsBlock, UnauthorizedBlock } from "../ManageTabs"; import { AccountsDetail } from "./AccountsDetail"; import { AvailableDetail } from "./AvailableDetail"; import { CalendarDetail } from "./CalendarDetail"; import { ConnectorsList } from "./ConnectorsList"; import { GithubDetail } from "./GithubDetail"; import { GmailDetail } from "./GmailDetail"; import { HubSpotDetail } from "./HubSpotDetail"; import { SlackDetail } from "./SlackDetail"; import { GRP } from "./ui"; // Connectors surface = LIST ⇄ per-connector DETAIL SUBPAGE (UX-DECISIONS §21). The // Integrations sub-nav never grows per-connector items; detail pages live behind a // `‹ Connectors` breadcrumb. Connectors without a bespoke page get GenericDetail so // every connected row navigates from day one. export interface DetailProps { c: Connector; cloud: CloudStatus | null; slack: SlackStatus | null; // live Slack health (relay/sign-in/tokens); null elsewhere onChanged: () => void; } // Bespoke pages register here; everything else gets GenericDetail below. const DETAIL_PAGES: Record JSX.Element> = { slack: (p) => , gmail: (p) => , google_calendar: (p) => , hubspot: (p) => , github: (p) => , // Generic multi-account connectors (accounts.py layer) share one page. notion: (p) => , attio: (p) => , posthog: (p) => , mixpanel: (p) => , amplitude: (p) => , apollo: (p) => , hunter: (p) => , }; export function ConnectorsSection() { const [detail, setDetail] = useState(null); const [connectors, setConnectors] = useState([]); const [mcpServers, setMcpServers] = useState([]); const [cloud, setCloud] = useState(null); const [slack, setSlack] = useState(null); const refresh = () => { getConnectors().then(setConnectors).catch(() => setConnectors([])); getMcpServers().then(setMcpServers).catch(() => setMcpServers([])); getCloudStatus().then(setCloud).catch(() => setCloud(null)); getSlackStatus().then(setSlack).catch(() => setSlack(null)); }; useEffect(() => { refresh(); // Poll: recent senders/parked arrive over time; sign-in + managed connects finish // in the system browser and surface on the next tick. const t = setInterval(refresh, 5000); return () => clearInterval(t); }, []); // While an MCP test/sign-in is in flight, poll fast so the chip flips to its // result (Live / Error / Needs sign-in) without the user touching anything. const mcpBusy = mcpServers.some((s) => s.status === "authorizing"); useEffect(() => { if (!mcpBusy) return; const t = setInterval(refresh, 2000); return () => clearInterval(t); }, [mcpBusy]); // Custom MCP entries route as "mcp:" so they can never collide with a // connector detail page of the same name. if (detail?.startsWith("mcp:")) { const s = mcpServers.find((x) => "mcp:" + x.name === detail); return (
{!s ? (
Loading…
) : ( setDetail(null)} /> )}
); } if (detail) { const c = connectors.find((x) => x.name === detail); const Page = DETAIL_PAGES[detail]; return (
{!c ? (
Loading…
) : !c.connected ? ( /* Pre-connect page (§38). When a connect completes, the poll flips c.connected and this same route re-renders as the connected page. */ ) : Page ? ( ) : ( setDetail(null)} /> )}
); } return ( ); } // Fallback detail page: status header + the connector's existing config blocks // (tools; allow-list/parked/listening for two-way) + Disconnect. Bespoke pages // (Slack/Gmail/HubSpot) replace this one connector at a time. function GenericDetail({ c, cloud: _cloud, slack: _slack, onChanged, onGone, }: DetailProps & { onGone: () => void }) { return (

{c.title}

{c.account || (c.auth === "none" ? "Built in" : "Connected")}
{c.auth !== "none" && ( )}
{c.two_way && (
{/* Channel subscriptions are a chat-platform concept — GitHub is two_way via the relay (inbound mentions) but has no channels. */} {c.channels && }
)}
); }