// noqa: js-006 - async init pattern is safe, loadData has try/catch // static/admin/js/content-page-edit.js // Use centralized logger const contentPageEditLog = window.LogConfig.loggers.contentPageEdit || window.LogConfig.createLogger('contentPageEdit'); // ============================================ // CONTENT PAGE EDITOR FUNCTION // ============================================ function contentPageEditor(pageId) { return { // Inherit base layout functionality from init-alpine.js ...data(), // Page identifier for sidebar active state currentPage: 'content-pages', // Editor state pageId: pageId, form: { slug: '', title: '', title_translations: {}, content: '', content_translations: {}, content_format: 'html', template: 'default', meta_description: '', meta_description_translations: {}, is_published: false, show_in_header: false, show_in_footer: true, show_in_legal: false, display_order: 0, platform_id: null, store_id: null }, platforms: [], stores: [], loading: false, loadingPlatforms: false, loadingStores: false, saving: false, error: null, successMessage: null, // Page type: 'content' or 'landing' pageType: 'content', // Translation language for title/content titleContentLang: 'fr', // ======================================== // HOMEPAGE SECTIONS STATE // ======================================== supportedLanguages: ['fr', 'de', 'en'], defaultLanguage: 'fr', currentLang: 'fr', openSection: null, sectionsLoaded: false, languageNames: { en: 'English', fr: 'Français', de: 'Deutsch', lb: 'Lëtzebuergesch' }, // Template-driven section palette sectionPalette: { 'default': ['hero', 'features', 'products', 'pricing', 'testimonials', 'gallery', 'contact_info', 'cta'], 'full': ['hero', 'features', 'testimonials', 'gallery', 'contact_info', 'cta'], }, sections: { hero: { enabled: true, badge_text: { translations: {} }, title: { translations: {} }, subtitle: { translations: {} }, background_type: 'gradient', buttons: [] }, features: { enabled: true, title: { translations: {} }, subtitle: { translations: {} }, features: [], layout: 'grid' }, pricing: { enabled: true, title: { translations: {} }, subtitle: { translations: {} }, use_subscription_tiers: true }, cta: { enabled: true, title: { translations: {} }, subtitle: { translations: {} }, buttons: [], background_type: 'gradient' } }, // Initialize async init() { contentPageEditLog.info('=== CONTENT PAGE EDITOR INITIALIZING ==='); contentPageEditLog.info('Page ID:', this.pageId); // Prevent multiple initializations if (window._contentPageEditInitialized) { contentPageEditLog.warn('Content page editor already initialized, skipping...'); return; } window._contentPageEditInitialized = true; // Load platforms and stores for dropdowns await Promise.all([this.loadPlatforms(), this.loadStores()]); if (this.pageId) { // Edit mode - load existing page contentPageEditLog.group('Loading page for editing'); await this.loadPage(); contentPageEditLog.groupEnd(); // Load sections if this is a landing page if (this.pageType === 'landing') { await this.loadSections(); } } else { // Create mode - use default values contentPageEditLog.info('Create mode - using default form values'); } contentPageEditLog.info('=== CONTENT PAGE EDITOR INITIALIZATION COMPLETE ==='); }, // Check if we should show section editor isHomepage: false, // Is a section available for the current template? isSectionAvailable(sectionName) { const palette = this.sectionPalette[this.form.template] || this.sectionPalette['full']; return palette.includes(sectionName); }, // Update homepage state updateIsHomepage() { this.isHomepage = this.form.slug === 'home'; }, // Update template when page type changes updatePageType() { if (this.pageType === 'landing') { this.form.template = 'full'; // Load sections if editing and not yet loaded if (this.pageId && !this.sectionsLoaded) { this.loadSections(); } } else { this.form.template = 'default'; } this.updateIsHomepage(); }, // ======================================== // TITLE/CONTENT TRANSLATION HELPERS // ======================================== getTranslatedTitle() { if (this.titleContentLang === this.defaultLanguage) { return this.form.title; } return (this.form.title_translations || {})[this.titleContentLang] || ''; }, setTranslatedTitle(value) { if (this.titleContentLang === this.defaultLanguage) { this.form.title = value; } else { if (!this.form.title_translations) this.form.title_translations = {}; this.form.title_translations[this.titleContentLang] = value; } }, getTranslatedContent() { if (this.titleContentLang === this.defaultLanguage) { return this.form.content; } return (this.form.content_translations || {})[this.titleContentLang] || ''; }, setTranslatedContent(value) { if (this.titleContentLang === this.defaultLanguage) { this.form.content = value; } else { if (!this.form.content_translations) this.form.content_translations = {}; this.form.content_translations[this.titleContentLang] = value; } }, getTranslatedMetaDescription() { if (this.titleContentLang === this.defaultLanguage) { return this.form.meta_description; } return (this.form.meta_description_translations || {})[this.titleContentLang] || ''; }, setTranslatedMetaDescription(value) { if (this.titleContentLang === this.defaultLanguage) { this.form.meta_description = value; } else { if (!this.form.meta_description_translations) this.form.meta_description_translations = {}; this.form.meta_description_translations[this.titleContentLang] = value; } }, // Load platforms for dropdown async loadPlatforms() { this.loadingPlatforms = true; try { contentPageEditLog.info('Loading platforms...'); const response = await apiClient.get('/admin/platforms?is_active=true'); const data = response.data || response; this.platforms = data.platforms || data.items || data || []; contentPageEditLog.info(`Loaded ${this.platforms.length} platforms`); // Set default platform if not editing and no platform selected if (!this.pageId && !this.form.platform_id && this.platforms.length > 0) { this.form.platform_id = this.platforms[0].id; } } catch (err) { contentPageEditLog.error('Error loading platforms:', err); this.platforms = []; } finally { this.loadingPlatforms = false; } }, // Load stores for dropdown async loadStores() { this.loadingStores = true; try { contentPageEditLog.info('Loading stores...'); const response = await apiClient.get('/admin/stores?is_active=true&limit=100'); const data = response.data || response; this.stores = data.stores || data.items || data || []; contentPageEditLog.info(`Loaded ${this.stores.length} stores`); } catch (err) { contentPageEditLog.error('Error loading stores:', err); this.stores = []; } finally { this.loadingStores = false; } }, // Load existing page async loadPage() { this.loading = true; this.error = null; try { contentPageEditLog.info(`Fetching page ${this.pageId}...`); const response = await apiClient.get(`/admin/content-pages/${this.pageId}`); contentPageEditLog.debug('API Response:', response); if (!response) { throw new Error('Invalid API response'); } // Handle response - API returns object directly const page = response.data || response; this.form = { slug: page.slug || '', title: page.title || '', title_translations: page.title_translations || {}, content: page.content || '', content_translations: page.content_translations || {}, content_format: page.content_format || 'html', template: page.template || 'default', meta_description: page.meta_description || '', meta_description_translations: page.meta_description_translations || {}, is_published: page.is_published || false, show_in_header: page.show_in_header || false, show_in_footer: page.show_in_footer !== undefined ? page.show_in_footer : true, show_in_legal: page.show_in_legal || false, display_order: page.display_order || 0, platform_id: page.platform_id, store_id: page.store_id }; // Set page type from template this.pageType = (this.form.template === 'full') ? 'landing' : 'content'; contentPageEditLog.info('Page loaded successfully'); // Update computed properties after loading this.updateIsHomepage(); // Re-initialize Quill editor content after page data is loaded // (Quill may have initialized before loadPage completed) this.syncQuillContent(); } catch (err) { contentPageEditLog.error('Error loading page:', err); this.error = err.message || 'Failed to load page'; } finally { this.loading = false; } }, // Sync Quill editor content after page data loads // Quill may initialize before loadPage completes, leaving editor empty syncQuillContent(retries = 5) { const quillContainer = document.getElementById('content-editor'); if (!quillContainer || !quillContainer.__quill) { // Quill not ready yet, retry if (retries > 0) { setTimeout(() => this.syncQuillContent(retries - 1), 100); } return; } const quill = quillContainer.__quill; if (this.form.content && quill.root.innerHTML !== this.form.content) { quill.root.innerHTML = this.form.content; // # noqa: SEC-015 contentPageEditLog.debug('Synced Quill content after page load'); } }, // ======================================== // SECTIONS METHODS // ======================================== // Load sections for landing pages async loadSections() { if (!this.pageId || this.pageType !== 'landing') { contentPageEditLog.debug('Skipping section load - not a landing page'); return; } try { contentPageEditLog.info('Loading sections...'); const response = await apiClient.get(`/admin/content-pages/${this.pageId}/sections`); const data = response.data || response; this.supportedLanguages = data.supported_languages || ['fr', 'de', 'en']; this.defaultLanguage = data.default_language || 'fr'; this.currentLang = this.defaultLanguage; this.titleContentLang = this.defaultLanguage; if (data.sections) { this.sections = this.mergeWithDefaults(data.sections); contentPageEditLog.info('Sections loaded:', Object.keys(data.sections)); } else { this.initializeEmptySections(); contentPageEditLog.info('No sections found - initialized empty structure'); } this.sectionsLoaded = true; } catch (err) { contentPageEditLog.error('Error loading sections:', err); } }, // Merge loaded sections with default structure mergeWithDefaults(loadedSections) { const defaults = this.getDefaultSectionStructure(); // Deep merge each section that exists in defaults for (const key of Object.keys(defaults)) { if (loadedSections[key]) { defaults[key] = { ...defaults[key], ...loadedSections[key] }; } } // Also preserve any extra sections from loaded data for (const key of Object.keys(loadedSections)) { if (!defaults[key]) { defaults[key] = loadedSections[key]; } } return defaults; }, // Get default section structure getDefaultSectionStructure() { const emptyTranslations = () => { const t = {}; this.supportedLanguages.forEach(lang => t[lang] = ''); return { translations: t }; }; return { hero: { enabled: true, badge_text: emptyTranslations(), title: emptyTranslations(), subtitle: emptyTranslations(), background_type: 'gradient', buttons: [] }, features: { enabled: true, title: emptyTranslations(), subtitle: emptyTranslations(), features: [], layout: 'grid' }, pricing: { enabled: true, title: emptyTranslations(), subtitle: emptyTranslations(), use_subscription_tiers: true }, cta: { enabled: true, title: emptyTranslations(), subtitle: emptyTranslations(), buttons: [], background_type: 'gradient' } }; }, // Initialize empty sections for all languages initializeEmptySections() { this.sections = this.getDefaultSectionStructure(); }, // Add a button to hero or cta section addButton(sectionName) { const newButton = { text: { translations: {} }, url: '', style: 'primary' }; this.supportedLanguages.forEach(lang => { newButton.text.translations[lang] = ''; }); this.sections[sectionName].buttons.push(newButton); contentPageEditLog.debug(`Added button to ${sectionName}`); }, // Remove a button from hero or cta section removeButton(sectionName, index) { this.sections[sectionName].buttons.splice(index, 1); contentPageEditLog.debug(`Removed button ${index} from ${sectionName}`); }, // Add a feature card addFeature() { const newFeature = { icon: '', title: { translations: {} }, description: { translations: {} } }; this.supportedLanguages.forEach(lang => { newFeature.title.translations[lang] = ''; newFeature.description.translations[lang] = ''; }); this.sections.features.features.push(newFeature); contentPageEditLog.debug('Added feature card'); }, // Remove a feature card removeFeature(index) { this.sections.features.features.splice(index, 1); contentPageEditLog.debug(`Removed feature ${index}`); }, // Save sections async saveSections() { if (!this.pageId || this.pageType !== 'landing') return; try { contentPageEditLog.info('Saving sections...'); await apiClient.put(`/admin/content-pages/${this.pageId}/sections`, this.sections); contentPageEditLog.info('Sections saved successfully'); } catch (err) { contentPageEditLog.error('Error saving sections:', err); throw err; } }, // Save page (create or update) async savePage() { if (this.saving) return; this.saving = true; this.error = null; this.successMessage = null; try { contentPageEditLog.info(this.pageId ? 'Updating page...' : 'Creating page...'); const payload = { slug: this.form.slug, title: this.form.title, title_translations: this.form.title_translations, content: this.form.content, content_translations: this.form.content_translations, content_format: this.form.content_format, template: this.form.template, meta_description: this.form.meta_description, meta_description_translations: this.form.meta_description_translations, is_published: this.form.is_published, show_in_header: this.form.show_in_header, show_in_footer: this.form.show_in_footer, show_in_legal: this.form.show_in_legal, display_order: this.form.display_order, platform_id: this.form.platform_id, store_id: this.form.store_id }; contentPageEditLog.debug('Payload:', payload); let response; if (this.pageId) { // Update existing page response = await apiClient.put(`/admin/content-pages/${this.pageId}`, payload); // Also save sections if this is a landing page if (this.pageType === 'landing' && this.sectionsLoaded) { await this.saveSections(); } this.successMessage = 'Page updated successfully!'; contentPageEditLog.info('Page updated'); } else { // Create new page - use store or platform endpoint based on selection const endpoint = this.form.store_id ? '/admin/content-pages/store' : '/admin/content-pages/platform'; response = await apiClient.post(endpoint, payload); this.successMessage = 'Page created successfully!'; contentPageEditLog.info('Page created', { endpoint, store_id: this.form.store_id }); // Redirect to edit page after creation const pageData = response.data || response; if (pageData && pageData.id) { setTimeout(() => { window.location.href = `/admin/content-pages/${pageData.id}/edit`; }, 1500); } } // Clear success message after 3 seconds setTimeout(() => { this.successMessage = null; }, 3000); } catch (err) { contentPageEditLog.error('Error saving page:', err); this.error = err.message || 'Failed to save page'; // Scroll to top to show error window.scrollTo({ top: 0, behavior: 'smooth' }); } finally { this.saving = false; } } }; }