/** * Onboarding state storage utilities * Simple localStorage wrapper following announcement-storage.ts pattern * Provides atomic writes to prevent race conditions */ export interface OnboardingState { completedSteps: string[] skippedSteps: string[] dismissed: boolean lastUpdated: string } const DEFAULT_STATE: OnboardingState = { completedSteps: [], skippedSteps: [], dismissed: false, lastUpdated: new Date().toISOString() } /** * Save onboarding state to localStorage (atomic write) */ export function saveOnboardingState(key: string, state: OnboardingState): void { if (typeof window === 'undefined') return try { localStorage.setItem(key, JSON.stringify(state)) console.log('💾 Saved onboarding state:', state) // Dispatch custom event for cross-tab sync if (typeof window !== 'undefined') { window.dispatchEvent( new CustomEvent('localStorageUpdate', { detail: { key, newValue: JSON.stringify(state) } }) ) } } catch (err) { console.warn('[onboarding-storage] Failed saving to localStorage:', err) } } /** * Load onboarding state from localStorage */ export function loadOnboardingState(key: string): OnboardingState { if (typeof window === 'undefined') return DEFAULT_STATE try { const raw = localStorage.getItem(key) if (!raw) return DEFAULT_STATE const parsed = JSON.parse(raw) as OnboardingState return parsed } catch (err) { console.warn('[onboarding-storage] Failed parsing localStorage data:', err) return DEFAULT_STATE } } /** * Mark multiple steps as complete in a single atomic update */ export function markMultipleComplete( key: string, stepIds: string[] ): OnboardingState { const state = loadOnboardingState(key) const newState: OnboardingState = { ...state, completedSteps: [...new Set([...state.completedSteps, ...stepIds])], skippedSteps: state.skippedSteps.filter(id => !stepIds.includes(id)), lastUpdated: new Date().toISOString() } saveOnboardingState(key, newState) return newState } /** * Mark a single step as complete */ export function markStepComplete(key: string, stepId: string): OnboardingState { return markMultipleComplete(key, [stepId]) } /** * Mark a step as skipped */ export function markStepSkipped(key: string, stepId: string): OnboardingState { const state = loadOnboardingState(key) const newState: OnboardingState = { ...state, skippedSteps: [...new Set([...state.skippedSteps, stepId])], completedSteps: state.completedSteps.filter(id => id !== stepId), lastUpdated: new Date().toISOString() } saveOnboardingState(key, newState) return newState } /** * Dismiss the onboarding walkthrough */ export function dismissOnboarding(key: string): OnboardingState { const state = loadOnboardingState(key) const newState: OnboardingState = { ...state, dismissed: true, lastUpdated: new Date().toISOString() } saveOnboardingState(key, newState) return newState } /** * Check if a step is complete */ export function isStepComplete(key: string, stepId: string): boolean { const state = loadOnboardingState(key) return state.completedSteps.includes(stepId) } /** * Check if a step is skipped */ export function isStepSkipped(key: string, stepId: string): boolean { const state = loadOnboardingState(key) return state.skippedSteps.includes(stepId) } /** * Check if onboarding is dismissed */ export function isOnboardingDismissed(key: string): boolean { const state = loadOnboardingState(key) return state.dismissed }