328 lines
11 KiB
JavaScript
328 lines
11 KiB
JavaScript
// 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 (using centralized logger)
|
|
// ============================================================================
|
|
|
|
// Use the pre-configured theme logger from centralized log-config.js
|
|
const themeLog = window.LogConfig.loggers.vendorTheme;
|
|
|
|
// ============================================================================
|
|
// 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',
|
|
size_base: '16px',
|
|
size_heading: '2rem'
|
|
},
|
|
layout: {
|
|
style: 'grid',
|
|
header_position: 'fixed',
|
|
product_card_style: 'card',
|
|
sidebar_position: 'left'
|
|
},
|
|
branding: {
|
|
logo_url: '',
|
|
favicon_url: '',
|
|
banner_url: ''
|
|
},
|
|
custom_css: '',
|
|
social_links: {
|
|
facebook: '',
|
|
instagram: '',
|
|
twitter: '',
|
|
linkedin: ''
|
|
}
|
|
},
|
|
|
|
// Available presets
|
|
presets: [],
|
|
selectedPreset: null,
|
|
|
|
// ====================================================================
|
|
// INITIALIZATION
|
|
// ====================================================================
|
|
|
|
async init() {
|
|
themeLog.info('Initializing vendor theme editor');
|
|
|
|
// Start performance timer
|
|
const startTime = performance.now();
|
|
|
|
try {
|
|
// Extract vendor code from URL
|
|
const urlParts = window.location.pathname.split('/');
|
|
this.vendorCode = urlParts[urlParts.indexOf('vendors') + 1];
|
|
|
|
themeLog.debug('Vendor code from URL:', this.vendorCode);
|
|
|
|
if (!this.vendorCode) {
|
|
throw new Error('Vendor code not found in URL');
|
|
}
|
|
|
|
// 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;
|
|
}
|
|
},
|
|
|
|
// ====================================================================
|
|
// DATA LOADING
|
|
// ====================================================================
|
|
|
|
async loadVendor() {
|
|
themeLog.info('Loading vendor data');
|
|
|
|
const url = `/admin/vendors/${this.vendorCode}`;
|
|
window.LogConfig.logApiCall('GET', url, null, 'request');
|
|
|
|
try {
|
|
// ✅ FIX: apiClient returns data directly, not response.data
|
|
const response = await apiClient.get(url);
|
|
|
|
// ✅ 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.error('Failed to load vendor:', error);
|
|
throw error;
|
|
}
|
|
},
|
|
|
|
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 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) {
|
|
this.themeData = {
|
|
...this.themeData,
|
|
...response.theme
|
|
};
|
|
}
|
|
|
|
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 resetTheme() {
|
|
if (!confirm('Reset theme to default? This cannot be undone.')) {
|
|
return;
|
|
}
|
|
|
|
themeLog.warn('Resetting theme to default');
|
|
this.saving = true;
|
|
|
|
try {
|
|
const url = `/admin/vendor-themes/${this.vendorCode}`;
|
|
window.LogConfig.logApiCall('DELETE', url, null, 'request');
|
|
|
|
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) {
|
|
window.LogConfig.logError(error, 'Reset Theme');
|
|
Utils.showToast('Failed to reset theme', 'error');
|
|
} finally {
|
|
this.saving = false;
|
|
}
|
|
},
|
|
|
|
// ====================================================================
|
|
// UTILITY METHODS
|
|
// ====================================================================
|
|
|
|
previewTheme() {
|
|
themeLog.debug('Opening theme preview');
|
|
const previewUrl = `/vendor/${this.vendor?.subdomain || this.vendorCode}`;
|
|
window.open(previewUrl, '_blank');
|
|
},
|
|
|
|
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;
|
|
}
|
|
};
|
|
}
|
|
|
|
// ============================================================================
|
|
// MODULE LOADED
|
|
// ============================================================================
|
|
|
|
themeLog.info('Vendor theme editor module loaded'); |