/** * Progress Tracker * Manages persistent storage of user progress through the onboarding flow * using browser local storage. * * Storage Schema: * { * "version": "1.0", * "tourCompleted": false, * "completedTooltips": ["welcome", "dns-priority", ...], * "currentStep": 3, * "completionTimestamp": "2024-01-15T10:30:00Z", * "dnsSetupDeferred": false, * "lastVisit": "2024-01-15T10:30:00Z" * } */ (function(window) { 'use strict'; /** * ProgressTracker class * Manages persistent storage of onboarding progress * * @class * @param {string} storageKey - The key to use for local storage (default: 'dashcaddy_onboarding') */ class ProgressTracker { constructor(storageKey = 'dashcaddy_onboarding') { this.storageKey = storageKey; this.storageVersion = '1.0'; // Initialize storage if it doesn't exist this._initializeStorage(); // Update last visit timestamp this._updateLastVisit(); } /** * Initialize storage with default values if it doesn't exist * @private */ _initializeStorage() { const existing = this._getStorage(); if (!existing || existing.version !== this.storageVersion) { const defaultState = { version: this.storageVersion, tourCompleted: false, completedTooltips: [], currentStep: 0, completionTimestamp: null, dnsSetupDeferred: false, lastVisit: new Date().toISOString() }; this._setStorage(defaultState); } } /** * Get the current storage state * @private * @returns {Object|null} The storage state or null if unavailable */ _getStorage() { try { const data = localStorage.getItem(this.storageKey); return data ? JSON.parse(data) : null; } catch (error) { console.error('[ProgressTracker] Error reading from storage:', error); return null; } } /** * Set the storage state * @private * @param {Object} state - The state to save */ _setStorage(state) { try { localStorage.setItem(this.storageKey, JSON.stringify(state)); } catch (error) { console.error('[ProgressTracker] Error writing to storage:', error); // Handle quota exceeded or storage unavailable // Fall back to session storage or in-memory storage this._handleStorageError(error); } } /** * Handle storage errors (quota exceeded, unavailable, etc.) * @private * @param {Error} error - The error that occurred */ _handleStorageError(error) { // Try session storage as fallback try { sessionStorage.setItem(this.storageKey, JSON.stringify(this._getStorage())); console.warn('[ProgressTracker] Falling back to session storage'); } catch (sessionError) { console.error('[ProgressTracker] Session storage also unavailable:', sessionError); // Could implement in-memory fallback here if needed } } /** * Update the last visit timestamp * @private */ _updateLastVisit() { const state = this._getStorage(); if (state) { state.lastVisit = new Date().toISOString(); this._setStorage(state); } } /** * Check if a specific tooltip has been completed * @param {string} tooltipId - The ID of the tooltip to check * @returns {boolean} True if the tooltip has been completed */ isTooltipCompleted(tooltipId) { const state = this._getStorage(); if (!state) return false; return state.completedTooltips.includes(tooltipId); } /** * Mark a tooltip as completed with timestamp * @param {string} tooltipId - The ID of the tooltip to mark as completed */ markTooltipCompleted(tooltipId) { const state = this._getStorage(); if (!state) return; // Add tooltip to completed list if not already there if (!state.completedTooltips.includes(tooltipId)) { state.completedTooltips.push(tooltipId); // Store timestamp for this specific tooltip if (!state.tooltipTimestamps) { state.tooltipTimestamps = {}; } state.tooltipTimestamps[tooltipId] = new Date().toISOString(); this._setStorage(state); } } /** * Check if the entire tour has been completed * @returns {boolean} True if the tour is completed */ isTourCompleted() { const state = this._getStorage(); if (!state) return false; return state.tourCompleted === true; } /** * Mark the entire tour as completed */ markTourCompleted() { const state = this._getStorage(); if (!state) return; state.tourCompleted = true; state.completionTimestamp = new Date().toISOString(); this._setStorage(state); } /** * Get the current step index * @returns {number} The current step index (0-based) */ getCurrentStep() { const state = this._getStorage(); if (!state) return 0; return state.currentStep || 0; } /** * Set the current step index * @param {number} stepIndex - The step index to set (0-based) */ setCurrentStep(stepIndex) { const state = this._getStorage(); if (!state) return; state.currentStep = stepIndex; this._setStorage(state); } /** * Reset all progress and clear storage */ resetProgress() { const defaultState = { version: this.storageVersion, tourCompleted: false, completedTooltips: [], currentStep: 0, completionTimestamp: null, dnsSetupDeferred: false, lastVisit: new Date().toISOString() }; this._setStorage(defaultState); } /** * Get the completion timestamp * @returns {Date|null} The completion timestamp or null if not completed */ getCompletionTimestamp() { const state = this._getStorage(); if (!state || !state.completionTimestamp) return null; return new Date(state.completionTimestamp); } /** * Check if DNS setup was deferred * @returns {boolean} True if DNS setup was deferred */ isDnsSetupDeferred() { const state = this._getStorage(); if (!state) return false; return state.dnsSetupDeferred === true; } /** * Mark DNS setup as deferred */ markDnsSetupDeferred() { const state = this._getStorage(); if (!state) return; state.dnsSetupDeferred = true; this._setStorage(state); } /** * Get the timestamp for a specific tooltip completion * @param {string} tooltipId - The ID of the tooltip * @returns {Date|null} The timestamp or null if not completed */ getTooltipTimestamp(tooltipId) { const state = this._getStorage(); if (!state || !state.tooltipTimestamps || !state.tooltipTimestamps[tooltipId]) { return null; } return new Date(state.tooltipTimestamps[tooltipId]); } /** * Get all completed tooltip IDs * @returns {string[]} Array of completed tooltip IDs */ getCompletedTooltips() { const state = this._getStorage(); if (!state) return []; return state.completedTooltips || []; } /** * Get the last visit timestamp * @returns {Date|null} The last visit timestamp */ getLastVisit() { const state = this._getStorage(); if (!state || !state.lastVisit) return null; return new Date(state.lastVisit); } } // Export to global scope window.ProgressTracker = ProgressTracker; console.log('[ProgressTracker] Module loaded'); })(window);