revamping frontend logging system and reorganising documentation

This commit is contained in:
2025-10-28 21:07:26 +01:00
parent 5c80ba17c5
commit b0cc0385f8
68 changed files with 3481 additions and 624 deletions

View File

@@ -1,22 +1,17 @@
// static/admin/js/vendor-theme.js
// static/admin/js/vendor-theme.js (FIXED VERSION)
/**
* Vendor Theme Editor - Alpine.js Component
* Manages theme customization for vendor shops
*
* REQUIRES: log-config.js to be loaded first
*/
// ============================================================================
// LOGGING CONFIGURATION
// LOGGING CONFIGURATION (using centralized logger)
// ============================================================================
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)
};
// Use the pre-configured theme logger from centralized log-config.js
const themeLog = window.LogConfig.loggers.vendorTheme;
// ============================================================================
// ALPINE.JS COMPONENT
@@ -50,254 +45,278 @@ function adminVendorTheme() {
},
fonts: {
heading: 'Inter, sans-serif',
body: 'Inter, sans-serif'
body: 'Inter, sans-serif',
size_base: '16px',
size_heading: '2rem'
},
layout: {
style: 'grid',
header: 'fixed',
product_card: 'modern'
header_position: 'fixed',
product_card_style: 'card',
sidebar_position: 'left'
},
branding: {
logo: null,
logo_dark: null,
favicon: null,
banner: null
logo_url: '',
favicon_url: '',
banner_url: ''
},
custom_css: ''
custom_css: '',
social_links: {
facebook: '',
instagram: '',
twitter: '',
linkedin: ''
}
},
// Available presets
presets: [],
selectedPreset: null,
// ============================================================================
// ====================================================================
// INITIALIZATION
// ============================================================================
// ====================================================================
async init() {
themeLog.info('=== VENDOR THEME EDITOR INITIALIZING ===');
themeLog.info('Initializing vendor theme editor');
// ✅ 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...');
// Start performance timer
const startTime = performance.now();
try {
const startTime = Date.now();
const response = await apiClient.get(`/admin/vendors/${this.vendorCode}`);
const duration = Date.now() - startTime;
// Extract vendor code from URL
const urlParts = window.location.pathname.split('/');
this.vendorCode = urlParts[urlParts.indexOf('vendors') + 1];
this.vendor = response;
themeLog.info(`Vendor loaded in ${duration}ms:`, this.vendor.name);
themeLog.debug('Vendor code from URL:', this.vendorCode);
} 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(`/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);
if (!this.vendorCode) {
throw new Error('Vendor code not found in URL');
}
} catch (error) {
themeLog.error('Failed to load theme:', error);
this.error = 'Failed to load theme';
Utils.showToast('Failed to load theme', 'error');
// Load data in parallel
themeLog.group('Loading theme data');
await Promise.all([
this.loadVendor(),
this.loadTheme(),
this.loadPresets()
]);
themeLog.groupEnd();
// Log performance
const duration = performance.now() - startTime;
window.LogConfig.logPerformance('Theme Editor Init', duration);
themeLog.info('Theme editor initialized successfully');
} catch (error) {
// Use centralized error logger
window.LogConfig.logError(error, 'Theme Editor Init');
this.error = error.message || 'Failed to initialize theme editor';
Utils.showToast(this.error, 'error');
} finally {
this.loading = false;
}
},
async loadPresets() {
themeLog.info('Loading presets...');
// ====================================================================
// DATA LOADING
// ====================================================================
async loadVendor() {
themeLog.info('Loading vendor data');
const url = `/admin/vendors/${this.vendorCode}`;
window.LogConfig.logApiCall('GET', url, null, 'request');
try {
const startTime = Date.now();
const response = await apiClient.get('/admin/vendor-themes/presets');
const duration = Date.now() - startTime;
// ✅ FIX: apiClient returns data directly, not response.data
const response = await apiClient.get(url);
this.presets = response.presets || [];
themeLog.info(`${this.presets.length} presets loaded in ${duration}ms`);
// ✅ Direct assignment - response IS the data
this.vendor = response;
window.LogConfig.logApiCall('GET', url, this.vendor, 'response');
themeLog.debug('Vendor loaded:', this.vendor);
} catch (error) {
themeLog.warn('Failed to load presets:', error);
// Non-critical error, continue without presets
themeLog.error('Failed to load vendor:', error);
throw error;
}
},
// ============================================================================
// PRESET OPERATIONS
// ============================================================================
async loadTheme() {
themeLog.info('Loading theme data');
const url = `/admin/vendor-themes/${this.vendorCode}`;
window.LogConfig.logApiCall('GET', url, null, 'request');
try {
// ✅ FIX: apiClient returns data directly
const response = await apiClient.get(url);
// Merge with default theme data
this.themeData = {
...this.themeData,
...response
};
window.LogConfig.logApiCall('GET', url, this.themeData, 'response');
themeLog.debug('Theme loaded:', this.themeData);
} catch (error) {
themeLog.warn('Failed to load theme, using defaults:', error);
// Continue with default theme
}
},
async loadPresets() {
themeLog.info('Loading theme presets');
const url = '/admin/vendor-themes/presets';
window.LogConfig.logApiCall('GET', url, null, 'request');
try {
// ✅ FIX: apiClient returns data directly
const response = await apiClient.get(url);
// ✅ Access presets directly from response, not response.data.presets
this.presets = response.presets || [];
window.LogConfig.logApiCall('GET', url, response, 'response');
themeLog.debug(`Loaded ${this.presets.length} presets`);
} catch (error) {
themeLog.error('Failed to load presets:', error);
this.presets = [];
}
},
// ====================================================================
// THEME OPERATIONS
// ====================================================================
async saveTheme() {
if (this.saving) return;
themeLog.info('Saving theme changes');
this.saving = true;
this.error = null;
const startTime = performance.now();
try {
const url = `/admin/vendor-themes/${this.vendorCode}`;
window.LogConfig.logApiCall('PUT', url, this.themeData, 'request');
// ✅ FIX: apiClient returns data directly
const response = await apiClient.put(url, this.themeData);
window.LogConfig.logApiCall('PUT', url, response, 'response');
const duration = performance.now() - startTime;
window.LogConfig.logPerformance('Save Theme', duration);
themeLog.info('Theme saved successfully');
Utils.showToast('Theme saved successfully', 'success');
} catch (error) {
window.LogConfig.logError(error, 'Save Theme');
this.error = 'Failed to save theme';
Utils.showToast(this.error, 'error');
} finally {
this.saving = false;
}
},
async applyPreset(presetName) {
themeLog.info(`Applying preset: ${presetName}`);
this.saving = true;
try {
const startTime = Date.now();
const response = await apiClient.post(
`/admin/vendor-themes/${this.vendorCode}/preset/${presetName}`
);
const duration = Date.now() - startTime;
const url = `/admin/vendor-themes/${this.vendorCode}/preset/${presetName}`;
window.LogConfig.logApiCall('POST', url, null, 'request');
// ✅ FIX: apiClient returns data directly
const response = await apiClient.post(url);
window.LogConfig.logApiCall('POST', url, response, 'response');
// ✅ FIX: Access theme directly from response, not response.data.theme
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 || ''
...this.themeData,
...response.theme
};
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');
themeLog.info(`Preset '${presetName}' applied successfully`);
Utils.showToast(`Applied ${presetName} preset`, 'success');
} catch (error) {
window.LogConfig.logError(error, 'Apply Preset');
Utils.showToast('Failed to apply preset', 'error');
} finally {
this.saving = false;
}
},
async resetToDefault() {
if (!confirm('Are you sure you want to reset to default theme? This will discard all customizations.')) {
async resetTheme() {
if (!confirm('Reset theme to default? This cannot be undone.')) {
return;
}
themeLog.info('Resetting to default theme');
await this.applyPreset('default');
},
// ============================================================================
// SAVE OPERATIONS
// ============================================================================
async saveTheme() {
themeLog.info('Saving theme:', this.themeData);
themeLog.warn('Resetting theme to default');
this.saving = true;
this.error = null;
try {
const startTime = Date.now();
const response = await apiClient.put(
`/admin/vendor-themes/${this.vendorCode}`,
this.themeData
);
const duration = Date.now() - startTime;
const url = `/admin/vendor-themes/${this.vendorCode}`;
window.LogConfig.logApiCall('DELETE', url, null, 'request');
if (response) {
Utils.showToast('Theme saved successfully', 'success');
themeLog.info(`Theme saved in ${duration}ms`);
}
await apiClient.delete(url);
window.LogConfig.logApiCall('DELETE', url, null, 'response');
// Reload theme data
await this.loadTheme();
themeLog.info('Theme reset successfully');
Utils.showToast('Theme reset to default', 'success');
} 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;
window.LogConfig.logError(error, 'Reset Theme');
Utils.showToast('Failed to reset theme', 'error');
} finally {
this.saving = false;
}
},
// ============================================================================
// HELPER METHODS
// ============================================================================
// ====================================================================
// UTILITY METHODS
// ====================================================================
formatDate(dateString) {
if (!dateString) return '-';
return Utils.formatDate(dateString);
previewTheme() {
themeLog.debug('Opening theme preview');
const previewUrl = `/vendor/${this.vendor?.subdomain || this.vendorCode}`;
window.open(previewUrl, '_blank');
},
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,
};
updateColor(key, value) {
themeLog.debug(`Color updated: ${key} = ${value}`);
this.themeData.colors[key] = value;
},
updateFont(type, value) {
themeLog.debug(`Font updated: ${type} = ${value}`);
this.themeData.fonts[type] = value;
},
updateLayout(key, value) {
themeLog.debug(`Layout updated: ${key} = ${value}`);
this.themeData.layout[key] = value;
}
};
}