diff --git a/status/js/core/init.js b/status/js/core/init.js index a93a631..5fa3617 100644 --- a/status/js/core/init.js +++ b/status/js/core/init.js @@ -58,7 +58,12 @@ if (d.success) window._updateAuthCard(d.config.enabled && d.config.isSetUp, d.config.sessionDuration); } catch (e) { /* ignore */ } } - // Lazy-load onboarding for first-time users, otherwise just add the tour button + if (window.__dashcaddySiteConfigLoaded) { + try { + await window.__dashcaddySiteConfigLoaded; + } catch (_) { /* ignore */ } + } + // Lazy-load onboarding only once per install, otherwise just add the tour button addTourButton(); if (shouldLoadOnboarding()) { loadOnboarding(); @@ -89,6 +94,9 @@ // Check if onboarding should auto-start (first-time user) function shouldLoadOnboarding() { + if (typeof SITE !== 'undefined' && SITE.onboardingCompleted) { + return false; + } try { const data = JSON.parse(localStorage.getItem('dashcaddy_onboarding')); return !data || (!data.tourCompleted && data.currentStep === 0); @@ -140,11 +148,10 @@ function addTourButton() { if (document.getElementById('restart-tour-btn')) return; - // Check if tour has been completed before - let tourDone = false; + let tourDone = typeof SITE !== 'undefined' && SITE.onboardingCompleted; try { const data = JSON.parse(localStorage.getItem('dashcaddy_onboarding')); - tourDone = data && data.tourCompleted; + tourDone = tourDone || !!(data && data.tourCompleted); } catch (_) {} // Before first completion: show in primary toolbar. After: tuck into Admin section. diff --git a/status/js/globals.js b/status/js/globals.js index 859178b..d7a6cee 100644 --- a/status/js/globals.js +++ b/status/js/globals.js @@ -35,9 +35,10 @@ const SITE = { configurationType: (_cachedCfg && _cachedCfg.configurationType) || 'homelab', domain: (_cachedCfg && _cachedCfg.domain) || '', defaults: (_cachedCfg && _cachedCfg.defaults) || {}, - routingMode: (_cachedCfg && _cachedCfg.routingMode) || 'subdomain' + routingMode: (_cachedCfg && _cachedCfg.routingMode) || 'subdomain', + onboardingCompleted: false }; -(async function loadSiteConfig() { +window.__dashcaddySiteConfigLoaded = (async function loadSiteConfig() { try { const r = await fetch('/api/v1/config'); if (r.ok) { @@ -56,6 +57,7 @@ const SITE = { if (c.domain) SITE.domain = c.domain; if (c.defaults) SITE.defaults = c.defaults; if (c.routingMode) SITE.routingMode = c.routingMode; + SITE.onboardingCompleted = c.onboardingCompleted === true; // Cache only non-sensitive display config (TLD, domain, routing mode) // DNS IPs and server topology are NOT cached — fetched from API each load localStorage.setItem('dashcaddy_site_config', JSON.stringify({ diff --git a/status/js/onboarding.js b/status/js/onboarding.js index 638294d..f690ade 100644 --- a/status/js/onboarding.js +++ b/status/js/onboarding.js @@ -22,6 +22,12 @@ try { console.log('[Onboarding] Initializing system...'); + if (window.__dashcaddySiteConfigLoaded) { + try { + await window.__dashcaddySiteConfigLoaded; + } catch (_) { /* ignore */ } + } + // Initialize Error Handler first errorHandler = new ErrorHandler(); console.log('[Onboarding] Error Handler initialized'); @@ -44,7 +50,8 @@ // Check if tour should auto-start if (tourManager.shouldAutoStart()) { - console.log('[Onboarding] Auto-starting tour for first-time user'); + console.log('[Onboarding] Auto-starting tour for first-time install'); + await progressTracker.markInstallOnboardingCompleted(); // Wait a bit for page to fully load setTimeout(() => { tourManager.startTour(); diff --git a/status/js/progress-tracker.js b/status/js/progress-tracker.js index 9ed9d8c..9fb2757 100644 --- a/status/js/progress-tracker.js +++ b/status/js/progress-tracker.js @@ -29,6 +29,7 @@ constructor(storageKey = 'dashcaddy_onboarding') { this.storageKey = storageKey; this.storageVersion = '1.0'; + this.installOnboardingCompleted = typeof SITE !== 'undefined' && SITE.onboardingCompleted === true; // Initialize storage if it doesn't exist this._initializeStorage(); @@ -159,6 +160,36 @@ return state.tourCompleted === true; } + /** + * Check if onboarding has already been consumed for this install. + * @returns {boolean} True if auto-onboarding should stay suppressed + */ + isInstallOnboardingCompleted() { + return this.installOnboardingCompleted === true; + } + + /** + * Persist install-wide onboarding completion so it only auto-starts once. + * Manual Help Tour restarts are still allowed via local state reset. + * @returns {Promise} + */ + async markInstallOnboardingCompleted() { + if (this.installOnboardingCompleted) return; + + this.installOnboardingCompleted = true; + if (typeof SITE !== 'undefined') SITE.onboardingCompleted = true; + + try { + await secureFetch('/api/v1/config', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ onboardingCompleted: true }) + }); + } catch (error) { + console.error('[ProgressTracker] Failed to persist install onboarding state:', error); + } + } + /** * Mark the entire tour as completed */ diff --git a/status/js/tour-manager.js b/status/js/tour-manager.js index f841892..7b60d2a 100644 --- a/status/js/tour-manager.js +++ b/status/js/tour-manager.js @@ -70,7 +70,8 @@ * Check if tour should auto-start */ shouldAutoStart() { - return !this.progressTracker.isTourCompleted() && + return !this.progressTracker.isInstallOnboardingCompleted() && + !this.progressTracker.isTourCompleted() && this.progressTracker.getCurrentStep() === 0; }