// 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: '', content: '', content_format: 'html', template: 'default', meta_description: '', meta_keywords: '', is_published: false, show_in_header: false, show_in_footer: true, show_in_legal: false, display_order: 0, platform_id: null, vendor_id: null }, platforms: [], vendors: [], loading: false, loadingPlatforms: false, loadingVendors: false, saving: false, error: null, successMessage: null, // ======================================== // 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' }, 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 vendors for dropdowns await Promise.all([this.loadPlatforms(), this.loadVendors()]); 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 homepage if (this.form.slug === 'home') { 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 (property, not getter for Alpine compatibility) isHomepage: false, // Update isHomepage when slug changes updateIsHomepage() { this.isHomepage = this.form.slug === 'home'; }, // 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 vendors for dropdown async loadVendors() { this.loadingVendors = true; try { contentPageEditLog.info('Loading vendors...'); const response = await apiClient.get('/admin/vendors?is_active=true&limit=100'); const data = response.data || response; this.vendors = data.vendors || data.items || data || []; contentPageEditLog.info(`Loaded ${this.vendors.length} vendors`); } catch (err) { contentPageEditLog.error('Error loading vendors:', err); this.vendors = []; } finally { this.loadingVendors = 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 || '', content: page.content || '', content_format: page.content_format || 'html', template: page.template || 'default', meta_description: page.meta_description || '', meta_keywords: page.meta_keywords || '', 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, vendor_id: page.vendor_id }; 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; contentPageEditLog.debug('Synced Quill content after page load'); } }, // ======================================== // HOMEPAGE SECTIONS METHODS // ======================================== // Load sections for homepage async loadSections() { if (!this.pageId || this.form.slug !== 'home') { contentPageEditLog.debug('Skipping section load - not a homepage'); return; } try { contentPageEditLog.info('Loading homepage 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; 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 for (const key of ['hero', 'features', 'pricing', 'cta']) { if (loadedSections[key]) { 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.isHomepage) 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, content: this.form.content, content_format: this.form.content_format, template: this.form.template, meta_description: this.form.meta_description, meta_keywords: this.form.meta_keywords, 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, vendor_id: this.form.vendor_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 homepage if (this.isHomepage && this.sectionsLoaded) { await this.saveSections(); } this.successMessage = 'Page updated successfully!'; contentPageEditLog.info('Page updated'); } else { // Create new page - use vendor or platform endpoint based on selection const endpoint = this.form.vendor_id ? '/admin/content-pages/vendor' : '/admin/content-pages/platform'; response = await apiClient.post(endpoint, payload); this.successMessage = 'Page created successfully!'; contentPageEditLog.info('Page created', { endpoint, vendor_id: this.form.vendor_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; } } }; }