// ========== DASHBOARD INITIALIZATION ========== (function () { function loadCustomServices() { const customServices = safeGet('custom-services'); if (customServices) { try { const services = JSON.parse(customServices); // Merge with default APPS, avoiding duplicates services.forEach(service => { if (!window.APPS.find(app => app.id === service.id)) { window.APPS.push(service); } }); } catch (e) { console.warn('Failed to load custom services:', e); } } } // Initialize custom services immediately so window.APPS is populated before buildGrid runs loadCustomServices(); // Staggered animation for top cards too function animateTopCards() { const topCards = document.querySelectorAll('.top .card'); topCards.forEach((card, index) => { card.style.opacity = '0'; card.style.transform = 'translateY(20px)'; setTimeout(() => { card.style.transition = 'opacity 0.3s cubic-bezier(0.4, 0, 0.2, 1), transform 0.3s cubic-bezier(0.4, 0, 0.2, 1)'; card.style.opacity = '1'; card.style.transform = 'translateY(0)'; }, index * 150); // 150ms delay between top cards }); } // Initialize dashboard (called after TOTP gate check or directly if TOTP disabled) // NOTE: loadServices comes from window.loadServices (exported by grid.js) let _dashboardInitialized = false; async function initializeDashboard() { if (_dashboardInitialized) { console.warn('[init] initializeDashboard called again, skipping duplicate'); return; } _dashboardInitialized = true; await window.loadServices(); window.buildGrid(); animateTopCards(); window.refreshAll(); setInterval(window.refreshAll, DC.POLL.DASHBOARD); if (typeof window.refreshCredsButtons === 'function') window.refreshCredsButtons(); // Update auth card (may have already been updated by the auto-load IIFE but ensure it's correct) if (typeof window._updateAuthCard === 'function') { try { const r = await fetch('/api/v1/totp/config', { cache: 'no-store' }); const d = await r.json(); 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 addTourButton(); if (shouldLoadOnboarding()) { loadOnboarding(); } } // Lazy-load onboarding bundle (52 KB) — only loaded when needed function loadOnboarding() { if (document.querySelector('script[src="/dist/onboarding.js"]')) return; // already loading/loaded const s = document.createElement('script'); s.src = '/dist/onboarding.js'; s.defer = true; document.head.appendChild(s); // Also load onboarding CSS if not already present if (!document.querySelector('link[href="/css/driver.min.css"]')) { const link = document.createElement('link'); link.rel = 'stylesheet'; link.href = '/css/driver.min.css'; document.head.appendChild(link); } if (!document.querySelector('link[href="/css/onboarding.css"]')) { const link = document.createElement('link'); link.rel = 'stylesheet'; link.href = '/css/onboarding.css'; document.head.appendChild(link); } } // Check if onboarding should auto-start (first-time user) function shouldLoadOnboarding() { try { const data = JSON.parse(localStorage.getItem('dashcaddy_onboarding')); return !data || (!data.tourCompleted && data.currentStep === 0); } catch (_) { return true; // No data means first-time user } } // ===== Collapsible toolbar sections ===== function initToolbarSections() { const sections = document.querySelectorAll('.tools-section'); if (!sections.length) return; // Restore saved state from localStorage let saved = {}; try { saved = JSON.parse(localStorage.getItem('toolbar-sections') || '{}'); } catch (_) {} sections.forEach(section => { const key = section.dataset.section; const header = section.querySelector('.tools-section-header'); if (!header) return; // Restore state (default: collapsed) if (saved[key]) { section.classList.add('open'); header.setAttribute('aria-expanded', 'true'); } header.addEventListener('click', (e) => { e.preventDefault(); const isOpen = section.classList.toggle('open'); header.setAttribute('aria-expanded', isOpen ? 'true' : 'false'); // Save state const state = {}; document.querySelectorAll('.tools-section').forEach(s => { state[s.dataset.section] = s.classList.contains('open'); }); localStorage.setItem('toolbar-sections', JSON.stringify(state)); }); }); } // Initialize toolbar sections on DOM ready initToolbarSections(); // Add restart tour button (loads bundle on click if not loaded) // Visible in primary toolbar until tour completed once, then moves to Admin section function addTourButton() { if (document.getElementById('restart-tour-btn')) return; // Check if tour has been completed before let tourDone = false; try { const data = JSON.parse(localStorage.getItem('dashcaddy_onboarding')); tourDone = data && data.tourCompleted; } catch (_) {} // Before first completion: show in primary toolbar. After: tuck into Admin section. const target = tourDone ? document.querySelector('.tools-section[data-section="admin"] .tools-section-items') : document.querySelector('.tools-primary'); if (!target) return; const button = document.createElement('button'); button.id = 'restart-tour-btn'; button.textContent = tourDone ? 'Help Tour' : '🎓 Help Tour'; button.title = 'Restart the onboarding tour'; button.onclick = () => { if (window.DashCaddyOnboarding) { window.DashCaddyOnboarding.restartTour(); } else { loadOnboarding(); // Wait for bundle to load, then start const check = setInterval(() => { if (window.DashCaddyOnboarding) { clearInterval(check); window.DashCaddyOnboarding.restartTour(); } }, 100); setTimeout(() => clearInterval(check), 5000); // give up after 5s } }; target.appendChild(button); } window.initializeDashboard = initializeDashboard; window.loadCustomServices = loadCustomServices; // TOTP-gated initialization (async () => { try { const totpRes = await fetch('/api/v1/totp/config', { cache: 'no-store' }); const totpData = await totpRes.json(); if (totpData.success && totpData.config.enabled) { // TOTP is enabled - check if we have a valid session const testRes = await fetch('/api/v1/totp/check-session', { cache: 'no-store' }); if (testRes.status === 401) { // Need TOTP verification - show overlay window._showTotpOverlay(); return; // initializeDashboard() will be called after successful verification } } } catch (e) { console.warn('TOTP check failed, proceeding normally:', e); } // TOTP disabled or session valid - initialize immediately initializeDashboard(); })(); })();