diff --git a/assets/apps/dashboard/src/Components/Content/Settings.js b/assets/apps/dashboard/src/Components/Content/Settings.js index 075fafa611..dfe99f213d 100644 --- a/assets/apps/dashboard/src/Components/Content/Settings.js +++ b/assets/apps/dashboard/src/Components/Content/Settings.js @@ -139,28 +139,30 @@ const Settings = () => {
- - {tab === 'general' && ( - - - - )} - {tab === 'performance' && ( - - - - )} - {tab === 'white-label' && ( - - - - )} - {tab === 'manage-modules' && ( - - - - )} - +
+ + {tab === 'general' && ( + + + + )} + {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..95456495d8 --- /dev/null +++ b/assets/apps/dashboard/src/Components/Content/Settings/PerformancePlugins.js @@ -0,0 +1,153 @@ +/* global neveDash */ +import { useSelect } from '@wordpress/data'; +import { useEffect, 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' + ); + + useEffect(() => { + if (!success) { + return; + } + + const timeoutId = window.setTimeout(() => { + setSuccess(false); + }, 1500); + + return () => window.clearTimeout(timeoutId); + }, [success]); + + 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 = 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( + (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; 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