From c0d64fb24a181eb5a03f590ea98a9f2fdfb4468c Mon Sep 17 00:00:00 2001 From: abaicus Date: Wed, 8 Jan 2025 22:09:16 +0200 Subject: [PATCH] feat: new backend dashboard - done free version [wip][ref Codeinwp/neve-pro-addon#2914] --- assets/apps/dashboard/src/Components/App.js | 40 ++- .../src/Components/Content/Changelog.js | 255 +++++++++++------- .../dashboard/src/Components/Content/Help.js | 134 --------- .../Content/ModuleGridPlaceholder.js | 11 +- .../src/Components/Content/Plugins.js | 27 -- .../dashboard/src/Components/Content/Pro.js | 11 +- .../Content/StarterSitesUnavailable.js | 26 +- .../src/Components/Content/Welcome.js | 5 +- .../apps/dashboard/src/Components/Header.js | 2 +- .../dashboard/src/Components/LicenseCard.js | 1 + .../apps/dashboard/src/Components/Loading.js | 233 ++++++++-------- .../dashboard/src/Components/ModuleCard.js | 4 +- .../src/Components/Plugin/InstallActivate.js | 1 - .../dashboard/src/Components/PluginsCard.js | 127 ++++++++- .../dashboard/src/Components/SupportCard.js | 5 +- .../dashboard/src/Hooks/usePluginActions.js | 191 +++++++++++++ assets/apps/dashboard/src/style.css | 2 +- assets/apps/dashboard/src/utils/common.js | 16 +- assets/apps/dashboard/src/utils/constants.js | 17 ++ inc/admin/dashboard/main.php | 69 ++++- inc/admin/dashboard/plugin_helper.php | 2 + 21 files changed, 724 insertions(+), 455 deletions(-) delete mode 100644 assets/apps/dashboard/src/Components/Content/Help.js delete mode 100644 assets/apps/dashboard/src/Components/Content/Plugins.js create mode 100644 assets/apps/dashboard/src/Hooks/usePluginActions.js diff --git a/assets/apps/dashboard/src/Components/App.js b/assets/apps/dashboard/src/Components/App.js index 1d0e62670d..2bd5178632 100644 --- a/assets/apps/dashboard/src/Components/App.js +++ b/assets/apps/dashboard/src/Components/App.js @@ -4,16 +4,26 @@ import TabsContent from './TabsContent'; import Sidebar from './Sidebar'; import Loading from './Loading'; import Snackbar from './Snackbar'; +import Container from '../Layout/Container'; import { fetchOptions } from '../utils/rest'; -import { withDispatch, withSelect } from '@wordpress/data'; -import { compose } from '@wordpress/compose'; +import { useDispatch, useSelect } from '@wordpress/data'; import { useState, useEffect } from '@wordpress/element'; import Deal from './Deal'; -import Container from '../Layout/Container'; -const App = ({ setSettings, toast, currentTab, setTab }) => { +const App = () => { const [loading, setLoading] = useState(true); + + const { setSettings, setTab } = useDispatch('neve-dashboard'); + + const { toast, currentTab } = useSelect((select) => { + const { getToast, getTab } = select('neve-dashboard'); + return { + toast: getToast(), + currentTab: getTab(), + }; + }); + useEffect(() => { fetchOptions().then((r) => { setSettings(r); @@ -24,7 +34,6 @@ const App = ({ setSettings, toast, currentTab, setTab }) => { if (loading) { return ; } - return (
@@ -32,13 +41,13 @@ const App = ({ setSettings, toast, currentTab, setTab }) => { {/**/} {'starter-sites' !== currentTab && } - +
{'starter-sites' !== currentTab && ( -
+
)} @@ -49,19 +58,4 @@ const App = ({ setSettings, toast, currentTab, setTab }) => { ); }; -export default compose( - withDispatch((dispatch) => { - const { setSettings, setTab } = dispatch('neve-dashboard'); - return { - setSettings: (object) => setSettings(object), - setTab: (tab) => setTab(tab), - }; - }), - withSelect((select) => { - const { getToast, getTab } = select('neve-dashboard'); - return { - toast: getToast(), - currentTab: getTab(), - }; - }) -)(App); +export default App; diff --git a/assets/apps/dashboard/src/Components/Content/Changelog.js b/assets/apps/dashboard/src/Components/Content/Changelog.js index 98548d7cd7..6f9033c725 100644 --- a/assets/apps/dashboard/src/Components/Content/Changelog.js +++ b/assets/apps/dashboard/src/Components/Content/Changelog.js @@ -1,113 +1,170 @@ /* global neveDash */ -import Accordion from '../Accordion'; -import classnames from 'classnames'; - +import cn from 'classnames'; +import { Clock, Crown, Rocket, Bug, Zap, CheckCircle } from 'lucide-react'; import { __ } from '@wordpress/i18n'; -import { Fragment, useState } from '@wordpress/element'; +import { useState } from '@wordpress/element'; + +import Card from '../../Layout/Card'; +import Button from '../Common/Button'; +import Pill from '../Common/Pill'; + +const TAB_CHOICES = { + FREE: 'free', + PRO: 'pro', +}; + +const CHANGE_TYPES = { + features: { + icon: , + label: __('Features', 'neve'), + }, + fixes: { + icon: , + label: __('Bug Fixes', 'neve'), + }, + tweaks: { + icon: , + label: __('Tweaks', 'neve'), + }, +}; + +const TabButton = ({ active, onClick, children }) => { + return ( + + ); +}; + +const ChangelogEntry = ({ data }) => { + const { version, tweaks, fixes, features, date } = data; + + const renderChangeList = (type, items) => { + if (!items?.length) return null; + const { icon, label } = CHANGE_TYPES[type]; + + return ( +
+
+ {icon} + + {label} + +
+
    + {items.map((item, index) => { + return ( +
  • + + +
  • + ); + })} +
+
+ ); + }; + + return ( +
+
+
+
+

+ {__('Version', 'neve')} {version} +

+ + {date} + +
+
+
+ {Object.entries({ features, fixes, tweaks }).map( + ([type, items]) => renderChangeList(type, items) + )} +
+
+
+ ); +}; const Changelog = () => { const { changelog, changelogPro } = neveDash; - const [showForPro, setShowForPro] = useState(false); + + const [shown, setShown] = useState(4); + const [activeTab, setActiveTab] = useState(TAB_CHOICES.FREE); + const changelogData = + activeTab === TAB_CHOICES.FREE ? changelog : changelogPro; return ( -
+
{changelogPro && ( -
- {__('Show changelog for', 'neve')} - { - setShowForPro(false); - }} - > - Neve - - { - setShowForPro(true); - }} + +
+
+ + + {__('Recent Updates', 'neve')} + +
+
+ setActiveTab(TAB_CHOICES.FREE)} + > + {__('Free Version', 'neve')} + + setActiveTab(TAB_CHOICES.PRO)} + > +
+ + {__('Pro Version', 'neve')} +
+
+
+
+
+ )} + + + {changelogData.slice(0, shown).map((entry) => { + const { version, tweaks, fixes, features } = entry; + + if ((!tweaks && !fixes && !features) || !version) { + return null; + } + + return ; + })} + + + {changelogData.length > shown && ( +
+
)} - {(showForPro ? changelogPro : changelog).map((entry, index) => { - const { date, version, tweaks, fixes, features } = entry; - if (!tweaks && !fixes && !features) { - return null; - } - const title = ( - - v{version} -{' '} - {date} - - ); - - return ( - - {features && ( -
-
- - {__('Features', 'neve')} - -
-
    - {features.map((feature, indexFeature) => ( -
  • - ))} -
-
- )} - {fixes && ( -
-
- - {__('Bug Fixes', 'neve')} - -
-
    - {fixes.map((fix, indexFixes) => ( -
  • - ))} -
-
- )} - {tweaks && ( -
-
- - {__('Tweaks', 'neve')} - -
-
    - {tweaks.map((tweak, indexTweak) => ( -
  • - ))} -
-
- )} -
- ); - })}
); }; diff --git a/assets/apps/dashboard/src/Components/Content/Help.js b/assets/apps/dashboard/src/Components/Content/Help.js deleted file mode 100644 index b48d63443d..0000000000 --- a/assets/apps/dashboard/src/Components/Content/Help.js +++ /dev/null @@ -1,134 +0,0 @@ -/* global neveDash */ -import Card from '../Card'; - -import { __ } from '@wordpress/i18n'; -import { Fragment } from '@wordpress/element'; -import { Button, Icon, ExternalLink } from '@wordpress/components'; - -const Help = (props) => { - const { setTab } = props; - - let { docsURL, codexURL, supportURL, whiteLabel, assets } = neveDash; - const { supportCardDescription, docsCardDescription } = neveDash.strings; - - if (whiteLabel && whiteLabel.agencyURL) { - supportURL = whiteLabel.agencyURL; - docsURL = whiteLabel.agencyURL; - } - - return ( - - {!whiteLabel && ( - - - {__('Learn More', 'neve')} - - - )} - - {!whiteLabel && ( - - {__('Go to Neve Codex', 'neve')} - - )} - - {__('Go to docs', 'neve')} - - {!whiteLabel && ( - - )} - - - {!whiteLabel && ( - - - {__('Learn More', 'neve')} - - - )} - - - - - - {!whiteLabel && ( - - - {__('Learn More', 'neve')} - - - )} - {!whiteLabel && ( - - - - )} - - ); -}; - -export default Help; diff --git a/assets/apps/dashboard/src/Components/Content/ModuleGridPlaceholder.js b/assets/apps/dashboard/src/Components/Content/ModuleGridPlaceholder.js index cd7f6aac53..2b53eeca3c 100644 --- a/assets/apps/dashboard/src/Components/Content/ModuleGridPlaceholder.js +++ b/assets/apps/dashboard/src/Components/Content/ModuleGridPlaceholder.js @@ -1,3 +1,4 @@ +/* global neveDash */ import { __ } from '@wordpress/i18n'; import { LucideSettings } from 'lucide-react'; import Card from '../../Layout/Card'; @@ -12,7 +13,7 @@ import Link from '../Common/Link'; const ModuleCardPlaceholder = ({ slug, title, description }) => { const CardIcon = NEVE_MODULE_ICON_MAP[slug] || LucideSettings; - const dummyToggle = ( + const ProBadge = ( {__('Pro', 'neve')} @@ -23,16 +24,18 @@ const ModuleCardPlaceholder = ({ slug, title, description }) => { icon={} title={title} className="bg-white p-6 rounded-lg shadow-sm" - afterTitle={dummyToggle} + afterTitle={ProBadge} > -

{description}

+

+ {description} +

); }; export default () => { return ( -
+

{__('Neve Pro Modules', 'neve')} diff --git a/assets/apps/dashboard/src/Components/Content/Plugins.js b/assets/apps/dashboard/src/Components/Content/Plugins.js deleted file mode 100644 index beab1d0386..0000000000 --- a/assets/apps/dashboard/src/Components/Content/Plugins.js +++ /dev/null @@ -1,27 +0,0 @@ -import PluginCard from '../PluginCard'; - -import { withSelect } from '@wordpress/data'; -import { Fragment } from '@wordpress/element'; - -const Header = ({ plugins }) => { - if (!plugins) { - return null; - } - - return ( - - {Object.keys(plugins).map((slug) => { - return ( - - ); - })} - - ); -}; - -export default withSelect((select) => { - const { getPlugins } = select('neve-dashboard'); - return { - plugins: getPlugins(), - }; -})(Header); diff --git a/assets/apps/dashboard/src/Components/Content/Pro.js b/assets/apps/dashboard/src/Components/Content/Pro.js index 0f2306e1a0..ad358d15fd 100644 --- a/assets/apps/dashboard/src/Components/Content/Pro.js +++ b/assets/apps/dashboard/src/Components/Content/Pro.js @@ -1,16 +1,13 @@ /* global neveDash */ +import { CircleFadingArrowUp } from 'lucide-react'; +import Notice from '../Common/Notice'; import ModuleCard from '../ModuleCard'; - const Pro = () => { const { modules, hasOldPro, strings } = neveDash; - if (true) { + if (hasOldPro) { return ( -
-
-

{strings.updateOldPro}

-
-
+ {strings.updateOldPro} ); } diff --git a/assets/apps/dashboard/src/Components/Content/StarterSitesUnavailable.js b/assets/apps/dashboard/src/Components/Content/StarterSitesUnavailable.js index ce5b05d2a2..9c2fd2bf20 100644 --- a/assets/apps/dashboard/src/Components/Content/StarterSitesUnavailable.js +++ b/assets/apps/dashboard/src/Components/Content/StarterSitesUnavailable.js @@ -2,16 +2,24 @@ import InstallActivate from '../Plugin/InstallActivate'; import { withSelect } from '@wordpress/data'; import Card from '../../Layout/Card'; +import Container from '../../Layout/Container'; +import { __ } from '@wordpress/i18n'; const BackgroundPlaceholder = () => (
+ className="block absolute overflow-hidden inset-0 bg-cover opacity-15 mx-auto max-w-[1300px]" + style={ + { + // height: 'calc: (100vh -100px)', + } + } + > + {__('Starter +
); const StarterSitesUnavailable = ({ templatesPluginData }) => { @@ -32,7 +40,7 @@ const StarterSitesUnavailable = ({ templatesPluginData }) => { }; return ( -
+ @@ -53,7 +61,7 @@ const StarterSitesUnavailable = ({ templatesPluginData }) => { }} /> -
+ ); }; diff --git a/assets/apps/dashboard/src/Components/Content/Welcome.js b/assets/apps/dashboard/src/Components/Content/Welcome.js index 4cadf55887..3b55aadcae 100644 --- a/assets/apps/dashboard/src/Components/Content/Welcome.js +++ b/assets/apps/dashboard/src/Components/Content/Welcome.js @@ -1,10 +1,11 @@ +/* global neveDash */ import { __ } from '@wordpress/i18n'; import { LucidePanelsTopLeft } from 'lucide-react'; import Card from '../../Layout/Card'; +import { NEVE_HAS_PRO, NEVE_HAS_VALID_PRO } from '../../utils/constants'; import Link from '../Common/Link'; -import ModuleGridPlaceholder from './ModuleGridPlaceholder'; import ModuleGrid from './ModuleGrid'; -import { NEVE_HAS_PRO, NEVE_HAS_VALID_PRO } from '../../utils/constants'; +import ModuleGridPlaceholder from './ModuleGridPlaceholder'; export default () => { return ( diff --git a/assets/apps/dashboard/src/Components/Header.js b/assets/apps/dashboard/src/Components/Header.js index 7c782a0fe0..025bca5ec8 100644 --- a/assets/apps/dashboard/src/Components/Header.js +++ b/assets/apps/dashboard/src/Components/Header.js @@ -37,7 +37,7 @@ const HeaderTopBar = ({ currentTab, setTab }) => {
{!NEVE_IS_WHITELABEL && ( {__('Neve diff --git a/assets/apps/dashboard/src/Components/LicenseCard.js b/assets/apps/dashboard/src/Components/LicenseCard.js index b9fb3af493..8819d2ed0a 100644 --- a/assets/apps/dashboard/src/Components/LicenseCard.js +++ b/assets/apps/dashboard/src/Components/LicenseCard.js @@ -80,6 +80,7 @@ const LicenseCard = () => {
{!whiteLabel && licenseCardDescription && (

{ - return ( -

-
-
-
-

- v2.6.2 - {!neveDash.whiteLabel && ( -
- )} +const Loading = () => ( +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
- {neveDash.notifications && ( -
- {Object.keys(neveDash.notifications).map( - (notification, index) => { - return ( -
- ); - } - )} +
+

+ +
+
+
+
+
+
+
- )} -
-
-
-
-

-

-
-

-

-

-

-
-
-
-

-

-
-
-
- -
- -
- -
- -
-
- -
- -
- -
- +
+ +
+
+
+
+
+ {[...Array(8)].map((_, i) => ( +
+
+
+
+
+
+
-
-
-
-
-

-

-
-

-

-
+ ))}
-
- {!neveDash.whiteLabel && ( - - )} + ))} +
- ); -}; +
+); export default Loading; diff --git a/assets/apps/dashboard/src/Components/ModuleCard.js b/assets/apps/dashboard/src/Components/ModuleCard.js index a39dda77d1..8db6cdc2a2 100644 --- a/assets/apps/dashboard/src/Components/ModuleCard.js +++ b/assets/apps/dashboard/src/Components/ModuleCard.js @@ -229,7 +229,7 @@ const ModuleCard = ({

{description + ' '} - {documentation.url && ( + {documentation?.url && ( {__('Learn More', 'neve')} @@ -244,7 +244,7 @@ const ModuleCard = ({ ))}

)} - {0 < options.length && + {0 < options?.length && true === getModuleStatus(slug) && -1 < tier && (
diff --git a/assets/apps/dashboard/src/Components/Plugin/InstallActivate.js b/assets/apps/dashboard/src/Components/Plugin/InstallActivate.js index 92bf801052..680c83b9d2 100644 --- a/assets/apps/dashboard/src/Components/Plugin/InstallActivate.js +++ b/assets/apps/dashboard/src/Components/Plugin/InstallActivate.js @@ -1,7 +1,6 @@ /* global neveDash */ import { useEffect, useState } from '@wordpress/element'; import { __, sprintf } from '@wordpress/i18n'; -import { LucideLoaderCircle } from 'lucide-react'; import { get } from '../../utils/rest'; import Button from '../Common/Button'; import Notice from '../Common/Notice'; diff --git a/assets/apps/dashboard/src/Components/PluginsCard.js b/assets/apps/dashboard/src/Components/PluginsCard.js index b0faf4784a..9192aa9528 100644 --- a/assets/apps/dashboard/src/Components/PluginsCard.js +++ b/assets/apps/dashboard/src/Components/PluginsCard.js @@ -1,12 +1,129 @@ +/* global neveDash */ +import usePluginActions from '../Hooks/usePluginActions'; +import Card from '../Layout/Card'; +import { NEVE_HIDE_PLUGINS, NEVE_PLUGIN_ICON_MAP } from '../utils/constants'; + +import { useSelect } from '@wordpress/data'; +import { useState } from '@wordpress/element'; import { __ } from '@wordpress/i18n'; +import cn from 'classnames'; +import { LoaderCircle, LucidePuzzle } from 'lucide-react'; +import Pill from './Common/Pill'; +import TransitionInOut from './Common/TransitionInOut'; +import Toast from './Toast'; -import { NEVE_HIDE_PLUGINS } from '../utils/constants'; -import Card from '../Layout/Card'; +const PluginCard = ({ slug, data }) => { + const ICON = NEVE_PLUGIN_ICON_MAP[slug] || LucidePuzzle; + + const [error, setError] = useState(null); + const [success, setSuccess] = useState(false); + + const { title, description } = data; + + const { doPluginAction, loading, buttonText } = usePluginActions( + slug, + true + ); + + const isPluginActive = useSelect((select) => { + const { getPlugins } = select('neve-dashboard'); + + const plugins = getPlugins(); + + return plugins[slug].cta === 'deactivate'; + }); + + if (isPluginActive && !success) { + return null; + } + + const handleClick = async () => { + setError(null); + + const result = await doPluginAction(); + + if (result.success) { + setSuccess(true); + + return; + } + + if (!result.success) { + setError(result.error); + } + }; + + return ( +
+
+
+ +

+ {title} +

+ + {!success && ( + + )} + {success && ( +
+ + + {__('Active', 'neve')} + + +
+ )} +
+

+ {description} +

+ + {error && ( +
+ +
+ )} +
+
+ ); +}; + +const PluginsCard = () => { + const { plugins } = neveDash; -const PluginsCard = () => {}; + if (NEVE_HIDE_PLUGINS || plugins.length < 1) { + return null; + } -const PluginCard = ({ slug, title, icon }) => { - ; + return ( + + {Object.entries(plugins).map(([slug, args]) => ( + + ))} + + ); }; export default PluginsCard; diff --git a/assets/apps/dashboard/src/Components/SupportCard.js b/assets/apps/dashboard/src/Components/SupportCard.js index 6f451a1d1f..75eb890b0e 100644 --- a/assets/apps/dashboard/src/Components/SupportCard.js +++ b/assets/apps/dashboard/src/Components/SupportCard.js @@ -1,7 +1,6 @@ -import { useSelect, withSelect } from '@wordpress/data'; -import { Button } from '@wordpress/components'; -import Link from './Common/Link'; +import { useSelect } from '@wordpress/data'; import { NEVE_STORE } from '../utils/constants'; +import Link from './Common/Link'; const SupportCard = () => { const { license } = useSelect((select) => { diff --git a/assets/apps/dashboard/src/Hooks/usePluginActions.js b/assets/apps/dashboard/src/Hooks/usePluginActions.js new file mode 100644 index 0000000000..551547b5c2 --- /dev/null +++ b/assets/apps/dashboard/src/Hooks/usePluginActions.js @@ -0,0 +1,191 @@ +/* global neveDash */ + +import { useState } from '@wordpress/element'; +import { useDispatch } from '@wordpress/data'; +import { __ } from '@wordpress/i18n'; +import { useEffect } from 'react'; + +const usePluginActions = (slug, activateAfterInstall = false) => { + const buttonLabelsMap = { + install: { + static: activateAfterInstall + ? __('Install & Activate', 'neve') + : __('Install', 'neve'), + loading: __('Installing', 'neve') + '...', + }, + activate: { + static: __('Activate', 'neve'), + loading: __('Activating', 'neve') + '...', + }, + deactivate: { + static: __('Deactivate', 'neve'), + loading: __('Deactivating', 'neve') + '...', + }, + }; + + const activateURL = neveDash.plugins[slug].activate; + const deactivateURL = neveDash.plugins[slug].deactivate; + + const [loading, setLoading] = useState(false); + const [currentCTA, setCurrentCTA] = useState(neveDash.plugins[slug].cta); + const [buttonText, setButtonText] = useState( + buttonLabelsMap[currentCTA].static + ); + + const { setPluginState } = useDispatch('neve-dashboard'); + + useEffect(() => { + setButtonText( + loading + ? buttonLabelsMap[currentCTA].loading + : buttonLabelsMap[currentCTA].static + ); + }, [loading, currentCTA]); + + const installPlugin = () => { + return new Promise((resolve) => { + wp.updates.ajax('install-plugin', { + slug, + success: () => resolve({ success: true }), + error: (error) => + resolve({ + success: false, + error: + error.errorMessage || + __('Could not install plugin.', 'neve'), + }), + }); + }); + }; + + const activatePlugin = async () => { + try { + const response = await window.fetch(activateURL, { + headers: { + 'X-WP-Nonce': neveDash.nonce, + }, + }); + + if (!response.ok) { + return { + success: false, + error: __('Could not activate plugin.', 'neve'), + }; + } + + return { success: true }; + } catch (error) { + return { + success: false, + error: + error.message || __('Could not activate plugin.', 'neve'), + }; + } + }; + + const deactivatePlugin = async () => { + try { + const response = await window.fetch(deactivateURL, { + headers: { + 'X-WP-Nonce': neveDash.nonce, + }, + }); + + if (!response.ok) { + return { + success: false, + error: __('Could not deactivate plugin.', 'neve'), + }; + } + + return { success: true }; + } catch (error) { + return { success: false, error: error.message }; + } + }; + + /** + * Do plugin action. + * + * @param {'activate'|'deactivate'|'install'|null} type - Action to perform. If null, it will use the currentCTA. + * @return {Promise<{success: boolean, error: Error}>} - Result of the action. + */ + const doPluginAction = async (type = null) => { + return await handlePluginAction(type || currentCTA); + }; + + /** + * Handle plugin action. + * + * @param {'activate'|'deactivate'|'install'} action - Action to perform. + * + * @return {Promise<{success: boolean, error: Error}>} - Result of the action (). + * + */ + const handlePluginAction = async (action) => { + setLoading(true); + + try { + let result; + + switch (action) { + case 'install': + setLoading(true); + result = await installPlugin(); + if (result.success) { + setPluginState(slug, 'activate'); + setCurrentCTA('activate'); + + if (activateAfterInstall) { + return await handlePluginAction('activate'); + } + } + break; + + case 'activate': + result = await activatePlugin(); + if (result.success) { + setPluginState(slug, 'deactivate'); + setCurrentCTA('deactivate'); + + if (slug === 'templates-patterns-collection') { + window.location.href = + neveDash.tpcAdminURL + + (neveDash.canInstallPlugins + ? '&onboarding=yes' + : ''); + } + } + break; + + case 'deactivate': + result = await deactivatePlugin(slug); + + if (result.success) { + setPluginState(slug, 'activate'); + setCurrentCTA('activate'); + } + + break; + + default: + result = { + success: false, + error: __('Invalid action', 'neve'), + }; + } + + return result; + } finally { + setLoading(false); + } + }; + + return { + loading, + buttonText, + doPluginAction, + }; +}; + +export default usePluginActions; diff --git a/assets/apps/dashboard/src/style.css b/assets/apps/dashboard/src/style.css index 7b6162d3a0..eacf688872 100644 --- a/assets/apps/dashboard/src/style.css +++ b/assets/apps/dashboard/src/style.css @@ -5,7 +5,7 @@ @tailwind utilities; #wpcontent { - @apply pl-0 font-sans; + @apply pl-0 font-sans min-h-[100vh]; } #wpbody-content > .notice, #wpbody-content > .error { diff --git a/assets/apps/dashboard/src/utils/common.js b/assets/apps/dashboard/src/utils/common.js index 62656a4c98..3732eb75b0 100644 --- a/assets/apps/dashboard/src/utils/common.js +++ b/assets/apps/dashboard/src/utils/common.js @@ -4,11 +4,11 @@ import compareVersions from 'compare-versions'; import StarterSitesUnavailable from '../Components/Content/StarterSitesUnavailable'; import Welcome from '../Components/Content/Welcome'; import Pro from '../Components/Content/Pro'; -import Plugins from '../Components/Content/Plugins'; -import Help from '../Components/Content/Help'; + import Changelog from '../Components/Content/Changelog'; import FreePro from '../Components/Content/FreePro'; import { __ } from '@wordpress/i18n'; +import { NEVE_HAS_VALID_PRO } from './constants'; const addUrlHash = (hash) => { window.location.hash = hash; @@ -43,9 +43,9 @@ const tabs = { label: __('Free vs Pro', 'neve'), render: () => , }, - plugins: { - label: __('Plugins', 'neve'), - render: () => , + settings: { + label: __('Settings', 'neve'), + render: () => , }, changelog: { render: () => , @@ -64,7 +64,7 @@ const properTPC = ) === 1; if (activeTPC && properTPC) { - delete tabs['starter-sites']['render']; + delete tabs['starter-sites'].render; tabs['starter-sites'].url = neveDash.tpcAdminURL; } @@ -78,8 +78,8 @@ if ( } if (neveDash.pro || neveDash.hasOldPro) { - tabs.pro = { - label: neveDash.strings.proTabTitle, + tabs.settings = { + label: __('Settings', 'neve'), render: () => , }; delete tabs['free-pro']; diff --git a/assets/apps/dashboard/src/utils/constants.js b/assets/apps/dashboard/src/utils/constants.js index 102a46abba..2bdfe80d34 100644 --- a/assets/apps/dashboard/src/utils/constants.js +++ b/assets/apps/dashboard/src/utils/constants.js @@ -16,6 +16,12 @@ import { LucideShield, LucideShoppingCart, LucideTypeOutline, + LucideToyBrick, + LucideLayoutTemplate, + LucideCreditCard, + LucideImage, + LucideTimer, + LucideRss, } from 'lucide-react'; export const NEVE_STORE = 'neve-dashboard'; @@ -47,3 +53,14 @@ export const NEVE_MODULE_ICON_MAP = { custom_sidebars: LucidePanelRightDashed, access_restriction: LucideShield, }; + +export const NEVE_PLUGIN_ICON_MAP = { + 'otter-blocks': LucideToyBrick, + 'templates-patterns-collection': LucideLayoutTemplate, + 'wp-full-stripe-free': LucideCreditCard, + 'optimole-wp': LucideImage, + 'wp-cloudflare-page-cache': LucideTimer, + 'feedzy-rss-feeds': LucideRss, + // 'hyve' + // 'sparks' +}; diff --git a/inc/admin/dashboard/main.php b/inc/admin/dashboard/main.php index b9efc5fb84..e54fdf73af 100755 --- a/inc/admin/dashboard/main.php +++ b/inc/admin/dashboard/main.php @@ -59,14 +59,14 @@ class Main { * * @var string */ - private $plugins_cache_key = 'neve_dash_useful_plugins'; + private $plugins_cache_key = 'neve_dash_useful_plugins_v2'; /** * Plugins Cache Hash key. * * @var string */ - private $plugins_cache_hash_key = 'neve_dash_useful_plugins_hash'; + private $plugins_cache_hash_key = 'neve_dash_useful_plugins_hash_v2'; /** * Main constructor. @@ -334,7 +334,7 @@ private function get_localization() { 'notifications' => $this->get_notifications(), 'customizerShortcuts' => $this->get_customizer_shortcuts(), 'plugins' => $this->get_useful_plugins(), - 'recommended_plugins' => $this->get_recommended_plugins(), + 'plugins' => $this->get_recommended_plugins(), 'modules' => $this->get_modules(), 'featureData' => $this->get_free_pro_features(), 'showFeedbackNotice' => $this->should_show_feedback_notice(), @@ -697,15 +697,64 @@ private function get_modules() { */ private function get_recommended_plugins() { $plugins = [ - 'otter-blocks' => 'wp', - 'templates-patterns-collection' => 'wp', - 'optimole-wp' => 'wp', - 'wp-cloudflare-page-cache' => 'wp', - 'feedzy-rss-feeds' => 'wp', - 'hyve' => 'ti', - 'sparks' => 'ti', + 'otter-blocks' => [ + 'title' => __( 'Otter Blocks', 'neve' ), + 'description' => __( 'Advanced blocks for modern WordPress editing', 'neve' ), + ], + 'templates-patterns-collection' => [ + 'title' => __( 'Starter Sites', 'neve' ), + 'description' => __( 'Import ready-made websites with a single click', 'neve' ), + ], + 'wp-full-stripe-free' => [ + 'title' => __( 'WP Full Pay', 'neve' ), + 'description' => __( 'Simple ecommerce solution with Stripe integration', 'neve' ), + ], + 'optimole-wp' => [ + 'title' => __( 'Optimole', 'neve' ), + 'description' => __( 'Smart image optimization and CDN', 'neve' ), + ], + 'wp-cloudflare-page-cache' => [ + 'title' => __( 'Super Page Cache', 'neve' ), + 'description' => __( 'Lightning-fast caching made simple', 'neve' ), + ], + 'feedzy-rss-feeds' => [ + 'title' => __( 'Feedzy', 'neve' ), + 'description' => __( 'RSS feeds aggregator and content curator', 'neve' ), + ], + // External ones. + // 'hyve' => [ + // 'title' => __('Hyve', 'neve'), + // 'description' => __('AI chatbot for your website', 'neve') + // ], + // 'sparks' => [ + // 'title' => __('Sparks', 'neve'), + // 'description' => __('WooCommerce enhancements', 'neve') + // ], ]; + foreach ( $plugins as $slug => $args ) { + + $action = $this->plugin_helper->get_plugin_state( $slug ); + + if ( $action === 'deactivate' ) { + unset( $plugins[ $slug ] ); + + continue; + } + + $plugins[ $slug ] = array_merge( + [ + 'cta' => $action, + 'path' => $this->plugin_helper->get_plugin_path( $slug ), + 'activate' => $this->plugin_helper->get_plugin_action_link( $slug ), + 'deactivate' => $this->plugin_helper->get_plugin_action_link( $slug, 'deactivate' ), + 'network' => $this->plugin_helper->get_is_network_wide( $slug ), + 'version' => $this->plugin_helper->get_plugin_version( $slug, '0.0.0' ), + ], + $args + ); + } + return $plugins; } diff --git a/inc/admin/dashboard/plugin_helper.php b/inc/admin/dashboard/plugin_helper.php index 6a142415e8..58d50abc18 100644 --- a/inc/admin/dashboard/plugin_helper.php +++ b/inc/admin/dashboard/plugin_helper.php @@ -54,6 +54,8 @@ public function get_plugin_path( $slug ) { return $slug . '/feedzy-rss-feed.php'; case 'wp-cloudflare-page-cache': return $slug . '/wp-cloudflare-super-page-cache.php'; + case 'wp-full-stripe-free': + return $slug . '/wp-full-stripe.php'; default: return $slug . '/' . $slug . '.php'; }