Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
92 changes: 70 additions & 22 deletions apps/cloud/src/web/pages/setup-mcp.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,14 @@ export const SetupMcpPage = () => {
// window where the shell paints over this still-mounted onboarding page.
const goToApp = () =>
navigate({ to: "/{-$orgSlug}", params: { orgSlug: organizationSlug ?? undefined } });
const goToIntegrationBrowse = () =>
navigate({
to: "/{-$orgSlug}/integrations/browse",
params: { orgSlug: organizationSlug ?? undefined },
});
const [origin, setOrigin] = useState<string | null>(null);
const [advancedOpen, setAdvancedOpen] = useState(false);
const [agentConnected, setAgentConnected] = useState(false);
const [elicitationMode, setElicitationMode] = useState<McpElicitationMode>("model");

useEffect(() => {
Expand Down Expand Up @@ -60,7 +66,7 @@ export const SetupMcpPage = () => {
<div className="mx-auto flex w-full max-w-lg flex-col gap-6">
<header className="flex flex-col gap-2">
<p className="text-xs font-medium uppercase tracking-wider text-muted-foreground">
Step 2 of 2
Step 2 of 3
</p>
<h1 className="font-sans font-semibold text-3xl">Connect your MCP client</h1>
<p className="text-sm text-muted-foreground">
Expand Down Expand Up @@ -150,28 +156,70 @@ export const SetupMcpPage = () => {
<p className="text-xs text-muted-foreground">Adds the server to a supported agent.</p>
</section>

<div className="flex items-center justify-between gap-3">
{/* oxlint-disable-next-line react/forbid-elements */}
<button
type="button"
onClick={() => {
trackEvent("setup_mcp_skipped");
void goToApp();
}}
className="text-xs text-muted-foreground transition-colors hover:text-foreground"
>
Skip for now
</button>
<Button
size="sm"
onClick={() => {
trackEvent("setup_mcp_completed");
void goToApp();
}}
{!agentConnected ? (
<div className="flex items-center justify-between gap-3">
{/* oxlint-disable-next-line react/forbid-elements */}
<button
type="button"
onClick={() => {
trackEvent("setup_mcp_skipped");
void goToApp();
}}
className="text-xs text-muted-foreground transition-colors hover:text-foreground"
>
Skip for now
</button>
<Button
size="sm"
onClick={() => {
trackEvent("setup_mcp_completed");
setAgentConnected(true);
}}
>
I&apos;ve connected my agent
</Button>
</div>
) : (
<section
className="flex flex-col gap-3 border-t border-border pt-6"
aria-label="First integration"
>
Continue to app
</Button>
</div>
<div>
<p className="text-xs font-medium uppercase tracking-wider text-muted-foreground">
Step 3 of 3
</p>
<h2 className="mt-2 text-sm font-medium text-foreground">
Add your first integration
</h2>
<p className="mt-1 text-sm leading-6 text-muted-foreground">
Choose an app you already use, such as Gmail, and connect it so your agent can start
helping with real work.
</p>
</div>
<div className="flex items-center justify-between gap-3">
{/* oxlint-disable-next-line react/forbid-elements */}
<button
type="button"
onClick={() => {
trackEvent("setup_mcp_skipped");
void goToApp();
}}
className="text-xs text-muted-foreground transition-colors hover:text-foreground"
>
Skip to workspace
</button>
<Button
size="sm"
onClick={() => {
trackEvent("integration_browse_opened", { via: "onboarding" });
void goToIntegrationBrowse();
}}
>
Choose an app
</Button>
</div>
</section>
)}
</div>
</div>
);
Expand Down
2 changes: 1 addition & 1 deletion e2e/cloud/auth-routing-flow.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ scenario(
);
await page.getByRole("button", { name: "Create organization" }).click();
await page.getByText("Connect your MCP client").waitFor({ timeout: 30_000 });
await page.getByRole("button", { name: "Continue to app" }).click();
await page.getByRole("button", { name: "Skip for now" }).click();
// The bare landing canonicalizes onto the new org's slug.
await page.waitForURL((url) => /^\/[a-z0-9-]+\/?$/.test(url.pathname), { timeout: 30_000 });
await page.getByText("Integrations").first().waitFor({ timeout: 30_000 });
Expand Down
2 changes: 1 addition & 1 deletion e2e/cloud/org-delete.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ scenario(
await page.getByPlaceholder("Northwind Labs").fill(ORG);
await page.getByRole("button", { name: "Create organization" }).click();
await page.getByText("Connect your MCP client").waitFor();
await page.getByRole("button", { name: "Continue to app" }).click();
await page.getByRole("button", { name: "Skip for now" }).click();
await page.getByText("Integrations").first().waitFor();
// The console canonicalizes onto the org's URL slug (/doomed-org).
await page.waitForURL((url) => /^\/[a-z0-9-]+\/?$/.test(url.pathname), {
Expand Down
4 changes: 2 additions & 2 deletions e2e/cloud/org-limit.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,8 @@ scenario(
await page.getByText("Connect your MCP client").waitFor();
});

await step("Continue into the app", async () => {
await page.getByRole("button", { name: "Continue to app" }).click();
await step("Skip onboarding and open the app", async () => {
await page.getByRole("button", { name: "Skip for now" }).click();
await page.getByText("Integrations").first().waitFor();
// Let the router navigation fully settle (slow on a cold dev server)
// before opening menus — a late remount closes them mid-interaction.
Expand Down
2 changes: 1 addition & 1 deletion e2e/cloud/org-multitab-cookie.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ scenario(
await tab1.getByPlaceholder("Northwind Labs").fill("Multitab A");
await tab1.getByRole("button", { name: "Create organization" }).click();
await tab1.getByText("Connect your MCP client").waitFor({ timeout: 30_000 });
await tab1.getByRole("button", { name: "Continue to app" }).click();
await tab1.getByRole("button", { name: "Skip for now" }).click();
await tab1.waitForURL((url) => /^\/[a-z0-9-]+\/?$/.test(url.pathname), { timeout: 30_000 });
await tab1.getByText("Integrations").first().waitFor({ timeout: 30_000 });
slugA = slugOf(tab1);
Expand Down
4 changes: 2 additions & 2 deletions e2e/cloud/org-switcher.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,8 @@ scenario(
await page.getByText("Connect your MCP client").waitFor();
});

await step("Continue into the app", async () => {
await page.getByRole("button", { name: "Continue to app" }).click();
await step("Skip onboarding and open the app", async () => {
await page.getByRole("button", { name: "Skip for now" }).click();
await page.getByText("Integrations").first().waitFor();
// Let the router navigation fully settle before opening menus — a late
// remount closes them mid-interaction. The console canonicalizes onto
Expand Down
4 changes: 4 additions & 0 deletions packages/react/src/api/analytics.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ describe("analytics seam", () => {
expect(() => trackEvent("integration_browse_opened", { via: "header" })).not.toThrow();
});

it("accepts the onboarding integration entry point", () => {
expect(() => trackEvent("integration_browse_opened", { via: "onboarding" })).not.toThrow();
});

it("forwards name and properties to the mounted client", () => {
const seen: Array<{ name: AnalyticsEventName; properties: unknown }> = [];
setAnalyticsClient((name, properties) => {
Expand Down
2 changes: 1 addition & 1 deletion packages/react/src/api/analytics.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ export interface AnalyticsEvents {
/** The full-page picker was opened. Replaces the connect dialog, whose
* `integration_connect_dialog_opened` this supersedes — keep both readable
* in dashboards spanning the change. */
integration_browse_opened: { via: "header" | "empty-state" | "sidebar" };
integration_browse_opened: { via: "header" | "empty-state" | "sidebar" | "onboarding" };
integration_detect_submitted: {
success: boolean;
detected_kind?: string;
Expand Down
Loading