mirror of
https://github.com/supermemoryai/supermemory.git
synced 2026-10-08 03:08:21 +00:00
Scope desktop onboarding state by account
This commit is contained in:
parent
65238152da
commit
c9bf702272
4 changed files with 88 additions and 45 deletions
|
|
@ -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"))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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" ? (
|
||||
<ToolsStep
|
||||
session={session}
|
||||
connectedTools={connectedTools}
|
||||
onConnectedToolsChange={setConnectedTools}
|
||||
onBack={() => 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()
|
||||
|
|
|
|||
|
|
@ -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<AuthStatus>("loading")
|
||||
const [session, setSession] = useState<AuthSession | null>(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 (
|
||||
|
|
|
|||
|
|
@ -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<DesktopOnboardingStatus>
|
||||
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<DesktopOnboardingStatus>,
|
||||
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,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue