From c9bf702272bdd70a0559715b2320067e81a89f4d Mon Sep 17 00:00:00 2001 From: Sreeram Sreedhar Date: Tue, 23 Jun 2026 21:26:24 -0700 Subject: [PATCH] Scope desktop onboarding state by account --- apps/desktop/app/login/page.tsx | 4 +- apps/desktop/app/onboarding/page.tsx | 28 ++++++---- apps/desktop/components/auth-guard.tsx | 28 ++++++---- apps/desktop/lib/onboarding.ts | 73 ++++++++++++++++++-------- 4 files changed, 88 insertions(+), 45 deletions(-) diff --git a/apps/desktop/app/login/page.tsx b/apps/desktop/app/login/page.tsx index 550c453b..b6d74798 100644 --- a/apps/desktop/app/login/page.tsx +++ b/apps/desktop/app/login/page.tsx @@ -51,8 +51,8 @@ export default function LoginPage() { setSubmittedEmail(null) setIsSubmitting(false) try { - await getSession() - router.replace(postAuthRedirectPath()) + const session = await getSession() + router.replace(postAuthRedirectPath(session)) } catch (err) { setError(formatError(err, "Could not validate browser sign-in")) } diff --git a/apps/desktop/app/onboarding/page.tsx b/apps/desktop/app/onboarding/page.tsx index 41cfbea7..71d7a59b 100644 --- a/apps/desktop/app/onboarding/page.tsx +++ b/apps/desktop/app/onboarding/page.tsx @@ -79,7 +79,7 @@ export default function DesktopOnboardingPage() { getSession() .then((nextSession) => { if (cancelled) return - const status = getDesktopOnboardingStatus() + const status = getDesktopOnboardingStatus(nextSession) setSession(nextSession) setStep(status.lastStep === "done" ? "welcome" : status.lastStep) setConnectedTools(status.connectedTools) @@ -94,23 +94,26 @@ export default function DesktopOnboardingPage() { } }, [router]) - const goToStep = useCallback((nextStep: DesktopOnboardingStep) => { - setStep(nextStep) - updateDesktopOnboardingStatus({ lastStep: nextStep }) - }, []) + const goToStep = useCallback( + (nextStep: DesktopOnboardingStep) => { + setStep(nextStep) + updateDesktopOnboardingStatus({ lastStep: nextStep }, session) + }, + [session], + ) const complete = useCallback( (nextConnectedTools = connectedTools) => { - completeDesktopOnboarding(nextConnectedTools) + completeDesktopOnboarding(nextConnectedTools, session) router.replace("/") }, - [connectedTools, router], + [connectedTools, router, session], ) const skip = useCallback(() => { - skipDesktopOnboarding(step) + skipDesktopOnboarding(step, session) router.replace("/") - }, [router, step]) + }, [router, session, step]) if (!authChecked || !session) { return ( @@ -148,6 +151,7 @@ export default function DesktopOnboardingPage() { ) : null} {step === "tools" ? ( goToStep("welcome")} @@ -233,11 +237,13 @@ function WelcomeStep({ } function ToolsStep({ + session, connectedTools, onConnectedToolsChange, onBack, onContinue, }: { + session: AuthSession connectedTools: DesktopToolId[] onConnectedToolsChange: (tools: DesktopToolId[]) => void onBack: () => void @@ -263,13 +269,13 @@ function ToolsStep({ .filter((tool) => tool.connected) .map((tool) => tool.id) onConnectedToolsChange(nextConnected) - updateDesktopOnboardingStatus({ connectedTools: nextConnected }) + updateDesktopOnboardingStatus({ connectedTools: nextConnected }, session) } catch (err) { setError(formatUnknownError(err, "Could not scan local tools")) } finally { setLoading(false) } - }, [onConnectedToolsChange]) + }, [onConnectedToolsChange, session]) useEffect(() => { refreshTools() diff --git a/apps/desktop/components/auth-guard.tsx b/apps/desktop/components/auth-guard.tsx index be2c2780..5adab3fb 100644 --- a/apps/desktop/components/auth-guard.tsx +++ b/apps/desktop/components/auth-guard.tsx @@ -2,23 +2,33 @@ import { useRouter } from "next/navigation" import { type ReactNode, useEffect, useState } from "react" -import { getSession } from "@/lib/auth" +import { getSession, type AuthSession } from "@/lib/auth" import { shouldShowDesktopOnboarding } from "@/lib/onboarding" type AuthStatus = "loading" | "authenticated" | "unauthenticated" -function useAuthStatus(): AuthStatus { +function useAuthStatus(): { + status: AuthStatus + session: AuthSession | null +} { const [status, setStatus] = useState("loading") + const [session, setSession] = useState(null) useEffect(() => { let cancelled = false getSession() - .then(() => { - if (!cancelled) setStatus("authenticated") + .then((nextSession) => { + if (!cancelled) { + setSession(nextSession) + setStatus("authenticated") + } }) .catch(() => { - if (!cancelled) setStatus("unauthenticated") + if (!cancelled) { + setSession(null) + setStatus("unauthenticated") + } }) return () => { @@ -26,11 +36,11 @@ function useAuthStatus(): AuthStatus { } }, []) - return status + return { status, session } } export function AuthGuard({ children }: { children: ReactNode }) { - const status = useAuthStatus() + const { status, session } = useAuthStatus() const router = useRouter() useEffect(() => { @@ -39,10 +49,10 @@ export function AuthGuard({ children }: { children: ReactNode }) { return } - if (status === "authenticated" && shouldShowDesktopOnboarding()) { + if (status === "authenticated" && shouldShowDesktopOnboarding(session)) { router.replace("/onboarding") } - }, [status, router]) + }, [status, session, router]) if (status !== "authenticated") { return ( diff --git a/apps/desktop/lib/onboarding.ts b/apps/desktop/lib/onboarding.ts index 41a7237c..8e69383f 100644 --- a/apps/desktop/lib/onboarding.ts +++ b/apps/desktop/lib/onboarding.ts @@ -4,6 +4,11 @@ import type { DesktopToolId } from "@/lib/tools" export type DesktopOnboardingStep = "welcome" | "tools" | "filesystem" | "done" +export type DesktopOnboardingOwner = { + userId?: string | null + email?: string | null +} + export type DesktopOnboardingStatus = { version: "v1" completed: boolean @@ -30,11 +35,19 @@ function storageAvailable() { ) } -export function getDesktopOnboardingStatus(): DesktopOnboardingStatus { +function storageKey(owner?: DesktopOnboardingOwner | null) { + const accountId = owner?.userId || owner?.email + if (!accountId) return STORAGE_KEY + return `${STORAGE_KEY}:${encodeURIComponent(accountId)}` +} + +export function getDesktopOnboardingStatus( + owner?: DesktopOnboardingOwner | null, +): DesktopOnboardingStatus { if (!storageAvailable()) return DEFAULT_STATUS try { - const raw = window.localStorage.getItem(STORAGE_KEY) + const raw = window.localStorage.getItem(storageKey(owner)) if (!raw) return DEFAULT_STATUS const parsed = JSON.parse(raw) as Partial if (parsed.version !== "v1") return DEFAULT_STATUS @@ -52,52 +65,66 @@ export function getDesktopOnboardingStatus(): DesktopOnboardingStatus { export function saveDesktopOnboardingStatus( status: DesktopOnboardingStatus, + owner?: DesktopOnboardingOwner | null, ): void { if (!storageAvailable()) return try { - window.localStorage.setItem(STORAGE_KEY, JSON.stringify(status)) + window.localStorage.setItem(storageKey(owner), JSON.stringify(status)) } catch {} } export function updateDesktopOnboardingStatus( patch: Partial, + owner?: DesktopOnboardingOwner | null, ): DesktopOnboardingStatus { const next: DesktopOnboardingStatus = { - ...getDesktopOnboardingStatus(), + ...getDesktopOnboardingStatus(owner), ...patch, version: "v1", } - saveDesktopOnboardingStatus(next) + saveDesktopOnboardingStatus(next, owner) return next } -export function shouldShowDesktopOnboarding() { - const status = getDesktopOnboardingStatus() +export function shouldShowDesktopOnboarding( + owner?: DesktopOnboardingOwner | null, +) { + const status = getDesktopOnboardingStatus(owner) return !status.completed && !status.skipped } -export function postAuthRedirectPath() { - return shouldShowDesktopOnboarding() ? "/onboarding" : "/" +export function postAuthRedirectPath(owner?: DesktopOnboardingOwner | null) { + return shouldShowDesktopOnboarding(owner) ? "/onboarding" : "/" } export function completeDesktopOnboarding( connectedTools: DesktopToolId[] = [], + owner?: DesktopOnboardingOwner | null, ) { - return updateDesktopOnboardingStatus({ - completed: true, - skipped: false, - completedAt: new Date().toISOString(), - lastStep: "done", - connectedTools, - }) + return updateDesktopOnboardingStatus( + { + completed: true, + skipped: false, + completedAt: new Date().toISOString(), + lastStep: "done", + connectedTools, + }, + owner, + ) } -export function skipDesktopOnboarding(lastStep: DesktopOnboardingStep) { - return updateDesktopOnboardingStatus({ - completed: false, - skipped: true, - skippedAt: new Date().toISOString(), - lastStep, - }) +export function skipDesktopOnboarding( + lastStep: DesktopOnboardingStep, + owner?: DesktopOnboardingOwner | null, +) { + return updateDesktopOnboardingStatus( + { + completed: false, + skipped: true, + skippedAt: new Date().toISOString(), + lastStep, + }, + owner, + ) }