From dcdadecaf4bd32cca94c650eae5c078a781e2b3c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Iv=C3=A1n=20Uruchurtu?= Date: Tue, 5 May 2026 09:52:57 -0600 Subject: [PATCH 1/5] fix(onboarding): add dashboard escape hatch --- .../_components/ProductOptionsContent.tsx | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/apps/web/src/app/get-started/personal/_components/ProductOptionsContent.tsx b/apps/web/src/app/get-started/personal/_components/ProductOptionsContent.tsx index 50b88b1977..55de7b605f 100644 --- a/apps/web/src/app/get-started/personal/_components/ProductOptionsContent.tsx +++ b/apps/web/src/app/get-started/personal/_components/ProductOptionsContent.tsx @@ -221,7 +221,21 @@ export default function WelcomeContent({ isAuthenticated }: WelcomeContentProps) /> - {!isAuthenticated ? ( + {isAuthenticated ? ( +

+ Not ready to choose?{' '} + + Skip to dashboard + + +

+ ) : (

- ) : null} + )} ); From b2ef07befa2a78210d7c395a02ee22fabe12d1da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Iv=C3=A1n=20Uruchurtu?= Date: Tue, 5 May 2026 12:09:48 -0600 Subject: [PATCH 2/5] fix(onboarding): refresh signed-in get-started footer --- apps/web/src/app/get-started/page.tsx | 5 +++-- .../_components/ProductOptionsContent.tsx | 16 ++++++++++------ 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/apps/web/src/app/get-started/page.tsx b/apps/web/src/app/get-started/page.tsx index 9f42e334ba..5bd4c4a4cb 100644 --- a/apps/web/src/app/get-started/page.tsx +++ b/apps/web/src/app/get-started/page.tsx @@ -1,5 +1,5 @@ import type { Metadata } from 'next'; -import { getUserFromAuth } from '@/lib/user.server'; +import { getProfileRedirectPath, getUserFromAuth } from '@/lib/user.server'; import { redirect } from 'next/navigation'; import ProductOptionsContent from './personal/_components/ProductOptionsContent'; import { PageContainer } from '@/components/layouts/PageContainer'; @@ -36,11 +36,12 @@ export default async function GetStartedPage() { } const isAuthenticated = !!user; + const dashboardHref = user ? await getProfileRedirectPath(user) : '/profile'; return (
- +
); diff --git a/apps/web/src/app/get-started/personal/_components/ProductOptionsContent.tsx b/apps/web/src/app/get-started/personal/_components/ProductOptionsContent.tsx index 55de7b605f..8e57814562 100644 --- a/apps/web/src/app/get-started/personal/_components/ProductOptionsContent.tsx +++ b/apps/web/src/app/get-started/personal/_components/ProductOptionsContent.tsx @@ -4,12 +4,14 @@ import KiloCrabIcon from '@/components/KiloCrabIcon'; import { cn } from '@/lib/utils'; import { ArrowRight, Briefcase, Check, Cloud, Download } from 'lucide-react'; import Link from 'next/link'; +import { useSession } from 'next-auth/react'; import type { ReactNode } from 'react'; import './ProductOptionsContent.css'; type WelcomeContentProps = { isAuthenticated: boolean; + dashboardHref: string; }; type RowCard = { @@ -94,10 +96,12 @@ function CardRow({ card, entranceDelayMs }: { card: RowCard; entranceDelayMs: nu ); } -export default function WelcomeContent({ isAuthenticated }: WelcomeContentProps) { - const cloudHref = getAuthenticatedHref(isAuthenticated, '/cloud'); - const kiloclawHref = getAuthenticatedHref(isAuthenticated, '/claw'); - const teamHref = getAuthenticatedHref(isAuthenticated, '/organizations/new'); +export default function WelcomeContent({ isAuthenticated, dashboardHref }: WelcomeContentProps) { + const session = useSession(); + const isSignedIn = isAuthenticated || session.status === 'authenticated'; + const cloudHref = getAuthenticatedHref(isSignedIn, '/cloud'); + const kiloclawHref = getAuthenticatedHref(isSignedIn, '/claw'); + const teamHref = getAuthenticatedHref(isSignedIn, '/organizations/new'); const signInHref = `/users/sign_in?callbackPath=/get-started`; const cards: RowCard[] = [ @@ -221,14 +225,14 @@ export default function WelcomeContent({ isAuthenticated }: WelcomeContentProps) /> - {isAuthenticated ? ( + {isSignedIn ? (

Not ready to choose?{' '} Skip to dashboard From 19048c479c975823b8263d10871353aa8d46fa38 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Iv=C3=A1n=20Uruchurtu?= Date: Tue, 5 May 2026 14:40:59 -0600 Subject: [PATCH 3/5] fix(onboarding): always show dashboard escape hatch --- .../_components/ProductOptionsContent.tsx | 55 ++++++++++--------- 1 file changed, 28 insertions(+), 27 deletions(-) diff --git a/apps/web/src/app/get-started/personal/_components/ProductOptionsContent.tsx b/apps/web/src/app/get-started/personal/_components/ProductOptionsContent.tsx index 8e57814562..c8c7d0da21 100644 --- a/apps/web/src/app/get-started/personal/_components/ProductOptionsContent.tsx +++ b/apps/web/src/app/get-started/personal/_components/ProductOptionsContent.tsx @@ -30,6 +30,10 @@ function getAuthenticatedHref(isAuthenticated: boolean, path: string) { return isAuthenticated ? path : `/users/sign_in?callbackPath=${path}`; } +function getSignInHref(path: string) { + return `/users/sign_in?callbackPath=${encodeURIComponent(path)}`; +} + function CardRow({ card, entranceDelayMs }: { card: RowCard; entranceDelayMs: number }) { const isPrimary = card.variant === 'primary'; const iconTone = card.iconTone ?? 'brand'; @@ -103,6 +107,7 @@ export default function WelcomeContent({ isAuthenticated, dashboardHref }: Welco const kiloclawHref = getAuthenticatedHref(isSignedIn, '/claw'); const teamHref = getAuthenticatedHref(isSignedIn, '/organizations/new'); const signInHref = `/users/sign_in?callbackPath=/get-started`; + const skipDashboardHref = isSignedIn ? dashboardHref : getSignInHref(dashboardHref); const cards: RowCard[] = [ { @@ -225,34 +230,30 @@ export default function WelcomeContent({ isAuthenticated, dashboardHref }: Welco /> - {isSignedIn ? ( -

- Not ready to choose?{' '} - - Skip to dashboard - - -

- ) : ( -

+ - Already have an account?{' '} - - Sign in - -

- )} + Skip to dashboard + + + + {!isSignedIn ? ( +

+ Already have an account?{' '} + + Sign in + +

+ ) : null} + ); From eb467b2f3b2d661e867e097f0e2ec1aa3c38a2c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Iv=C3=A1n=20Uruchurtu?= Date: Tue, 5 May 2026 14:44:38 -0600 Subject: [PATCH 4/5] test(onboarding): cover get-started dashboard link --- .../e2e/get-started-dashboard-link.spec.ts | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 apps/web/tests/e2e/get-started-dashboard-link.spec.ts diff --git a/apps/web/tests/e2e/get-started-dashboard-link.spec.ts b/apps/web/tests/e2e/get-started-dashboard-link.spec.ts new file mode 100644 index 0000000000..1f3fb248a4 --- /dev/null +++ b/apps/web/tests/e2e/get-started-dashboard-link.spec.ts @@ -0,0 +1,41 @@ +import { test, expect } from '@chromatic-com/playwright'; +import { randomUUID } from 'crypto'; + +test.describe('/get-started dashboard escape hatch', () => { + test.use({ storageState: { cookies: [], origins: [] } }); + + test('shows the dashboard link to signed-out users', async ({ page }) => { + await page.goto('/get-started'); + + const skipLink = page.getByRole('link', { name: /skip to dashboard/i }); + await expect(skipLink).toBeVisible(); + await expect(skipLink).toHaveAttribute('href', /callbackPath=%2Fprofile/); + }); + + test('shows the dashboard link after fake login and survey skip', async ({ page }) => { + const uniqueId = randomUUID().slice(0, 8); + const testEmail = `test-get-started-${uniqueId}+stytchpass@example.com`; + + await page.goto(`/users/sign_in?fakeUser=${encodeURIComponent(testEmail)}`); + await page.waitForURL( + url => + url.pathname === '/customer-source-survey' || + url.pathname === '/get-started' || + url.pathname === '/profile', + { timeout: 30000, waitUntil: 'networkidle' } + ); + + if (new URL(page.url()).pathname === '/customer-source-survey') { + await page.getByRole('button', { name: 'Skip' }).click(); + await page.waitForURL(url => url.pathname === '/get-started' || url.pathname === '/profile', { + timeout: 15000, + waitUntil: 'networkidle', + }); + } + + await page.goto('/get-started'); + const skipLink = page.getByRole('link', { name: /skip to dashboard/i }); + await expect(skipLink).toBeVisible(); + await expect(skipLink).toHaveAttribute('href', /\/profile|\/organizations\//); + }); +}); From 7c6b34667c291cc4d0fd7a6663fda5654e38bd18 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Iv=C3=A1n=20Uruchurtu?= Date: Tue, 5 May 2026 14:49:39 -0600 Subject: [PATCH 5/5] fix(onboarding): simplify dashboard escape hatch --- apps/web/src/app/get-started/page.tsx | 5 +-- .../_components/ProductOptionsContent.tsx | 38 ++++++++----------- .../e2e/get-started-dashboard-link.spec.ts | 4 +- 3 files changed, 19 insertions(+), 28 deletions(-) diff --git a/apps/web/src/app/get-started/page.tsx b/apps/web/src/app/get-started/page.tsx index 5bd4c4a4cb..9f42e334ba 100644 --- a/apps/web/src/app/get-started/page.tsx +++ b/apps/web/src/app/get-started/page.tsx @@ -1,5 +1,5 @@ import type { Metadata } from 'next'; -import { getProfileRedirectPath, getUserFromAuth } from '@/lib/user.server'; +import { getUserFromAuth } from '@/lib/user.server'; import { redirect } from 'next/navigation'; import ProductOptionsContent from './personal/_components/ProductOptionsContent'; import { PageContainer } from '@/components/layouts/PageContainer'; @@ -36,12 +36,11 @@ export default async function GetStartedPage() { } const isAuthenticated = !!user; - const dashboardHref = user ? await getProfileRedirectPath(user) : '/profile'; return (
- +
); diff --git a/apps/web/src/app/get-started/personal/_components/ProductOptionsContent.tsx b/apps/web/src/app/get-started/personal/_components/ProductOptionsContent.tsx index c8c7d0da21..dca5590043 100644 --- a/apps/web/src/app/get-started/personal/_components/ProductOptionsContent.tsx +++ b/apps/web/src/app/get-started/personal/_components/ProductOptionsContent.tsx @@ -4,14 +4,12 @@ import KiloCrabIcon from '@/components/KiloCrabIcon'; import { cn } from '@/lib/utils'; import { ArrowRight, Briefcase, Check, Cloud, Download } from 'lucide-react'; import Link from 'next/link'; -import { useSession } from 'next-auth/react'; import type { ReactNode } from 'react'; import './ProductOptionsContent.css'; type WelcomeContentProps = { isAuthenticated: boolean; - dashboardHref: string; }; type RowCard = { @@ -30,10 +28,6 @@ function getAuthenticatedHref(isAuthenticated: boolean, path: string) { return isAuthenticated ? path : `/users/sign_in?callbackPath=${path}`; } -function getSignInHref(path: string) { - return `/users/sign_in?callbackPath=${encodeURIComponent(path)}`; -} - function CardRow({ card, entranceDelayMs }: { card: RowCard; entranceDelayMs: number }) { const isPrimary = card.variant === 'primary'; const iconTone = card.iconTone ?? 'brand'; @@ -100,14 +94,11 @@ function CardRow({ card, entranceDelayMs }: { card: RowCard; entranceDelayMs: nu ); } -export default function WelcomeContent({ isAuthenticated, dashboardHref }: WelcomeContentProps) { - const session = useSession(); - const isSignedIn = isAuthenticated || session.status === 'authenticated'; - const cloudHref = getAuthenticatedHref(isSignedIn, '/cloud'); - const kiloclawHref = getAuthenticatedHref(isSignedIn, '/claw'); - const teamHref = getAuthenticatedHref(isSignedIn, '/organizations/new'); +export default function WelcomeContent({ isAuthenticated }: WelcomeContentProps) { + const cloudHref = getAuthenticatedHref(isAuthenticated, '/cloud'); + const kiloclawHref = getAuthenticatedHref(isAuthenticated, '/claw'); + const teamHref = getAuthenticatedHref(isAuthenticated, '/organizations/new'); const signInHref = `/users/sign_in?callbackPath=/get-started`; - const skipDashboardHref = isSignedIn ? dashboardHref : getSignInHref(dashboardHref); const cards: RowCard[] = [ { @@ -230,20 +221,21 @@ export default function WelcomeContent({ isAuthenticated, dashboardHref }: Welco /> -
+ Not ready to choose?{' '} Skip to dashboard - + - - {!isSignedIn ? ( -

+ {!isAuthenticated ? ( + <> + ยท Already have an account?{' '} Sign in -

+ ) : null} -
+

); diff --git a/apps/web/tests/e2e/get-started-dashboard-link.spec.ts b/apps/web/tests/e2e/get-started-dashboard-link.spec.ts index 1f3fb248a4..d7f61cfc00 100644 --- a/apps/web/tests/e2e/get-started-dashboard-link.spec.ts +++ b/apps/web/tests/e2e/get-started-dashboard-link.spec.ts @@ -9,7 +9,7 @@ test.describe('/get-started dashboard escape hatch', () => { const skipLink = page.getByRole('link', { name: /skip to dashboard/i }); await expect(skipLink).toBeVisible(); - await expect(skipLink).toHaveAttribute('href', /callbackPath=%2Fprofile/); + await expect(skipLink).toHaveAttribute('href', '/profile'); }); test('shows the dashboard link after fake login and survey skip', async ({ page }) => { @@ -36,6 +36,6 @@ test.describe('/get-started dashboard escape hatch', () => { await page.goto('/get-started'); const skipLink = page.getByRole('link', { name: /skip to dashboard/i }); await expect(skipLink).toBeVisible(); - await expect(skipLink).toHaveAttribute('href', /\/profile|\/organizations\//); + await expect(skipLink).toHaveAttribute('href', '/profile'); }); });