diff --git a/.changeset/sidebar-integration-connect.md b/.changeset/sidebar-integration-connect.md new file mode 100644 index 0000000000..6e658a994b --- /dev/null +++ b/.changeset/sidebar-integration-connect.md @@ -0,0 +1,5 @@ +--- +"@executor-js/react": patch +--- + +Add a shortcut beside the sidebar integration list that opens the existing integration picker. diff --git a/e2e/scenarios/provider-plugins-ui.test.ts b/e2e/scenarios/provider-plugins-ui.test.ts index add10ccd15..483de886c9 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: "Connect an integration", 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..6981b5d440 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( /> ))} -
+
Integrations +
@@ -415,6 +428,7 @@ export function Shell(props: ShellProps) { const lastPathname = useRef(pathname); const [mobileSidebarOpen, setMobileSidebarOpen] = useState(false); const [commandPaletteOpen, setCommandPaletteOpen] = useState(false); + const [connectIntegrationOpen, setConnectIntegrationOpen] = useState(false); if (lastPathname.current !== pathname) { lastPathname.current = pathname; if (mobileSidebarOpen) setMobileSidebarOpen(false); @@ -432,12 +446,17 @@ export function Shell(props: ShellProps) { return (
+ {/* Desktop sidebar */} @@ -481,6 +500,11 @@ export function Shell(props: ShellProps) { setMobileSidebarOpen(false); setCommandPaletteOpen(true); }} + onOpenIntegrationConnect={() => { + setMobileSidebarOpen(false); + setConnectIntegrationOpen(true); + trackEvent("integration_connect_dialog_opened"); + }} />
diff --git a/packages/react/src/pages/integrations.tsx b/packages/react/src/pages/integrations.tsx index f1780b75cb..cab03dca55 100644 --- a/packages/react/src/pages/integrations.tsx +++ b/packages/react/src/pages/integrations.tsx @@ -152,7 +152,7 @@ const looksLikeUrl = (raw: string): boolean => { return false; }; -function ConnectDialog(props: { open: boolean; onOpenChange: (open: boolean) => void }) { +export function ConnectDialog(props: { open: boolean; onOpenChange: (open: boolean) => void }) { const integrationPlugins = useIntegrationPlugins(); const doDetect = useAtomSet(detectIntegration, { mode: "promiseExit" }); const navigate = useNavigate();