Some checks failed
Migrated ~68 native browser confirm() calls across 74 files to use the project's confirm_modal/confirm_modal_dynamic Jinja2 macros, providing consistent styled confirmation dialogs instead of plain browser popups. Modules updated: core, tenancy, cms, marketplace, messaging, billing, customers, orders, cart. Uses danger/warning/info variants and double-confirm pattern for destructive delete operations. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
333 lines
11 KiB
JavaScript
333 lines
11 KiB
JavaScript
// static/admin/js/store-theme.js (FIXED VERSION)
|
|
/**
|
|
* Store Theme Editor - Alpine.js Component
|
|
* Manages theme customization for store 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.storeTheme;
|
|
|
|
// ============================================================================
|
|
// ALPINE.JS COMPONENT
|
|
// ============================================================================
|
|
|
|
function adminStoreTheme() {
|
|
return {
|
|
// ✅ CRITICAL: Inherit base layout functionality
|
|
...data(),
|
|
|
|
// ✅ CRITICAL: Set page identifier
|
|
currentPage: 'store-theme',
|
|
|
|
// Page state
|
|
storeCode: null,
|
|
store: null,
|
|
loading: true,
|
|
saving: false,
|
|
error: null,
|
|
showResetThemeModal: false,
|
|
|
|
// Theme data structure matching StoreTheme 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() {
|
|
// Load i18n translations
|
|
await I18n.loadModule('tenancy');
|
|
|
|
// Guard against multiple initialization
|
|
if (window._adminStoreThemeInitialized) return;
|
|
window._adminStoreThemeInitialized = true;
|
|
|
|
themeLog.info('Initializing store theme editor');
|
|
|
|
// Start performance timer
|
|
const startTime = performance.now();
|
|
|
|
try {
|
|
// Extract store code from URL
|
|
const urlParts = window.location.pathname.split('/');
|
|
this.storeCode = urlParts[urlParts.indexOf('stores') + 1];
|
|
|
|
themeLog.debug('Store code from URL:', this.storeCode);
|
|
|
|
if (!this.storeCode) {
|
|
throw new Error('Store code not found in URL');
|
|
}
|
|
|
|
// Load data in parallel
|
|
themeLog.group('Loading theme data');
|
|
|
|
await Promise.all([
|
|
this.loadStore(),
|
|
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 loadStore() {
|
|
themeLog.info('Loading store data');
|
|
|
|
const url = `/admin/stores/${this.storeCode}`;
|
|
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.store = response;
|
|
|
|
window.LogConfig.logApiCall('GET', url, this.store, 'response');
|
|
themeLog.debug('Store loaded:', this.store);
|
|
|
|
} catch (error) {
|
|
themeLog.error('Failed to load store:', error);
|
|
throw error;
|
|
}
|
|
},
|
|
|
|
async loadTheme() {
|
|
themeLog.info('Loading theme data');
|
|
|
|
const url = `/admin/store-themes/${this.storeCode}`;
|
|
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/store-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/store-themes/${this.storeCode}`;
|
|
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(I18n.t('tenancy.messages.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/store-themes/${this.storeCode}/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(I18n.t('tenancy.messages.failed_to_apply_preset'), 'error');
|
|
} finally {
|
|
this.saving = false;
|
|
}
|
|
},
|
|
|
|
async resetTheme() {
|
|
themeLog.warn('Resetting theme to default');
|
|
this.saving = true;
|
|
|
|
try {
|
|
const url = `/admin/store-themes/${this.storeCode}`;
|
|
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(I18n.t('tenancy.messages.theme_reset_to_default'), 'success');
|
|
|
|
} catch (error) {
|
|
window.LogConfig.logError(error, 'Reset Theme');
|
|
Utils.showToast(I18n.t('tenancy.messages.failed_to_reset_theme'), 'error');
|
|
} finally {
|
|
this.saving = false;
|
|
}
|
|
},
|
|
|
|
// ====================================================================
|
|
// UTILITY METHODS
|
|
// ====================================================================
|
|
|
|
previewTheme() {
|
|
themeLog.debug('Opening theme preview');
|
|
const previewUrl = `/store/${this.store?.subdomain || this.storeCode}`;
|
|
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('Store theme editor module loaded');
|