import { useEffect, useState } from '@wordpress/element'; import { __ } from '@wordpress/i18n'; import { TextareaControl, TextControl } from '@wordpress/components'; import apiFetch from '@wordpress/api-fetch'; import { Alert, Button, Card, Toggle } from '../../../components/ui'; import { useNotification } from '../../../contexts/NotificationContext'; import FontDeliveryHealthPanel from './FontDeliveryHealthPanel'; import * as React from 'react'; interface AssetSettings { js_minify_enabled: boolean; js_minify_inline: boolean; minify_html: boolean; remove_html_comments: boolean; host_google_fonts_locally: boolean; font_display_swap: boolean; optm_skip_roles: string[]; content_visibility_enabled: boolean; content_visibility_selectors: string; content_visibility_size_hint: string; } interface AssetOptimizationSettingsProps { onClose?: () => void; embedded?: boolean; } const DEFAULT_SETTINGS: AssetSettings = { js_minify_enabled: false, js_minify_inline: false, minify_html: false, remove_html_comments: true, host_google_fonts_locally: false, font_display_swap: false, optm_skip_roles: [], content_visibility_enabled: false, content_visibility_selectors: '', content_visibility_size_hint: 'auto 800px', }; const normalizeBooleanSetting = (value: unknown): boolean => { if (typeof value === 'boolean') { return value; } if (typeof value === 'number') { return value !== 0; } if (typeof value === 'string') { const normalized = value.trim().toLowerCase(); if (['1', 'true', 'yes', 'on', 'enabled'].includes(normalized)) { return true; } if (['0', 'false', 'no', 'off', 'disabled', ''].includes(normalized)) { return false; } } return Boolean(value); }; const normalizeStringList = (value: unknown): string[] => { if (Array.isArray(value)) { return value.map((item) => String(item).trim()).filter(Boolean); } if (typeof value === 'string') { return value.split(/[\r\n,]+/).map((item) => item.trim()).filter(Boolean); } return []; }; const normalizeAssetSettings = (value: Partial): Partial => { const jsMinifyEnabled = value.js_minify_enabled ?? (value as any).js_minify; return { ...value, js_minify_enabled: normalizeBooleanSetting(jsMinifyEnabled), js_minify_inline: normalizeBooleanSetting(value.js_minify_inline), minify_html: normalizeBooleanSetting(value.minify_html), remove_html_comments: normalizeBooleanSetting(value.remove_html_comments), host_google_fonts_locally: normalizeBooleanSetting(value.host_google_fonts_locally), font_display_swap: normalizeBooleanSetting(value.font_display_swap), optm_skip_roles: normalizeStringList(value.optm_skip_roles), content_visibility_enabled: normalizeBooleanSetting(value.content_visibility_enabled), content_visibility_selectors: typeof value.content_visibility_selectors === 'string' ? value.content_visibility_selectors : '', content_visibility_size_hint: typeof value.content_visibility_size_hint === 'string' && value.content_visibility_size_hint.trim() ? value.content_visibility_size_hint : 'auto 800px', }; }; const toggleRowClass = 'pr:px-5 pr:py-4 pr:rounded-lg pr:border pr:border-gray-200 pr:bg-white pr:shadow-xs'; const AssetOptimizationSettings: React.FC = () => { const [settings, setSettings] = useState(DEFAULT_SETTINGS); const [loading, setLoading] = useState(true); const [saving, setSaving] = useState(false); const [notice, setNotice] = useState<{ type: 'success' | 'error' | 'info'; message: string } | null>(null); const { showNotification } = useNotification(); useEffect(() => { const loadSettings = async () => { setLoading(true); try { const response = await apiFetch<{ settings?: Partial }>({ path: '/prorank-seo/v1/performance/asset-settings', }); setSettings({ ...DEFAULT_SETTINGS, ...normalizeAssetSettings({ ...DEFAULT_SETTINGS, ...(response?.settings || {}), }), }); } catch (error: any) { const message = error?.message || __('Failed to load asset optimisation settings.', 'prorank-seo'); setNotice({ type: 'error', message }); } finally { setLoading(false); } }; loadSettings(); }, []); const updateSetting = (key: K, value: AssetSettings[K]) => { setSettings((prev) => ({ ...prev, [key]: value })); }; const saveSettings = async () => { setSaving(true); setNotice(null); try { const normalizedSettings = normalizeAssetSettings(settings) as AssetSettings; await apiFetch({ path: '/prorank-seo/v1/performance/asset-settings', method: 'POST', data: { settings: normalizedSettings }, }); setSettings(normalizedSettings); setNotice({ type: 'success', message: __('Asset optimisation settings saved.', 'prorank-seo') }); showNotification(__('Asset optimisation settings saved.', 'prorank-seo'), 'success'); } catch (error: any) { const message = error?.message || __('Failed to save asset optimisation settings.', 'prorank-seo'); setNotice({ type: 'error', message }); showNotification(message, 'error'); } finally { setSaving(false); } }; if (loading) { return
{__('Loading asset optimisation settings…', 'prorank-seo')}
; } return (
{notice && }

{__('JavaScript Minification', 'prorank-seo')}

{__('Compress front-end JavaScript files to reduce transfer size. Keep these off if another performance plugin already minifies or rewrites JavaScript output.', 'prorank-seo')}

updateSetting('js_minify_enabled', value)} /> updateSetting('js_minify_inline', value)} description={__('Enable this only if your site outputs a lot of inline script blocks and no other optimizer is already touching final HTML/script output.', 'prorank-seo')} />

{__('HTML Optimisation', 'prorank-seo')}

{__('Trim page output by minifying HTML and removing comments where safe. Avoid stacking these with another plugin that already rewrites final HTML.', 'prorank-seo')}

updateSetting('minify_html', value)} /> updateSetting('remove_html_comments', value)} />

{__('Font Optimisation', 'prorank-seo')}

{__('Host Google Fonts locally and add font-display=swap to improve rendering stability and reduce third-party requests.', 'prorank-seo')}

updateSetting('host_google_fonts_locally', value)} /> updateSetting('font_display_swap', value)} />
{/* Read-only Font Delivery Health (reuses get_stats; no toggles) */}

{__('Role-Based Optimisation Skip', 'prorank-seo')}

{__('Skip CSS and JavaScript rewriting for logged-in users with selected roles. Useful for editors, shop managers, and support staff using dynamic admin bars or front-end tooling.', 'prorank-seo')}

updateSetting('optm_skip_roles', normalizeStringList(value))} rows={4} placeholder="administrator editor shop_manager" />

{__('Content Visibility', 'prorank-seo')}

{__('Apply content-visibility:auto to selected below-fold containers so the browser can skip rendering off-screen sections until needed.', 'prorank-seo')}

updateSetting('content_visibility_enabled', value)} /> {settings.content_visibility_enabled && ( <> updateSetting('content_visibility_selectors', value)} rows={4} placeholder=".site-main section:nth-of-type(n+3) .below-fold" /> updateSetting('content_visibility_size_hint', value)} placeholder="auto 800px" /> )}

{__('Automatic Free Runtime Optimisations', 'prorank-seo')}

  • {__('CLS prevention is handled by the free Performance runtime when the Performance module is enabled.', 'prorank-seo')}
  • {__('Core Web Vitals beacon collection is handled automatically by the free Performance runtime when its monitoring modules are enabled.', 'prorank-seo')}
  • {__('Advanced Critical CSS, Unused CSS removal, script isolation, and video optimisation are not part of the free build.', 'prorank-seo')}
); }; export default AssetOptimizationSettings;