diff --git a/.changeset/sidebar-integration-connect.md b/.changeset/sidebar-integration-connect.md new file mode 100644 index 0000000000..84a2b8a240 --- /dev/null +++ b/.changeset/sidebar-integration-connect.md @@ -0,0 +1,15 @@ +--- +"executor": patch +--- + +**Connect an integration from the sidebar** + +The sidebar lists your integrations on every console route, but connecting +another one meant navigating back to the integrations page to reach its Connect +action — the picker state was owned by that page, so the shared shell could +render the list without being able to open the flow behind it. + +The connect dialog now belongs to the shell. A labelled plus button sits beside +the sidebar's Integrations heading and opens the same picker, records the same +event, and leaves the current route in place behind it. On mobile the navigation +drawer closes first so the dialog gets the full viewport. diff --git a/e2e/scenarios/provider-plugins-ui.test.ts b/e2e/scenarios/provider-plugins-ui.test.ts index fd8dda0efa..81ba31e2c2 100644 --- a/e2e/scenarios/provider-plugins-ui.test.ts +++ b/e2e/scenarios/provider-plugins-ui.test.ts @@ -14,10 +14,12 @@ scenario( const identity = yield* target.newIdentity(); yield* browser.session(identity, async ({ page, step }) => { - await step("Open the integrations picker", async () => { - await visit(page, "/integrations"); + await step("Open the integrations picker from the sidebar", async () => { + await visit(page, "/policies"); await clickToReveal( - page.getByRole("button", { name: "Connect" }), + page + .getByRole("navigation") + .getByRole("button", { name: "Browse integrations", exact: true }), page.getByRole("dialog", { name: "Connect an integration" }), ); }); diff --git a/packages/react/src/multiplayer/shell.tsx b/packages/react/src/multiplayer/shell.tsx index 9a92cd0ff9..795e16716a 100644 --- a/packages/react/src/multiplayer/shell.tsx +++ b/packages/react/src/multiplayer/shell.tsx @@ -2,7 +2,7 @@ import { Link, Outlet, useLocation, useParams } from "@tanstack/react-router"; import { useEffect, useRef, useState, type ReactNode } from "react"; import { useAtomValue } from "@effect/atom-react"; import * as AsyncResult from "effect/unstable/reactivity/AsyncResult"; -import { BookOpen, Command, ExternalLink } from "lucide-react"; +import { BookOpen, Command, ExternalLink, PlusIcon } from "lucide-react"; import type { Integration } from "@executor-js/sdk/shared"; import { integrationsOptimisticAtom } from "../api/atoms"; import { trackEvent } from "../api/analytics"; @@ -23,6 +23,7 @@ import { } from "../components/integration-favicon"; import { CommandPalette } from "../components/command-palette"; import { Wordmark } from "../components/wordmark"; +import { ConnectDialog } from "../pages/integrations"; import { useClientPlugins, useIntegrationPlugins } from "@executor-js/sdk/client"; import { useAuth } from "./auth-context"; @@ -347,6 +348,7 @@ function SidebarContent( onNavigate?: () => void; showBrand?: boolean; onOpenCommands: () => void; + onOpenIntegrationConnect: () => void; }, ) { const plugins = useClientPlugins(); @@ -382,8 +384,19 @@ function SidebarContent( /> ))} -