import { useEffect } from "react"; import { Link, useNavigate } from "@tanstack/react-router"; import { useAtomValue, useAtomRefresh } from "@effect/atom-react"; import * as AsyncResult from "effect/unstable/reactivity/AsyncResult"; import { IntegrationSlug } from "@executor-js/sdk/shared"; import { integrationAtom, integrationsOptimisticAtom } from "../api/atoms"; import { ErrorState } from "../components/error-state"; import { Skeleton } from "../components/skeleton"; import { isAsyncResultLoading } from "../lib/async-result"; import { useExecutorDocumentTitle } from "../lib/document-title"; // --------------------------------------------------------------------------- // `/connect/` — the stable, shareable deep link that drops a // person straight into the connect flow for ONE integration ("connect your // Linear") instead of the dashboard. // // This page owns no connect UI of its own. It resolves the slug against the // tenant's catalog and forwards into the integration detail page's existing // account handoff (`?addAccount=1`), which is the same surface the detail // page's own "Add connection" button opens — multi-method tab strips, OAuth // popups, existing-connection lists and "add another account" all come for // free, and stay correct as that flow evolves. // // Deliberately keyed on the INTEGRATION SLUG, not a plugin id: the slug is the // catalog identity a customer can write down, whereas plugin routes are on // their way out. The redirect is `replace: true` so the deep link does not // linger in history and Back leaves the connect flow rather than bouncing. // --------------------------------------------------------------------------- export function ConnectIntegrationPage(props: { readonly integrationSlug: string }) { const { integrationSlug } = props; const slug = IntegrationSlug.make(integrationSlug); const integration = useAtomValue(integrationAtom(slug)); const refreshIntegrations = useAtomRefresh(integrationsOptimisticAtom); const navigate = useNavigate(); useExecutorDocumentTitle("Connect"); // The catalog read is a client-side find over the integrations list, so an // unknown slug resolves to Success(null) rather than a failure — the missing // case is a not-found state, and only a genuine fetch failure is an error. const resolved = AsyncResult.isSuccess(integration) ? integration.value : null; const loading = isAsyncResultLoading(integration); const failed = AsyncResult.isFailure(integration); const missing = !loading && !failed && resolved === null; useEffect(() => { if (resolved === null) return; void navigate({ to: "/{-$orgSlug}/integrations/$namespace", params: { namespace: String(resolved.slug) }, // The NUMBER 1, so the router serializes the canonical `?addAccount=1` // that agent-generated handoff links already use (a string would be // quoted as `%221%22`). search: { tab: "accounts", addAccount: 1 }, replace: true, }); }, [resolved, navigate]); if (failed) { return ( ); } if (missing) { return (

Unknown integration: {integrationSlug}

This integration is not in your workspace.

Back to integrations
); } // Loading, or resolved and mid-redirect: the same quiet placeholder, so the // hand-off never flashes an empty frame between catalog load and navigation. return (
); } function ConnectStateFrame(props: { readonly children: React.ReactNode }) { return (
{props.children}
); }