import { Suspense, useMemo, type ReactNode } from "react"; import { Link } from "@tanstack/react-router"; import { useAtomRefresh, useAtomValue } from "@effect/atom-react"; import * as AsyncResult from "effect/unstable/reactivity/AsyncResult"; import { PlusIcon } from "lucide-react"; import type { Integration } from "@executor-js/sdk/shared"; import { useIntegrationPlugins, type IntegrationPlugin } from "@executor-js/sdk/client"; import { integrationsOptimisticAtom } from "../api/atoms"; import { trackEvent } from "../api/analytics"; import { McpInstallCard } from "../components/mcp-install-card"; import { WorkspaceAdminHint } from "../components/workspace-admin-hint"; import { Button } from "../components/button"; import { PageContainer, PageHeader } from "../components/page"; import { CardStack, CardStackContent, CardStackEntry, CardStackEntryActions, CardStackEntryContent, CardStackEntryDescription, CardStackEntryTitle, CardStackHeader, } from "../components/card-stack"; import { IntegrationFavicon, integrationInferredUrl, integrationPresetIconUrl, } from "../components/integration-favicon"; import { groupIntegrations, type IntegrationFamilyGroup } from "../lib/integration-grouping"; import { IntegrationHealthSummary } from "../components/integration-health-summary"; import { IntegrationIconWithAccount } from "../components/integration-icon-with-account"; import { Skeleton } from "../components/skeleton"; import { useExecutorDocumentTitle } from "../lib/document-title"; import { ErrorState } from "../components/error-state"; import { isAsyncResultLoading } from "../lib/async-result"; import { useCanCreateWorkspaceConnections } from "../multiplayer/use-admin-nav"; const KIND_TO_PLUGIN_KEY: Record = { openapi: "openapi", mcp: "mcp", graphql: "graphql", googleDiscovery: "google", }; // --------------------------------------------------------------------------- // Page // --------------------------------------------------------------------------- export function IntegrationsPage() { useExecutorDocumentTitle("Integrations"); const integrations = useAtomValue(integrationsOptimisticAtom); const refreshIntegrations = useAtomRefresh(integrationsOptimisticAtom); const canCreate = useCanCreateWorkspaceConnections(); return ( trackEvent("integration_browse_opened", { via: "header" })} > Add integration ) : ( ) } />
{isAsyncResultLoading(integrations) ? ( ) : ( AsyncResult.match(integrations, { onInitial: () => , onFailure: () => ( ), onSuccess: ({ value }) => { if (value.length === 0) { return ; } return (
); }, }) )} ); } // --------------------------------------------------------------------------- // Empty state // --------------------------------------------------------------------------- function EmptyIntegrations({ canCreate }: { readonly canCreate: boolean }) { return (

No integrations yet

{canCreate ? "Connect an integration to start curating tools." : "Ask a workspace admin to add an integration."}

{canCreate ? ( ) : ( )}
); } // --------------------------------------------------------------------------- // Integration grid — flat list of catalog integrations, click-through to detail // --------------------------------------------------------------------------- function IntegrationGrid(props: { integrations: readonly Integration[] }) { const integrationPlugins = useIntegrationPlugins(); const pluginByKind = useMemo(() => { const out = new Map(); for (const p of integrationPlugins) out.set(p.key, p); return out; }, [integrationPlugins]); const items = useMemo(() => groupIntegrations(props.integrations), [props.integrations]); const renderEntry = (integration: Integration) => { const pluginKey = KIND_TO_PLUGIN_KEY[integration.kind] ?? integration.kind; const plugin = pluginByKind.get(pluginKey); const SummaryComponent = plugin?.summary; const slug = String(integration.slug); const name = integration.name || slug; return ( {name} {slug} {SummaryComponent && ( )} ); }; const rendered: ReactNode[] = []; let flatRun: Integration[] = []; const flushFlat = () => { if (flatRun.length === 0) return; const run = flatRun; flatRun = []; rendered.push( {run.map(renderEntry)} , ); }; for (const item of items) { if (item.type === "single") { flatRun.push(item.integration); continue; } flushFlat(); rendered.push( , ); } flushFlat(); return
{rendered}
; } function IntegrationFamilyGroupCard(props: { group: IntegrationFamilyGroup; plugin: IntegrationPlugin | undefined; renderEntry: (integration: Integration) => ReactNode; }) { const { group, plugin, renderEntry } = props; const headerIcon = plugin?.presets?.find((preset) => preset.family === group.family && preset.icon)?.icon ?? null; return ( {group.label} {group.members.length} {group.members.map(renderEntry)} ); } // --------------------------------------------------------------------------- // Loading skeleton // --------------------------------------------------------------------------- function IntegrationsGridSkeleton() { return ( {Array.from({ length: 4 }).map((_, i) => (
))}
); }