From 205add853f654a951583cd59c19a35d7dda2af9f Mon Sep 17 00:00:00 2001 From: Nikhil-Doye Date: Mon, 20 Oct 2025 17:23:49 -0400 Subject: [PATCH] Add onboarding system with comprehensive management and UI components - Introduced OnboardingManager utility for managing onboarding preferences and state, including localStorage integration for persistence. - Implemented OnboardingDemo component to showcase onboarding status and actions, enhancing user interaction with onboarding features. - Developed OnboardingModal component for guided onboarding experience, allowing users to navigate through steps and manage preferences. - Enhanced WorkflowList and WorkflowToolbar components to conditionally display onboarding based on user preferences and workflow count. - Added unit tests for OnboardingManager to ensure reliable functionality and error handling in onboarding processes. --- src/__tests__/utils/onboardingManager.test.ts | 315 ++++++++++++++++++ src/components/OnboardingDemo.tsx | 239 +++++++++++++ src/components/OnboardingModal.tsx | 142 +++++++- src/components/WorkflowList.tsx | 58 +++- src/components/WorkflowToolbar.tsx | 26 ++ src/utils/onboardingManager.ts | 195 +++++++++++ 6 files changed, 959 insertions(+), 16 deletions(-) create mode 100644 src/__tests__/utils/onboardingManager.test.ts create mode 100644 src/components/OnboardingDemo.tsx create mode 100644 src/utils/onboardingManager.ts diff --git a/src/__tests__/utils/onboardingManager.test.ts b/src/__tests__/utils/onboardingManager.test.ts new file mode 100644 index 0000000..703869d --- /dev/null +++ b/src/__tests__/utils/onboardingManager.test.ts @@ -0,0 +1,315 @@ +import { OnboardingManager } from "../../utils/onboardingManager"; + +// Mock localStorage +const localStorageMock = (() => { + let store: Record = {}; + + return { + getItem: (key: string) => store[key] || null, + setItem: (key: string, value: string) => { + store[key] = value; + }, + removeItem: (key: string) => { + delete store[key]; + }, + clear: () => { + store = {}; + }, + }; +})(); + +Object.defineProperty(window, "localStorage", { + value: localStorageMock, +}); + +describe("OnboardingManager", () => { + beforeEach(() => { + localStorageMock.clear(); + }); + + describe("getPreferences", () => { + it("should return default preferences when no stored data", () => { + const prefs = OnboardingManager.getPreferences(); + + expect(prefs).toEqual({ + hasSeenOnboarding: false, + hasOptedOut: false, + lastShownVersion: "0.0.0", + showOnboardingOnStartup: true, + completedSteps: [], + }); + }); + + it("should return stored preferences when available", () => { + const storedPrefs = { + hasSeenOnboarding: true, + hasOptedOut: false, + lastShownVersion: "1.0.0", + showOnboardingOnStartup: false, + completedSteps: [1, 2, 3], + }; + + localStorageMock.setItem( + "onboardingPreferences", + JSON.stringify(storedPrefs) + ); + + const prefs = OnboardingManager.getPreferences(); + expect(prefs).toEqual(storedPrefs); + }); + + it("should handle corrupted localStorage data gracefully", () => { + localStorageMock.setItem("onboardingPreferences", "invalid json"); + + const prefs = OnboardingManager.getPreferences(); + expect(prefs).toEqual({ + hasSeenOnboarding: false, + hasOptedOut: false, + lastShownVersion: "0.0.0", + showOnboardingOnStartup: true, + completedSteps: [], + }); + }); + }); + + describe("savePreferences", () => { + it("should save preferences to localStorage", () => { + const prefs = { + hasSeenOnboarding: true, + hasOptedOut: false, + lastShownVersion: "1.0.0", + showOnboardingOnStartup: true, + completedSteps: [1, 2], + }; + + OnboardingManager.savePreferences(prefs); + + const stored = JSON.parse( + localStorageMock.getItem("onboardingPreferences") || "{}" + ); + expect(stored).toEqual(prefs); + }); + + it("should merge with existing preferences", () => { + // Set initial preferences + const initialPrefs = { + hasSeenOnboarding: false, + hasOptedOut: false, + lastShownVersion: "0.0.0", + showOnboardingOnStartup: true, + completedSteps: [], + }; + localStorageMock.setItem( + "onboardingPreferences", + JSON.stringify(initialPrefs) + ); + + // Update only some fields + OnboardingManager.savePreferences({ + hasSeenOnboarding: true, + completedSteps: [1, 2], + }); + + const stored = JSON.parse( + localStorageMock.getItem("onboardingPreferences") || "{}" + ); + expect(stored).toEqual({ + hasSeenOnboarding: true, + hasOptedOut: false, + lastShownVersion: "0.0.0", + showOnboardingOnStartup: true, + completedSteps: [1, 2], + }); + }); + }); + + describe("shouldShowOnboarding", () => { + it("should show onboarding for first-time users with no workflows", () => { + const shouldShow = OnboardingManager.shouldShowOnboarding(0); + expect(shouldShow).toBe(true); + }); + + it("should not show onboarding if user has opted out", () => { + OnboardingManager.optOutOfOnboarding(); + const shouldShow = OnboardingManager.shouldShowOnboarding(0); + expect(shouldShow).toBe(false); + }); + + it("should not show onboarding if user has workflows", () => { + const shouldShow = OnboardingManager.shouldShowOnboarding(1); + expect(shouldShow).toBe(false); + }); + + it("should show onboarding if user wants to see it on startup", () => { + OnboardingManager.enableOnboardingOnStartup(); + const shouldShow = OnboardingManager.shouldShowOnboarding(0); + expect(shouldShow).toBe(true); + }); + + it("should not show onboarding if user disabled startup option", () => { + OnboardingManager.markOnboardingCompleted(); + OnboardingManager.disableOnboardingOnStartup(); + const shouldShow = OnboardingManager.shouldShowOnboarding(0); + expect(shouldShow).toBe(false); + }); + }); + + describe("markOnboardingCompleted", () => { + it("should mark onboarding as completed", () => { + OnboardingManager.markOnboardingCompleted([1, 2, 3]); + + const prefs = OnboardingManager.getPreferences(); + expect(prefs.hasSeenOnboarding).toBe(true); + expect(prefs.hasOptedOut).toBe(false); + expect(prefs.lastShownVersion).toBe("1.0.0"); + expect(prefs.completedSteps).toEqual([1, 2, 3]); + }); + }); + + describe("optOutOfOnboarding", () => { + it("should opt out of onboarding", () => { + OnboardingManager.optOutOfOnboarding(); + + const prefs = OnboardingManager.getPreferences(); + expect(prefs.hasOptedOut).toBe(true); + expect(prefs.hasSeenOnboarding).toBe(true); + }); + }); + + describe("resetOnboarding", () => { + it("should reset all onboarding preferences", () => { + // Set some preferences first + OnboardingManager.markOnboardingCompleted([1, 2, 3]); + OnboardingManager.disableOnboardingOnStartup(); + + // Reset + OnboardingManager.resetOnboarding(); + + const prefs = OnboardingManager.getPreferences(); + expect(prefs).toEqual({ + hasSeenOnboarding: false, + hasOptedOut: false, + lastShownVersion: "0.0.0", + showOnboardingOnStartup: true, + completedSteps: [], + }); + }); + }); + + describe("enableOnboardingOnStartup", () => { + it("should enable onboarding on startup", () => { + OnboardingManager.disableOnboardingOnStartup(); + OnboardingManager.enableOnboardingOnStartup(); + + const prefs = OnboardingManager.getPreferences(); + expect(prefs.showOnboardingOnStartup).toBe(true); + expect(prefs.hasOptedOut).toBe(false); + }); + }); + + describe("disableOnboardingOnStartup", () => { + it("should disable onboarding on startup", () => { + OnboardingManager.disableOnboardingOnStartup(); + + const prefs = OnboardingManager.getPreferences(); + expect(prefs.showOnboardingOnStartup).toBe(false); + }); + }); + + describe("isNewVersionAvailable", () => { + it("should detect new version when last shown version is different", () => { + OnboardingManager.savePreferences({ lastShownVersion: "0.9.0" }); + + const isNew = OnboardingManager.isNewVersionAvailable(); + expect(isNew).toBe(true); + }); + + it("should not detect new version when versions match", () => { + OnboardingManager.savePreferences({ lastShownVersion: "1.0.0" }); + + const isNew = OnboardingManager.isNewVersionAvailable(); + expect(isNew).toBe(false); + }); + }); + + describe("getProgressPercentage", () => { + it("should calculate progress percentage correctly", () => { + OnboardingManager.savePreferences({ completedSteps: [1, 2, 3] }); + + const progress = OnboardingManager.getProgressPercentage(); + expect(progress).toBe(50); // 3 out of 6 steps = 50% + }); + + it("should return 0 when no steps completed", () => { + OnboardingManager.savePreferences({ completedSteps: [] }); + + const progress = OnboardingManager.getProgressPercentage(); + expect(progress).toBe(0); + }); + + it("should return 100 when all steps completed", () => { + OnboardingManager.savePreferences({ completedSteps: [1, 2, 3, 4, 5, 6] }); + + const progress = OnboardingManager.getProgressPercentage(); + expect(progress).toBe(100); + }); + }); + + describe("hasCompletedStep", () => { + it("should return true for completed steps", () => { + OnboardingManager.savePreferences({ completedSteps: [1, 3, 5] }); + + expect(OnboardingManager.hasCompletedStep(1)).toBe(true); + expect(OnboardingManager.hasCompletedStep(3)).toBe(true); + expect(OnboardingManager.hasCompletedStep(5)).toBe(true); + }); + + it("should return false for incomplete steps", () => { + OnboardingManager.savePreferences({ completedSteps: [1, 3, 5] }); + + expect(OnboardingManager.hasCompletedStep(2)).toBe(false); + expect(OnboardingManager.hasCompletedStep(4)).toBe(false); + expect(OnboardingManager.hasCompletedStep(6)).toBe(false); + }); + }); + + describe("markStepCompleted", () => { + it("should mark a step as completed", () => { + OnboardingManager.savePreferences({ completedSteps: [1, 2] }); + OnboardingManager.markStepCompleted(3); + + const prefs = OnboardingManager.getPreferences(); + expect(prefs.completedSteps).toEqual([1, 2, 3]); + }); + + it("should not duplicate completed steps", () => { + OnboardingManager.savePreferences({ completedSteps: [1, 2, 3] }); + OnboardingManager.markStepCompleted(2); + + const prefs = OnboardingManager.getPreferences(); + expect(prefs.completedSteps).toEqual([1, 2, 3]); + }); + }); + + describe("getStatus", () => { + it("should return comprehensive status information", () => { + OnboardingManager.savePreferences({ + hasSeenOnboarding: true, + hasOptedOut: false, + lastShownVersion: "0.9.0", + showOnboardingOnStartup: true, + completedSteps: [1, 2, 3], + }); + + const status = OnboardingManager.getStatus(); + + expect(status).toEqual({ + hasSeenOnboarding: true, + hasOptedOut: false, + isNewVersionAvailable: true, + progressPercentage: 50, + showOnStartup: true, + }); + }); + }); +}); diff --git a/src/components/OnboardingDemo.tsx b/src/components/OnboardingDemo.tsx new file mode 100644 index 0000000..0db2c66 --- /dev/null +++ b/src/components/OnboardingDemo.tsx @@ -0,0 +1,239 @@ +import React, { useState, useEffect } from "react"; +import { OnboardingManager } from "../utils/onboardingManager"; +import { Card, CardContent, CardHeader, CardTitle } from "./ui/card"; +import { Button } from "./ui/button"; +import { Badge } from "./ui/badge"; +import { + CheckCircle, + XCircle, + RotateCcw, + Eye, + EyeOff, + Settings, + HelpCircle, + AlertCircle, +} from "lucide-react"; + +export const OnboardingDemo: React.FC = () => { + const [status, setStatus] = useState(OnboardingManager.getStatus()); + const [showOnboarding, setShowOnboarding] = useState(false); + + useEffect(() => { + // Update status when component mounts + setStatus(OnboardingManager.getStatus()); + }, []); + + const handleReset = () => { + OnboardingManager.resetOnboarding(); + setStatus(OnboardingManager.getStatus()); + }; + + const handleToggleStartup = () => { + if (status.showOnStartup) { + OnboardingManager.disableOnboardingOnStartup(); + } else { + OnboardingManager.enableOnboardingOnStartup(); + } + setStatus(OnboardingManager.getStatus()); + }; + + const handleOptOut = () => { + OnboardingManager.optOutOfOnboarding(); + setStatus(OnboardingManager.getStatus()); + }; + + const handleMarkCompleted = () => { + OnboardingManager.markOnboardingCompleted([1, 2, 3, 4, 5, 6]); + setStatus(OnboardingManager.getStatus()); + }; + + const handleShowOnboarding = () => { + setShowOnboarding(true); + }; + + const handleOnboardingClose = () => { + setShowOnboarding(false); + setStatus(OnboardingManager.getStatus()); + }; + + return ( +
+ + + + + Onboarding System Demo + + + +
+ {/* Status Overview */} +
+

Current Status

+
+
+ + Has seen onboarding: + + + {status.hasSeenOnboarding ? "Yes" : "No"} + +
+
+ Has opted out: + + {status.hasOptedOut ? "Yes" : "No"} + +
+
+ + Show on startup: + + + {status.showOnStartup ? "Yes" : "No"} + +
+
+ + New version available: + + + {status.isNewVersionAvailable ? "Yes" : "No"} + +
+
+ Progress: + {status.progressPercentage}% +
+
+
+ + {/* Actions */} +
+

Actions

+
+ + + + + + + + + +
+
+
+ + {/* Progress Bar */} +
+
+ + Tutorial Progress + + + {status.progressPercentage}% + +
+
+
+
+
+ + {/* Feature Highlights */} +
+

+ + Enhanced Features +

+
    +
  • • Persistent opt-out status with localStorage
  • +
  • • Progress tracking across sessions
  • +
  • • Version detection for new tutorial content
  • +
  • • Settings panel with startup preferences
  • +
  • • Easy re-access from navigation
  • +
  • • Step-by-step completion tracking
  • +
+
+ + + + {/* Onboarding Modal would be rendered here in a real app */} + {showOnboarding && ( +
+ + + Onboarding Modal + + +

+ This would show the actual OnboardingModal component with all + its enhanced features. +

+ +
+
+
+ )} +
+ ); +}; diff --git a/src/components/OnboardingModal.tsx b/src/components/OnboardingModal.tsx index 0694c5e..9f81ee3 100644 --- a/src/components/OnboardingModal.tsx +++ b/src/components/OnboardingModal.tsx @@ -1,4 +1,4 @@ -import React, { useState } from "react"; +import React, { useState, useEffect } from "react"; import { ArrowRight, ArrowLeft, @@ -9,6 +9,10 @@ import { ArrowDownToLine, Key, Settings, + X, + RotateCcw, + Eye, + EyeOff, } from "lucide-react"; import { Dialog, @@ -19,10 +23,12 @@ import { DialogFooter, } from "./ui/dialog"; import { Button } from "./ui/button"; +import { OnboardingManager } from "../utils/onboardingManager"; interface OnboardingModalProps { isOpen: boolean; onClose: () => void; + onComplete?: () => void; } const steps = [ @@ -208,17 +214,39 @@ const steps = [ export const OnboardingModal: React.FC = ({ isOpen, onClose, + onComplete, }) => { const [currentStep, setCurrentStep] = useState(0); + const [showSettings, setShowSettings] = useState(false); + const [preferences, setPreferences] = useState( + OnboardingManager.getPreferences() + ); + + useEffect(() => { + if (isOpen) { + setPreferences(OnboardingManager.getPreferences()); + // Start from the first incomplete step + const firstIncompleteStep = steps.findIndex( + (step) => !OnboardingManager.hasCompletedStep(step.id) + ); + setCurrentStep(Math.max(0, firstIncompleteStep)); + } + }, [isOpen]); if (!isOpen) return null; const currentStepData = steps[currentStep]; const isFirstStep = currentStep === 0; const isLastStep = currentStep === steps.length - 1; + const progress = ((currentStep + 1) / steps.length) * 100; const handleNext = () => { + // Mark current step as completed + OnboardingManager.markStepCompleted(currentStepData.id); + if (isLastStep) { + OnboardingManager.markOnboardingCompleted(); + onComplete?.(); onClose(); } else { setCurrentStep(currentStep + 1); @@ -232,28 +260,64 @@ export const OnboardingModal: React.FC = ({ }; const handleSkip = () => { + OnboardingManager.optOutOfOnboarding(); onClose(); }; + const handleReset = () => { + OnboardingManager.resetOnboarding(); + setCurrentStep(0); + setPreferences(OnboardingManager.getPreferences()); + }; + + const handleToggleStartup = () => { + const newShowOnStartup = !preferences.showOnboardingOnStartup; + if (newShowOnStartup) { + OnboardingManager.enableOnboardingOnStartup(); + } else { + OnboardingManager.disableOnboardingOnStartup(); + } + setPreferences(OnboardingManager.getPreferences()); + }; + return ( {/* Header */}
-
+
-
- +
+ {currentStepData.title} + {OnboardingManager.hasCompletedStep(currentStepData.id) && ( + + )} {currentStepData.description}
+
+ + +
{/* Progress Bar */} @@ -284,6 +348,76 @@ export const OnboardingModal: React.FC = ({ {currentStepData.content}
+ {/* Settings Panel */} + {showSettings && ( +
+

+ Onboarding Settings +

+
+
+
+ +

+ Display this tutorial when you have no workflows +

+
+ +
+ +
+
+ +

+ Start the tutorial from the beginning +

+
+ +
+ +
+
+

+ Progress: {OnboardingManager.getProgressPercentage()}% + complete +

+

+ Completed steps: {preferences.completedSteps.length} of{" "} + {steps.length} +

+
+
+
+
+ )} + {/* Footer */}
diff --git a/src/components/WorkflowList.tsx b/src/components/WorkflowList.tsx index a9963f3..6233396 100644 --- a/src/components/WorkflowList.tsx +++ b/src/components/WorkflowList.tsx @@ -1,6 +1,7 @@ import React, { useState, useEffect } from "react"; import { useWorkflowStore } from "../store/workflowStore"; import { OnboardingModal } from "./OnboardingModal"; +import { OnboardingManager } from "../utils/onboardingManager"; import { Plus, Play, @@ -36,10 +37,9 @@ export const WorkflowList: React.FC = ({ const [editingWorkflowName, setEditingWorkflowName] = useState(""); const [showOnboarding, setShowOnboarding] = useState(false); - // Show onboarding for first-time users + // Show onboarding based on preferences and workflow count useEffect(() => { - const hasSeenOnboarding = localStorage.getItem("hasSeenOnboarding"); - if (!hasSeenOnboarding && workflows.length === 0) { + if (OnboardingManager.shouldShowOnboarding(workflows.length)) { setShowOnboarding(true); } }, [workflows.length]); @@ -91,7 +91,11 @@ export const WorkflowList: React.FC = ({ const handleOnboardingClose = () => { setShowOnboarding(false); - localStorage.setItem("hasSeenOnboarding", "true"); + }; + + const handleOnboardingComplete = () => { + setShowOnboarding(false); + // OnboardingManager.markOnboardingCompleted() is called in the modal }; const handleShowOnboarding = () => { @@ -158,14 +162,43 @@ export const WorkflowList: React.FC = ({

- +
+ + + {/* Tooltip with onboarding status */} +
+
+
Tutorial Status
+
+ {OnboardingManager.getStatus().hasSeenOnboarding ? ( + <> + Progress:{" "} + {OnboardingManager.getStatus().progressPercentage}% + {OnboardingManager.getStatus() + .isNewVersionAvailable && ( +
+ New version available! +
+ )} + + ) : ( + "Not started yet" + )} +
+
+
+
+
); diff --git a/src/components/WorkflowToolbar.tsx b/src/components/WorkflowToolbar.tsx index 71a7c38..219e7fc 100644 --- a/src/components/WorkflowToolbar.tsx +++ b/src/components/WorkflowToolbar.tsx @@ -13,6 +13,7 @@ import { Clock, Activity, GitBranch, + HelpCircle, } from "lucide-react"; import { downloadWorkflow, @@ -21,6 +22,8 @@ import { import { ApiKeysSettings } from "./ApiKeysSettings"; import { DatabaseConnectionManager } from "./DatabaseConnectionManager"; import { WorkflowScheduler } from "./WorkflowScheduler"; +import { OnboardingModal } from "./OnboardingModal"; +import { OnboardingManager } from "../utils/onboardingManager"; import { Button } from "./ui/button"; export const WorkflowToolbar: React.FC = () => { @@ -39,6 +42,7 @@ export const WorkflowToolbar: React.FC = () => { const [showDatabaseManager, setShowDatabaseManager] = useState(false); const [showScheduler, setShowScheduler] = useState(false); const [showSaveSuccess, setShowSaveSuccess] = useState(false); + const [showOnboarding, setShowOnboarding] = useState(false); const handleSave = () => { if (currentWorkflow) { @@ -158,6 +162,19 @@ export const WorkflowToolbar: React.FC = () => { Schedule + +
{/* Execution Mode Selector */} @@ -262,6 +279,15 @@ export const WorkflowToolbar: React.FC = () => { workflowId={currentWorkflow?.id} /> )} + + {/* Onboarding Modal */} + {showOnboarding && ( + setShowOnboarding(false)} + onComplete={() => setShowOnboarding(false)} + /> + )}
); }; diff --git a/src/utils/onboardingManager.ts b/src/utils/onboardingManager.ts new file mode 100644 index 0000000..7e70eef --- /dev/null +++ b/src/utils/onboardingManager.ts @@ -0,0 +1,195 @@ +export interface OnboardingPreferences { + hasSeenOnboarding: boolean; + hasOptedOut: boolean; + lastShownVersion: string; + showOnboardingOnStartup: boolean; + completedSteps: number[]; +} + +const ONBOARDING_VERSION = "1.0.0"; +const STORAGE_KEY = "onboardingPreferences"; + +export class OnboardingManager { + /** + * Get current onboarding preferences from localStorage + */ + static getPreferences(): OnboardingPreferences { + try { + const stored = localStorage.getItem(STORAGE_KEY); + if (stored) { + const parsed = JSON.parse(stored); + return { + hasSeenOnboarding: parsed.hasSeenOnboarding || false, + hasOptedOut: parsed.hasOptedOut || false, + lastShownVersion: parsed.lastShownVersion || "0.0.0", + showOnboardingOnStartup: parsed.showOnboardingOnStartup !== false, // Default to true + completedSteps: parsed.completedSteps || [], + }; + } + } catch (error) { + console.warn("Failed to parse onboarding preferences:", error); + } + + // Return default preferences + return { + hasSeenOnboarding: false, + hasOptedOut: false, + lastShownVersion: "0.0.0", + showOnboardingOnStartup: true, + completedSteps: [], + }; + } + + /** + * Save onboarding preferences to localStorage + */ + static savePreferences(preferences: Partial): void { + try { + const current = this.getPreferences(); + const updated = { ...current, ...preferences }; + localStorage.setItem(STORAGE_KEY, JSON.stringify(updated)); + } catch (error) { + console.warn("Failed to save onboarding preferences:", error); + } + } + + /** + * Check if onboarding should be shown + */ + static shouldShowOnboarding(workflowCount: number = 0): boolean { + const prefs = this.getPreferences(); + + // Don't show if user has opted out + if (prefs.hasOptedOut) { + return false; + } + + // Show if user hasn't seen onboarding and has no workflows + if (!prefs.hasSeenOnboarding && workflowCount === 0) { + return true; + } + + // Show if user wants to see onboarding on startup + if (prefs.showOnboardingOnStartup && workflowCount === 0) { + return true; + } + + // Show if there's a new version of onboarding + if (this.isNewVersionAvailable()) { + return true; + } + + return false; + } + + /** + * Mark onboarding as completed + */ + static markOnboardingCompleted(completedSteps: number[] = []): void { + this.savePreferences({ + hasSeenOnboarding: true, + hasOptedOut: false, + lastShownVersion: ONBOARDING_VERSION, + completedSteps, + }); + } + + /** + * Opt out of onboarding + */ + static optOutOfOnboarding(): void { + this.savePreferences({ + hasOptedOut: true, + hasSeenOnboarding: true, + }); + } + + /** + * Reset onboarding preferences (for testing or user request) + */ + static resetOnboarding(): void { + this.savePreferences({ + hasSeenOnboarding: false, + hasOptedOut: false, + lastShownVersion: "0.0.0", + showOnboardingOnStartup: true, + completedSteps: [], + }); + } + + /** + * Enable onboarding on startup + */ + static enableOnboardingOnStartup(): void { + this.savePreferences({ + showOnboardingOnStartup: true, + hasOptedOut: false, + }); + } + + /** + * Disable onboarding on startup + */ + static disableOnboardingOnStartup(): void { + this.savePreferences({ + showOnboardingOnStartup: false, + }); + } + + /** + * Check if a new version of onboarding is available + */ + static isNewVersionAvailable(): boolean { + const prefs = this.getPreferences(); + return prefs.lastShownVersion !== ONBOARDING_VERSION; + } + + /** + * Get onboarding progress percentage + */ + static getProgressPercentage(): number { + const prefs = this.getPreferences(); + const totalSteps = 6; // Assuming 6 steps in onboarding + return Math.round((prefs.completedSteps.length / totalSteps) * 100); + } + + /** + * Check if user has completed a specific step + */ + static hasCompletedStep(stepId: number): boolean { + const prefs = this.getPreferences(); + return prefs.completedSteps.includes(stepId); + } + + /** + * Mark a specific step as completed + */ + static markStepCompleted(stepId: number): void { + const prefs = this.getPreferences(); + if (!prefs.completedSteps.includes(stepId)) { + this.savePreferences({ + completedSteps: [...prefs.completedSteps, stepId], + }); + } + } + + /** + * Get onboarding status for display + */ + static getStatus(): { + hasSeenOnboarding: boolean; + hasOptedOut: boolean; + isNewVersionAvailable: boolean; + progressPercentage: number; + showOnStartup: boolean; + } { + const prefs = this.getPreferences(); + return { + hasSeenOnboarding: prefs.hasSeenOnboarding, + hasOptedOut: prefs.hasOptedOut, + isNewVersionAvailable: this.isNewVersionAvailable(), + progressPercentage: this.getProgressPercentage(), + showOnStartup: prefs.showOnboardingOnStartup, + }; + } +}