From d04caf2d1831a82dd336442e8766d822a9a92c28 Mon Sep 17 00:00:00 2001 From: harshit Date: Tue, 11 Aug 2026 01:14:19 +0530 Subject: [PATCH 1/3] feat(dashboard): add recommended plugins in performance tab --- .../src/Components/Content/Settings.js | 45 +++--- .../Content/Settings/PerformancePlugins.js | 139 ++++++++++++++++++ 2 files changed, 166 insertions(+), 18 deletions(-) create mode 100644 assets/apps/dashboard/src/Components/Content/Settings/PerformancePlugins.js diff --git a/assets/apps/dashboard/src/Components/Content/Settings.js b/assets/apps/dashboard/src/Components/Content/Settings.js index 075fafa611..99a022da7d 100644 --- a/assets/apps/dashboard/src/Components/Content/Settings.js +++ b/assets/apps/dashboard/src/Components/Content/Settings.js @@ -13,6 +13,7 @@ import { import { useSelect } from '@wordpress/data'; import Card from '../../Layout/Card'; +import PerformancePlugins from './Settings/PerformancePlugins'; import { NEVE_HAS_PRO, NEVE_SHOW_WHITELABEL, @@ -139,28 +140,36 @@ const Settings = () => {
- - {tab === 'general' && ( - - - - )} +
+ + {tab === 'general' && ( + + + + )} + {tab === 'performance' && ( + + + + )} + {tab === 'white-label' && ( + + + + )} + {tab === 'manage-modules' && ( + + + + )} + + {tab === 'performance' && ( - + )} - {tab === 'white-label' && ( - - - - )} - {tab === 'manage-modules' && ( - - - - )} - +
); }; diff --git a/assets/apps/dashboard/src/Components/Content/Settings/PerformancePlugins.js b/assets/apps/dashboard/src/Components/Content/Settings/PerformancePlugins.js new file mode 100644 index 0000000000..f2bc2d4f39 --- /dev/null +++ b/assets/apps/dashboard/src/Components/Content/Settings/PerformancePlugins.js @@ -0,0 +1,139 @@ +/* global neveDash */ +import { useSelect } from '@wordpress/data'; +import { useState } from '@wordpress/element'; +import { __ } from '@wordpress/i18n'; +import cn from 'classnames'; +import { LoaderCircle, LucidePuzzle, LucideRocket } from 'lucide-react'; + +import Pill from '../../Common/Pill'; +import Toast from '../../Common/Toast'; +import TransitionInOut from '../../Common/TransitionInOut'; +import Card from '../../../Layout/Card'; +import usePluginActions from '../../../Hooks/usePluginActions'; +import { + NEVE_HIDE_PLUGINS, + NEVE_PLUGIN_ICON_MAP, + NEVE_STORE, +} from '../../../utils/constants'; + +const PROMOTED_SLUGS = ['optimole-wp', 'wp-cloudflare-page-cache']; + +const PluginCard = ({ slug }) => { + const ICON = NEVE_PLUGIN_ICON_MAP[slug] || LucidePuzzle; + // Titles and descriptions are already localized in inc/admin/dashboard/main.php. + const { title, description } = neveDash.plugins[slug]; + + const [error, setError] = useState(null); + const [success, setSuccess] = useState(false); + + const { doPluginAction, loading, buttonText } = usePluginActions( + slug, + true + ); + + const isPluginActive = useSelect( + (select) => select(NEVE_STORE).getPlugins()[slug]?.cta === 'deactivate' + ); + + if (isPluginActive && !success) { + return null; + } + + const handleClick = async () => { + setError(null); + + window.tiTrk?.with('neve').set(slug, { + feature: 'performance-plugin-promo', + featureComponent: slug, + }); + + const result = await doPluginAction(); + + if (result.success) { + setSuccess(true); + + return; + } + + setError(result.error); + }; + + return ( +
+
+ +

{title}

+ + {success && ( +
+ + {__('Active', 'neve')} + +
+ )} +
+ + {/* grow keeps the CTAs aligned when descriptions wrap to different heights */} +

+ {description} +

+ + {!success && ( + + )} + + {error && ( +
+ +
+ )} +
+ ); +}; + +const PerformancePlugins = () => { + const plugins = neveDash.plugins || {}; + + // A promoted plugin can be absent entirely, e.g. Super Page Cache is dropped when SPC Pro is installed. + const availableSlugs = PROMOTED_SLUGS.filter( + (slug) => plugins[slug] && plugins[slug].cta !== 'deactivate' + ); + + if (NEVE_HIDE_PLUGINS || availableSlugs.length < 1) { + return null; + } + + return ( + } + title={__('Recommended Plugins', 'neve')} + > +
1, + })} + > + {availableSlugs.map((slug) => ( + + ))} +
+
+ ); +}; + +export default PerformancePlugins; From 5d0663dd1cf0901cc2aedc77da05802e14554c35 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 11 Aug 2026 13:10:52 +0000 Subject: [PATCH 2/3] fix: hide promoted plugin cards after successful activation Co-authored-by: harshitarora-in <56164789+harshitarora-in@users.noreply.github.com> --- .../Content/Settings/PerformancePlugins.js | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/assets/apps/dashboard/src/Components/Content/Settings/PerformancePlugins.js b/assets/apps/dashboard/src/Components/Content/Settings/PerformancePlugins.js index f2bc2d4f39..9c03d81695 100644 --- a/assets/apps/dashboard/src/Components/Content/Settings/PerformancePlugins.js +++ b/assets/apps/dashboard/src/Components/Content/Settings/PerformancePlugins.js @@ -1,6 +1,6 @@ /* global neveDash */ import { useSelect } from '@wordpress/data'; -import { useState } from '@wordpress/element'; +import { useEffect, useState } from '@wordpress/element'; import { __ } from '@wordpress/i18n'; import cn from 'classnames'; import { LoaderCircle, LucidePuzzle, LucideRocket } from 'lucide-react'; @@ -35,6 +35,18 @@ const PluginCard = ({ slug }) => { (select) => select(NEVE_STORE).getPlugins()[slug]?.cta === 'deactivate' ); + useEffect(() => { + if (!success) { + return; + } + + const timeoutId = window.setTimeout(() => { + setSuccess(false); + }, 1500); + + return () => window.clearTimeout(timeoutId); + }, [success]); + if (isPluginActive && !success) { return null; } @@ -107,7 +119,7 @@ const PluginCard = ({ slug }) => { }; const PerformancePlugins = () => { - const plugins = neveDash.plugins || {}; + const plugins = useSelect((select) => select(NEVE_STORE).getPlugins(), []); // A promoted plugin can be absent entirely, e.g. Super Page Cache is dropped when SPC Pro is installed. const availableSlugs = PROMOTED_SLUGS.filter( From e5ac5bcd8256085edc255daf3b370ddf39b4ebf3 Mon Sep 17 00:00:00 2001 From: harshit Date: Tue, 18 Aug 2026 20:56:41 +0530 Subject: [PATCH 3/3] refactor(dashboard): render performance plugins inside the tab content Move PerformancePlugins into PerformanceTabContent so it reuses the tab's existing conditional and TransitionWrapper, instead of duplicating both in Settings.js. Nested inside the settings card, the component's own Card rendered a white box inside a white box, so add a `flat` variant to Card that keeps the header but drops the box styling. It defaults to false, leaving the other 20 usages unchanged. Co-Authored-By: Claude Opus 5 (1M context) --- assets/apps/dashboard/src/Components/Content/Settings.js | 7 ------- .../src/Components/Content/Settings/PerformancePlugins.js | 2 ++ .../Components/Content/Settings/PerformanceTabContent.js | 3 +++ assets/apps/dashboard/src/Layout/Card.js | 6 ++++-- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/assets/apps/dashboard/src/Components/Content/Settings.js b/assets/apps/dashboard/src/Components/Content/Settings.js index 99a022da7d..dfe99f213d 100644 --- a/assets/apps/dashboard/src/Components/Content/Settings.js +++ b/assets/apps/dashboard/src/Components/Content/Settings.js @@ -13,7 +13,6 @@ import { import { useSelect } from '@wordpress/data'; import Card from '../../Layout/Card'; -import PerformancePlugins from './Settings/PerformancePlugins'; import { NEVE_HAS_PRO, NEVE_SHOW_WHITELABEL, @@ -163,12 +162,6 @@ const Settings = () => { )}
- - {tab === 'performance' && ( - - - - )} ); diff --git a/assets/apps/dashboard/src/Components/Content/Settings/PerformancePlugins.js b/assets/apps/dashboard/src/Components/Content/Settings/PerformancePlugins.js index 9c03d81695..95456495d8 100644 --- a/assets/apps/dashboard/src/Components/Content/Settings/PerformancePlugins.js +++ b/assets/apps/dashboard/src/Components/Content/Settings/PerformancePlugins.js @@ -132,6 +132,8 @@ const PerformancePlugins = () => { return ( } title={__('Recommended Plugins', 'neve')} > diff --git a/assets/apps/dashboard/src/Components/Content/Settings/PerformanceTabContent.js b/assets/apps/dashboard/src/Components/Content/Settings/PerformanceTabContent.js index 1efe58f04e..91219f1188 100644 --- a/assets/apps/dashboard/src/Components/Content/Settings/PerformanceTabContent.js +++ b/assets/apps/dashboard/src/Components/Content/Settings/PerformanceTabContent.js @@ -11,6 +11,7 @@ import ToggleControl from '../../Controls/ToggleControl'; import Button from '../../Common/Button'; import useLicenseData from '../../../Hooks/useLicenseData'; import OptionGroup from './OptionGroup'; +import PerformancePlugins from './PerformancePlugins'; const LOCAL_HOSTING_OPTION = 'enable_local_fonts'; @@ -115,6 +116,8 @@ export default () => { {(isLicenseValid && ) || } + + ); }; diff --git a/assets/apps/dashboard/src/Layout/Card.js b/assets/apps/dashboard/src/Layout/Card.js index d00980209e..186b842050 100644 --- a/assets/apps/dashboard/src/Layout/Card.js +++ b/assets/apps/dashboard/src/Layout/Card.js @@ -7,12 +7,14 @@ export default ({ afterTitle, className = '', id = null, + flat = false, }) => { + // `flat` keeps the header styling but drops the box, for cards nested inside another one. const classes = cn( [ - 'p-6 rounded-lg shadow-sm', { - 'bg-white': !className.includes('bg-'), + 'p-6 rounded-lg shadow-sm': !flat, + 'bg-white': !flat && !className.includes('bg-'), }, ], className