/* global wp */ import { useState, useCallback, useEffect } from '@wordpress/element'; import { __ } from '@wordpress/i18n'; import apiFetch from '@wordpress/api-fetch'; import ModuleNav from '../../components/ModuleNav'; import ProrankHeader from '../../components/ProrankHeader'; import ProrankDivider from '../../components/ProrankDivider'; import { useNotification } from '../../contexts/NotificationContext'; import CachingDeliverySettings from '../../settings/Performance/CachingDelivery/CachingDelivery'; import ImageOptimizationSettings from '../../settings/Performance/ImageOptimization/ImageOptimizationSettings'; import AssetOptimizationSettings from '../../settings/Performance/AssetOptimization/AssetOptimizationSettings'; import DatabaseServerSettings from '../../settings/Performance/DatabaseServer/DatabaseServerSettings'; import BloatControlPanel from '../../settings/Performance/DatabaseServer/BloatControlPanel'; import headerBadges from '../../utils/headerBadges'; import * as React from 'react'; // TypeScript Interfaces interface IndividualModule { slug: string; name: string; description?: string; enabled: boolean; available: boolean; } interface PerformanceModule { slug: string; name: string; description: string; icon: string; tier: 'free'; enabled: boolean; available: boolean; modules?: IndividualModule[]; } interface ModuleGroup { slug: string; name: string; enabled: boolean; available: boolean; performance_beta?: boolean; } interface ModuleGroupConfig { modules: string[]; name: string; description: string; icon: string; tier: 'free'; } interface ModuleGroupMap { [key: string]: ModuleGroupConfig; } interface ApiModulesResponse { modules: IndividualModule[]; } interface ApiBetaResponse { value: boolean; } const Performance: React.FC = () => { const [activeTab, setActiveTab] = useState('caching-delivery'); const [modules, setModules] = useState([]); const [moduleGroup, setModuleGroup] = useState(null); const [loading, setLoading] = useState(true); const [updating, setUpdating] = useState>({}); const [betaEnabled, setBetaEnabled] = useState(true); const [betaLoading, setBetaLoading] = useState(false); const { showNotification } = useNotification(); const loadModules = useCallback(async () => { setLoading(true); try { // Load individual modules from API const response = await apiFetch({ path: '/prorank-seo/v1/modules/by-group/performance', }); // Store individual modules for reference const individualModules = response.modules || []; // Create grouped module cards based on loaded modules const moduleGroups: ModuleGroupMap = { 'caching-delivery': { modules: ['cache', 'modern_cache', 'browser_cache', 'js_opt', 'css_opt', 'resource_hints'], name: __('Caching & CDN', 'prorank-seo'), description: __( 'Page cache, CDN integration, and CSS/JS optimization for faster load times', 'prorank-seo' ), icon: 'dashicons-performance', tier: 'free', }, 'asset-optimisation': { modules: [ 'js_minify', 'html_minify', 'font_optimization', 'cls_prevention', 'lcp_beacon', ], name: __('Asset Optimisation', 'prorank-seo'), description: __( 'CSS/JS/HTML optimization, font optimization, CLS prevention, and LCP beacon tools', 'prorank-seo' ), icon: 'dashicons-editor-code', tier: 'free', }, 'image-optimisation': { modules: ['lazyload', 'image_optimization'], name: __('Image Optimisation', 'prorank-seo'), description: __( 'Lazy loading, WebP/AVIF conversion and optimized delivery', 'prorank-seo' ), icon: 'dashicons-format-image', tier: 'free', }, 'database-server': { modules: ['database_optimization'], name: __('Database & Server', 'prorank-seo'), description: __( 'Database optimization and server performance recommendations', 'prorank-seo' ), icon: 'dashicons-database', tier: 'free', }, }; // Build grouped cards with aggregated data const performanceModules: PerformanceModule[] = Object.entries(moduleGroups).map( ([groupSlug, groupConfig]) => { // Find modules that belong to this group const groupModules = individualModules.filter((m) => groupConfig.modules.includes(m.slug) ); const availableGroupModules = groupModules.filter((m) => m.available !== false); const anyEnabled = availableGroupModules.some((m) => m.enabled); const anyAvailable = availableGroupModules.length > 0; return { slug: groupSlug, name: groupConfig.name, description: groupConfig.description, icon: groupConfig.icon, tier: groupConfig.tier, enabled: anyEnabled, available: anyAvailable, modules: groupModules, // Store individual modules for reference }; } ); const nonEmptyModules = performanceModules.filter((m) => (m.modules?.length || 0) > 0); const visibleModules = nonEmptyModules.filter((m) => m.available !== false); setModules(visibleModules); setActiveTab((current) => visibleModules.some((m) => m.slug === current) ? current : (visibleModules[0]?.slug ?? current) ); // Get beta toggle state let betaStatus = false; try { const betaResponse = await apiFetch({ path: '/prorank-seo/v1/performance-beta', }); betaStatus = betaResponse.value || false; setBetaEnabled(betaStatus); } catch (error) { // Default to false if error } const performanceGroup: ModuleGroup = { slug: 'performance', name: __('Performance', 'prorank-seo'), enabled: visibleModules.some((m) => m.enabled), available: visibleModules.length > 0, performance_beta: betaStatus, }; setModuleGroup(performanceGroup); } catch (error) { // Show error state instead of using fallback data showNotification( __('Failed to load modules. Please refresh the page.', 'prorank-seo'), 'error' ); setModules([]); setModuleGroup(null); } finally { setLoading(false); } }, []); // Set page title useEffect(() => { document.title = __('Performance', 'prorank-seo') + ' ‹ ' + __('ProRank SEO', 'prorank-seo') + ' — WordPress'; }, []); // Load modules from API useEffect(() => { loadModules(); }, [loadModules]); const toggleModule = async (module: PerformanceModule) => { const slug = module.slug; const newState = !module.enabled; setUpdating((prev) => ({ ...prev, [slug]: true })); try { // For grouped modules, toggle all individual modules if (module.modules && module.modules.length > 0) { // Toggle all modules in the group const togglePromises = module.modules .filter((individualModule) => individualModule.available !== false) .map((individualModule) => apiFetch({ path: `/prorank-seo/v1/modules/${individualModule.slug}/toggle`, method: 'POST', data: { enabled: newState }, }) ); if (togglePromises.length > 0) { await Promise.all(togglePromises); } } else { // Fallback for individual modules await apiFetch({ path: `/prorank-seo/v1/modules/${slug}/toggle`, method: 'POST', data: { enabled: newState }, }); } // Update module state setModules((prev) => prev.map((m) => (m.slug === slug ? { ...m, enabled: newState } : m)) ); showNotification( newState ? __('Module group enabled successfully', 'prorank-seo') : __('Module group disabled successfully', 'prorank-seo'), 'success' ); } catch (error) { // Show error and don't update UI if API fails showNotification( error.message || __('Failed to toggle module', 'prorank-seo'), 'error' ); } finally { setUpdating((prev) => ({ ...prev, [slug]: false })); } }; const toggleBeta = async () => { setBetaLoading(true); const newState = !betaEnabled; try { await apiFetch({ path: '/prorank-seo/v1/performance-beta', method: 'POST', data: { value: newState }, }); setBetaEnabled(newState); showNotification( newState ? __('Performance Beta features enabled', 'prorank-seo') : __('Performance Beta features disabled', 'prorank-seo'), 'success' ); } catch (error: any) { showNotification( error.message || __('Failed to toggle beta features', 'prorank-seo'), 'error' ); } finally { setBetaLoading(false); } }; const toggleModuleGroup = async () => { if (!moduleGroup) return; const newState = !moduleGroup.enabled; setUpdating((prev) => ({ ...prev, group: true })); try { await apiFetch({ path: `/prorank-seo/v1/modules/groups/${moduleGroup.slug}/status`, method: 'POST', data: { enabled: newState }, }); setModuleGroup((prev) => (prev ? { ...prev, enabled: newState } : prev)); setModules((prev) => prev.map((m) => ({ ...m, enabled: m.available !== undefined && m.available !== null ? newState : false, })) ); showNotification( newState ? __('All modules enabled successfully', 'prorank-seo') : __('All modules disabled successfully', 'prorank-seo'), 'success' ); } catch (error: any) { showNotification( error.message || __('Failed to toggle module group', 'prorank-seo'), 'error' ); } finally { setUpdating((prev) => ({ ...prev, group: false })); } }; const renderTabContent = () => { const activeModule = modules.find((m) => m.slug === activeTab); if (!activeModule) { return null; } const isEnabled = activeModule.enabled; // In free build, unavailable modules should show a neutral unavailable state. if (activeModule.available === false) { return (

{activeModule.name}

{__('This module is not available in this build.', 'prorank-seo')}

); } // If module is disabled, show enable message (matches Technical SEO) if (!isEnabled) { const disabledNotice = (

{activeModule.name}

{__( 'This module is currently disabled. Enable it to access its settings and features.', 'prorank-seo' )}

); // The Bloat Control panel reuses existing Advanced/Head Cleanup // settings and is independent of the database_optimization module — // surface it even when Database & Server is disabled. if (activeModule.slug === 'database-server') { return (
{disabledNotice}
); } return disabledNotice; } // Render the appropriate settings component (only when enabled - matches Technical SEO) const renderSettings = () => { switch (activeModule.slug) { case 'caching-delivery': return {}} betaEnabled={betaEnabled} />; case 'image-optimisation': return ( {}} moduleEnabled={ activeModule.modules?.some( (module) => module.slug === 'image_optimization' && module.enabled ) ?? false } /> ); case 'asset-optimisation': return {}} />; case 'database-server': return {}} />; default: return (

{__('Module Not Found', 'prorank-seo')}

{__('The selected module does not have settings available.', 'prorank-seo')}

); } }; return
{renderSettings()}
; }; if (loading) { return (

{__('Loading modules…', 'prorank-seo')}

); } return (
{/* Main Content */}
{ return { id: module.slug, label: module.name, badge: null, badgeVariant: null, hasToggle: true, toggleValue: module.enabled, toggleDisabled: updating[module.slug] || module.available === false, }; })} activeModule={activeTab} onModuleChange={setActiveTab} onToggleChange={(moduleId, checked) => { const module = modules.find((item) => item.slug === moduleId); if (!module || module.enabled === checked) return; toggleModule(module); }} />
{renderTabContent()}
); }; export default Performance;