309 lines
11 KiB
JavaScript
309 lines
11 KiB
JavaScript
// 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'); |