// static/admin/js/vendor-theme.js /** * Vendor Theme Editor - Alpine.js Component * Manages theme customization for vendor shops */ // ============================================================================ // LOGGING CONFIGURATION // ============================================================================ const THEME_LOG_LEVEL = 3; // 1=error, 2=warn, 3=info, 4=debug const themeLog = { error: (...args) => THEME_LOG_LEVEL >= 1 && console.error('❌ [THEME ERROR]', ...args), warn: (...args) => THEME_LOG_LEVEL >= 2 && console.warn('⚠️ [THEME WARN]', ...args), info: (...args) => THEME_LOG_LEVEL >= 3 && console.info('ℹ️ [THEME INFO]', ...args), debug: (...args) => THEME_LOG_LEVEL >= 4 && console.log('🔍 [THEME DEBUG]', ...args) }; // ============================================================================ // ALPINE.JS COMPONENT // ============================================================================ function adminVendorTheme() { return { // ✅ CRITICAL: Inherit base layout functionality ...data(), // ✅ CRITICAL: Set page identifier currentPage: 'vendor-theme', // Page state vendorCode: null, vendor: null, loading: true, saving: false, error: null, // Theme data structure matching VendorTheme model themeData: { theme_name: 'default', colors: { primary: '#6366f1', secondary: '#8b5cf6', accent: '#ec4899', background: '#ffffff', text: '#1f2937', border: '#e5e7eb' }, fonts: { heading: 'Inter, sans-serif', body: 'Inter, sans-serif' }, layout: { style: 'grid', header: 'fixed', product_card: 'modern' }, branding: { logo: null, logo_dark: null, favicon: null, banner: null }, custom_css: '' }, // Available presets presets: [], // ============================================================================ // INITIALIZATION // ============================================================================ async init() { themeLog.info('=== VENDOR THEME EDITOR INITIALIZING ==='); // ✅ CRITICAL: Prevent multiple initializations if (window._vendorThemeInitialized) { themeLog.warn('Theme editor already initialized, skipping...'); return; } window._vendorThemeInitialized = true; const startTime = Date.now(); // Get vendor code from URL this.vendorCode = this.getVendorCodeFromURL(); themeLog.info('Vendor code:', this.vendorCode); // Load data await Promise.all([ this.loadVendorData(), this.loadTheme(), this.loadPresets() ]); const duration = Date.now() - startTime; themeLog.info(`=== THEME EDITOR INITIALIZATION COMPLETE (${duration}ms) ===`); }, // ============================================================================ // URL HELPERS // ============================================================================ getVendorCodeFromURL() { const pathParts = window.location.pathname.split('/'); const vendorIndex = pathParts.indexOf('vendors'); return pathParts[vendorIndex + 1]; }, // ============================================================================ // DATA LOADING // ============================================================================ async loadVendorData() { themeLog.info('Loading vendor data...'); try { const startTime = Date.now(); const response = await apiClient.get(`/api/v1/admin/vendors/${this.vendorCode}`); const duration = Date.now() - startTime; this.vendor = response; themeLog.info(`Vendor loaded in ${duration}ms:`, this.vendor.name); } catch (error) { themeLog.error('Failed to load vendor:', error); this.error = 'Failed to load vendor data'; Utils.showToast('Failed to load vendor data', 'error'); } }, async loadTheme() { themeLog.info('Loading theme...'); this.loading = true; this.error = null; try { const startTime = Date.now(); const response = await apiClient.get(`/api/v1/admin/vendor-themes/${this.vendorCode}`); const duration = Date.now() - startTime; if (response) { // Merge loaded theme with defaults this.themeData = { theme_name: response.theme_name || 'default', colors: { ...this.themeData.colors, ...(response.colors || {}) }, fonts: { heading: response.fonts?.heading || this.themeData.fonts.heading, body: response.fonts?.body || this.themeData.fonts.body }, layout: { style: response.layout?.style || this.themeData.layout.style, header: response.layout?.header || this.themeData.layout.header, product_card: response.layout?.product_card || this.themeData.layout.product_card }, branding: { ...this.themeData.branding, ...(response.branding || {}) }, custom_css: response.custom_css || '' }; themeLog.info(`Theme loaded in ${duration}ms:`, this.themeData.theme_name); } } catch (error) { themeLog.error('Failed to load theme:', error); this.error = 'Failed to load theme'; Utils.showToast('Failed to load theme', 'error'); } finally { this.loading = false; } }, async loadPresets() { themeLog.info('Loading presets...'); try { const startTime = Date.now(); const response = await apiClient.get('/api/v1/admin/vendor-themes/presets'); const duration = Date.now() - startTime; this.presets = response.presets || []; themeLog.info(`${this.presets.length} presets loaded in ${duration}ms`); } catch (error) { themeLog.warn('Failed to load presets:', error); // Non-critical error, continue without presets } }, // ============================================================================ // PRESET OPERATIONS // ============================================================================ async applyPreset(presetName) { themeLog.info(`Applying preset: ${presetName}`); this.saving = true; try { const startTime = Date.now(); const response = await apiClient.post( `/api/v1/admin/vendor-themes/${this.vendorCode}/preset/${presetName}` ); const duration = Date.now() - startTime; if (response && response.theme) { // Update theme data with preset this.themeData = { theme_name: response.theme.theme_name, colors: response.theme.colors || this.themeData.colors, fonts: response.theme.fonts || this.themeData.fonts, layout: response.theme.layout || this.themeData.layout, branding: response.theme.branding || this.themeData.branding, custom_css: response.theme.custom_css || '' }; Utils.showToast(`Applied ${presetName} preset successfully`, 'success'); themeLog.info(`Preset applied in ${duration}ms`); } } catch (error) { themeLog.error('Failed to apply preset:', error); const message = error.response?.data?.detail || 'Failed to apply preset'; Utils.showToast(message, 'error'); } finally { this.saving = false; } }, async resetToDefault() { if (!confirm('Are you sure you want to reset to default theme? This will discard all customizations.')) { return; } themeLog.info('Resetting to default theme'); await this.applyPreset('default'); }, // ============================================================================ // SAVE OPERATIONS // ============================================================================ async saveTheme() { themeLog.info('Saving theme:', this.themeData); this.saving = true; this.error = null; try { const startTime = Date.now(); const response = await apiClient.put( `/api/v1/admin/vendor-themes/${this.vendorCode}`, this.themeData ); const duration = Date.now() - startTime; if (response) { Utils.showToast('Theme saved successfully', 'success'); themeLog.info(`Theme saved in ${duration}ms`); } } catch (error) { themeLog.error('Failed to save theme:', error); const message = error.response?.data?.detail || 'Failed to save theme'; Utils.showToast(message, 'error'); this.error = message; } finally { this.saving = false; } }, // ============================================================================ // HELPER METHODS // ============================================================================ formatDate(dateString) { if (!dateString) return '-'; return Utils.formatDate(dateString); }, getPreviewStyle() { return { '--color-primary': this.themeData.colors.primary, '--color-secondary': this.themeData.colors.secondary, '--color-accent': this.themeData.colors.accent, '--color-background': this.themeData.colors.background, '--color-text': this.themeData.colors.text, '--color-border': this.themeData.colors.border, '--font-heading': this.themeData.fonts.heading, '--font-body': this.themeData.fonts.body, }; } }; } // ============================================================================ // MODULE LOADED // ============================================================================ themeLog.info('Vendor theme editor module loaded');