Files
orion/app/modules/cms/static/admin/js/store-theme.js
Samir Boulahtit 51a2114e02 refactor(cms): migrate store theme UI from tenancy to CMS module
Move store theme admin pages, templates, and JS from tenancy module
to CMS module where the data layer (model, service, API, schemas)
already lives. Eliminates split ownership.

Moved:
- Route handlers: GET /store-themes, GET /stores/{code}/theme
- Templates: store-theme.html, store-themes.html
- JS: store-theme.js, store-themes.js
- Updated static references: tenancy_static → cms_static

Deleted old tenancy files (no remaining references).
Menu item in CMS definition already pointed to correct route.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-18 10:30:09 +02:00

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');