/** * Alpine.js v3 global data initialization * Provides theme toggle, menu controls, sidebar sections, and page state */ function data() { // ───────────────────────────────────────────────────────────────── // Theme (dark mode) persistence // ───────────────────────────────────────────────────────────────── function getThemeFromLocalStorage() { if (window.localStorage.getItem('dark')) { return JSON.parse(window.localStorage.getItem('dark')) } return ( !!window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches ) } function setThemeToLocalStorage(value) { window.localStorage.setItem('dark', value) } // ───────────────────────────────────────────────────────────────── // Sidebar sections persistence // ───────────────────────────────────────────────────────────────── const SIDEBAR_STORAGE_KEY = 'admin_sidebar_sections'; // Default state: Platform Administration open, others closed const defaultSections = { superAdmin: true, // Super admin section (only visible to super admins) platformAdmin: true, vendorOps: false, marketplace: false, billing: false, contentMgmt: false, devTools: false, platformHealth: false, monitoring: false, settingsSection: false }; function getSidebarSectionsFromStorage() { try { const stored = window.localStorage.getItem(SIDEBAR_STORAGE_KEY); if (stored) { return { ...defaultSections, ...JSON.parse(stored) }; } } catch (e) { console.warn('Failed to parse sidebar sections from localStorage:', e); } return { ...defaultSections }; } function saveSidebarSectionsToStorage(sections) { try { window.localStorage.setItem(SIDEBAR_STORAGE_KEY, JSON.stringify(sections)); } catch (e) { console.warn('Failed to save sidebar sections to localStorage:', e); } } // ───────────────────────────────────────────────────────────────── // Last visited page tracking (for redirect after login) // ───────────────────────────────────────────────────────────────── const LAST_PAGE_KEY = 'admin_last_visited_page'; const currentPath = window.location.pathname; // Save current page (exclude login, logout, error pages) if (currentPath.startsWith('/admin/') && !currentPath.includes('/login') && !currentPath.includes('/logout') && !currentPath.includes('/errors/')) { try { window.localStorage.setItem(LAST_PAGE_KEY, currentPath); } catch (e) { // Ignore storage errors } } // Helper to get admin profile from localStorage function getAdminProfileFromStorage() { try { // Check admin_user first (set by login), then adminProfile (legacy) const stored = window.localStorage.getItem('admin_user') || window.localStorage.getItem('adminProfile'); if (stored) { return JSON.parse(stored); } } catch (e) { console.warn('Failed to parse admin profile from localStorage:', e); } return null; } // Map pages to their parent sections const pageSectionMap = { // Super Admin section 'admin-users': 'superAdmin', // Platform Administration companies: 'platformAdmin', vendors: 'platformAdmin', messages: 'platformAdmin', // Vendor Operations (Products, Customers, Inventory, Orders, Shipping) 'marketplace-products': 'vendorOps', 'vendor-products': 'vendorOps', customers: 'vendorOps', inventory: 'vendorOps', orders: 'vendorOps', // Future: shipping will map to 'vendorOps' // Marketplace 'marketplace-letzshop': 'marketplace', // Content Management 'platform-homepage': 'contentMgmt', 'content-pages': 'contentMgmt', 'vendor-theme': 'contentMgmt', // Developer Tools components: 'devTools', icons: 'devTools', // Platform Health testing: 'platformHealth', 'code-quality': 'platformHealth', // Platform Monitoring imports: 'monitoring', 'background-tasks': 'monitoring', logs: 'monitoring', 'notifications-settings': 'monitoring', // Platform Settings settings: 'settingsSection', profile: 'settingsSection', 'api-keys': 'settingsSection' }; return { // ───────────────────────────────────────────────────────────────── // Theme // ───────────────────────────────────────────────────────────────── dark: getThemeFromLocalStorage(), toggleTheme() { this.dark = !this.dark setThemeToLocalStorage(this.dark) }, // ───────────────────────────────────────────────────────────────── // Mobile side menu // ───────────────────────────────────────────────────────────────── isSideMenuOpen: false, toggleSideMenu() { this.isSideMenuOpen = !this.isSideMenuOpen }, closeSideMenu() { this.isSideMenuOpen = false }, // ───────────────────────────────────────────────────────────────── // Notifications menu // ───────────────────────────────────────────────────────────────── isNotificationsMenuOpen: false, toggleNotificationsMenu() { this.isNotificationsMenuOpen = !this.isNotificationsMenuOpen }, closeNotificationsMenu() { this.isNotificationsMenuOpen = false }, // ───────────────────────────────────────────────────────────────── // Profile menu // ───────────────────────────────────────────────────────────────── isProfileMenuOpen: false, toggleProfileMenu() { this.isProfileMenuOpen = !this.isProfileMenuOpen }, closeProfileMenu() { this.isProfileMenuOpen = false }, // ───────────────────────────────────────────────────────────────── // Pages menu (legacy) // ───────────────────────────────────────────────────────────────── isPagesMenuOpen: false, togglePagesMenu() { this.isPagesMenuOpen = !this.isPagesMenuOpen }, // ───────────────────────────────────────────────────────────────── // Collapsible sidebar sections // ───────────────────────────────────────────────────────────────── openSections: getSidebarSectionsFromStorage(), toggleSection(section) { this.openSections[section] = !this.openSections[section]; saveSidebarSectionsToStorage(this.openSections); }, // Auto-expand section containing current page expandSectionForCurrentPage() { const section = pageSectionMap[this.currentPage]; if (section && !this.openSections[section]) { this.openSections[section] = true; saveSidebarSectionsToStorage(this.openSections); } }, // ───────────────────────────────────────────────────────────────── // Page identifier - will be set by individual pages // ───────────────────────────────────────────────────────────────── currentPage: '', // ───────────────────────────────────────────────────────────────── // Admin profile and super admin flag // ───────────────────────────────────────────────────────────────── adminProfile: getAdminProfileFromStorage(), get isSuperAdmin() { return this.adminProfile?.is_super_admin === true; } } } /** * Language selector component for i18n support * Used by language_selector macros in templates * * @param {string} currentLang - Current language code (e.g., 'fr') * @param {Array} enabledLanguages - Array of enabled language codes * @returns {Object} Alpine.js component data */ function languageSelector(currentLang, enabledLanguages) { return { isLangOpen: false, currentLang: currentLang || 'fr', languages: enabledLanguages || ['fr', 'de', 'en'], languageNames: { 'en': 'English', 'fr': 'Français', 'de': 'Deutsch', 'lb': 'Lëtzebuergesch' }, languageFlags: { 'en': 'gb', 'fr': 'fr', 'de': 'de', 'lb': 'lu' }, async setLanguage(lang) { if (lang === this.currentLang) { this.isLangOpen = false; return; } try { const response = await fetch('/api/v1/language/set', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ language: lang }) }); if (response.ok) { this.currentLang = lang; window.location.reload(); } } catch (error) { console.error('Failed to set language:', error); } this.isLangOpen = false; } }; } // Export to window for use in templates window.languageSelector = languageSelector; /** * Header messages badge component * Shows unread message count in header */ function headerMessages() { return { unreadCount: 0, pollInterval: null, async init() { await this.fetchUnreadCount(); // Poll every 30 seconds this.pollInterval = setInterval(() => this.fetchUnreadCount(), 30000); }, destroy() { if (this.pollInterval) { clearInterval(this.pollInterval); } }, async fetchUnreadCount() { try { const response = await apiClient.get('/admin/messages/unread-count'); this.unreadCount = response.total_unread || 0; } catch (error) { // Silently fail - don't spam console for auth issues on login page } } }; } // Export to window window.headerMessages = headerMessages; /** * Platform Settings Utility * Provides cached access to platform-wide settings */ const PlatformSettings = { // Cache key and TTL CACHE_KEY: 'platform_settings_cache', CACHE_TTL: 5 * 60 * 1000, // 5 minutes /** * Get cached settings or fetch from API */ async get() { try { const cached = localStorage.getItem(this.CACHE_KEY); if (cached) { const { data, timestamp } = JSON.parse(cached); if (Date.now() - timestamp < this.CACHE_TTL) { return data; } } // Fetch from API const response = await apiClient.get('/admin/settings/display/public'); const settings = { rows_per_page: response.rows_per_page || 20 }; // Cache the result localStorage.setItem(this.CACHE_KEY, JSON.stringify({ data: settings, timestamp: Date.now() })); return settings; } catch (error) { console.warn('Failed to load platform settings, using defaults:', error); return { rows_per_page: 20 }; } }, /** * Get rows per page setting */ async getRowsPerPage() { const settings = await this.get(); return settings.rows_per_page; }, /** * Clear the cache (call after saving settings) */ clearCache() { localStorage.removeItem(this.CACHE_KEY); } }; // Export to window window.PlatformSettings = PlatformSettings;