/** * Fomo Notifications - Admin JavaScript * * @package King_Addons */ (function($) { 'use strict'; /** * Main Fomo Notifications Admin Class */ const KngFomoAdmin = { /** * Current wizard data */ wizardData: { step: 1, notification_id: 0, name: '', type: '', source: '', source_config: {}, design: { template: 'default', position: 'bottom-left', animation: 'slide', bg_color: '#ffffff', text_color: '#1d1d1f', accent_color: '#0071e3', border_radius: 16, shadow: true }, content: { title: '', message: '', image_type: 'product', custom_image: '', show_time: true, time_format: 'relative', cta_text: '', cta_url: '' }, display: { delay: 3, duration: 5, interval: 10, max_per_session: 5, devices: ['desktop', 'tablet', 'mobile'], pages: 'all', page_rules: [], audience: 'all', exclude_logged_in: false }, customize: { z_index: 99999, close_button: true, click_action: 'link', sound: false, analytics: true } }, /** * Initialize */ init: function() { this.bindEvents(); this.initWizard(); this.initCharts(); this.initToggles(); }, /** * Bind events */ bindEvents: function() { const self = this; // Navigation $(document).on('click', '.kng-fomo-nav-item', function(e) { const href = $(this).attr('href'); if (href && href.indexOf('#') !== 0) { return; } e.preventDefault(); const tab = $(this).data('tab'); self.switchTab(tab); }); // Wizard steps $(document).on('click', '.kng-fomo-wizard-step', function() { const step = parseInt($(this).data('step')); if (step <= self.wizardData.step || self.canGoToStep(step)) { self.goToStep(step); } }); // Wizard navigation buttons $(document).on('click', '.kng-fomo-wizard-prev', function() { self.prevStep(); }); $(document).on('click', '.kng-fomo-wizard-next', function() { self.nextStep(); }); // Notification name (separate from content title) $(document).on('change input', '#kng-fomo-notif-name', function() { self.wizardData.name = $(this).val(); }); // Notification type selection — type also determines the source $(document).on('change', 'input[name="notification_type"]', function() { var type = $(this).val(); var isNewType = (self.wizardData.type !== type); self.wizardData.type = type; self.wizardData.source = type; // type IS the source self.updateSourceOptions(); self.updateSourceConfig(); // Auto-populate content defaults and source_config for the selected type if (isNewType) { self.applyTypeDefaults(type); } }); // Design template selection $(document).on('click', '.kng-fomo-template-card', function() { $('.kng-fomo-template-card').removeClass('is-selected'); $(this).addClass('is-selected'); self.wizardData.design.template = $(this).data('template'); self.updatePreview(); }); // Position selection - buttons $(document).on('click', '.kng-fomo-position-btn', function() { const position = $(this).data('position'); $('.kng-fomo-position-btn').removeClass('is-active'); $(this).addClass('is-active'); $('.kng-fomo-position-dot').removeClass('is-active'); $('.kng-fomo-position-dot[data-pos="' + position + '"]').addClass('is-active'); self.wizardData.design.position = position; self.updatePreview(); }); // Position selection - dots (visual preview) $(document).on('click', '.kng-fomo-position-dot', function() { const position = $(this).data('pos'); $('.kng-fomo-position-dot').removeClass('is-active'); $(this).addClass('is-active'); $('.kng-fomo-position-btn').removeClass('is-active'); $('.kng-fomo-position-btn[data-position="' + position + '"]').addClass('is-active'); self.wizardData.design.position = position; self.updatePreview(); }); // Form inputs $(document).on('change input', '.kng-fomo-wizard [data-field]', function() { const field = $(this).data('field'); const section = $(this).data('section'); const value = $(this).is(':checkbox') ? $(this).is(':checked') : $(this).val(); if (section && self.wizardData[section]) { self.wizardData[section][field] = value; } else { self.wizardData[field] = value; } self.updatePreview(); }); // Color pickers $(document).on('input', '.kng-fomo-color-picker', function() { const field = $(this).data('field'); const value = $(this).val(); $(this).siblings('.kng-fomo-color-input').val(value); self.wizardData.design[field] = value; self.updatePreview(); }); $(document).on('input', '.kng-fomo-color-input', function() { const field = $(this).data('field'); const value = $(this).val(); $(this).siblings('.kng-fomo-color-picker').val(value); self.wizardData.design[field] = value; self.updatePreview(); }); // Save notification $(document).on('click', '.kng-fomo-save-notification', function() { self.saveNotification(); }); // Toggle notification status $(document).on('change', '.kng-fomo-toggle input', function() { const notificationId = $(this).closest('tr').data('id') || $(this).data('id'); const status = $(this).is(':checked') ? 'enabled' : 'disabled'; self.toggleNotification(notificationId, status); }); // Delete notification $(document).on('click', '.kng-fomo-delete', function(e) { e.preventDefault(); const notificationId = $(this).closest('tr').data('id') || $(this).data('id'); self.deleteNotification(notificationId); }); // Duplicate notification $(document).on('click', '.kng-fomo-duplicate', function(e) { e.preventDefault(); const notificationId = $(this).closest('tr').data('id') || $(this).data('id'); self.duplicateNotification(notificationId); }); // Edit notification $(document).on('click', '.kng-fomo-edit', function(e) { e.preventDefault(); const notificationId = $(this).closest('tr').data('id') || $(this).data('id'); self.loadNotification(notificationId); }); // Import/Export $(document).on('click', '.kng-fomo-export', function() { self.exportNotifications(); }); $(document).on('click', '.kng-fomo-import', function() { self.importNotifications(); }); // Settings save $(document).on('click', '.kng-fomo-save-settings', function() { self.saveSettings(); }); // Modal $(document).on('click', '.kng-fomo-modal-close, .kng-fomo-modal-overlay', function(e) { if (e.target === this) { self.closeModal(); } }); // Analytics date range $(document).on('change', '.kng-fomo-date-range', function() { self.loadAnalytics($(this).val()); }); // Tab switching $(document).on('click', '.kng-fomo-tab', function() { const tabId = $(this).data('tab'); self.switchContentTab(tabId); }); // Device checkboxes $(document).on('change', '.kng-fomo-device-check', function() { const device = $(this).val(); const checked = $(this).is(':checked'); if (checked) { if (!self.wizardData.display.devices.includes(device)) { self.wizardData.display.devices.push(device); } } else { self.wizardData.display.devices = self.wizardData.display.devices.filter(d => d !== device); } }); // Page rules $(document).on('click', '.kng-fomo-add-rule', function() { self.addPageRule(); }); $(document).on('click', '.kng-fomo-remove-rule', function() { $(this).closest('.kng-fomo-page-rule').remove(); self.collectPageRules(); }); $(document).on('change', '.kng-fomo-page-rule select, .kng-fomo-page-rule input', function() { self.collectPageRules(); }); }, /** * Initialize wizard */ initWizard: function() { if (!$('.kng-fomo-wizard').length) { return; } // Check if editing existing notification const editId = this.getUrlParam('edit'); if (editId) { this.loadNotification(editId); } else { this.goToStep(1); } }, /** * Go to step */ goToStep: function(step) { this.wizardData.step = step; // Update steps UI $('.kng-fomo-wizard-step').each(function() { const stepNum = parseInt($(this).data('step')); $(this).removeClass('is-active is-completed'); if (stepNum === step) { $(this).addClass('is-active'); } else if (stepNum < step) { $(this).addClass('is-completed'); } }); // Show/hide content panels $('.kng-fomo-wizard-panel').removeClass('is-active'); $('.kng-fomo-wizard-panel[data-step="' + step + '"]').addClass('is-active'); // Update buttons if (step === 1) { $('.kng-fomo-wizard-prev').hide(); } else { $('.kng-fomo-wizard-prev').show(); } if (step === 5) { $('.kng-fomo-wizard-next').hide(); $('.kng-fomo-save-notification').show(); } else { $('.kng-fomo-wizard-next').show(); $('.kng-fomo-save-notification').hide(); } // Update preview this.updatePreview(); // Scroll to top const wizardEl = $('.kng-fomo-wizard').get(0); if (wizardEl) { wizardEl.scrollIntoView({ behavior: 'smooth', block: 'start' }); } }, /** * Previous step */ prevStep: function() { if (this.wizardData.step > 1) { this.goToStep(this.wizardData.step - 1); } }, /** * Next step */ nextStep: function() { if (this.validateStep(this.wizardData.step)) { if (this.wizardData.step < 5) { this.goToStep(this.wizardData.step + 1); } } }, /** * Validate step */ validateStep: function(step) { let isValid = true; let message = ''; switch (step) { case 1: if (!this.wizardData.type) { message = kngFomoAdmin.i18n.select_type || 'Please select a notification type.'; isValid = false; } break; case 2: // Template has default value, so this is always valid break; case 3: // Content is optional for some notification types break; } if (!isValid && message) { this.showToast(message, 'error'); } return isValid; }, /** * Can go to step */ canGoToStep: function(step) { for (let i = 1; i < step; i++) { if (!this.validateStep(i)) { return false; } } return true; }, /** * Update source options based on type */ updateSourceOptions: function() { const type = this.wizardData.type; const $sources = $('.kng-fomo-source-options'); if (!$sources.length) { return; } // Show relevant sources $sources.find('.kng-fomo-radio-card').hide(); $sources.find('.kng-fomo-radio-card[data-type="' + type + '"], .kng-fomo-radio-card[data-type="all"]').show(); }, /** * Update source configuration */ updateSourceConfig: function() { const source = this.wizardData.source; const $config = $('.kng-fomo-source-config'); if (!$config.length) { return; } // Hide all config panels $config.find('.kng-fomo-source-config-panel').hide(); // Show relevant config $config.find('.kng-fomo-source-config-panel[data-source="' + source + '"]').fadeIn(200); }, /** * Update live preview */ updatePreview: function() { const $preview = $('.kng-fomo-preview-notification'); if (!$preview.length) { return; } const data = this.wizardData; // Update position class $preview.removeClass('pos-top-left pos-top-right pos-bottom-left pos-bottom-right pos-top-center pos-bottom-center'); $preview.addClass('pos-' + data.design.position); // Update styles $preview.css({ '--bg-color': data.design.bg_color, '--text-color': data.design.text_color, '--accent-color': data.design.accent_color, '--border-radius': data.design.border_radius + 'px' }); // Update content if (data.content.title) { $preview.find('.kng-fomo-preview-title').text(data.content.title); } if (data.content.message) { $preview.find('.kng-fomo-preview-message').text(data.content.message); } // Toggle shadow if (data.design.shadow) { $preview.addClass('has-shadow'); } else { $preview.removeClass('has-shadow'); } // Toggle close button if (data.customize.close_button) { $preview.find('.kng-fomo-preview-close').show(); } else { $preview.find('.kng-fomo-preview-close').hide(); } }, /** * Save notification */ saveNotification: function() { const self = this; const $btn = $('.kng-fomo-save-notification'); const normalizedDisplay = this.buildDisplayPayload(this.wizardData.display || {}); $btn.prop('disabled', true).addClass('is-loading'); $.ajax({ url: kngFomoAdmin.ajaxUrl, type: 'POST', data: { action: 'kng_fomo_save_notification', nonce: kngFomoAdmin.nonce, id: this.wizardData.notification_id, name: this.wizardData.name || this.wizardData.content.title || 'Untitled Notification', title: this.wizardData.content.title || '', status: 'disabled', type: this.wizardData.type, source: this.wizardData.source, source_config: JSON.stringify(this.wizardData.source_config), design: JSON.stringify(this.wizardData.design), content: JSON.stringify(this.wizardData.content), display: JSON.stringify(normalizedDisplay), customize: JSON.stringify(this.wizardData.customize) }, success: function(response) { if (response.success) { self.showToast(kngFomoAdmin.i18n.saved, 'success'); // Redirect to list setTimeout(function() { window.location.href = kngFomoAdmin.listUrl; }, 1000); } else { self.showToast(response.data.message || kngFomoAdmin.i18n.error, 'error'); } }, error: function() { self.showToast(kngFomoAdmin.i18n.error, 'error'); }, complete: function() { $btn.prop('disabled', false).removeClass('is-loading'); } }); }, /** * Load notification for editing */ loadNotification: function(id) { const self = this; $.ajax({ url: kngFomoAdmin.ajaxUrl, type: 'POST', data: { action: 'kng_fomo_get_notification', nonce: kngFomoAdmin.nonce, notification_id: id }, success: function(response) { if (response.success) { if (response.data && response.data.display) { response.data.display = self.normalizeDisplayFromServer(response.data.display); } self.wizardData = $.extend(true, self.wizardData, response.data); self.populateWizardFields(); self.goToStep(1); } else { self.showToast(response.data.message || kngFomoAdmin.i18n.error, 'error'); } }, error: function() { self.showToast(kngFomoAdmin.i18n.error, 'error'); } }); }, /** * Populate wizard fields from loaded data */ populateWizardFields: function() { const data = this.wizardData; data.display = this.normalizeDisplayFromServer(data.display || {}); // Notification name (separate from content title) if (data.name) { $('#kng-fomo-notif-name').val(data.name); } // Type if (data.type) { $('input[name="notification_type"][value="' + data.type + '"]').prop('checked', true); // Source = type (wizard has no separate source selector) if (!data.source || data.source === 'manual') { data.source = data.type; } this.updateSourceOptions(); this.updateSourceConfig(); } // Source config (populate form fields FROM saved data) if (data.source_config && typeof data.source_config === 'object') { var sc = data.source_config; Object.keys(sc).forEach(function(key) { var $el = $('[data-field="' + key + '"][data-section="source_config"]'); if ($el.length) { if ($el.is('select[multiple]') && Array.isArray(sc[key])) { $el.val(sc[key]); } else { $el.val(sc[key]); } } }); } // Design - Template if (data.design.template) { $('.kng-fomo-template-card').removeClass('is-selected'); $('.kng-fomo-template-card[data-template="' + data.design.template + '"]').addClass('is-selected'); } // Design - Position if (data.design.position) { $('.kng-fomo-position-btn').removeClass('is-active'); $('.kng-fomo-position-btn[data-position="' + data.design.position + '"]').addClass('is-active'); $('.kng-fomo-position-dot').removeClass('is-active'); $('.kng-fomo-position-dot[data-pos="' + data.design.position + '"]').addClass('is-active'); } // Colors $('[data-field="bg_color"]').val(data.design.bg_color); $('[data-field="text_color"]').val(data.design.text_color); $('[data-field="accent_color"]').val(data.design.accent_color); $('[data-field="border_radius"]').val(data.design.border_radius); $('[data-field="shadow"]').prop('checked', data.design.shadow); // Content (title template, message template) $('[data-field="title"][data-section="content"]').val(data.content.title); $('[data-field="message"][data-section="content"]').val(data.content.message); $('[data-field="cta_text"]').val(data.content.cta_text); $('[data-field="cta_url"]').val(data.content.cta_url); // Show auto-hint if content has {{}} templates if (data.content.title && data.content.title.indexOf('{{') !== -1) { $('.kng-fomo-content-auto-hint').show(); } // Display $('[data-field="delay"]').val(data.display.delay); $('[data-field="duration"]').val(data.display.duration); $('[data-field="interval"]').val(data.display.interval); $('[data-field="max_per_session"]').val(data.display.max_per_session); $('[data-field="pages"]').val(data.display.pages || 'all'); $('[data-field="audience"]').val(data.display.audience || 'all'); $('[data-field="loop"]').prop('checked', data.display.loop !== false); $('[data-field="random"]').prop('checked', !!data.display.random); // Devices $('.kng-fomo-device-check').prop('checked', false); (data.display.devices || []).forEach(function(device) { $('.kng-fomo-device-check[value="' + device + '"]').prop('checked', true); }); // Page rules this.renderPageRules(data.display.page_rules || []); // Customize $('[data-field="z_index"]').val(data.customize.z_index); $('[data-field="close_button"]').prop('checked', data.customize.close_button); $('[data-field="sound"]').prop('checked', data.customize.sound); $('[data-field="analytics"]').prop('checked', data.customize.analytics); this.updatePreview(); }, /** * Apply type-specific defaults when user selects a notification type. * Auto-fills content templates, source_config defaults, and shows hint. */ applyTypeDefaults: function(type) { var defaults = (window.kngFomoAdmin && kngFomoAdmin.typeDefaults) ? kngFomoAdmin.typeDefaults[type] : null; if (!defaults) return; // Auto-fill content templates if currently empty or non-template var cd = defaults.content_defaults || {}; var currentTitle = this.wizardData.content.title || ''; var currentMessage = this.wizardData.content.message || ''; if (!currentTitle || currentTitle.indexOf('{{') === -1) { this.wizardData.content.title = cd.title || ''; $('[data-field="title"][data-section="content"]').val(this.wizardData.content.title); } if (!currentMessage || currentMessage.indexOf('{{') === -1) { this.wizardData.content.message = cd.message || ''; $('[data-field="message"][data-section="content"]').val(this.wizardData.content.message); } // Show auto-hint if we set templates if (this.wizardData.content.title && this.wizardData.content.title.indexOf('{{') !== -1) { $('.kng-fomo-content-auto-hint').show(); } else { $('.kng-fomo-content-auto-hint').hide(); } // Auto-populate source_config with defaults from server var scd = defaults.source_config_defaults || {}; Object.keys(scd).forEach(function(key) { if (this.wizardData.source_config[key] === undefined) { this.wizardData.source_config[key] = scd[key]; } // Also set in the form var $el = $('[data-field="' + key + '"][data-section="source_config"]'); if ($el.length && !$el.val()) { $el.val(scd[key]); } }.bind(this)); // Also set image type default for certain types if (type === 'wordpress_comments' || type === 'reviews' || type === 'email_subscription') { this.wizardData.content.image_type = 'avatar'; $('[data-field="image_type"]').val('avatar'); } else if (type === 'woocommerce_sales') { this.wizardData.content.image_type = 'product'; $('[data-field="image_type"]').val('product'); } this.updatePreview(); }, /** * Normalize server display payload to wizard shape */ normalizeDisplayFromServer: function(display) { const out = $.extend(true, { delay: 3, duration: 5, interval: 10, max_per_session: 5, devices: ['desktop', 'tablet', 'mobile'], pages: 'all', page_rules: [], audience: 'all', exclude_logged_in: false, loop: true, random: false }, display || {}); if (!Array.isArray(out.devices) || !out.devices.length) { out.devices = ['desktop', 'tablet', 'mobile']; } // Audience legacy -> wizard if (!out.audience) { const displayFor = out.display_for || 'everyone'; if (displayFor === 'logged_in') out.audience = 'logged_in'; else if (displayFor === 'logged_out' || displayFor === 'guests') out.audience = 'logged_out'; else out.audience = 'all'; } // Pages/rules legacy -> wizard if (!Array.isArray(out.page_rules)) { out.page_rules = []; } if (!out.pages) { out.pages = 'all'; } if (!out.page_rules.length && (out.show_on === 'include' || out.show_on === 'exclude')) { const ids = out.show_on === 'include' ? (out.include_pages || []) : (out.exclude_pages || []); if (Array.isArray(ids) && ids.length) { out.pages = 'specific'; out.page_rules = ids.map(function(id) { return { type: out.show_on, condition: 'page', value: String(id) }; }); } } if (out.page_rules.length) { out.pages = 'specific'; } return out; }, /** * Build display payload with legacy-compatible keys */ buildDisplayPayload: function(display) { const out = $.extend(true, {}, display || {}); // Audience wizard -> legacy if (out.audience === 'logged_in') out.display_for = 'logged_in'; else if (out.audience === 'logged_out') out.display_for = 'logged_out'; else out.display_for = 'everyone'; // Page rules wizard -> legacy const rules = Array.isArray(out.page_rules) ? out.page_rules : []; if (out.pages === 'specific' && rules.length) { const includeIds = []; const excludeIds = []; rules.forEach(function(rule) { if (!rule || (rule.condition !== 'page' && rule.condition !== 'post')) return; if (!/^\d+$/.test(String(rule.value || '').trim())) return; const id = parseInt(rule.value, 10); if (rule.type === 'exclude') excludeIds.push(id); else includeIds.push(id); }); if (includeIds.length) { out.show_on = 'include'; out.include_pages = includeIds; out.exclude_pages = []; } else if (excludeIds.length) { out.show_on = 'exclude'; out.exclude_pages = excludeIds; out.include_pages = []; } else { out.show_on = 'everywhere'; out.include_pages = []; out.exclude_pages = []; } } else { out.show_on = 'everywhere'; out.include_pages = []; out.exclude_pages = []; } return out; }, /** * Toggle notification status */ toggleNotification: function(id, status) { const self = this; $.ajax({ url: kngFomoAdmin.ajaxUrl, type: 'POST', data: { action: 'kng_fomo_toggle_status', nonce: kngFomoAdmin.nonce, id: id, status: status }, success: function(response) { if (response.success) { self.showToast(kngFomoAdmin.i18n.saved, 'success'); } else { self.showToast(response.data.message || kngFomoAdmin.i18n.error, 'error'); } }, error: function() { self.showToast(kngFomoAdmin.i18n.error, 'error'); } }); }, /** * Delete notification */ deleteNotification: function(id) { const self = this; if (!confirm(kngFomoAdmin.i18n.confirmDelete)) { return; } $.ajax({ url: kngFomoAdmin.ajaxUrl, type: 'POST', data: { action: 'kng_fomo_delete_notification', nonce: kngFomoAdmin.nonce, id: id }, success: function(response) { if (response.success) { self.showToast(kngFomoAdmin.i18n.deleted, 'success'); $('tr[data-id="' + id + '"]').fadeOut(300, function() { $(this).remove(); self.checkEmptyState(); }); } else { self.showToast(response.data.message || kngFomoAdmin.i18n.error, 'error'); } }, error: function() { self.showToast(kngFomoAdmin.i18n.error, 'error'); } }); }, /** * Duplicate notification */ duplicateNotification: function(id) { const self = this; $.ajax({ url: kngFomoAdmin.ajaxUrl, type: 'POST', data: { action: 'kng_fomo_duplicate_notification', nonce: kngFomoAdmin.nonce, id: id }, success: function(response) { if (response.success) { self.showToast(kngFomoAdmin.i18n.duplicated, 'success'); setTimeout(function() { window.location.reload(); }, 500); } else { self.showToast(response.data.message || kngFomoAdmin.i18n.error, 'error'); } }, error: function() { self.showToast(kngFomoAdmin.i18n.error, 'error'); } }); }, /** * Check empty state */ checkEmptyState: function() { if ($('.kng-fomo-table tbody tr').length === 0) { $('.kng-fomo-table-wrap').hide(); $('.kng-fomo-empty').show(); } }, /** * Add page rule */ addPageRule: function() { const $container = $('.kng-fomo-page-rules'); const template = `