Make onboarding tour install-wide instead of per-browser
Persist onboardingCompleted flag server-side via /api/v1/config so the tour only auto-starts once per DashCaddy installation, not on every new browser that connects. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -58,7 +58,12 @@
|
|||||||
if (d.success) window._updateAuthCard(d.config.enabled && d.config.isSetUp, d.config.sessionDuration);
|
if (d.success) window._updateAuthCard(d.config.enabled && d.config.isSetUp, d.config.sessionDuration);
|
||||||
} catch (e) { /* ignore */ }
|
} 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();
|
addTourButton();
|
||||||
if (shouldLoadOnboarding()) {
|
if (shouldLoadOnboarding()) {
|
||||||
loadOnboarding();
|
loadOnboarding();
|
||||||
@@ -89,6 +94,9 @@
|
|||||||
|
|
||||||
// Check if onboarding should auto-start (first-time user)
|
// Check if onboarding should auto-start (first-time user)
|
||||||
function shouldLoadOnboarding() {
|
function shouldLoadOnboarding() {
|
||||||
|
if (typeof SITE !== 'undefined' && SITE.onboardingCompleted) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
const data = JSON.parse(localStorage.getItem('dashcaddy_onboarding'));
|
const data = JSON.parse(localStorage.getItem('dashcaddy_onboarding'));
|
||||||
return !data || (!data.tourCompleted && data.currentStep === 0);
|
return !data || (!data.tourCompleted && data.currentStep === 0);
|
||||||
@@ -140,11 +148,10 @@
|
|||||||
function addTourButton() {
|
function addTourButton() {
|
||||||
if (document.getElementById('restart-tour-btn')) return;
|
if (document.getElementById('restart-tour-btn')) return;
|
||||||
|
|
||||||
// Check if tour has been completed before
|
let tourDone = typeof SITE !== 'undefined' && SITE.onboardingCompleted;
|
||||||
let tourDone = false;
|
|
||||||
try {
|
try {
|
||||||
const data = JSON.parse(localStorage.getItem('dashcaddy_onboarding'));
|
const data = JSON.parse(localStorage.getItem('dashcaddy_onboarding'));
|
||||||
tourDone = data && data.tourCompleted;
|
tourDone = tourDone || !!(data && data.tourCompleted);
|
||||||
} catch (_) {}
|
} catch (_) {}
|
||||||
|
|
||||||
// Before first completion: show in primary toolbar. After: tuck into Admin section.
|
// Before first completion: show in primary toolbar. After: tuck into Admin section.
|
||||||
|
|||||||
@@ -35,9 +35,10 @@ const SITE = {
|
|||||||
configurationType: (_cachedCfg && _cachedCfg.configurationType) || 'homelab',
|
configurationType: (_cachedCfg && _cachedCfg.configurationType) || 'homelab',
|
||||||
domain: (_cachedCfg && _cachedCfg.domain) || '',
|
domain: (_cachedCfg && _cachedCfg.domain) || '',
|
||||||
defaults: (_cachedCfg && _cachedCfg.defaults) || {},
|
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 {
|
try {
|
||||||
const r = await fetch('/api/v1/config');
|
const r = await fetch('/api/v1/config');
|
||||||
if (r.ok) {
|
if (r.ok) {
|
||||||
@@ -56,6 +57,7 @@ const SITE = {
|
|||||||
if (c.domain) SITE.domain = c.domain;
|
if (c.domain) SITE.domain = c.domain;
|
||||||
if (c.defaults) SITE.defaults = c.defaults;
|
if (c.defaults) SITE.defaults = c.defaults;
|
||||||
if (c.routingMode) SITE.routingMode = c.routingMode;
|
if (c.routingMode) SITE.routingMode = c.routingMode;
|
||||||
|
SITE.onboardingCompleted = c.onboardingCompleted === true;
|
||||||
// Cache only non-sensitive display config (TLD, domain, routing mode)
|
// Cache only non-sensitive display config (TLD, domain, routing mode)
|
||||||
// DNS IPs and server topology are NOT cached — fetched from API each load
|
// DNS IPs and server topology are NOT cached — fetched from API each load
|
||||||
localStorage.setItem('dashcaddy_site_config', JSON.stringify({
|
localStorage.setItem('dashcaddy_site_config', JSON.stringify({
|
||||||
|
|||||||
@@ -22,6 +22,12 @@
|
|||||||
try {
|
try {
|
||||||
console.log('[Onboarding] Initializing system...');
|
console.log('[Onboarding] Initializing system...');
|
||||||
|
|
||||||
|
if (window.__dashcaddySiteConfigLoaded) {
|
||||||
|
try {
|
||||||
|
await window.__dashcaddySiteConfigLoaded;
|
||||||
|
} catch (_) { /* ignore */ }
|
||||||
|
}
|
||||||
|
|
||||||
// Initialize Error Handler first
|
// Initialize Error Handler first
|
||||||
errorHandler = new ErrorHandler();
|
errorHandler = new ErrorHandler();
|
||||||
console.log('[Onboarding] Error Handler initialized');
|
console.log('[Onboarding] Error Handler initialized');
|
||||||
@@ -44,7 +50,8 @@
|
|||||||
|
|
||||||
// Check if tour should auto-start
|
// Check if tour should auto-start
|
||||||
if (tourManager.shouldAutoStart()) {
|
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
|
// Wait a bit for page to fully load
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
tourManager.startTour();
|
tourManager.startTour();
|
||||||
|
|||||||
@@ -29,6 +29,7 @@
|
|||||||
constructor(storageKey = 'dashcaddy_onboarding') {
|
constructor(storageKey = 'dashcaddy_onboarding') {
|
||||||
this.storageKey = storageKey;
|
this.storageKey = storageKey;
|
||||||
this.storageVersion = '1.0';
|
this.storageVersion = '1.0';
|
||||||
|
this.installOnboardingCompleted = typeof SITE !== 'undefined' && SITE.onboardingCompleted === true;
|
||||||
|
|
||||||
// Initialize storage if it doesn't exist
|
// Initialize storage if it doesn't exist
|
||||||
this._initializeStorage();
|
this._initializeStorage();
|
||||||
@@ -159,6 +160,36 @@
|
|||||||
return state.tourCompleted === true;
|
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<void>}
|
||||||
|
*/
|
||||||
|
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
|
* Mark the entire tour as completed
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -70,7 +70,8 @@
|
|||||||
* Check if tour should auto-start
|
* Check if tour should auto-start
|
||||||
*/
|
*/
|
||||||
shouldAutoStart() {
|
shouldAutoStart() {
|
||||||
return !this.progressTracker.isTourCompleted() &&
|
return !this.progressTracker.isInstallOnboardingCompleted() &&
|
||||||
|
!this.progressTracker.isTourCompleted() &&
|
||||||
this.progressTracker.getCurrentStep() === 0;
|
this.progressTracker.getCurrentStep() === 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user