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..4e59635aee --- /dev/null +++ b/assets/apps/dashboard/src/Components/Content/Settings/PerformancePlugins.js @@ -0,0 +1,161 @@ +/* global neveDash */ +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 ControlWrap from '../../Controls/ControlWrap'; +import usePluginActions from '../../../Hooks/usePluginActions'; +import { + NEVE_HIDE_PLUGINS, + NEVE_PLUGIN_ICON_MAP, +} from '../../../utils/constants'; + +const PROMOTED_SLUGS = ['optimole-wp', 'wp-cloudflare-page-cache']; + +// How long the "Active" pill stays up before the card removes itself. +const ACTIVE_PILL_TIMEOUT = 1500; + +const PluginCard = ({ slug, data, onDismiss }) => { + const ICON = NEVE_PLUGIN_ICON_MAP[slug] || LucidePuzzle; + // Titles and descriptions are already localized in inc/admin/dashboard/main.php. + const { title, description } = data; + + const [error, setError] = useState(null); + const [success, setSuccess] = useState(false); + + const { doPluginAction, loading, buttonText } = usePluginActions( + slug, + true + ); + + useEffect(() => { + if (!success) { + return; + } + + // Only `success` belongs in the deps: onDismiss is recreated on every + // parent render and would restart a countdown already in progress. + const timeoutId = window.setTimeout(() => { + onDismiss(slug); + }, ACTIVE_PILL_TIMEOUT); + + return () => window.clearTimeout(timeoutId); + }, [success]); + + 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 = () => { + // Snapshot: a card leaves this list when it asks to, not when the store + // changes, so the "Active" pill outlives the activation request. + const [visibleSlugs, setVisibleSlugs] = useState(() => + PROMOTED_SLUGS.filter((slug) => { + // Super Page Cache is dropped server side when SPC Pro is + // installed, so a promoted slug can be missing entirely. + const plugin = neveDash.plugins[slug]; + + return plugin && plugin.cta !== 'deactivate'; + }) + ); + + if (NEVE_HIDE_PLUGINS || visibleSlugs.length < 1) { + return null; + } + + const handleDismiss = (dismissed) => + setVisibleSlugs((current) => + current.filter((slug) => slug !== dismissed) + ); + + return ( + +
1, + })} + > + {visibleSlugs.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..3207a74e19 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'; @@ -114,6 +115,8 @@ export default () => { /> {(isLicenseValid && ) || } + + );