HEX
Server: Apache/2.4.65 (Debian)
System: Linux 88f31f35b0b8 6.1.0-38-amd64 #1 SMP PREEMPT_DYNAMIC Debian 6.1.147-1 (2025-08-02) x86_64
User: www-data (33)
PHP: 8.2.29
Disabled: NONE
Upload Files
File: /var/www/html/wp-content/plugins/wp-seopress/assets/js/seopress-promo-modal.js
/**
 * SEOPress Promotion Modal
 *
 * Standalone React component using wp-components Modal.
 * Loads on SEOPress admin pages to display promotional modals.
 *
 * @package SEOPress
 * @since 9.6.0
 */

(function() {
	'use strict';

	const { createElement, render, useState, useEffect } = wp.element;
	// React 18+ uses createRoot, fallback to render for older versions
	const createRoot = wp.element.createRoot || null;
	const { Modal, Button } = wp.components;

	/**
	 * Promotion Modal Component
	 */
	function PromoModal({ promotion, onDismiss }) {
		const [isOpen, setIsOpen] = useState(true);

		if (!isOpen || !promotion) {
			return null;
		}

		const handleDismiss = () => {
			setIsOpen(false);
			if (onDismiss) {
				onDismiss(promotion.id, promotion.dismiss_duration || 30);
			}
		};

		const handleCtaClick = () => {
			// Track click
			if (window.seopressPromotions && window.seopressPromotions.stats_endpoint) {
				if (navigator.sendBeacon) {
					const data = new FormData();
					data.append('ad_id', promotion.id);
					data.append('action', 'click');
					navigator.sendBeacon(window.seopressPromotions.stats_endpoint, data);
				}
			}
		};

		const bgColor = promotion.styling?.background_color || '#4E21E7';
		const textColor = promotion.styling?.text_color || '#FFFFFF';
		const buttonStyle = promotion.styling?.button_style || 'primary';

		// Button styles based on API setting
		const getButtonStyles = () => {
			const baseStyles = {
				display: 'inline-block',
				padding: '14px 32px',
				borderRadius: '8px',
				textDecoration: 'none',
				fontSize: '15px',
				fontWeight: '600',
				textAlign: 'center',
				cursor: 'pointer'
			};

			switch (buttonStyle) {
				case 'secondary':
					return {
						...baseStyles,
						backgroundColor: 'transparent',
						color: textColor,
						border: `2px solid ${textColor}`
					};
				case 'link':
					return {
						...baseStyles,
						backgroundColor: 'transparent',
						color: textColor,
						border: 'none',
						textDecoration: 'underline',
						padding: '14px 0'
					};
				case 'primary':
				default:
					return {
						...baseStyles,
						backgroundColor: textColor,
						color: bgColor,
						border: 'none'
					};
			}
		};

		return createElement(
			Modal,
			{
				title: null,
				onRequestClose: handleDismiss,
				className: 'seopress-promo-modal',
				overlayClassName: 'seopress-promo-modal-overlay',
				isDismissible: promotion.dismissible !== false,
			},
			createElement(
				'div',
				{
					className: 'seopress-promo-modal-content',
					style: { backgroundColor: bgColor, color: textColor }
				},
				// Icon
				promotion.icon && createElement(
					'div',
					{ className: 'seopress-promo-modal-icon' },
					createElement('span', {
						className: `dashicons dashicons-${promotion.icon}`,
						style: { color: textColor }
					})
				),
				// Title
				promotion.title && createElement(
					'h2',
					{
						className: 'seopress-promo-modal-title',
						style: { color: textColor }
					},
					promotion.title
				),
				// Body
				promotion.body && createElement(
					'p',
					{
						className: 'seopress-promo-modal-body',
						style: { color: textColor, opacity: 0.9 }
					},
					promotion.body
				),
				// Actions
				createElement(
					'div',
					{ className: 'seopress-promo-modal-actions' },
					// CTA Button
					promotion.cta_url && createElement(
						'a',
						{
							href: promotion.cta_url,
							target: '_blank',
							rel: 'noopener noreferrer',
							className: 'seopress-promo-modal-cta',
							style: getButtonStyles(),
							onClick: handleCtaClick
						},
						promotion.cta_text || 'Learn More'
					),
					// Dismiss Button (if dismissible)
					promotion.dismissible !== false && createElement(
						Button,
						{
							variant: 'link',
							onClick: handleDismiss,
							className: 'seopress-promo-modal-dismiss',
							style: { color: textColor, opacity: 0.8 }
						},
						'Remind me later'
					)
				)
			)
		);
	}

	/**
	 * Initialize the modal
	 */
	function init() {
		// Check if promotion data exists
		if (!window.seopressPromoModal || !window.seopressPromoModal.promotion) {
			return;
		}

		const promotion = window.seopressPromoModal.promotion;

		// Create mount point
		const mountPoint = document.createElement('div');
		mountPoint.id = 'seopress-promo-modal-root';
		document.body.appendChild(mountPoint);

		// Handle dismiss
		const handleDismiss = (promoId, duration) => {
			// Send AJAX to save dismissal
			if (window.seopressPromotions && window.seopressPromotions.dismiss_nonce) {
				const formData = new FormData();
				formData.append('action', 'seopress_dismiss_promotion');
				formData.append('promo_id', promoId);
				formData.append('duration', duration);
				formData.append('_ajax_nonce', window.seopressPromotions.dismiss_nonce);

				fetch(window.ajaxurl || '/wp-admin/admin-ajax.php', {
					method: 'POST',
					body: formData
				});

				// Track dismiss
				if (window.seopressPromotions.stats_endpoint) {
					if (navigator.sendBeacon) {
						const data = new FormData();
						data.append('ad_id', promoId);
						data.append('action', 'dismiss');
						navigator.sendBeacon(window.seopressPromotions.stats_endpoint, data);
					}
				}
			}
		};

		// Render (React 18+ uses createRoot, fallback to render for older versions)
		const modalElement = createElement(PromoModal, {
			promotion: promotion,
			onDismiss: handleDismiss
		});

		if (createRoot) {
			const root = createRoot(mountPoint);
			root.render(modalElement);
		} else {
			render(modalElement, mountPoint);
		}
	}

	// Initialize when DOM is ready
	if (document.readyState === 'loading') {
		document.addEventListener('DOMContentLoaded', init);
	} else {
		init();
	}
})();