From cf3c8f0827eb0a7394620d6cf90c70f7a9b086ef Mon Sep 17 00:00:00 2001 From: abaicus Date: Tue, 17 Dec 2024 12:49:03 +0200 Subject: [PATCH 01/24] feat: rename dashboard main page [ref Codeinwp/neve-pro-addon#2914] --- inc/admin/dashboard/main.php | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/inc/admin/dashboard/main.php b/inc/admin/dashboard/main.php index ccd65b8f8f..1fc2e96df0 100755 --- a/inc/admin/dashboard/main.php +++ b/inc/admin/dashboard/main.php @@ -208,10 +208,8 @@ public function register() { // Add Dashboard submenu. Same slug as parent to allow renaming the automatic submenu that is added. add_submenu_page( // phpcs:ignore WPThemeReview.PluginTerritory.NoAddAdminPages.add_menu_pages_add_submenu_page $theme_page, - /* translators: %s - Theme name */ - sprintf( __( '%s Options', 'neve' ), wp_kses_post( $theme['name'] ) ), - /* translators: %s - Theme name */ - sprintf( __( '%s Options', 'neve' ), wp_kses_post( $theme['name'] ) ), + __( 'Dashboard', 'neve' ), + __( 'Dashboard', 'neve' ), $capability, $theme_page, [ $this, 'render' ] From edbfe05e3573a51d483cbf0e7cedc0bcd73da671 Mon Sep 17 00:00:00 2001 From: abaicus Date: Tue, 17 Dec 2024 12:52:40 +0200 Subject: [PATCH 02/24] chore: configure tailwindcss [ref Codeinwp/neve-pro-addon#2914] --- assets/apps/dashboard/src/dashboard.js | 2 +- assets/apps/dashboard/src/style.css | 5 + assets/apps/dashboard/src/tailwind.config.js | 9 + package.json | 1 + postcss.config.js | 3 + yarn.lock | 484 +++++++++++++++++-- 6 files changed, 476 insertions(+), 28 deletions(-) create mode 100644 assets/apps/dashboard/src/style.css create mode 100644 assets/apps/dashboard/src/tailwind.config.js create mode 100644 postcss.config.js diff --git a/assets/apps/dashboard/src/dashboard.js b/assets/apps/dashboard/src/dashboard.js index b60ba8dead..6cacefe98c 100644 --- a/assets/apps/dashboard/src/dashboard.js +++ b/assets/apps/dashboard/src/dashboard.js @@ -1,7 +1,7 @@ import { registerStore } from '@wordpress/data'; import { render } from '@wordpress/element'; -import './style.scss'; +import './style.css'; import App from './Components/App'; import actions from './store/actions'; diff --git a/assets/apps/dashboard/src/style.css b/assets/apps/dashboard/src/style.css new file mode 100644 index 0000000000..ce6ee37347 --- /dev/null +++ b/assets/apps/dashboard/src/style.css @@ -0,0 +1,5 @@ +@config './tailwind.config.js'; + +@tailwind base; +@tailwind components; +@tailwind utilities; diff --git a/assets/apps/dashboard/src/tailwind.config.js b/assets/apps/dashboard/src/tailwind.config.js new file mode 100644 index 0000000000..12ab71cc41 --- /dev/null +++ b/assets/apps/dashboard/src/tailwind.config.js @@ -0,0 +1,9 @@ +/** @type {import('tailwindcss').Config} */ +module.exports = { + content: ['assets/apps/dashboard/src/**/*.js'], + theme: { + extend: { + colors: {}, + }, + }, +}; diff --git a/package.json b/package.json index 029eadae98..aa08ea6d4a 100644 --- a/package.json +++ b/package.json @@ -149,6 +149,7 @@ "simple-git-hooks": "^2.2.0", "size-limit": "4.6.0", "style-loader": "^2.0.0", + "tailwindcss": "^3.4.16", "ts-loader": "^8.0.18", "typescript": "^4.1.5", "url-slug": "^2.3.2", diff --git a/postcss.config.js b/postcss.config.js new file mode 100644 index 0000000000..b2cd363091 --- /dev/null +++ b/postcss.config.js @@ -0,0 +1,3 @@ +module.exports = { + plugins: [require('tailwindcss')], +}; diff --git a/yarn.lock b/yarn.lock index 337b3b7e7a..3e2998bc0f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2,6 +2,11 @@ # yarn lockfile v1 +"@alloc/quick-lru@^5.2.0": + version "5.2.0" + resolved "https://registry.yarnpkg.com/@alloc/quick-lru/-/quick-lru-5.2.0.tgz#7bf68b20c0a350f936915fcae06f58e32007ce30" + integrity sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw== + "@babel/code-frame@7.10.4": version "7.10.4" resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.10.4.tgz#168da1a36e90da68ae8d49c0f1b48c7c6249213a" @@ -2224,6 +2229,18 @@ resolved "https://registry.yarnpkg.com/@icons/material/-/material-0.2.4.tgz#e90c9f71768b3736e76d7dd6783fc6c2afa88bc8" integrity sha512-QPcGmICAPbGLGb6F/yNf/KzKqvFx8z5qx3D1yFqVAjoFmXK35EgyW+cJ57Te3CNsmzblwtzakLGFqHPqrfb4Tw== +"@isaacs/cliui@^8.0.2": + version "8.0.2" + resolved "https://registry.yarnpkg.com/@isaacs/cliui/-/cliui-8.0.2.tgz#b37667b7bc181c168782259bab42474fbf52b550" + integrity sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA== + dependencies: + string-width "^5.1.2" + string-width-cjs "npm:string-width@^4.2.0" + strip-ansi "^7.0.1" + strip-ansi-cjs "npm:strip-ansi@^6.0.1" + wrap-ansi "^8.1.0" + wrap-ansi-cjs "npm:wrap-ansi@^7.0.0" + "@istanbuljs/load-nyc-config@^1.0.0": version "1.1.0" resolved "https://registry.yarnpkg.com/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz#fd3db1d59ecf7cf121e80650bb86712f9b55eced" @@ -2411,6 +2428,38 @@ "@types/yargs" "^15.0.0" chalk "^4.0.0" +"@jridgewell/gen-mapping@^0.3.2": + version "0.3.8" + resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz#4f0e06362e01362f823d348f1872b08f666d8142" + integrity sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA== + dependencies: + "@jridgewell/set-array" "^1.2.1" + "@jridgewell/sourcemap-codec" "^1.4.10" + "@jridgewell/trace-mapping" "^0.3.24" + +"@jridgewell/resolve-uri@^3.1.0": + version "3.1.2" + resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz#7a0ee601f60f99a20c7c7c5ff0c80388c1189bd6" + integrity sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw== + +"@jridgewell/set-array@^1.2.1": + version "1.2.1" + resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.2.1.tgz#558fb6472ed16a4c850b889530e6b36438c49280" + integrity sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A== + +"@jridgewell/sourcemap-codec@^1.4.10", "@jridgewell/sourcemap-codec@^1.4.14": + version "1.5.0" + resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz#3188bcb273a414b0d215fd22a58540b989b9409a" + integrity sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ== + +"@jridgewell/trace-mapping@^0.3.24": + version "0.3.25" + resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz#15f190e98895f3fc23276ee14bc76b675c2e50f0" + integrity sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ== + dependencies: + "@jridgewell/resolve-uri" "^3.1.0" + "@jridgewell/sourcemap-codec" "^1.4.14" + "@mdx-js/loader@^1.6.19": version "1.6.22" resolved "https://registry.yarnpkg.com/@mdx-js/loader/-/loader-1.6.22.tgz#d9e8fe7f8185ff13c9c8639c048b123e30d322c4" @@ -2627,6 +2676,11 @@ dependencies: "@octokit/openapi-types" "^5.3.2" +"@pkgjs/parseargs@^0.11.0": + version "0.11.0" + resolved "https://registry.yarnpkg.com/@pkgjs/parseargs/-/parseargs-0.11.0.tgz#a77ea742fab25775145434eb1d2328cf5013ac33" + integrity sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg== + "@playwright/test@^1.32.3": version "1.32.3" resolved "https://registry.yarnpkg.com/@playwright/test/-/test-1.32.3.tgz#75be8346d4ef289896835e1d2a86fdbe3d9be92a" @@ -5910,6 +5964,11 @@ ansi-regex@^5.0.0, ansi-regex@^5.0.1: resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== +ansi-regex@^6.0.1: + version "6.1.0" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-6.1.0.tgz#95ec409c69619d6cb1b8b34f14b660ef28ebd654" + integrity sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA== + ansi-styles@^2.2.1: version "2.2.1" resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-2.2.1.tgz#b432dd3358b634cf75e1e4664368240533c1ddbe" @@ -5929,6 +5988,11 @@ ansi-styles@^4.0.0, ansi-styles@^4.1.0: dependencies: color-convert "^2.0.1" +ansi-styles@^6.1.0: + version "6.2.1" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-6.2.1.tgz#0e62320cf99c21afff3b3012192546aacbfb05c5" + integrity sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug== + ansi-to-html@^0.6.11: version "0.6.14" resolved "https://registry.yarnpkg.com/ansi-to-html/-/ansi-to-html-0.6.14.tgz#65fe6d08bba5dd9db33f44a20aec331e0010dad8" @@ -5946,6 +6010,11 @@ ansistyles@~0.1.3: resolved "https://registry.yarnpkg.com/ansistyles/-/ansistyles-0.1.3.tgz#5de60415bda071bb37127854c864f41b23254539" integrity sha1-XeYEFb2gcbs3EnhUyGT0GyMlRTk= +any-promise@^1.0.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/any-promise/-/any-promise-1.3.0.tgz#abc6afeedcea52e809cdc0376aed3ce39635d17f" + integrity sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A== + anymatch@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-2.0.0.tgz#bcb24b4f37934d9aa7ac17b4adaf89e7c76ef2eb" @@ -6006,6 +6075,11 @@ are-we-there-yet@~1.1.2: delegates "^1.0.0" readable-stream "^2.0.6" +arg@^5.0.2: + version "5.0.2" + resolved "https://registry.yarnpkg.com/arg/-/arg-5.0.2.tgz#c81433cc427c92c4dcf4865142dbca6f15acd59c" + integrity sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg== + argparse@^1.0.2, argparse@^1.0.7: version "1.0.10" resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" @@ -6981,6 +7055,13 @@ braces@^3.0.1, braces@~3.0.2: dependencies: fill-range "^7.0.1" +braces@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.3.tgz#490332f40919452272d55a8480adc0c441358789" + integrity sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA== + dependencies: + fill-range "^7.1.1" + brcast@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/brcast/-/brcast-2.0.2.tgz#2db16de44140e418dc37fab10beec0369e78dcef" @@ -7325,7 +7406,7 @@ camel-case@^4.1.1: pascal-case "^3.1.2" tslib "^2.0.3" -camelcase-css@2.0.1: +camelcase-css@2.0.1, camelcase-css@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/camelcase-css/-/camelcase-css-2.0.1.tgz#ee978f6947914cc30c6b44741b6ed1df7f043fd5" integrity sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA== @@ -7563,6 +7644,21 @@ chokidar@^3.4.1: optionalDependencies: fsevents "~2.3.1" +chokidar@^3.6.0: + version "3.6.0" + resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.6.0.tgz#197c6cc669ef2a8dc5e7b4d97ee4e092c3eb0d5b" + integrity sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw== + dependencies: + anymatch "~3.1.2" + braces "~3.0.2" + glob-parent "~5.1.2" + is-binary-path "~2.1.0" + is-glob "~4.0.1" + normalize-path "~3.0.0" + readdirp "~3.6.0" + optionalDependencies: + fsevents "~2.3.2" + chownr@^1.1.1, chownr@^1.1.2, chownr@^1.1.4: version "1.1.4" resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.4.tgz#6fc9d7b42d32a583596337666e7d08084da2cc6b" @@ -7961,7 +8057,7 @@ commander@^3.0.2: resolved "https://registry.yarnpkg.com/commander/-/commander-3.0.2.tgz#6837c3fb677ad9933d1cfba42dd14d5117d6b39e" integrity sha512-Gar0ASD4BDyKC4hl4DwHqDrmvjoxWKZigVnAbn5H1owvm4CxCPdb0HQDehwNYMJpla5+M2tPmPARzhtYuwpHow== -commander@^4.1.1: +commander@^4.0.0, commander@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/commander/-/commander-4.1.1.tgz#9fd602bd936294e9e9ef46a3f4d6964044b18068" integrity sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA== @@ -9096,6 +9192,11 @@ did-you-mean@^0.0.1: levenshtein "*" underscore "*" +didyoumean@^1.2.2: + version "1.2.2" + resolved "https://registry.yarnpkg.com/didyoumean/-/didyoumean-1.2.2.tgz#989346ffe9e839b4555ecf5666edea0d3e8ad037" + integrity sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw== + diff-sequences@^26.6.2: version "26.6.2" resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-26.6.2.tgz#48ba99157de1923412eed41db6b6d4aa9ca7c0b1" @@ -9139,6 +9240,11 @@ discontinuous-range@1.0.0: resolved "https://registry.yarnpkg.com/discontinuous-range/-/discontinuous-range-1.0.0.tgz#e38331f0844bba49b9a9cb71c771585aab1bc65a" integrity sha1-44Mx8IRLukm5qctxx3FYWqsbxlo= +dlv@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/dlv/-/dlv-1.1.3.tgz#5c198a8a11453596e751494d49874bc7732f2e79" + integrity sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA== + doctrine@1.5.0: version "1.5.0" resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-1.5.0.tgz#379dce730f6166f76cefa4e6707a159b02c5a6fa" @@ -9405,6 +9511,11 @@ eachr@^3.2.0: editions "^2.2.0" typechecker "^4.9.0" +eastasianwidth@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/eastasianwidth/-/eastasianwidth-0.2.0.tgz#696ce2ec0aa0e6ea93a397ffcf24aa7840c827cb" + integrity sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA== + ecc-jsbn@~0.1.1: version "0.1.2" resolved "https://registry.yarnpkg.com/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz#3a83a904e54353287874c564b7549386849a98c9" @@ -9493,7 +9604,7 @@ emoji-regex@^8.0.0: resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== -emoji-regex@^9.0.0: +emoji-regex@^9.0.0, emoji-regex@^9.2.2: version "9.2.2" resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-9.2.2.tgz#840c8803b0d8047f4ff0cf963176b32d4ef3ed72" integrity sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg== @@ -10521,6 +10632,17 @@ fast-glob@^3.2.5: micromatch "^4.0.2" picomatch "^2.2.1" +fast-glob@^3.3.2: + version "3.3.2" + resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.3.2.tgz#a904501e57cfdd2ffcded45e99a54fef55e46129" + integrity sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow== + dependencies: + "@nodelib/fs.stat" "^2.0.2" + "@nodelib/fs.walk" "^1.2.3" + glob-parent "^5.1.2" + merge2 "^1.3.0" + micromatch "^4.0.4" + fast-json-parse@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/fast-json-parse/-/fast-json-parse-1.0.3.tgz#43e5c61ee4efa9265633046b770fb682a7577c4d" @@ -10685,6 +10807,13 @@ fill-range@^7.0.1: dependencies: to-regex-range "^5.0.1" +fill-range@^7.1.1: + version "7.1.1" + resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.1.1.tgz#44265d3cac07e3ea7dc247516380643754a05292" + integrity sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg== + dependencies: + to-regex-range "^5.0.1" + filter-obj@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/filter-obj/-/filter-obj-1.1.0.tgz#9b311112bc6c6127a16e016c6c5d7f19e0805c5b" @@ -10885,6 +11014,14 @@ for-own@^1.0.0: dependencies: for-in "^1.0.1" +foreground-child@^3.1.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/foreground-child/-/foreground-child-3.3.0.tgz#0ac8644c06e431439f8561db8ecf29a7b5519c77" + integrity sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg== + dependencies: + cross-spawn "^7.0.0" + signal-exit "^4.0.1" + forever-agent@~0.6.1: version "0.6.1" resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91" @@ -11071,6 +11208,11 @@ function-bind@^1.1.1: resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== +function-bind@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.2.tgz#2c02d864d97f3ea6c8830c464cbd11ab6eab7a1c" + integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA== + function.prototype.name@^1.1.0: version "1.1.4" resolved "https://registry.yarnpkg.com/function.prototype.name/-/function.prototype.name-1.1.4.tgz#e4ea839b9d3672ae99d0efd9f38d9191c5eaac83" @@ -11335,6 +11477,13 @@ glob-parent@^5.0.0, glob-parent@^5.1.0, glob-parent@^5.1.2, glob-parent@~5.1.0, dependencies: is-glob "^4.0.1" +glob-parent@^6.0.2: + version "6.0.2" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-6.0.2.tgz#6d237d99083950c79290f24c7642a3de9a28f9e3" + integrity sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A== + dependencies: + is-glob "^4.0.3" + glob-promise@^3.4.0: version "3.4.0" resolved "https://registry.yarnpkg.com/glob-promise/-/glob-promise-3.4.0.tgz#b6b8f084504216f702dc2ce8c9bc9ac8866fdb20" @@ -11352,6 +11501,18 @@ glob-to-regexp@^0.4.1: resolved "https://registry.yarnpkg.com/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz#c75297087c851b9a578bd217dd59a92f59fe546e" integrity sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw== +glob@^10.3.10: + version "10.4.5" + resolved "https://registry.yarnpkg.com/glob/-/glob-10.4.5.tgz#f4d9f0b90ffdbab09c9d77f5f29b4262517b0956" + integrity sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg== + dependencies: + foreground-child "^3.1.0" + jackspeak "^3.1.2" + minimatch "^9.0.4" + minipass "^7.1.2" + package-json-from-dist "^1.0.0" + path-scurry "^1.11.1" + glob@^7.0.0, glob@^7.0.3, glob@^7.0.5, glob@^7.1.1, glob@^7.1.2, glob@^7.1.4, glob@~7.1.1, glob@~7.1.6: version "7.1.6" resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6" @@ -11927,6 +12088,13 @@ hash.js@^1.0.0, hash.js@^1.0.3: inherits "^2.0.3" minimalistic-assert "^1.0.1" +hasown@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.2.tgz#003eaf91be7adc372e84ec59dc37252cedb80003" + integrity sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ== + dependencies: + function-bind "^1.1.2" + hast-to-hyperscript@^9.0.0: version "9.0.1" resolved "https://registry.yarnpkg.com/hast-to-hyperscript/-/hast-to-hyperscript-9.0.1.tgz#9b67fd188e4c81e8ad66f803855334173920218d" @@ -12709,6 +12877,13 @@ is-color-stop@^1.0.0: rgb-regex "^1.0.1" rgba-regex "^1.0.0" +is-core-module@^2.16.0: + version "2.16.0" + resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.16.0.tgz#6c01ffdd5e33c49c1d2abfa93334a85cb56bd81c" + integrity sha512-urTSINYfAYgcbLb0yDQ6egFm6h3Mo1DcF9EkyXSRjjzdHbsulg01qhwWuXdOoUBuTkbQ80KDboXa0vFJ+BDH+g== + dependencies: + hasown "^2.0.2" + is-core-module@^2.2.0, is-core-module@^2.7.0: version "2.7.0" resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.7.0.tgz#3c0ef7d31b4acfc574f80c58409d568a836848e3" @@ -13289,6 +13464,15 @@ iterate-value@^1.0.2: es-get-iterator "^1.0.2" iterate-iterator "^1.0.1" +jackspeak@^3.1.2: + version "3.4.3" + resolved "https://registry.yarnpkg.com/jackspeak/-/jackspeak-3.4.3.tgz#8833a9d89ab4acde6188942bd1c53b6390ed5a8a" + integrity sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw== + dependencies: + "@isaacs/cliui" "^8.0.2" + optionalDependencies: + "@pkgjs/parseargs" "^0.11.0" + jake@^10.6.1: version "10.8.2" resolved "https://registry.yarnpkg.com/jake/-/jake-10.8.2.tgz#ebc9de8558160a66d82d0eadc6a2e58fbc500a7b" @@ -13739,6 +13923,11 @@ jit-grunt@~0.10.0: resolved "https://registry.yarnpkg.com/jit-grunt/-/jit-grunt-0.10.0.tgz#008c3a7fe1e96bd0d84e260ea1fa1783457f79c2" integrity sha1-AIw6f+Hpa9DYTiYOofoXg0V/ecI= +jiti@^1.21.6: + version "1.21.6" + resolved "https://registry.yarnpkg.com/jiti/-/jiti-1.21.6.tgz#6c7f7398dd4b3142767f9a168af2f317a428d268" + integrity sha512-2yTgeWTWzMWkHu6Jp9NKgePDaYHbntiwvYuuJLbbN9vl7DC9DvXKOB2BC3ZZ92D3cvV/aflH0osDfwpHepQ53w== + joi@^17.3.0: version "17.4.2" resolved "https://registry.yarnpkg.com/joi/-/joi-17.4.2.tgz#02f4eb5cf88e515e614830239379dcbbe28ce7f7" @@ -14251,6 +14440,11 @@ lilconfig@^2.0.3: resolved "https://registry.yarnpkg.com/lilconfig/-/lilconfig-2.0.3.tgz#68f3005e921dafbd2a2afb48379986aa6d2579fd" integrity sha512-EHKqr/+ZvdKCifpNrJCKxBTgk5XupZA3y/aCPY9mxfgBzmgh93Mt/WqjjQ38oMxXuvDokaKiM3lAgvSH2sjtHg== +lilconfig@^3.0.0, lilconfig@^3.1.3: + version "3.1.3" + resolved "https://registry.yarnpkg.com/lilconfig/-/lilconfig-3.1.3.tgz#a1bcfd6257f9585bf5ae14ceeebb7b559025e4c4" + integrity sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw== + line-height@^0.3.1: version "0.3.1" resolved "https://registry.yarnpkg.com/line-height/-/line-height-0.3.1.tgz#4b1205edde182872a5efa3c8f620b3187a9c54c9" @@ -14634,6 +14828,11 @@ lowlight@^1.14.0: fault "^1.0.0" highlight.js "~10.7.0" +lru-cache@^10.2.0: + version "10.4.3" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-10.4.3.tgz#410fc8a17b70e598013df257c2446b7f3383f119" + integrity sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ== + lru-cache@^4.0.1: version "4.1.5" resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-4.1.5.tgz#8bbe50ea85bed59bc9e33dcab8235ee9bcf443cd" @@ -15277,6 +15476,14 @@ micromatch@^4.0.0, micromatch@^4.0.2, micromatch@^4.0.4: braces "^3.0.1" picomatch "^2.2.3" +micromatch@^4.0.8: + version "4.0.8" + resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.8.tgz#d66fa18f3a47076789320b9b1af32bd86d9fa202" + integrity sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA== + dependencies: + braces "^3.0.3" + picomatch "^2.3.1" + micromodal@^0.4.6: version "0.4.6" resolved "https://registry.yarnpkg.com/micromodal/-/micromodal-0.4.6.tgz#0425ad026c47923208cf826de6b58ed0693cb25a" @@ -15365,6 +15572,13 @@ minimatch@^5.0.1: dependencies: brace-expansion "^2.0.1" +minimatch@^9.0.4: + version "9.0.5" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-9.0.5.tgz#d74f9dd6b57d83d8e98cfb82133b03978bc929e5" + integrity sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow== + dependencies: + brace-expansion "^2.0.1" + minimist-options@4.1.0, minimist-options@^4.0.2: version "4.1.0" resolved "https://registry.yarnpkg.com/minimist-options/-/minimist-options-4.1.0.tgz#c0655713c53a8a2ebd77ffa247d342c40f010619" @@ -15456,6 +15670,11 @@ minipass@^5.0.0: resolved "https://registry.yarnpkg.com/minipass/-/minipass-5.0.0.tgz#3e9788ffb90b694a5d0ec94479a45b5d8738133d" integrity sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ== +"minipass@^5.0.0 || ^6.0.2 || ^7.0.0", minipass@^7.1.2: + version "7.1.2" + resolved "https://registry.yarnpkg.com/minipass/-/minipass-7.1.2.tgz#93a9626ce5e5e66bd4db86849e7515e92340a707" + integrity sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw== + minizlib@^1.2.1: version "1.3.3" resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-1.3.3.tgz#2290de96818a34c29551c8a8d301216bd65a861d" @@ -15594,6 +15813,15 @@ mute-stream@0.0.8, mute-stream@~0.0.4: resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.8.tgz#1630c42b2251ff81e2a283de96a5497ea92e5e0d" integrity sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA== +mz@^2.7.0: + version "2.7.0" + resolved "https://registry.yarnpkg.com/mz/-/mz-2.7.0.tgz#95008057a56cafadc2bc63dde7f9ff6955948e32" + integrity sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q== + dependencies: + any-promise "^1.0.0" + object-assign "^4.0.1" + thenify-all "^1.0.0" + nan@^2.12.1: version "2.14.2" resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.2.tgz#f5376400695168f4cc694ac9393d0c9585eeea19" @@ -15614,6 +15842,11 @@ nanoid@^3.1.10, nanoid@^3.1.28, nanoid@^3.1.30: resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.1.30.tgz#63f93cc548d2a113dc5dfbc63bfa09e2b9b64362" integrity sha512-zJpuPDwOv8D2zq2WRoMe1HsfZthVewpel9CAvTfc/2mBD1uUT/agc5f7GHGWXlYkFvi1mVxe4IjvP2HNrop7nQ== +nanoid@^3.3.7: + version "3.3.8" + resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.8.tgz#b1be3030bee36aaff18bacb375e5cce521684baf" + integrity sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w== + nanomatch@^1.2.9: version "1.2.13" resolved "https://registry.yarnpkg.com/nanomatch/-/nanomatch-1.2.13.tgz#b87a8aa4fc0de8fe6be88895b38983ff265bd119" @@ -16294,6 +16527,11 @@ object-filter@^1.0.2: resolved "https://registry.yarnpkg.com/object-filter/-/object-filter-1.0.2.tgz#af0b797ffebeaf8a52c6637cedbe8816cfec1bc8" integrity sha1-rwt5f/6+r4pSxmN87b6IFs/sG8g= +object-hash@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/object-hash/-/object-hash-3.0.0.tgz#73f97f753e7baffc0e2cc9d6e079079744ac82e9" + integrity sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw== + object-inspect@^1.11.0, object-inspect@^1.9.0: version "1.11.0" resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.11.0.tgz#9dceb146cedd4148a0d9e51ab88d34cf509922b1" @@ -16675,6 +16913,11 @@ p-try@^2.0.0: resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== +package-json-from-dist@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz#4f1471a010827a86f94cfd9b0727e36d267de505" + integrity sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw== + package-json@^4.0.0: version "4.0.1" resolved "https://registry.yarnpkg.com/package-json/-/package-json-4.0.1.tgz#8869a0401253661c4c4ca3da6c2121ed555f5eed" @@ -16894,7 +17137,7 @@ path-key@^3.0.0, path-key@^3.1.0: resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== -path-parse@^1.0.6: +path-parse@^1.0.6, path-parse@^1.0.7: version "1.0.7" resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== @@ -16911,6 +17154,14 @@ path-root@^0.1.1: dependencies: path-root-regex "^0.1.0" +path-scurry@^1.11.1: + version "1.11.1" + resolved "https://registry.yarnpkg.com/path-scurry/-/path-scurry-1.11.1.tgz#7960a668888594a0720b12a911d1a742ab9f11d2" + integrity sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA== + dependencies: + lru-cache "^10.2.0" + minipass "^5.0.0 || ^6.0.2 || ^7.0.0" + path-to-regexp@0.1.7: version "0.1.7" resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c" @@ -16975,12 +17226,17 @@ picocolors@^1.0.0: resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c" integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ== +picocolors@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.1.1.tgz#3d321af3eab939b083c8f929a1d12cda81c26b6b" + integrity sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA== + picomatch@^2.0.4, picomatch@^2.2.2: version "2.2.2" resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.2.2.tgz#21f333e9b6b8eaff02468f5146ea406d345f4dad" integrity sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg== -picomatch@^2.0.5: +picomatch@^2.0.5, picomatch@^2.3.1: version "2.3.1" resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== @@ -16995,7 +17251,7 @@ pidtree@^0.3.0: resolved "https://registry.yarnpkg.com/pidtree/-/pidtree-0.3.1.tgz#ef09ac2cc0533df1f3250ccf2c4d366b0d12114a" integrity sha512-qQbW94hLHEqCg7nhby4yRC7G2+jYHY4Rguc2bjw7Uug4GIJuu1tvf2uHaZv5Q8zdt+WKJ6qK1FOI6amaWUo5FA== -pify@^2.0.0: +pify@^2.0.0, pify@^2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c" integrity sha1-7RQaasBDqEnqWISY59yosVMw6Qw= @@ -17263,6 +17519,22 @@ postcss-html@^0.36.0: dependencies: htmlparser2 "^3.10.0" +postcss-import@^15.1.0: + version "15.1.0" + resolved "https://registry.yarnpkg.com/postcss-import/-/postcss-import-15.1.0.tgz#41c64ed8cc0e23735a9698b3249ffdbf704adc70" + integrity sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew== + dependencies: + postcss-value-parser "^4.0.0" + read-cache "^1.0.0" + resolve "^1.1.7" + +postcss-js@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/postcss-js/-/postcss-js-4.0.1.tgz#61598186f3703bab052f1c4f7d805f3991bee9d2" + integrity sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw== + dependencies: + camelcase-css "^2.0.1" + postcss-less@^3.1.4: version "3.1.4" resolved "https://registry.yarnpkg.com/postcss-less/-/postcss-less-3.1.4.tgz#369f58642b5928ef898ffbc1a6e93c958304c5ad" @@ -17278,6 +17550,14 @@ postcss-load-config@^2.0.0: cosmiconfig "^5.0.0" import-cwd "^2.0.0" +postcss-load-config@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/postcss-load-config/-/postcss-load-config-4.0.2.tgz#7159dcf626118d33e299f485d6afe4aff7c4a3e3" + integrity sha512-bSVhyJGL00wMVoPUzAVAnbEoWyqRxkjv64tUl427SKnPrENtq6hJwUojroMz2VB+Q1edmi4IfrAPpami5VVgMQ== + dependencies: + lilconfig "^3.0.0" + yaml "^2.3.4" + postcss-loader@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/postcss-loader/-/postcss-loader-3.0.0.tgz#6b97943e47c72d845fa9e03f273773d4e8dd6c2d" @@ -17480,6 +17760,13 @@ postcss-modules-values@^4.0.0: dependencies: icss-utils "^5.0.0" +postcss-nested@^6.2.0: + version "6.2.0" + resolved "https://registry.yarnpkg.com/postcss-nested/-/postcss-nested-6.2.0.tgz#4c2d22ab5f20b9cb61e2c5c5915950784d068131" + integrity sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ== + dependencies: + postcss-selector-parser "^6.1.1" + postcss-normalize-charset@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/postcss-normalize-charset/-/postcss-normalize-charset-4.0.1.tgz#8b35add3aee83a136b0471e0d59be58a50285dd4" @@ -17735,6 +18022,14 @@ postcss-selector-parser@^6.0.2, postcss-selector-parser@^6.0.4, postcss-selector cssesc "^3.0.0" util-deprecate "^1.0.2" +postcss-selector-parser@^6.1.1, postcss-selector-parser@^6.1.2: + version "6.1.2" + resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz#27ecb41fb0e3b6ba7a1ec84fff347f734c7929de" + integrity sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg== + dependencies: + cssesc "^3.0.0" + util-deprecate "^1.0.2" + postcss-svgo@^4.0.3: version "4.0.3" resolved "https://registry.yarnpkg.com/postcss-svgo/-/postcss-svgo-4.0.3.tgz#343a2cdbac9505d416243d496f724f38894c941e" @@ -17780,6 +18075,11 @@ postcss-value-parser@^3.0.0: resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz#9ff822547e2893213cf1c30efa51ac5fd1ba8281" integrity sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ== +postcss-value-parser@^4.0.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz#723c09920836ba6d3e5af019f92bc0971c02e514" + integrity sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ== + postcss-value-parser@^4.0.2, postcss-value-parser@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.1.0.tgz#443f6a20ced6481a2bda4fa8532a6e55d789a2cb" @@ -17820,6 +18120,15 @@ postcss@^8.2.15: picocolors "^0.2.1" source-map-js "^0.6.2" +postcss@^8.4.47: + version "8.4.49" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.49.tgz#4ea479048ab059ab3ae61d082190fabfd994fe19" + integrity sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA== + dependencies: + nanoid "^3.3.7" + picocolors "^1.1.1" + source-map-js "^1.2.1" + prelude-ls@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" @@ -18698,6 +19007,13 @@ reactcss@^1.2.0: dependencies: lodash "^4.0.1" +read-cache@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/read-cache/-/read-cache-1.0.0.tgz#e664ef31161166c9751cdbe8dbcf86b5fb58f774" + integrity sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA== + dependencies: + pify "^2.3.0" + read-cmd-shim@^1.0.1, read-cmd-shim@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/read-cmd-shim/-/read-cmd-shim-1.0.5.tgz#87e43eba50098ba5a32d0ceb583ab8e43b961c16" @@ -19358,6 +19674,15 @@ resolve@^1.1.6, resolve@^1.1.7, resolve@^1.10.0, resolve@^1.11.0, resolve@^1.12. is-core-module "^2.2.0" path-parse "^1.0.6" +resolve@^1.22.8: + version "1.22.9" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.9.tgz#6da76e4cdc57181fa4471231400e8851d0a924f3" + integrity sha512-QxrmX1DzraFIi9PxdG5VkRfRwIgjwyud+z/iBwfRRrVmHc+P9Q7u2lSSpQ6bjr2gy5lrqIiU9vb6iAeGf2400A== + dependencies: + is-core-module "^2.16.0" + path-parse "^1.0.7" + supports-preserve-symlinks-flag "^1.0.0" + restore-cursor@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-3.1.0.tgz#39f67c54b3a7a58cea5236d95cf0034239631f7e" @@ -19976,6 +20301,11 @@ signal-exit@^3.0.7: resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== +signal-exit@^4.0.1: + version "4.1.0" + resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-4.1.0.tgz#952188c1cbd546070e2dd20d0f41c0ae0530cb04" + integrity sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw== + signale@^1.2.1: version "1.4.0" resolved "https://registry.yarnpkg.com/signale/-/signale-1.4.0.tgz#c4be58302fb0262ac00fc3d886a7c113759042f1" @@ -20186,6 +20516,11 @@ source-map-js@^0.6.2: resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-0.6.2.tgz#0bb5de631b41cfbda6cfba8bd05a80efdfd2385e" integrity sha512-/3GptzWzu0+0MBQFrDKzw/DvvMTUORvgY6k6jd/VS6iCR4RDTKWH6v6WPwQoUO8667uQEf9Oe38DxAYWY5F/Ug== +source-map-js@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.2.1.tgz#1ce5650fddd87abc099eda37dcff024c2667ae46" + integrity sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA== + source-map-loader@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/source-map-loader/-/source-map-loader-3.0.0.tgz#f2a04ee2808ad01c774dea6b7d2639839f3b3049" @@ -20500,6 +20835,15 @@ string-template@~0.2.1: resolved "https://registry.yarnpkg.com/string-template/-/string-template-0.2.1.tgz#42932e598a352d01fc22ec3367d9d84eec6c9add" integrity sha1-QpMuWYo1LQH8IuwzZ9nYTuxsmt0= +"string-width-cjs@npm:string-width@^4.2.0", "string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.2.0, string-width@^4.2.3: + version "4.2.3" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" + integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.1" + string-width@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/string-width/-/string-width-1.0.2.tgz#118bdf5b8cdc51a2a7e70d211e07e2b0b9b107d3" @@ -20517,15 +20861,6 @@ string-width@^1.0.1: is-fullwidth-code-point "^2.0.0" strip-ansi "^4.0.0" -"string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.2.0, string-width@^4.2.3: - version "4.2.3" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" - integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== - dependencies: - emoji-regex "^8.0.0" - is-fullwidth-code-point "^3.0.0" - strip-ansi "^6.0.1" - string-width@^3.0.0, string-width@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/string-width/-/string-width-3.1.0.tgz#22767be21b62af1081574306f69ac51b62203961" @@ -20544,6 +20879,15 @@ string-width@^4.0.0, string-width@^4.1.0, string-width@^4.2.2: is-fullwidth-code-point "^3.0.0" strip-ansi "^6.0.0" +string-width@^5.0.1, string-width@^5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-5.1.2.tgz#14f8daec6d81e7221d2a357e668cab73bdbca794" + integrity sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA== + dependencies: + eastasianwidth "^0.2.0" + emoji-regex "^9.2.2" + strip-ansi "^7.0.1" + "string.prototype.matchall@^4.0.0 || ^3.0.1", string.prototype.matchall@^4.0.2: version "4.0.4" resolved "https://registry.yarnpkg.com/string.prototype.matchall/-/string.prototype.matchall-4.0.4.tgz#608f255e93e072107f5de066f81a2dfb78cf6b29" @@ -20633,6 +20977,13 @@ stringify-package@^1.0.0, stringify-package@^1.0.1: resolved "https://registry.yarnpkg.com/stringify-package/-/stringify-package-1.0.1.tgz#e5aa3643e7f74d0f28628b72f3dad5cecfc3ba85" integrity sha512-sa4DUQsYciMP1xhKWGuFM04fB0LG/9DlluZoSVywUMRNvzid6XucHK0/90xGxRoHrAaROrcHK1aPKaijCtSrhg== +"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + dependencies: + ansi-regex "^5.0.1" + strip-ansi@6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.0.tgz#0b1571dd7669ccd4f3e06e14ef1eed26225ae532" @@ -20661,12 +21012,12 @@ strip-ansi@^5.0.0, strip-ansi@^5.1.0, strip-ansi@^5.2.0: dependencies: ansi-regex "^4.1.0" -strip-ansi@^6.0.0, strip-ansi@^6.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" - integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== +strip-ansi@^7.0.1: + version "7.1.0" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.1.0.tgz#d5b6568ca689d8561370b0707685d22434faff45" + integrity sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ== dependencies: - ansi-regex "^5.0.1" + ansi-regex "^6.0.1" strip-bom@^2.0.0: version "2.0.0" @@ -20854,6 +21205,19 @@ stylis@^4.0.3: resolved "https://registry.yarnpkg.com/stylis/-/stylis-4.0.10.tgz#446512d1097197ab3f02fb3c258358c3f7a14240" integrity sha512-m3k+dk7QeJw660eIKRRn3xPF6uuvHs/FFzjX3HQ5ove0qYsiygoAhwn5a3IYKaZPo5LrYD0rfVmtv1gNY1uYwg== +sucrase@^3.35.0: + version "3.35.0" + resolved "https://registry.yarnpkg.com/sucrase/-/sucrase-3.35.0.tgz#57f17a3d7e19b36d8995f06679d121be914ae263" + integrity sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA== + dependencies: + "@jridgewell/gen-mapping" "^0.3.2" + commander "^4.0.0" + glob "^10.3.10" + lines-and-columns "^1.1.6" + mz "^2.7.0" + pirates "^4.0.1" + ts-interface-checker "^0.1.9" + sugarss@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/sugarss/-/sugarss-2.0.0.tgz#ddd76e0124b297d40bf3cca31c8b22ecb43bc61d" @@ -20902,6 +21266,11 @@ supports-hyperlinks@^2.0.0, supports-hyperlinks@^2.1.0: has-flag "^4.0.0" supports-color "^7.0.0" +supports-preserve-symlinks-flag@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" + integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== + svg-parser@^2.0.2: version "2.0.4" resolved "https://registry.yarnpkg.com/svg-parser/-/svg-parser-2.0.4.tgz#fdc2e29e13951736140b76cb122c8ee6630eb6b5" @@ -20981,6 +21350,34 @@ table@^6.0.7: slice-ansi "^4.0.0" string-width "^4.2.0" +tailwindcss@^3.4.16: + version "3.4.16" + resolved "https://registry.yarnpkg.com/tailwindcss/-/tailwindcss-3.4.16.tgz#35a7c3030844d6000fc271878db4096b6a8d2ec9" + integrity sha512-TI4Cyx7gDiZ6r44ewaJmt0o6BrMCT5aK5e0rmJ/G9Xq3w7CX/5VXl/zIPEJZFUK5VEqwByyhqNPycPlvcK4ZNw== + dependencies: + "@alloc/quick-lru" "^5.2.0" + arg "^5.0.2" + chokidar "^3.6.0" + didyoumean "^1.2.2" + dlv "^1.1.3" + fast-glob "^3.3.2" + glob-parent "^6.0.2" + is-glob "^4.0.3" + jiti "^1.21.6" + lilconfig "^3.1.3" + micromatch "^4.0.8" + normalize-path "^3.0.0" + object-hash "^3.0.0" + picocolors "^1.1.1" + postcss "^8.4.47" + postcss-import "^15.1.0" + postcss-js "^4.0.1" + postcss-load-config "^4.0.2" + postcss-nested "^6.2.0" + postcss-selector-parser "^6.1.2" + resolve "^1.22.8" + sucrase "^3.35.0" + tannin@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/tannin/-/tannin-1.2.0.tgz#1da6fe65280dca4c3d84efb075b077b1b94362a6" @@ -21195,6 +21592,20 @@ text-table@0.2.0, text-table@^0.2.0, text-table@~0.2.0: resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" integrity sha1-f17oI66AUgfACvLfSoTsP8+lcLQ= +thenify-all@^1.0.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/thenify-all/-/thenify-all-1.6.0.tgz#1a1918d402d8fc3f98fbf234db0bcc8cc10e9726" + integrity sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA== + dependencies: + thenify ">= 3.1.0 < 4" + +"thenify@>= 3.1.0 < 4": + version "3.3.1" + resolved "https://registry.yarnpkg.com/thenify/-/thenify-3.3.1.tgz#8932e686a4066038a016dd9e2ca46add9838a95f" + integrity sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw== + dependencies: + any-promise "^1.0.0" + throat@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/throat/-/throat-5.0.0.tgz#c5199235803aad18754a667d659b5e72ce16764b" @@ -21469,6 +21880,11 @@ ts-essentials@^2.0.3: resolved "https://registry.yarnpkg.com/ts-essentials/-/ts-essentials-2.0.12.tgz#c9303f3d74f75fa7528c3d49b80e089ab09d8745" integrity sha512-3IVX4nI6B5cc31/GFFE+i8ey/N2eA0CZDbo6n0yrz0zDX8ZJ8djmU1p+XRz7G3is0F3bB3pu2pAroFdAWQKU3w== +ts-interface-checker@^0.1.9: + version "0.1.13" + resolved "https://registry.yarnpkg.com/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz#784fd3d679722bc103b1b4b8030bcddb5db2a699" + integrity sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA== + ts-loader@^8.0.18: version "8.3.0" resolved "https://registry.yarnpkg.com/ts-loader/-/ts-loader-8.3.0.tgz#83360496d6f8004fab35825279132c93412edf33" @@ -22621,6 +23037,15 @@ worker-rpc@^0.1.0: dependencies: microevent.ts "~0.1.1" +"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" + integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + wrap-ansi@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-2.1.0.tgz#d8fc3d284dd05794fe84973caecdd1cf824fdd85" @@ -22647,14 +23072,14 @@ wrap-ansi@^6.2.0: string-width "^4.1.0" strip-ansi "^6.0.0" -wrap-ansi@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" - integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== +wrap-ansi@^8.1.0: + version "8.1.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214" + integrity sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ== dependencies: - ansi-styles "^4.0.0" - string-width "^4.1.0" - strip-ansi "^6.0.0" + ansi-styles "^6.1.0" + string-width "^5.0.1" + strip-ansi "^7.0.1" wrappy@1: version "1.0.2" @@ -22757,6 +23182,11 @@ yaml@^1.10.0, yaml@^1.10.2, yaml@^1.7.2: resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.2.tgz#2301c5ffbf12b467de8da2333a459e29e7920e4b" integrity sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg== +yaml@^2.3.4: + version "2.6.1" + resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.6.1.tgz#42f2b1ba89203f374609572d5349fb8686500773" + integrity sha512-7r0XPzioN/Q9kXBro/XPnA6kznR73DHq+GXh5ON7ZozRO6aMjbmiBuKste2wslTFkC5d1dw0GooOCepZXJ2SAg== + yargs-parser@^15.0.1: version "15.0.3" resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-15.0.3.tgz#316e263d5febe8b38eef61ac092b33dfcc9b1115" From c0146092042830f460825aeb079e20e87ed7fa3d Mon Sep 17 00:00:00 2001 From: abaicus Date: Thu, 19 Dec 2024 15:08:31 +0200 Subject: [PATCH 03/24] feat: new backend dashboard [wip][ref Codeinwp/neve-pro-addon#2914] --- assets/apps/dashboard/src/Components/App.js | 38 +-- .../dashboard/src/Components/Common/Button.js | 77 +++++++ .../dashboard/src/Components/Common/Link.js | 36 +++ .../dashboard/src/Components/Common/Notice.js | 79 +++++++ .../Components/{ => Common}/Notification.js | 140 +++++++----- .../dashboard/src/Components/Common/Pill.js | 23 ++ .../dashboard/src/Components/Common/Toggle.js | 28 +++ .../src/Components/Common/Tooltip.js | 20 ++ .../src/Components/Common/TransitionInOut.js | 14 ++ .../Content/CustomLayoutsUnavailable.js | 85 ------- .../src/Components/Content/FreePro.js | 192 +++++++++++++--- .../src/Components/Content/ModuleGrid.js | 6 + .../Content/ModuleGridPlaceholder.js | 59 +++++ .../dashboard/src/Components/Content/Pro.js | 2 +- .../dashboard/src/Components/Content/Start.js | 178 --------------- .../Content/StarterSitesUnavailable.js | 68 +++--- .../src/Components/Content/Welcome.js | 33 +++ .../dashboard/src/Components/FeatureRow.js | 122 ---------- .../apps/dashboard/src/Components/Header.js | 216 ++++++++++++++---- .../dashboard/src/Components/LicenseCard.js | 146 ++++++------ .../dashboard/src/Components/Notifications.js | 7 +- .../src/Components/Plugin/InstallActivate.js | 115 +++++----- .../dashboard/src/Components/PluginsCard.js | 12 + .../apps/dashboard/src/Components/Sidebar.js | 202 ++++++++-------- .../apps/dashboard/src/Components/Snackbar.js | 33 ++- .../dashboard/src/Components/SupportCard.js | 44 ++-- .../dashboard/src/Components/TabsContent.js | 4 +- assets/apps/dashboard/src/Components/Toast.js | 74 ++++-- assets/apps/dashboard/src/Layout/Card.js | 36 +++ assets/apps/dashboard/src/Layout/Container.js | 12 + assets/apps/dashboard/src/dashboard.js | 12 +- assets/apps/dashboard/src/style.css | 28 +++ assets/apps/dashboard/src/tailwind.config.js | 4 +- assets/apps/dashboard/src/utils/common.js | 18 +- assets/apps/dashboard/src/utils/constants.js | 49 ++++ assets/img/dashboard/logo.svg | 8 +- inc/admin/dashboard/main.php | 208 +++++++++++------ package.json | 4 +- yarn.lock | 145 ++++++++++++ 39 files changed, 1610 insertions(+), 967 deletions(-) create mode 100644 assets/apps/dashboard/src/Components/Common/Button.js create mode 100644 assets/apps/dashboard/src/Components/Common/Link.js create mode 100644 assets/apps/dashboard/src/Components/Common/Notice.js rename assets/apps/dashboard/src/Components/{ => Common}/Notification.js (60%) create mode 100644 assets/apps/dashboard/src/Components/Common/Pill.js create mode 100644 assets/apps/dashboard/src/Components/Common/Toggle.js create mode 100644 assets/apps/dashboard/src/Components/Common/Tooltip.js create mode 100644 assets/apps/dashboard/src/Components/Common/TransitionInOut.js delete mode 100644 assets/apps/dashboard/src/Components/Content/CustomLayoutsUnavailable.js create mode 100644 assets/apps/dashboard/src/Components/Content/ModuleGrid.js create mode 100644 assets/apps/dashboard/src/Components/Content/ModuleGridPlaceholder.js delete mode 100644 assets/apps/dashboard/src/Components/Content/Start.js create mode 100644 assets/apps/dashboard/src/Components/Content/Welcome.js delete mode 100644 assets/apps/dashboard/src/Components/FeatureRow.js create mode 100644 assets/apps/dashboard/src/Components/PluginsCard.js create mode 100644 assets/apps/dashboard/src/Layout/Card.js create mode 100644 assets/apps/dashboard/src/Layout/Container.js create mode 100644 assets/apps/dashboard/src/utils/constants.js diff --git a/assets/apps/dashboard/src/Components/App.js b/assets/apps/dashboard/src/Components/App.js index 1e356e1da9..1d0e62670d 100644 --- a/assets/apps/dashboard/src/Components/App.js +++ b/assets/apps/dashboard/src/Components/App.js @@ -8,8 +8,9 @@ import { fetchOptions } from '../utils/rest'; import { withDispatch, withSelect } from '@wordpress/data'; import { compose } from '@wordpress/compose'; -import { useState, Fragment, useEffect } from '@wordpress/element'; +import { useState, useEffect } from '@wordpress/element'; import Deal from './Deal'; +import Container from '../Layout/Container'; const App = ({ setSettings, toast, currentTab, setTab }) => { const [loading, setLoading] = useState(true); @@ -19,27 +20,32 @@ const App = ({ setSettings, toast, currentTab, setTab }) => { setLoading(false); }); }, []); + if (loading) { return ; } + return ( - -
-
-
-
- - {'starter-sites' !== currentTab && } - -
- {'starter-sites' !== currentTab && - 'custom-layouts' !== currentTab && ( - - )} +
+
+ + {/**/} + {'starter-sites' !== currentTab && } + + +
+
-
+ + {'starter-sites' !== currentTab && ( +
+ +
+ )} + + {toast && } - +
); }; diff --git a/assets/apps/dashboard/src/Components/Common/Button.js b/assets/apps/dashboard/src/Components/Common/Button.js new file mode 100644 index 0000000000..cd06b52110 --- /dev/null +++ b/assets/apps/dashboard/src/Components/Common/Button.js @@ -0,0 +1,77 @@ +import cn from 'classnames'; +import { LoaderCircle } from 'lucide-react'; +import PropTypes from 'prop-types'; + +const Button = (props) => { + const { + href, + onClick, + className, + isSubmit, + isPrimary, + isSecondary, + isLink, + children, + disabled, + loading, + } = props; + + const classNames = cn([ + 'flex items-center px-3 py-2 transition-colors duration-150 rounded text-sm border gap-2', + { + 'border-transparent bg-blue-600 text-white hover:bg-blue-700 hover:text-white': + isPrimary, + 'border-blue-600 text-blue-600 hover:bg-blue-600 hover:text-white': + isSecondary, + 'border-transparent text-gray-600 hover:text-gray-900': isLink, + 'cursor-not-allowed opacity-50 pointer-events-none': disabled, + }, + className, + ]); + + const passedProps = { + className: classNames, + onClick, + }; + + if (isSubmit) { + passedProps.type = 'submit'; + } + + if (href) { + passedProps.href = href; + } + + if (props.target) { + passedProps.target = props.target; + + if (props.target === '_blank') { + passedProps.rel = 'noopener noreferrer'; + } + } + + const TAG = href && !onClick ? 'a' : 'button'; + + return ( + + {loading && } + + {children} + + ); +}; + +Button.propTypes = { + href: PropTypes.string, + onClick: PropTypes.func, + className: PropTypes.string, + isSubmit: PropTypes.bool, + isPrimary: PropTypes.bool, + isSecondary: PropTypes.bool, + isLink: PropTypes.bool, + children: PropTypes.node, + disabled: PropTypes.bool, + target: PropTypes.oneOf(['_blank', '_self', '_parent', '_top']), +}; + +export default Button; diff --git a/assets/apps/dashboard/src/Components/Common/Link.js b/assets/apps/dashboard/src/Components/Common/Link.js new file mode 100644 index 0000000000..147f59b602 --- /dev/null +++ b/assets/apps/dashboard/src/Components/Common/Link.js @@ -0,0 +1,36 @@ +import { __ } from '@wordpress/i18n'; + +import { LucideExternalLink } from 'lucide-react'; +import cn from 'classnames'; + +export default ({ text, url, isExternal, className }) => { + const linkClasses = cn([ + 'text-blue-600 hover:text-blue-700 hover:underline text-sm', + className, + { + 'inline-flex gap-1.5 items-center': isExternal, + }, + ]); + if (!isExternal) { + return ( + + {text} + + ); + } + + return ( + + {text} + + {__('(opens in a new tab)', 'neve')} + + + + ); +}; diff --git a/assets/apps/dashboard/src/Components/Common/Notice.js b/assets/apps/dashboard/src/Components/Common/Notice.js new file mode 100644 index 0000000000..1c038a78d3 --- /dev/null +++ b/assets/apps/dashboard/src/Components/Common/Notice.js @@ -0,0 +1,79 @@ +import { useEffect } from '@wordpress/element'; +import cn from 'classnames'; +import { + LucideCircleAlert, + LucideCircleCheck, + LucideCircleX, + LucideInfo, +} from 'lucide-react'; + +const Notice = ({ + children, + className = '', + + // Type variants + isSuccess = false, + isWarning = false, + isError = false, + + // Optional custom icon + icon: CustomIcon, + isAutoDismiss: autoDismiss = 0, + onDismiss = () => {}, +}) => { + useEffect(() => { + if (autoDismiss < 1000) return; + + const dismissTimeout = setTimeout(() => { + onDismiss(); + }, autoDismiss); + + return () => { + clearTimeout(dismissTimeout); + }; + }, [autoDismiss, onDismiss]); + + if (!children) return null; + + const getTypeClasses = () => { + if (isSuccess) return 'border-lime-300 bg-lime-50 text-lime-800'; + if (isWarning) return 'border-orange-300 bg-orange-50 text-yellow-800'; + if (isError) return 'border-red-300 bg-red-50 text-red-800'; + return 'border-sky-300 bg-sky-50 text-sky-800'; + }; + + const getIcon = () => { + if (CustomIcon) return CustomIcon; + if (isSuccess) return LucideCircleCheck; + if (isWarning) return LucideCircleAlert; + if (isError) return LucideCircleX; + return LucideInfo; + }; + + const iconColor = { + 'text-lime-500': isSuccess, + 'text-orange-500': isWarning, + 'text-red-500': isError, + 'text-sky-500': !isSuccess && !isWarning && !isError, + }; + + const Icon = getIcon(); + + return ( +
+ + +
+ {children &&
{children}
} +
+
+ ); +}; + +export default Notice; diff --git a/assets/apps/dashboard/src/Components/Notification.js b/assets/apps/dashboard/src/Components/Common/Notification.js similarity index 60% rename from assets/apps/dashboard/src/Components/Notification.js rename to assets/apps/dashboard/src/Components/Common/Notification.js index b8cf8a94b4..27a7a8d28c 100644 --- a/assets/apps/dashboard/src/Components/Notification.js +++ b/assets/apps/dashboard/src/Components/Common/Notification.js @@ -1,26 +1,60 @@ /* global neveDash */ -import classnames from 'classnames'; +import cn from 'classnames'; -import { sprintf, __ } from '@wordpress/i18n'; import { useState } from '@wordpress/element'; -import { external } from '@wordpress/icons'; -import { Button, Dashicon, Icon, Tooltip } from '@wordpress/components'; +import { __, sprintf } from '@wordpress/i18n'; + +import { + LucideCircleCheck, + LucideCircleX, + LucideExternalLink, +} from 'lucide-react'; +import Card from '../../Layout/Card'; +import Button from './Button'; +import Tooltip from './Tooltip'; +import TransitionInOut from './TransitionInOut'; +import { useEffect } from 'react'; const Notification = ({ data, slug }) => { - // eslint-disable-next-line no-unused-vars const [hidden, setHidden] = useState(false); const { text, cta, type, update, url, targetBlank } = data; const { canInstallPlugins } = neveDash; const [inProgress, setInProgress] = useState(false); const [done, setDone] = useState(false); const [errorMessage, setErrorMessage] = useState(null); - const classes = classnames([ - 'notification', - slug, - type && !done ? type : '', + + useEffect(() => { + let timeout; + if (done === 'done') { + timeout = setTimeout(() => { + setHidden(true); + }, 5000); + } + + return () => { + clearTimeout(timeout); + }; + }, [done]); + + const classes = cn([ + 'text-white !p-3 rounded flex flex-col md:flex-row items-center justify-between gap-4', { - 'success hidden': 'done' === done, - error: 'error' === done, + 'bg-blue-500': type === 'info' || (!type && !done), + 'bg-amber-500': 'warning' === type && !done, + 'bg-emerald-500': 'success' === type || 'done' === done, + 'bg-red-500': + ('error' === type && 'done' !== done) || 'error' === done, + }, + ]); + + const buttonClasses = cn([ + 'bg-white border-white hover:opacity-90 text-blue-500 font-medium', + { + '!text-blue-600': type === 'info' || (!type && !done), + '!text-amber-600': 'warning' === type && !done, + '!text-emerald-600': 'success' === type || 'done' === done, + '!text-red-600': + ('error' === type && 'done' !== done) || 'error' === done, }, ]); @@ -119,23 +153,16 @@ const Notification = ({ data, slug }) => { } return ( ); }; @@ -152,10 +179,6 @@ const Notification = ({ data, slug }) => { __('Neve Pro', 'neve') ) )} - position="top center" - style={{ - opacity: 1, - }} > {ctaContent()} @@ -165,17 +188,21 @@ const Notification = ({ data, slug }) => { const UpdateNotification = () => { return ( -
- {!done &&

{text}

} + <> + {!done && ( +

+ {text} +

+ )} {'done' === done && ( -

- +

+ {__('Done!', 'neve')}

)} {'error' === done && ( -

- +

+ {errorMessage || __( 'An error occurred. Please reload the page and try again.', @@ -184,33 +211,38 @@ const Notification = ({ data, slug }) => {

)} {wrappedButtonContent} -
- ); - }; - - const LinkNotification = () => { - return ( -
-

- {url && cta && ( - - )} -

+ ); }; - if (update) { - return ; - } + const LinkNotification = () => ( + <> +

+ {url && cta && ( + + )} + + ); - return ; + return ( + + + {update ? : } + + + ); }; export default Notification; diff --git a/assets/apps/dashboard/src/Components/Common/Pill.js b/assets/apps/dashboard/src/Components/Common/Pill.js new file mode 100644 index 0000000000..d732a7e1fd --- /dev/null +++ b/assets/apps/dashboard/src/Components/Common/Pill.js @@ -0,0 +1,23 @@ +import cn from 'classnames'; + +export default ({ children, type = 'primary', className }) => { + if (!children) { + return null; + } + + const typeClasses = { + primary: 'bg-blue-100 text-blue-700', + secondary: 'bg-gray-100 text-gray-700', + success: 'bg-green-100 text-green-700', + error: 'bg-red-100 text-red-700', + warning: 'bg-yellow-100 text-yellow-700', + }; + + const classes = cn([ + typeClasses[type], + 'px-1.5 py-0.5 text-sm font-medium rounded leading-none uppercase', + className, + ]); + + return {children}; +}; diff --git a/assets/apps/dashboard/src/Components/Common/Toggle.js b/assets/apps/dashboard/src/Components/Common/Toggle.js new file mode 100644 index 0000000000..0a82469904 --- /dev/null +++ b/assets/apps/dashboard/src/Components/Common/Toggle.js @@ -0,0 +1,28 @@ +import { Switch, Label, Field } from '@headlessui/react'; +import cn from 'classnames'; + +export default ({ checked, onToggle, label, disabled = false, className }) => { + const switchClasses = cn( + 'group inline-flex h-6 w-11 items-center rounded-full bg-gray-300 transition data-[checked]:bg-blue-600', + { + 'cursor-not-allowed opacity-50': disabled, + } + ); + + const wrapClasses = cn('flex items-center gap-3', className); + + return ( + + + + + + + + ); +}; diff --git a/assets/apps/dashboard/src/Components/Common/Tooltip.js b/assets/apps/dashboard/src/Components/Common/Tooltip.js new file mode 100644 index 0000000000..0306d820af --- /dev/null +++ b/assets/apps/dashboard/src/Components/Common/Tooltip.js @@ -0,0 +1,20 @@ +import { useState } from '@wordpress/element'; + +export default ({ text, children }) => { + const [isVisible, setIsVisible] = useState(false); + + return ( +

setIsVisible(true)} + onMouseLeave={() => setIsVisible(false)} + > + {children} + {isVisible && ( +
+ {text} +
+ )} +
+ ); +}; diff --git a/assets/apps/dashboard/src/Components/Common/TransitionInOut.js b/assets/apps/dashboard/src/Components/Common/TransitionInOut.js new file mode 100644 index 0000000000..65fdc7906e --- /dev/null +++ b/assets/apps/dashboard/src/Components/Common/TransitionInOut.js @@ -0,0 +1,14 @@ +import { Transition } from '@headlessui/react'; + +export default ({ children, show }) => { + const className = `transition + data-[closed]:transform data-[closed]:-translate-y-2 data-[closed]:opacity-0 + data-[open]:transform data-[open]:translate-y-0 data-[open]:opacity-100 + duration-300 ease-in-out`; + + return ( + +
{children}
+
+ ); +}; diff --git a/assets/apps/dashboard/src/Components/Content/CustomLayoutsUnavailable.js b/assets/apps/dashboard/src/Components/Content/CustomLayoutsUnavailable.js deleted file mode 100644 index 53b4781c28..0000000000 --- a/assets/apps/dashboard/src/Components/Content/CustomLayoutsUnavailable.js +++ /dev/null @@ -1,85 +0,0 @@ -/* global neveDash */ -import { __ } from '@wordpress/i18n'; -import { Button } from '@wordpress/components'; -import { compose } from '@wordpress/compose'; -import { withSelect } from '@wordpress/data'; -import { useEffect } from '@wordpress/element'; - -const CustomLayoutsUnavailable = ({ license, setTab }) => { - const { customLayoutsNeveProURL, assets } = neveDash; - - const hasPro = neveDash.pro || neveDash.hasOldPro; - const secondButtonMessage = hasPro - ? __('Activate', 'neve') - : __('Free vs Pro', 'neve'); - const navigateToFreeVsPro = () => { - setTab('free-pro'); - }; - - const navigateToProActivate = () => { - setTab('pro'); - }; - - useEffect(() => { - if (license && 'valid' === license.valid) { - setTab('pro'); - window.location.href = 'edit.php?post_type=neve_custom_layouts'; - } - }, [license]); - - return ( -
-
-
- Neve -

{__('Custom Layouts', 'neve')}

-

- {__( - 'Get access to all Pro features and power-up your website', - 'neve' - )} -

- - - -
-
- ); -}; - -export default compose( - withSelect((select) => { - const { getLicense } = select('neve-dashboard'); - return { - license: getLicense(), - }; - }) -)(CustomLayoutsUnavailable); diff --git a/assets/apps/dashboard/src/Components/Content/FreePro.js b/assets/apps/dashboard/src/Components/Content/FreePro.js index 828c98a37b..9c629d9623 100644 --- a/assets/apps/dashboard/src/Components/Content/FreePro.js +++ b/assets/apps/dashboard/src/Components/Content/FreePro.js @@ -1,47 +1,173 @@ /* global neveDash */ -import FeatureRow from '../FeatureRow'; import { __ } from '@wordpress/i18n'; -import { Button } from '@wordpress/components'; -const Pro = () => { - const { featureData } = neveDash; +import { + CheckCircle2, + XCircle, + HelpCircle, + ArrowRight, + BookOpen, +} from 'lucide-react'; + +import Card from '../../Layout/Card'; +import Tooltip from '../Common/Tooltip'; +import Button from '../Common/Button'; + +const IntroCard = () => { + const upgradeList = [ + __('Need advanced header/footer customization options', 'neve'), + __( + 'Run an online store and want enhanced WooCommerce features', + 'neve' + ), + __('Build multilingual or RTL websites', 'neve'), + __('Create websites for clients and need white-labeling', 'neve'), + ]; + return ( -
- - - - - - - {featureData.map((item, index) => ( - - ))} - -
- NeveNeve Pro
- -
-

+ +

+

{__( - 'Get access to all Pro features and power-up your website', + "While Neve's free version includes everything you need to build a great website, Neve Pro adds advanced customization options and specific features for e-commerce, multilingual sites, and client projects. Here's a detailed comparison to help you decide if Pro is right for your needs.", 'neve' )}

- +
+ + {__('Considering an upgrade?', 'neve')} + + {__('Pro features are most helpful if you:', 'neve')} +
    + {upgradeList.map((item, index) => ( +
  • • {item}
  • + ))} +
+
+
+ + ); +}; + +const FreeProCard = () => ( + +
+
{__('Feature', 'neve')}
+
+
+ {__('Free', 'neve')} +
+
+ {__('Pro', 'neve')} +
+ + {neveDash.featureData.map(({ section, items }) => ( +
+
+

+ {section} +

+
+ + {items.map((item, index) => ( +
+
+
+
+ + {item.title} + + {item.tooltip && ( + + + + )} +
+

+ {item.description} +

+
+
+
+ {item.free ? ( + + ) : ( + + )} +
+
+ +
+
+
+
+ ))} +
+ ))} +
+); + +const UpsellCard = () => { + return ( + +
+

+ {__('Need help deciding?', 'neve')} +

+
+

+ {__( + 'Our support team is happy to answer your questions about specific Pro features and help you determine if they match your needs.', + 'neve' + )} +

+
+
+ {__( + 'Average response time: ~8 hours during business days', + 'neve' + )} +
+
+
+
+ + +
+
+
); }; -export default Pro; +export default () => { + return ( +
+ + +
+ ); +}; diff --git a/assets/apps/dashboard/src/Components/Content/ModuleGrid.js b/assets/apps/dashboard/src/Components/Content/ModuleGrid.js new file mode 100644 index 0000000000..bb7a63a6e1 --- /dev/null +++ b/assets/apps/dashboard/src/Components/Content/ModuleGrid.js @@ -0,0 +1,6 @@ +import { __ } from '@wordpress/i18n'; +import { NEVE_HAS_VALID_PRO } from '../../utils/constants'; + +export default () => { + return
; +}; diff --git a/assets/apps/dashboard/src/Components/Content/ModuleGridPlaceholder.js b/assets/apps/dashboard/src/Components/Content/ModuleGridPlaceholder.js new file mode 100644 index 0000000000..cd7f6aac53 --- /dev/null +++ b/assets/apps/dashboard/src/Components/Content/ModuleGridPlaceholder.js @@ -0,0 +1,59 @@ +import { __ } from '@wordpress/i18n'; +import { LucideSettings } from 'lucide-react'; +import Card from '../../Layout/Card'; +import { + NEVE_HAS_VALID_PRO, + NEVE_MODULE_ICON_MAP, +} from '../../utils/constants'; +import Tooltip from '../Common/Tooltip'; +import Pill from '../Common/Pill'; +import Link from '../Common/Link'; + +const ModuleCardPlaceholder = ({ slug, title, description }) => { + const CardIcon = NEVE_MODULE_ICON_MAP[slug] || LucideSettings; + + const dummyToggle = ( + + {__('Pro', 'neve')} + + ); + + return ( + } + title={title} + className="bg-white p-6 rounded-lg shadow-sm" + afterTitle={dummyToggle} + > +

{description}

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

+ {__('Neve Pro Modules', 'neve')} +

+ +
+ {Object.entries(neveDash.modules).map( + ([slug, { nicename, description }]) => ( + + ) + )} +
+ ); +}; diff --git a/assets/apps/dashboard/src/Components/Content/Pro.js b/assets/apps/dashboard/src/Components/Content/Pro.js index 27144e9911..0f2306e1a0 100644 --- a/assets/apps/dashboard/src/Components/Content/Pro.js +++ b/assets/apps/dashboard/src/Components/Content/Pro.js @@ -4,7 +4,7 @@ import ModuleCard from '../ModuleCard'; const Pro = () => { const { modules, hasOldPro, strings } = neveDash; - if (hasOldPro) { + if (true) { return (
diff --git a/assets/apps/dashboard/src/Components/Content/Start.js b/assets/apps/dashboard/src/Components/Content/Start.js deleted file mode 100644 index b19f5b66b1..0000000000 --- a/assets/apps/dashboard/src/Components/Content/Start.js +++ /dev/null @@ -1,178 +0,0 @@ -/* global neveDash */ -import Card from '../Card'; -import { tabs } from '../../utils/common'; - -import { __ } from '@wordpress/i18n'; -import { Fragment } from '@wordpress/element'; -import { Button, ExternalLink } from '@wordpress/components'; -import { withSelect } from '@wordpress/data'; - -const Start = (props) => { - const { setTab, tier } = props; - const { pro, whiteLabel, customizerShortcuts, tpcAdminURL } = neveDash; - const starterSitesHidden = whiteLabel && whiteLabel.hideStarterSites; - - const renderCustomizerLinks = () => { - const split = Math.ceil(customizerShortcuts.length / 2); - const parts = [ - customizerShortcuts.slice(0, split), - customizerShortcuts.slice(split), - ]; - return ( -
- {parts.map((column, index) => { - return ( -
- {column.map((item, indexColumn) => { - return ( - - - {indexColumn !== column.length - 1 && ( -
- )} -
- ); - })} -
- ); - })} -
- ); - }; - - return ( - <> - {!starterSitesHidden && ( - - {!neveDash.isValidLicense && ( -

{neveDash.strings.starterSitesCardUpsellMessage}

- )} - -
- {tabs['starter-sites'] ? ( - - ) : ( - - )} - {!neveDash.isValidLicense && ( - - )} -
-
- )} - - {renderCustomizerLinks()} - - - {!whiteLabel && ( - <> - - - - - {tier !== 3 && ( - - {__('Discover Templates Cloud', 'neve')} - - )} - {tier === 3 && ( - - {__('Learn how to use Templates Cloud', 'neve')} - - )} - - - )} - {!pro && ( - <> - - - {__('Learn more', 'neve')} - - - - - {__('Learn more', 'neve')} - - - - )} - - ); -}; - -export default withSelect((select) => { - const { getLicenseTier } = select('neve-dashboard'); - return { - tier: getLicenseTier(), - }; -})(Start); diff --git a/assets/apps/dashboard/src/Components/Content/StarterSitesUnavailable.js b/assets/apps/dashboard/src/Components/Content/StarterSitesUnavailable.js index d17430307c..ce5b05d2a2 100644 --- a/assets/apps/dashboard/src/Components/Content/StarterSitesUnavailable.js +++ b/assets/apps/dashboard/src/Components/Content/StarterSitesUnavailable.js @@ -1,48 +1,58 @@ /* global neveDash */ import InstallActivate from '../Plugin/InstallActivate'; import { withSelect } from '@wordpress/data'; +import Card from '../../Layout/Card'; + +const BackgroundPlaceholder = () => ( +
+); const StarterSitesUnavailable = ({ templatesPluginData }) => { - const { tpcPath, tpcAdminURL, canInstallPlugins, assets } = neveDash; - const activateRedirect = - tpcAdminURL + (canInstallPlugins ? '&onboarding=yes' : ''); + const { tpcPath, tpcAdminURL, canInstallPlugins } = neveDash; const currentState = templatesPluginData?.cta || 'install'; + const activateRedirect = `${tpcAdminURL}${ + canInstallPlugins ? '&onboarding=yes' : '' + }`; + const description = { + __html: + 'deactivate' === currentState + ? neveDash.strings.starterSitesUnavailableUpdate + : neveDash.strings.starterSitesUnavailableActive, + }; + + const redirectToStarterSites = () => { + window.location.href = activateRedirect; + }; return ( -
-
-
+
+ + + +

+ { - window.location.href = activateRedirect; - }} - successUpdate={() => { - window.location.href = activateRedirect; - }} - description={ - <> -

- {'deactivate' === currentState - ? neveDash.strings - .starterSitesUnavailableUpdate - : neveDash.strings - .starterSitesUnavailableActive} -

-
- - } /> -
+
); }; diff --git a/assets/apps/dashboard/src/Components/Content/Welcome.js b/assets/apps/dashboard/src/Components/Content/Welcome.js new file mode 100644 index 0000000000..4cadf55887 --- /dev/null +++ b/assets/apps/dashboard/src/Components/Content/Welcome.js @@ -0,0 +1,33 @@ +import { __ } from '@wordpress/i18n'; +import { LucidePanelsTopLeft } from 'lucide-react'; +import Card from '../../Layout/Card'; +import Link from '../Common/Link'; +import ModuleGridPlaceholder from './ModuleGridPlaceholder'; +import ModuleGrid from './ModuleGrid'; +import { NEVE_HAS_PRO, NEVE_HAS_VALID_PRO } from '../../utils/constants'; + +export default () => { + return ( +
+ + {!NEVE_HAS_PRO || !NEVE_HAS_VALID_PRO ? ( + + ) : ( + + )} +
+ ); +}; + +const CustomizerShortcutsCard = () => ( + } + > +
+ {neveDash.customizerShortcuts.map(({ text, link }) => ( + + ))} +
+
+); diff --git a/assets/apps/dashboard/src/Components/FeatureRow.js b/assets/apps/dashboard/src/Components/FeatureRow.js deleted file mode 100644 index eb27cb1c8d..0000000000 --- a/assets/apps/dashboard/src/Components/FeatureRow.js +++ /dev/null @@ -1,122 +0,0 @@ -/* global neveDash */ -import { Dashicon, ExternalLink } from '@wordpress/components'; -import { useState, createInterpolateElement } from '@wordpress/element'; -import { __ } from '@wordpress/i18n'; -import classnames from 'classnames'; - -const FeatureRow = ({ item }) => { - const { title, description, inLite, docsLink, presentational } = item; - const [tooltip, toggleTooltip] = useState(false); - - const showTooltip = () => toggleTooltip(true); - const hideTooltip = () => toggleTooltip(false); - - const renderFeatureTitle = () => { - if (!presentational) { - return title; - } - - return ( - - {title} - - ); - }; - - const renderInfoIcon = () => { - if (!presentational) { - return ; - } - }; - - const renderNeveLiteIndicator = () => { - if (!presentational) { - return ( - - - - ); - } - }; - - const renderNeveProIndicator = () => { - if (!presentational) { - return ( - - - - ); - } - }; - - return ( - - -
-

{renderFeatureTitle()}

- { - e.preventDefault(); - showTooltip(); - }} - onMouseLeave={(e) => { - e.preventDefault(); - hideTooltip(); - }} - onFocus={(e) => { - e.preventDefault(); - showTooltip(); - }} - onBlur={(e) => { - e.preventDefault(); - hideTooltip(); - }} - > - {renderInfoIcon()} - - {tooltip && ( -
-
-

- {description + ' '} - {docsLink && - typeof createInterpolateElement !== - 'undefined' && - createInterpolateElement( - __( - 'More details here.', - 'neve' - ), - { - external_link: ( - - #dumptext - - ), - } - )} -

-
-
- )} -
-
- - {renderNeveLiteIndicator()} - {renderNeveProIndicator()} - - ); -}; - -export default FeatureRow; diff --git a/assets/apps/dashboard/src/Components/Header.js b/assets/apps/dashboard/src/Components/Header.js index 4161b102cf..7c782a0fe0 100644 --- a/assets/apps/dashboard/src/Components/Header.js +++ b/assets/apps/dashboard/src/Components/Header.js @@ -1,17 +1,170 @@ /* global neveDash */ import { addUrlHash, getTabHash, tabs } from '../utils/common'; -import classnames from 'classnames'; +import cn from 'classnames'; import { __ } from '@wordpress/i18n'; -import { useEffect } from '@wordpress/element'; +import { Fragment, useEffect } from '@wordpress/element'; + +import { LucideBookOpen, LucideFileText } from 'lucide-react'; +import { useDispatch, useSelect } from '@wordpress/data'; +import { + NEVE_HAS_VALID_PRO, + NEVE_IS_WHITELABEL, + NEVE_STORE, +} from '../utils/constants'; +import Container from '../Layout/Container'; +import Pill from './Common/Pill'; +import Button from './Common/Button'; + +const HeaderTopBar = ({ currentTab, setTab }) => { + const NAV_BUTTONS = { + help: { + label: __('Documentation', 'neve'), + icon: , + url: neveDash.docsURL, + }, + changelog: { + label: __('Changelog', 'neve'), + icon: , + hide: NEVE_IS_WHITELABEL, + }, + }; + + return ( +
+ +
+
+ {!NEVE_IS_WHITELABEL && ( + {__('Neve + )} + + {neveDash.strings.header} + + + {NEVE_HAS_VALID_PRO + ? __('Pro', 'neve') + : __('Free', 'neve')} + + + {neveDash.version} + +
+
+ {Object.entries(NAV_BUTTONS).map( + ([slug, { label, icon, url, hide }], index) => { + if (hide) { + return null; + } + + const props = {}; + + if (!url) { + props.onClick = (e) => { + e.preventDefault(); + setTab(slug); + }; + if (currentTab === slug) { + props.className = '!text-blue-600'; + } + } else { + props.href = url; + props.target = '_blank'; + } + + return ( + + + + {index < + Object.keys(NAV_BUTTONS).length - + 1 && ( +
+ )} + + ); + } + )} +
+
+ +
+ ); +}; + +const Navigation = ({ setTab, currentTab }) => { + return ( +
+ + + +
+ ); +}; + +const Header = () => { + const { currentTab } = useSelect((select) => { + const { getTab } = select(NEVE_STORE); + return { + currentTab: getTab(), + }; + }); + + const { setTab } = useDispatch(NEVE_STORE); -const Header = (props) => { const setTabToCurrentHash = () => { const hash = getTabHash(); if (null === hash) { return; } - props.setTab(hash); + setTab(hash); }; useEffect(() => { @@ -23,54 +176,19 @@ const Header = (props) => { }; }, []); - const renderHead = () => { - return ( -
-

{neveDash.strings.header}

- {neveDash.version} - {!neveDash.whiteLabel && ( - {__('Neve - )} -
- ); - }; - - const renderNavigation = () => { - const { currentTab, setTab } = props; - return ( - - ); + const handleTabSwitch = (slug) => { + setTab(slug); + addUrlHash(slug); }; return ( -
-
- {renderHead()} - {renderNavigation(props)} +
+
+ +
); diff --git a/assets/apps/dashboard/src/Components/LicenseCard.js b/assets/apps/dashboard/src/Components/LicenseCard.js index a2de9e6e81..b9fb3af493 100644 --- a/assets/apps/dashboard/src/Components/LicenseCard.js +++ b/assets/apps/dashboard/src/Components/LicenseCard.js @@ -1,22 +1,33 @@ /* global neveDash */ -import { send, fetchOptions } from '../utils/rest'; +import { fetchOptions, send } from '../utils/rest'; import Toast from './Toast'; -import classnames from 'classnames'; import { __ } from '@wordpress/i18n'; -import { Button, Dashicon } from '@wordpress/components'; import { Fragment, useState } from '@wordpress/element'; -import { withDispatch, withSelect } from '@wordpress/data'; -import { compose } from '@wordpress/compose'; +import { useDispatch, useSelect } from '@wordpress/data'; +import Card from '../Layout/Card'; +import { NEVE_STORE } from '../utils/constants'; +import Pill from './Common/Pill'; +import { LucideCircleCheck, LucideCircleX } from 'lucide-react'; +import Button from './Common/Button'; -const LicenseCard = ({ isVisible, setSettings, changeLicense, license }) => { +const LicenseCard = () => { const { proApi } = neveDash; + + const { changeLicense, setSettings } = useDispatch(NEVE_STORE); + + const { license } = useSelect((select) => { + const { getLicense } = select(NEVE_STORE); + return { + license: getLicense(), + }; + }); + const [key, setKey] = useState( license && 'valid' === license.valid ? license.key || '' : '' ); const [status, setStatus] = useState(false); - // const [ expiration, setExpiration ] = useState(license.expiration || ''); const [toast, setToast] = useState(''); const [toastType, setToastType] = useState('success'); @@ -24,9 +35,6 @@ const LicenseCard = ({ isVisible, setSettings, changeLicense, license }) => { const { whiteLabel, strings } = neveDash; const { licenseCardHeading, licenseCardDescription } = strings; - if (!isVisible) { - return null; - } const toggleLicense = () => { const toDo = 'valid' === valid ? 'deactivate' : 'activate'; setStatus('activate' === toDo ? 'activating' : 'deactivating'); @@ -49,23 +57,27 @@ const LicenseCard = ({ isVisible, setSettings, changeLicense, license }) => { if (whiteLabel && whiteLabel.hideLicense) { return null; } - let statusLabel = ''; - if (!status) { - if ('valid' === valid) { - statusLabel = __('Deactivate', 'neve'); - } else { - statusLabel = __('Activate', 'neve'); + + const getStatusLabel = () => { + const statusLabelMap = { + activating: __('Activating', 'neve'), + deactivating: __('Deactivating', 'neve'), + activate: __('Activate', 'neve'), + deactivate: __('Deactivate', 'neve'), + }; + + if (!status) { + return 'valid' === valid + ? __('Deactivate', 'neve') + : __('Activate', 'neve'); } - } - if ('activating' === status) { - statusLabel = __('Activating', 'neve'); - statusLabel = __('Deactivating', 'neve'); - } + + return statusLabelMap[status]; + }; return ( - + ); }; -export default compose( - withDispatch((dispatch) => { - const { changeLicense, setSettings } = dispatch('neve-dashboard'); - return { - setSettings: (object) => setSettings(object), - changeLicense: (data) => { - changeLicense(data); - }, - }; - }), - withSelect((select) => { - const { getLicense } = select('neve-dashboard'); - return { - license: getLicense(), - }; - }) -)(LicenseCard); +export default LicenseCard; diff --git a/assets/apps/dashboard/src/Components/Notifications.js b/assets/apps/dashboard/src/Components/Notifications.js index 19f82ac5cb..a676773d91 100644 --- a/assets/apps/dashboard/src/Components/Notifications.js +++ b/assets/apps/dashboard/src/Components/Notifications.js @@ -1,5 +1,6 @@ /* global neveDash */ -import Notification from './Notification'; +import Notification from './Common/Notification'; +import Container from '../Layout/Container'; const Notifications = () => { if (!neveDash.notifications) { @@ -10,7 +11,7 @@ const Notifications = () => { } return ( -
+ {Object.keys(neveDash.notifications).map((slug, index) => { return ( { /> ); })} -
+ ); }; diff --git a/assets/apps/dashboard/src/Components/Plugin/InstallActivate.js b/assets/apps/dashboard/src/Components/Plugin/InstallActivate.js index f1759422d9..92bf801052 100644 --- a/assets/apps/dashboard/src/Components/Plugin/InstallActivate.js +++ b/assets/apps/dashboard/src/Components/Plugin/InstallActivate.js @@ -1,8 +1,17 @@ /* global neveDash */ +import { useEffect, useState } from '@wordpress/element'; +import { __, sprintf } from '@wordpress/i18n'; +import { LucideLoaderCircle } from 'lucide-react'; import { get } from '../../utils/rest'; -import { sprintf, __ } from '@wordpress/i18n'; -import { useState, useEffect } from '@wordpress/element'; -import { Button, Tooltip } from '@wordpress/components'; +import Button from '../Common/Button'; +import Notice from '../Common/Notice'; +import Tooltip from '../Common/Tooltip'; +import TransitionInOut from '../Common/TransitionInOut'; + +const STATUS = { + INSTALLING: 'installing', + ACTIVATING: 'activating', +}; const InstallActivate = ({ labels = {}, @@ -21,8 +30,8 @@ const InstallActivate = ({ isOtterProInstalled, } = neveDash; const [progress, setProgress] = useState(false); - // const [updating, setUpdating] = useState(false); - const [error, setError] = useState(false); + + const [error, setError] = useState(''); const [currentState, setCurrentState] = useState(pluginState); const [buttonLabels, setButtonLabels] = useState({ firstLabel: false, @@ -30,31 +39,9 @@ const InstallActivate = ({ activating: `${__('Activating', 'neve')}...`, installActivate: __('Install and Activate', 'neve'), activate: __('Activate', 'neve'), + ...labels, }); - const setCustomLabels = (customLabels) => { - setButtonLabels({ - ...buttonLabels, - ...(customLabels.firstLabel !== false && { - firstLabel: customLabels.firstLabel, - }), - ...(customLabels.installing && { - installing: customLabels.installing, - }), - ...(customLabels.activating && { - activating: customLabels.activating, - }), - ...(customLabels.installActivate && { - installActivate: customLabels.installActivate, - }), - ...(customLabels.activate && { activate: customLabels.activate }), - }); - }; - - useEffect(() => { - setCustomLabels(labels); - }, []); - const getLabel = (type) => { return progress || !buttonLabels.firstLabel ? buttonLabels[type] @@ -66,7 +53,8 @@ const InstallActivate = ({ }; const installPlugin = () => { - setProgress('installing'); + setProgress(STATUS.INSTALLING); + hideFirstLabel(); wp.updates.ajax('install-plugin', { slug, @@ -90,7 +78,7 @@ const InstallActivate = ({ }; const activatePlugin = () => { - setProgress('activating'); + setProgress(STATUS.ACTIVATING); setCurrentState('activate'); hideFirstLabel(); const activationURL = activateURL; @@ -132,8 +120,6 @@ const InstallActivate = ({ }); }; - const isProgress = (type) => progress === type; - const renderNoticeContent = () => { const isSuperAdmin = canInstallPlugins && canActivatePlugins; @@ -171,63 +157,70 @@ const InstallActivate = ({ return actionsAreDisabled(); }; + const installing = progress === STATUS.INSTALLING; + const activating = progress === STATUS.ACTIVATING; + const buttonMap = { install: ( ), activate: activateURL && ( ), }; - const wrappedButtonContent = actionsAreDisabled() ? ( - { + if (actionsAreDisabled()) { + const text = sprintf( // translators: %s: Plugin name. __('Ask your admin to enable %s on your site', 'neve'), name - )} - position="top center" - > - {buttonMap[currentState]} - - ) : ( - buttonMap[currentState] - ); + ); + + return {buttonMap[currentState]}; + } + + return buttonMap[currentState]; + }; return ( <> {description} - {buttonMap.hasOwnProperty(currentState) && wrappedButtonContent} + {!!buttonMap[currentState] && } ); }; - return error ?

{error}

: renderNoticeContent(); + return ( + <> + + {{error}} + + {!error && renderNoticeContent()} + + ); }; export default InstallActivate; diff --git a/assets/apps/dashboard/src/Components/PluginsCard.js b/assets/apps/dashboard/src/Components/PluginsCard.js new file mode 100644 index 0000000000..b0faf4784a --- /dev/null +++ b/assets/apps/dashboard/src/Components/PluginsCard.js @@ -0,0 +1,12 @@ +import { __ } from '@wordpress/i18n'; + +import { NEVE_HIDE_PLUGINS } from '../utils/constants'; +import Card from '../Layout/Card'; + +const PluginsCard = () => {}; + +const PluginCard = ({ slug, title, icon }) => { + ; +}; + +export default PluginsCard; diff --git a/assets/apps/dashboard/src/Components/Sidebar.js b/assets/apps/dashboard/src/Components/Sidebar.js index 233f56f800..b82e1dc1bd 100644 --- a/assets/apps/dashboard/src/Components/Sidebar.js +++ b/assets/apps/dashboard/src/Components/Sidebar.js @@ -3,113 +3,111 @@ import { changeOption } from '../utils/rest'; import SupportCard from './SupportCard'; import LicenseCard from './LicenseCard'; import { __ } from '@wordpress/i18n'; -import { ToggleControl, ExternalLink } from '@wordpress/components'; -import { createInterpolateElement, useState } from '@wordpress/element'; -import { withDispatch, withSelect } from '@wordpress/data'; -import { compose } from '@wordpress/compose'; +import { useState } from '@wordpress/element'; +import { useDispatch, useSelect } from '@wordpress/data'; +import { + NEVE_HAS_PRO, + NEVE_HIDE_PLUGINS, + NEVE_IS_WHITELABEL, + NEVE_STORE, +} from '../utils/constants'; +import Card from '../Layout/Card'; +import Link from './Common/Link'; +import Toggle from './Common/Toggle'; +import PluginsCard from './PluginsCard'; -const Sidebar = ({ currentTab, setToast, loggerValue, setLogger }) => { - const [tracking, setTracking] = useState('yes' === loggerValue); +const ReviewCard = () => ( + +

+ {__( + 'Are you are enjoying Neve? We would love to hear your feedback.', + 'neve' + )} +

+ + +
+); + +const ContributingCard = () => { + const loggerEnabled = useSelect((select) => { + const { getOption } = select(NEVE_STORE); + + return getOption('neve_logger_flag'); + }); + + const [tracking, setTracking] = useState('yes' === loggerEnabled); + + const { setToast, setLogger } = useDispatch(NEVE_STORE); + + const handleTrackingChange = (value) => { + setTracking(value); + changeOption( + 'neve_logger_flag', + value ? 'yes' : 'no', + false, + false + ).then((r) => { + if (!r.success) { + setToast( + __('Could not update option. Please try again.', 'neve') + ); + setTracking(!value); + return false; + } + setLogger(value ? 'yes' : 'no'); + setToast(__('Option Updated', 'neve')); + }); + }; + + return ( + +

+ {__( + 'Become a contributor by opting in to our anonymous data tracking. We guarantee no sensitive data is collected.', + 'neve' + )} +

+ + + + +
+ ); +}; +const Sidebar = () => { return ( -
- {!neveDash.whiteLabel && neveDash.pro && } - {neveDash.pro && } - {!neveDash.whiteLabel && ( - +
+ {NEVE_HAS_PRO && } + + {!NEVE_IS_WHITELABEL && NEVE_HAS_PRO && } + + {!NEVE_IS_WHITELABEL && ( + <> + + + )} + + {!NEVE_HIDE_PLUGINS && }
); }; -export default compose( - withDispatch((dispatch) => { - const { setToast, setLogger } = dispatch('neve-dashboard'); - return { - setToast: (message) => setToast(message), - setLogger: (value) => setLogger(value), - }; - }), - withSelect((select) => { - const { getOption } = select('neve-dashboard'); - return { - loggerValue: getOption('neve_logger_flag'), - }; - }) -)(Sidebar); +export default Sidebar; diff --git a/assets/apps/dashboard/src/Components/Snackbar.js b/assets/apps/dashboard/src/Components/Snackbar.js index 9e6e92c671..444ec85ec6 100644 --- a/assets/apps/dashboard/src/Components/Snackbar.js +++ b/assets/apps/dashboard/src/Components/Snackbar.js @@ -1,9 +1,19 @@ import { Snackbar } from '@wordpress/components'; -import { withDispatch, withSelect } from '@wordpress/data'; +import { useDispatch, useSelect } from '@wordpress/data'; import { useEffect } from '@wordpress/element'; -import { compose } from '@wordpress/compose'; import { __ } from '@wordpress/i18n'; -const GlobalSnackbar = ({ toast, setToast }) => { +import { NEVE_STORE } from '../utils/constants'; + +const GlobalSnackbar = () => { + const { setToast } = useDispatch(NEVE_STORE); + + const { toast } = useSelect((select) => { + const { getToast } = select(NEVE_STORE); + return { + toast: getToast, + }; + }); + useEffect(() => { setTimeout(() => { setToast(null); @@ -24,22 +34,9 @@ const GlobalSnackbar = ({ toast, setToast }) => { return (
- {renderToast} + {renderToast}
); }; -export default compose( - withDispatch((dispatch) => { - const { setToast } = dispatch('neve-dashboard'); - return { - setToast: (message) => setToast(message), - }; - }), - withSelect((select) => { - const { getToast } = select('neve-dashboard'); - return { - toast: () => getToast(), - }; - }) -)(GlobalSnackbar); +export default GlobalSnackbar; diff --git a/assets/apps/dashboard/src/Components/SupportCard.js b/assets/apps/dashboard/src/Components/SupportCard.js index a18c971b29..6f451a1d1f 100644 --- a/assets/apps/dashboard/src/Components/SupportCard.js +++ b/assets/apps/dashboard/src/Components/SupportCard.js @@ -1,7 +1,16 @@ -import { withSelect } from '@wordpress/data'; +import { useSelect, withSelect } from '@wordpress/data'; import { Button } from '@wordpress/components'; +import Link from './Common/Link'; +import { NEVE_STORE } from '../utils/constants'; + +const SupportCard = () => { + const { license } = useSelect((select) => { + const { getLicense } = select(NEVE_STORE); + return { + license: getLicense(), + }; + }); -const SupportCard = ({ license }) => { if (!license || !license.valid || 'valid' !== license.valid) { return null; } @@ -11,31 +20,14 @@ const SupportCard = ({ license }) => { return null; } - const buttonStyle = { - width: '100%', - justifyContent: 'center', - fontWeight: '700', - fontSize: '14px', - padding: '28px 0', - backgroundColor: '#ffffff', - marginBottom: '24px', - }; - return ( - + ); }; -export default withSelect((select) => { - const { getLicense } = select('neve-dashboard'); - return { - license: getLicense(), - }; -})(SupportCard); +export default SupportCard; diff --git a/assets/apps/dashboard/src/Components/TabsContent.js b/assets/apps/dashboard/src/Components/TabsContent.js index 888dc96fc1..12ea40c627 100644 --- a/assets/apps/dashboard/src/Components/TabsContent.js +++ b/assets/apps/dashboard/src/Components/TabsContent.js @@ -1,9 +1,7 @@ import { tabs } from '../utils/common'; -import classnames from 'classnames'; const TabsContent = ({ currentTab, setTab }) => { - const classes = classnames(['tab-content', 'columns', currentTab]); - return
{tabs[currentTab].render(setTab)}
; + return tabs[currentTab].render(setTab); }; export default TabsContent; diff --git a/assets/apps/dashboard/src/Components/Toast.js b/assets/apps/dashboard/src/Components/Toast.js index 2e39064159..ca6851c000 100644 --- a/assets/apps/dashboard/src/Components/Toast.js +++ b/assets/apps/dashboard/src/Components/Toast.js @@ -1,32 +1,64 @@ -import classnames from 'classnames'; +import cn from 'classnames'; -import { useEffect } from '@wordpress/element'; -import { Dashicon } from '@wordpress/components'; +import { useEffect, useState } from '@wordpress/element'; +import { + LucideCircleAlert, + LucideCircleCheck, + LucideCircleX, + LucideInfo, +} from 'lucide-react'; +import TransitionInOut from './Common/TransitionInOut'; const Toast = ({ message, dismiss, time, type = 'info' }) => { + const [show, setShow] = useState(false); + useEffect(() => { - const timeout = setTimeout(() => { + setShow(true); + + const timeBeforeHide = time || 2000; + const timeBeforeDismiss = timeBeforeHide + 1000; + + const hideTimeout = setTimeout(() => { + setShow(false); + }, timeBeforeHide); + + const dismissTimeout = setTimeout(() => { dismiss(''); - clearTimeout(timeout); - }, time || 2000); - }); + }, timeBeforeDismiss); + + return () => { + clearTimeout(hideTimeout); + clearTimeout(dismissTimeout); + }; + }, []); + const iconMap = { - info: 'info', - error: 'no', - success: 'yes', - warning: 'warning', + info: LucideInfo, + error: LucideCircleX, + success: LucideCircleCheck, + warning: LucideCircleAlert, }; - const classes = classnames([ - 'toast', - 'components-animate__appear', - 'is-from-middle', - type, - ]); + + const classes = cn( + 'px-2 py-1.5 flex items-center text-sm border rounded gap-2', + { + 'border-sky-300 bg-sky-50 text-sky-800': type === 'info', + 'border-red-300 bg-red-50 text-red-800': type === 'error', + 'border-lime-300 bg-lime-50 text-lime-800': type === 'success', + 'border-orange-300 bg-orange-50 text-orange-800': + type === 'warning', + } + ); + + const ICON = iconMap[type]; + return ( -
- - {message} -
+ +
+ + {message} +
+
); }; diff --git a/assets/apps/dashboard/src/Layout/Card.js b/assets/apps/dashboard/src/Layout/Card.js new file mode 100644 index 0000000000..0f9149efe3 --- /dev/null +++ b/assets/apps/dashboard/src/Layout/Card.js @@ -0,0 +1,36 @@ +import cn from 'classnames'; + +export default ({ icon, title, children, afterTitle, className = '' }) => { + const classes = cn( + [ + 'p-6 rounded-lg shadow-sm', + { + 'bg-white': !className.includes('bg-'), + }, + ], + className + ); + + return ( +
+ {(icon || title) && ( +
+ {icon && ( + + {icon} + + )} + {title && ( +

+ {title} +

+ )} + {afterTitle && ( +
{afterTitle}
+ )} +
+ )} + {children} +
+ ); +}; diff --git a/assets/apps/dashboard/src/Layout/Container.js b/assets/apps/dashboard/src/Layout/Container.js new file mode 100644 index 0000000000..5d6876e092 --- /dev/null +++ b/assets/apps/dashboard/src/Layout/Container.js @@ -0,0 +1,12 @@ +import cn from 'classnames'; + +export default ({ children, className }) => ( +
+ {children} +
+); diff --git a/assets/apps/dashboard/src/dashboard.js b/assets/apps/dashboard/src/dashboard.js index 6cacefe98c..5d71f30392 100644 --- a/assets/apps/dashboard/src/dashboard.js +++ b/assets/apps/dashboard/src/dashboard.js @@ -1,5 +1,5 @@ import { registerStore } from '@wordpress/data'; -import { render } from '@wordpress/element'; +import { render, StrictMode } from '@wordpress/element'; import './style.css'; import App from './Components/App'; @@ -7,14 +7,20 @@ import App from './Components/App'; import actions from './store/actions'; import reducer from './store/reducer'; import selectors from './store/selectors'; +import { NEVE_STORE } from './utils/constants'; + import './utils/module-observer'; import './utils/survey'; -registerStore('neve-dashboard', { +registerStore(NEVE_STORE, { reducer, actions, selectors, }); -const Root = () => ; +const Root = () => ( + + + +); render(, document.getElementById('neve-dashboard')); diff --git a/assets/apps/dashboard/src/style.css b/assets/apps/dashboard/src/style.css index ce6ee37347..7b6162d3a0 100644 --- a/assets/apps/dashboard/src/style.css +++ b/assets/apps/dashboard/src/style.css @@ -3,3 +3,31 @@ @tailwind base; @tailwind components; @tailwind utilities; + +#wpcontent { + @apply pl-0 font-sans; +} + +#wpbody-content > .notice, #wpbody-content > .error { + @apply hidden; +} + +#wpcontent, #wpbody, #wpbody-content, #neve-dashboard { + @apply flex flex-col grow; +} + +#wpwrap { + @apply min-h-full; +} + +#wpwrap { + @apply bg-gray-50 relative; +} + +#adminmenumain { + @apply shrink; +} + +#neve-dashboard { + line-height: 1.5; +} diff --git a/assets/apps/dashboard/src/tailwind.config.js b/assets/apps/dashboard/src/tailwind.config.js index 12ab71cc41..b648c321a0 100644 --- a/assets/apps/dashboard/src/tailwind.config.js +++ b/assets/apps/dashboard/src/tailwind.config.js @@ -3,7 +3,9 @@ module.exports = { content: ['assets/apps/dashboard/src/**/*.js'], theme: { extend: { - colors: {}, + zIndex: { + max: 9999, + }, }, }, }; diff --git a/assets/apps/dashboard/src/utils/common.js b/assets/apps/dashboard/src/utils/common.js index fcd574f9ba..62656a4c98 100644 --- a/assets/apps/dashboard/src/utils/common.js +++ b/assets/apps/dashboard/src/utils/common.js @@ -2,8 +2,7 @@ import compareVersions from 'compare-versions'; import StarterSitesUnavailable from '../Components/Content/StarterSitesUnavailable'; -import CustomLayoutsUnavailable from '../Components/Content/CustomLayoutsUnavailable'; -import Start from '../Components/Content/Start'; +import Welcome from '../Components/Content/Welcome'; import Pro from '../Components/Content/Pro'; import Plugins from '../Components/Content/Plugins'; import Help from '../Components/Content/Help'; @@ -34,16 +33,12 @@ const getTabHash = () => { const tabs = { start: { label: __('Welcome', 'neve'), - render: (setTab) => , + render: () => , }, 'starter-sites': { label: __('Starter Sites', 'neve'), render: () => , }, - 'custom-layouts': { - label: __('Custom Layouts', 'neve'), - render: (setTab) => , - }, 'free-pro': { label: __('Free vs Pro', 'neve'), render: () => , @@ -52,12 +47,7 @@ const tabs = { label: __('Plugins', 'neve'), render: () => , }, - help: { - label: __('Help', 'neve'), - render: (setTab) => , - }, changelog: { - label: __('Changelog', 'neve'), render: () => , }, }; @@ -74,7 +64,9 @@ const properTPC = ) === 1; if (activeTPC && properTPC) { - delete tabs['starter-sites']; + delete tabs['starter-sites']['render']; + + tabs['starter-sites'].url = neveDash.tpcAdminURL; } if ( diff --git a/assets/apps/dashboard/src/utils/constants.js b/assets/apps/dashboard/src/utils/constants.js new file mode 100644 index 0000000000..102a46abba --- /dev/null +++ b/assets/apps/dashboard/src/utils/constants.js @@ -0,0 +1,49 @@ +/* globals neveDash */ + +import { + LucideArrowUp, + LucideGalleryVertical, + LucideGauge, + LucideGraduationCap, + LucideNewspaper, + LucidePalette, + LucidePanelRightDashed, + LucidePanelTopDashed, + LucidePin, + LucideScanBarcode, + LucideScroll, + LucideSettings, + LucideShield, + LucideShoppingCart, + LucideTypeOutline, +} from 'lucide-react'; + +export const NEVE_STORE = 'neve-dashboard'; +export const NEVE_HAS_VALID_PRO = + (neveDash.pro || neveDash.hasOldPro) && + neveDash.license && + neveDash.license.valid === 'valid'; + +export const NEVE_HAS_PRO = neveDash.pro; + +export const NEVE_IS_WHITELABEL = neveDash.whiteLabel; + +export const NEVE_HIDE_PLUGINS = neveDash.hidePluginsTab; + +export const NEVE_MODULE_ICON_MAP = { + hfg_module: LucidePanelTopDashed, + woocommerce_booster: LucideShoppingCart, + easy_digital_downloads: LucideScanBarcode, + blog_pro: LucideNewspaper, + post_type_enhancements: LucidePin, + scroll_to_top: LucideArrowUp, + performance_features: LucideGauge, + block_editor_booster: LucideScroll, + white_label: LucideSettings, + custom_layouts: LucideGalleryVertical, + elementor_booster: LucidePalette, + lifterlms_booster: LucideGraduationCap, + typekit_fonts: LucideTypeOutline, + custom_sidebars: LucidePanelRightDashed, + access_restriction: LucideShield, +}; diff --git a/assets/img/dashboard/logo.svg b/assets/img/dashboard/logo.svg index 24ec3ce97e..5454c306d4 100644 --- a/assets/img/dashboard/logo.svg +++ b/assets/img/dashboard/logo.svg @@ -1,3 +1,7 @@ - - + diff --git a/inc/admin/dashboard/main.php b/inc/admin/dashboard/main.php index 1fc2e96df0..b9efc5fb84 100755 --- a/inc/admin/dashboard/main.php +++ b/inc/admin/dashboard/main.php @@ -334,28 +334,31 @@ private function get_localization() { 'notifications' => $this->get_notifications(), 'customizerShortcuts' => $this->get_customizer_shortcuts(), 'plugins' => $this->get_useful_plugins(), + 'recommended_plugins' => $this->get_recommended_plugins(), + 'modules' => $this->get_modules(), 'featureData' => $this->get_free_pro_features(), 'showFeedbackNotice' => $this->should_show_feedback_notice(), 'allfeaturesNeveProURL' => tsdk_translate_link( tsdk_utmify( 'https://themeisle.com/themes/neve/upgrade/', 'seeallfeatures', 'freevspropage' ), 'query' ), 'startSitesgetNeveProURL' => tsdk_translate_link( tsdk_utmify( 'https://themeisle.com/themes/neve/upgrade/', 'welcomestartersitescard', 'nevedashboard' ), 'query' ), 'customLayoutsNeveProURL' => tsdk_translate_link( tsdk_utmify( 'https://themeisle.com/themes/neve/upgrade/', 'customlayoutscard', 'nevedashboard' ), 'query' ), 'upgradeURL' => apply_filters( 'neve_upgrade_link_from_child_theme_filter', tsdk_translate_link( tsdk_utmify( 'https://themeisle.com/themes/neve/upgrade/', 'getpronow', 'freevspropage' ), 'query' ) ), + 'upgradeURLModules' => apply_filters( 'neve_upgrade_link_from_child_theme_filter', tsdk_translate_link( tsdk_utmify( 'https://themeisle.com/themes/neve/upgrade/', 'getpronow', 'welcomepage' ), 'query' ) ), 'supportURL' => esc_url( 'https://wordpress.org/support/theme/neve/' ), 'docsURL' => esc_url( 'https://docs.themeisle.com/article/946-neve-doc' ), 'codexURL' => esc_url( 'https://codex.nevewp.com/' ), 'strings' => [ 'proTabTitle' => wp_kses_post( $plugin_name ), /* translators: %s - Theme name */ - 'header' => sprintf( __( '%s Options', 'neve' ), wp_kses_post( $theme_name ) ), + 'header' => wp_kses_post( $theme_name ), /* translators: %s - Theme name */ 'starterSitesCardDescription' => sprintf( __( '%s now comes with a sites library with various designs to pick from. Visit our collection of demos that are constantly being added.', 'neve' ), wp_kses_post( $theme_name ) ), 'starterSitesCardUpsellMessage' => esc_html__( 'Upgrade to the Pro version and get instant access to all Premium Starter Sites — including Expert Sites — and much more.', 'neve' ), /* translators: %s - Theme name */ 'starterSitesTabDescription' => sprintf( __( 'With %s, you can choose from multiple unique demos, specially designed for you, that can be installed with a single click. You just need to choose your favorite, and we will take care of everything else.', 'neve' ), wp_kses_post( $theme_name ) ), /* translators: 1 - Theme name, 2 - Cloud Templates & Patterns Collection */ - 'starterSitesUnavailableActive' => sprintf( __( 'In order to be able to import any starter sites for %1$s you would need to have the %2$s plugin active.', 'neve' ), wp_kses_post( $theme_name ), 'Cloud Templates & Patterns Collection' ), + 'starterSitesUnavailableActive' => sprintf( __( 'In order to be able to import any starter sites for %1$s you would need to have the %2$s plugin active.', 'neve' ), wp_kses_post( $theme_name ), 'Starter Sites & Templates by Neve' ), /* translators: %s - Theme name */ - 'starterSitesUnavailableUpdate' => sprintf( __( 'In order to be able to import any starter sites for %1$s you would need to have the %2$s plugin updated to the latest version.', 'neve' ), wp_kses_post( $theme_name ), 'Cloud Templates & Patterns Collection' ), + 'starterSitesUnavailableUpdate' => sprintf( __( 'In order to be able to import any starter sites for %1$s you would need to have the %2$s plugin updated to the latest version.', 'neve' ), wp_kses_post( $theme_name ), 'Starter Sites & Templates by Neve' ), /* translators: %s - Theme name */ 'supportCardDescription' => sprintf( __( 'We want to make sure you have the best experience using %1$s, and that is why we have gathered all the necessary information here for you. We hope you will enjoy using %1$s as much as we enjoy creating great products.', 'neve' ), wp_kses_post( $theme_name ) ), /* translators: %s - Theme name */ @@ -367,8 +370,8 @@ private function get_localization() { /* translators: %1$s - Author link - Themeisle */ 'licenseCardDescription' => sprintf( // translators: store name (Themeisle) - __( 'Enter your license from %1$s purchase history in order to get plugin updates', 'neve' ), - 'ThemeIsle' . esc_html__( '(opens in a new tab)', 'neve' ) . '' + __( 'Enter your license from %1$s purchase history in order to get plugin updates', 'neve' ) . '.', + 'Themeisle' . esc_html__( '(opens in a new tab)', 'neve' ) . '' ), ], 'changelog' => $this->cl_handler->get_changelog( get_template_directory() . '/CHANGELOG.md' ), @@ -567,82 +570,143 @@ private function get_doc_link( $utm_term, $url ) { private function get_free_pro_features() { return [ [ - 'title' => __( 'Header/Footer builder', 'neve' ), - 'description' => __( 'Easily build your header and footer by dragging and dropping all the important elements in the real-time WordPress Customizer. More advanced options are available in PRO.', 'neve' ), - 'inLite' => true, - 'docsLink' => $this->get_doc_link( 'Header/Footer builder', 'https://docs.themeisle.com/category/1251-neve-header-builder' ), - ], - [ - 'title' => __( 'Page Builder Compatibility', 'neve' ), - 'description' => __( 'Neve is fully compatible with Gutenberg, the new WordPress editor and for all of you page builder fans, Neve has full compatibility with Elementor, Beaver Builder, and all the other popular page builders.', 'neve' ), - 'inLite' => true, - 'docsLink' => $this->get_doc_link( 'Page Builder Compatibility', 'https://docs.themeisle.com/article/946-neve-doc#pagebuilders' ), - ], - [ - 'title' => __( 'Header Booster', 'neve' ), - 'description' => __( 'Take the header builder to a new level with new awesome components: socials, contact, breadcrumbs, language switcher, multiple HTML, sticky and transparent menu, page header builder and many more.', 'neve' ), - 'inLite' => false, - 'docsLink' => $this->get_doc_link( 'Header Booster', 'https://docs.themeisle.com/article/1057-header-booster-documentation' ), - ], - [ - 'title' => __( 'Page Header Builder', 'neve' ), - 'description' => __( 'The Page Header is the horizontal area that sits directly below the header and contains the page/post title. Easily design an attractive Page Header area using our dedicated builder.', 'neve' ), - 'inLite' => false, - 'docsLink' => $this->get_doc_link( 'Page Header Builder', 'https://docs.themeisle.com/article/1262-neve-page-header' ), - ], - [ - 'title' => __( 'Custom Layouts', 'neve' ), - 'description' => __( 'Powerful Custom Layouts builder which allows you to easily create your own header, footer or custom content on any of the hook locations available in the theme.', 'neve' ), - 'inLite' => false, - 'docsLink' => $this->get_doc_link( 'Custom Layouts', 'https://docs.themeisle.com/article/1062-custom-layouts-module' ), - ], - [ - 'title' => __( 'Blog Booster', 'neve' ), - 'description' => __( 'Give a huge boost to your entire blogging experience with features specially designed for increased user experience.', 'neve' ) . ' ' . __( 'Sharing, custom article sorting, comments integrations, number of minutes needed to read an article and many more.', 'neve' ), - 'inLite' => false, - 'docsLink' => $this->get_doc_link( 'Blog Booster', 'https://docs.themeisle.com/article/1059-blog-booster-documentation' ), - ], - [ - 'title' => __( 'Elementor Booster', 'neve' ), - 'description' => __( 'Leverage the true flexibility of Elementor with powerful addons and templates that you can import with just one click.', 'neve' ), - 'inLite' => false, - 'docsLink' => $this->get_doc_link( 'Elementor Booster', 'https://docs.themeisle.com/article/1063-elementor-booster-module-documentation' ), - ], - [ - 'title' => __( 'WooCommerce Booster', 'neve' ), - 'description' => __( 'Empower your online store with awesome new features, specially designed for a smooth WooCommerce integration.', 'neve' ) . ' ' . __( 'Wishlist, quick view, video products, advanced reviews, multiple dedicated layouts and many more.', 'neve' ), - 'inLite' => false, - 'docsLink' => $this->get_doc_link( 'WooCommerce Booster', 'https://docs.themeisle.com/article/1058-woocommerce-booster-documentation' ), + 'section' => __( 'Header Features', 'neve' ), + 'items' => [ + [ + 'title' => __( 'Advanced Header Components', 'neve' ), + 'description' => __( 'Additional components for contact info, language switcher, and HTML sections', 'neve' ), + ], + [ + 'title' => __( 'Dynamic Headers', 'neve' ), + 'description' => __( 'Sticky and transparent headers with per-page settings', 'neve' ), + ], + ], ], [ - 'title' => __( 'LifterLMS Booster', 'neve' ), - 'description' => __( 'Make your LifterLMS pages look stunning with our PRO design options. Specially created to help you set up your online courses with minimum customizations.', 'neve' ), - 'inLite' => false, - 'docsLink' => $this->get_doc_link( 'LifterLMS Booster', 'https://docs.themeisle.com/article/1084-lifterlms-booster-documentation' ), + 'section' => __( 'Blog Features', 'neve' ), + 'items' => [ + [ + 'title' => __( 'Blog Layouts', 'neve' ), + 'description' => __( 'Additional blog and archive page layouts with custom sorting options', 'neve' ), + ], + [ + 'title' => __( 'Advanced Blog Features', 'neve' ), + 'description' => __( 'Reading time, social sharing, and related posts functionality', 'neve' ), + ], + ], ], [ - 'title' => __( 'Typekit(Adobe) Fonts', 'neve' ), - 'description' => __( "The module allows for an easy way of enabling new awesome Adobe (previous Typekit) Fonts in Neve's Typography options.", 'neve' ), - 'inLite' => false, - 'docsLink' => $this->get_doc_link( 'Typekit(Adobe) Fonts', 'https://docs.themeisle.com/article/1085-typekit-fonts-documentation' ), + 'section' => __( 'WooCommerce Features', 'neve' ), + 'items' => [ + [ + 'title' => __( 'Shop Customization', 'neve' ), + 'description' => __( 'Advanced product layouts, quick view, wishlist functionality', 'neve' ), + ], + ], ], [ - 'title' => __( 'White Label', 'neve' ), - 'description' => __( "For any developer or agency out there building websites for their own clients, we've made it easy to present the theme as your own.", 'neve' ), - 'inLite' => false, - 'docsLink' => $this->get_doc_link( 'White Label', 'https://docs.themeisle.com/article/1061-white-label-module-documentation' ), + 'section' => __( 'Additional Features', 'neve' ), + 'items' => [ + [ + 'title' => __( 'Multilingual Support', 'neve' ), + 'description' => __( 'Enhanced WPML, Weglot and Polylang compatibility', 'neve' ), + ], + [ + 'title' => __( 'White Label', 'neve' ), + 'description' => __( 'Remove Neve branding for client websites', 'neve' ), + ], + ], ], - [ - 'title' => __( 'Scroll To Top', 'neve' ), + ]; + } + + /** + * Get modules. + * + * @return array[] + */ + private function get_modules() { + return array( + 'hfg_module' => array( + 'nicename' => __( 'Header Booster', 'neve' ), + 'description' => __( 'Extend your header with more components and settings, build sticky/transparent headers or display them conditionally.', 'neve' ), + ), + 'woocommerce_booster' => array( + 'nicename' => __( 'WooCommerce Booster', 'neve' ), + 'description' => __( 'Empower your online store with awesome new features, specially designed for a smooth WooCommerce integration.', 'neve' ), + ), + 'easy_digital_downloads' => array( + 'nicename' => __( 'Easy Digital Downloads Booster', 'neve' ), + 'description' => __( 'Enhance your Easy Digital Downloads store with additional customization settings.', 'neve' ), + ), + 'blog_pro' => array( + 'nicename' => __( 'Blog Booster', 'neve' ), + 'description' => __( 'Give a huge boost to your entire blogging experience with features specially designed for increased user experience.', 'neve' ), + ), + 'post_type_enhancements' => array( + 'nicename' => __( 'Post types enhancements', 'neve' ), + 'description' => __( 'Enable Neve post enhancements for custom post types.', 'neve' ), + ), + 'scroll_to_top' => array( + 'nicename' => __( 'Scroll To Top', 'neve' ), 'description' => __( 'Simple but effective module to help you navigate back to the top of the really long pages.', 'neve' ), - 'inLite' => false, - 'docsLink' => $this->get_doc_link( 'Scroll To Top', 'https://docs.themeisle.com/article/1060-scroll-to-top-module-documentation' ), - ], - [ - 'title' => __( 'See all PRO features', 'neve' ), - 'presentational' => true, - ], + ), + 'performance_features' => array( + 'nicename' => __( 'Performance', 'neve' ), + 'description' => __( 'Simple and effective optimizations options to enhance the performance of your site.', 'neve' ), + ), + 'block_editor_booster' => array( + 'nicename' => __( 'Block Editor Booster', 'neve' ), + 'description' => __( 'Do more with the Block Editor with Otter\'s additional blocks made specifically for Neve Pro.', 'neve' ), + ), + 'white_label' => array( + 'nicename' => __( 'White Label', 'neve' ), + 'description' => __( 'For any developer or agency out there building websites for their own clients, we\'ve made it easy to present Neve as your own and use your brand name instead.', 'neve' ), + ), + 'custom_layouts' => array( + 'nicename' => __( 'Custom Layouts', 'neve' ), + 'description' => __( 'Easily create custom headers and footers as well as adding your own custom code or content in any location across your site and display them conditionally.', 'neve' ), + ), + 'elementor_booster' => array( + 'nicename' => __( 'Elementor Booster', 'neve' ), + 'description' => __( 'Leverage the true flexibility of Elementor with powerful addons and templates that you can import with just one click.', 'neve' ), + ), + 'lifterlms_booster' => array( + 'nicename' => __( 'LifterLMS Booster', 'neve' ), + 'description' => __( 'Boost your users learning process with cool new features designed to work smoothly with LifterLMS.', 'neve' ), + ), + 'typekit_fonts' => array( + 'nicename' => __( 'Typekit Fonts', 'neve' ), + 'description' => __( 'Easily embed Adobe Fonts in your WordPress website.', 'neve' ), + ), + 'custom_sidebars' => array( + 'nicename' => __( 'Custom Sidebars', 'neve' ), + 'description' => __( 'Easily create different set of widgets that can be shown throughout your website. Add display conditions to make the content of your sidebar relevant.', 'neve' ), + ), + 'access_restriction' => array( + 'nicename' => __( 'Content restriction', 'neve' ), + 'description' => __( 'Optionally restrict access to specific parts of your website to certain users, user roles, or require a password to access.', 'neve' ), + ), + ); + } + + /** + * Get the recommended plugins. + * + * @return array + */ + 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', ]; + + return $plugins; } /** diff --git a/package.json b/package.json index aa08ea6d4a..2263e9c252 100644 --- a/package.json +++ b/package.json @@ -50,12 +50,14 @@ "pre-commit": "npx lint-staged" }, "dependencies": { + "@headlessui/react": "^2.2.0", "@neve-wp/components": "file:./assets/apps/components", "array-move": "^3.0.1", "classnames": "^2.2.6", "eslint-plugin-prettier": "^3.3.1", "fuse.js": "^6.4.1", - "prop-types": "^15.7.2", + "lucide-react": "^0.468.0", + "prop-types": "^15.8.1", "react-sortablejs": "^6.0.0", "react-visibility-sensor": "^5.1.1", "sortablejs": "^1.13.0", diff --git a/yarn.lock b/yarn.lock index 3e2998bc0f..abedfd7127 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2179,6 +2179,42 @@ minimatch "^3.0.4" strip-json-comments "^3.1.1" +"@floating-ui/core@^1.6.0": + version "1.6.8" + resolved "https://registry.yarnpkg.com/@floating-ui/core/-/core-1.6.8.tgz#aa43561be075815879305965020f492cdb43da12" + integrity sha512-7XJ9cPU+yI2QeLS+FCSlqNFZJq8arvswefkZrYI1yQBbftw6FyrZOxYSh+9S7z7TpeWlRt9zJ5IhM1WIL334jA== + dependencies: + "@floating-ui/utils" "^0.2.8" + +"@floating-ui/dom@^1.0.0": + version "1.6.12" + resolved "https://registry.yarnpkg.com/@floating-ui/dom/-/dom-1.6.12.tgz#6333dcb5a8ead3b2bf82f33d6bc410e95f54e556" + integrity sha512-NP83c0HjokcGVEMeoStg317VD9W7eDlGK7457dMBANbKA6GJZdc7rjujdgqzTaz93jkGgc5P/jeWbaCHnMNc+w== + dependencies: + "@floating-ui/core" "^1.6.0" + "@floating-ui/utils" "^0.2.8" + +"@floating-ui/react-dom@^2.1.2": + version "2.1.2" + resolved "https://registry.yarnpkg.com/@floating-ui/react-dom/-/react-dom-2.1.2.tgz#a1349bbf6a0e5cb5ded55d023766f20a4d439a31" + integrity sha512-06okr5cgPzMNBy+Ycse2A6udMi4bqwW/zgBF/rwjcNqWkyr82Mcg8b0vjX8OJpZFy/FKjJmw6wV7t44kK6kW7A== + dependencies: + "@floating-ui/dom" "^1.0.0" + +"@floating-ui/react@^0.26.16": + version "0.26.28" + resolved "https://registry.yarnpkg.com/@floating-ui/react/-/react-0.26.28.tgz#93f44ebaeb02409312e9df9507e83aab4a8c0dc7" + integrity sha512-yORQuuAtVpiRjpMhdc0wJj06b9JFjrYF4qp96j++v2NBpbi6SEGF7donUJ3TMieerQ6qVkAv1tgr7L4r5roTqw== + dependencies: + "@floating-ui/react-dom" "^2.1.2" + "@floating-ui/utils" "^0.2.8" + tabbable "^6.0.0" + +"@floating-ui/utils@^0.2.8": + version "0.2.8" + resolved "https://registry.yarnpkg.com/@floating-ui/utils/-/utils-0.2.8.tgz#21a907684723bbbaa5f0974cf7730bd797eb8e62" + integrity sha512-kym7SodPp8/wloecOpcmSnWJsK7M0E5Wg8UcFA+uO4B9s5d0ywXOEro/8HM9x0rW+TljRzul/14UYz3TleT3ig== + "@gar/promisify@^1.0.1", "@gar/promisify@^1.1.3": version "1.1.3" resolved "https://registry.yarnpkg.com/@gar/promisify/-/promisify-1.1.3.tgz#555193ab2e3bb3b6adc3d551c9c030d9e860daf6" @@ -2196,6 +2232,16 @@ dependencies: "@hapi/hoek" "^9.0.0" +"@headlessui/react@^2.2.0": + version "2.2.0" + resolved "https://registry.yarnpkg.com/@headlessui/react/-/react-2.2.0.tgz#a8e32f0899862849a1ce1615fa280e7891431ab7" + integrity sha512-RzCEg+LXsuI7mHiSomsu/gBJSjpupm6A1qIZ5sWjd7JhARNlMiSA4kKfJpCKwU9tE+zMRterhhrP74PvfJrpXQ== + dependencies: + "@floating-ui/react" "^0.26.16" + "@react-aria/focus" "^3.17.1" + "@react-aria/interactions" "^3.21.3" + "@tanstack/react-virtual" "^3.8.1" + "@humanwhocodes/config-array@^0.5.0": version "0.5.0" resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.5.0.tgz#1407967d4c6eecd7388f83acf1eaf4d0c6e58ef9" @@ -2723,6 +2769,45 @@ prop-types "^15.6.1" react-lifecycles-compat "^3.0.4" +"@react-aria/focus@^3.17.1": + version "3.19.0" + resolved "https://registry.yarnpkg.com/@react-aria/focus/-/focus-3.19.0.tgz#82b9a5b83f023b943a7970df3d059f49d61df05d" + integrity sha512-hPF9EXoUQeQl1Y21/rbV2H4FdUR2v+4/I0/vB+8U3bT1CJ+1AFj1hc/rqx2DqEwDlEwOHN+E4+mRahQmlybq0A== + dependencies: + "@react-aria/interactions" "^3.22.5" + "@react-aria/utils" "^3.26.0" + "@react-types/shared" "^3.26.0" + "@swc/helpers" "^0.5.0" + clsx "^2.0.0" + +"@react-aria/interactions@^3.21.3", "@react-aria/interactions@^3.22.5": + version "3.22.5" + resolved "https://registry.yarnpkg.com/@react-aria/interactions/-/interactions-3.22.5.tgz#9cd8c93b8b6988f1d315d3efb450119d1432bbb8" + integrity sha512-kMwiAD9E0TQp+XNnOs13yVJghiy8ET8L0cbkeuTgNI96sOAp/63EJ1FSrDf17iD8sdjt41LafwX/dKXW9nCcLQ== + dependencies: + "@react-aria/ssr" "^3.9.7" + "@react-aria/utils" "^3.26.0" + "@react-types/shared" "^3.26.0" + "@swc/helpers" "^0.5.0" + +"@react-aria/ssr@^3.9.7": + version "3.9.7" + resolved "https://registry.yarnpkg.com/@react-aria/ssr/-/ssr-3.9.7.tgz#d89d129f7bbc5148657e6c952ac31c9353183770" + integrity sha512-GQygZaGlmYjmYM+tiNBA5C6acmiDWF52Nqd40bBp0Znk4M4hP+LTmI0lpI1BuKMw45T8RIhrAsICIfKwZvi2Gg== + dependencies: + "@swc/helpers" "^0.5.0" + +"@react-aria/utils@^3.26.0": + version "3.26.0" + resolved "https://registry.yarnpkg.com/@react-aria/utils/-/utils-3.26.0.tgz#833cbfa33e75d15835b757791b3f754432d2f948" + integrity sha512-LkZouGSjjQ0rEqo4XJosS4L3YC/zzQkfRM3KoqK6fUOmUJ9t0jQ09WjiF+uOoG9u+p30AVg3TrZRUWmoTS+koQ== + dependencies: + "@react-aria/ssr" "^3.9.7" + "@react-stately/utils" "^3.10.5" + "@react-types/shared" "^3.26.0" + "@swc/helpers" "^0.5.0" + clsx "^2.0.0" + "@react-spring/animated@~9.3.0": version "9.3.0" resolved "https://registry.yarnpkg.com/@react-spring/animated/-/animated-9.3.0.tgz#294f7696e450c4ae3abd2b59a6dd08bf70b53d3f" @@ -2768,6 +2853,18 @@ "@react-spring/shared" "~9.3.0" "@react-spring/types" "~9.3.0" +"@react-stately/utils@^3.10.5": + version "3.10.5" + resolved "https://registry.yarnpkg.com/@react-stately/utils/-/utils-3.10.5.tgz#47bb91cd5afd1bafe39353614e5e281b818ebccc" + integrity sha512-iMQSGcpaecghDIh3mZEpZfoFH3ExBwTtuBEcvZ2XnGzCgQjeYXcMdIUwAfVQLXFTdHUHGF6Gu6/dFrYsCzySBQ== + dependencies: + "@swc/helpers" "^0.5.0" + +"@react-types/shared@^3.26.0": + version "3.26.0" + resolved "https://registry.yarnpkg.com/@react-types/shared/-/shared-3.26.0.tgz#21a8b579f0097ee78de18e3e580421ced89e4c8c" + integrity sha512-6FuPqvhmjjlpEDLTiYx29IJCbCNWPlsyO+ZUmCUXzhUv2ttShOXfw8CmeHWHftT/b2KweAWuzqSlfeXPR76jpw== + "@rollup/plugin-commonjs@^11.1.0": version "11.1.0" resolved "https://registry.yarnpkg.com/@rollup/plugin-commonjs/-/plugin-commonjs-11.1.0.tgz#60636c7a722f54b41e419e1709df05c7234557ef" @@ -3858,6 +3955,13 @@ "@svgr/plugin-svgo" "^5.5.0" loader-utils "^2.0.0" +"@swc/helpers@^0.5.0": + version "0.5.15" + resolved "https://registry.yarnpkg.com/@swc/helpers/-/helpers-0.5.15.tgz#79efab344c5819ecf83a43f3f9f811fc84b516d7" + integrity sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g== + dependencies: + tslib "^2.8.0" + "@tannin/compile@^1.1.0": version "1.1.0" resolved "https://registry.yarnpkg.com/@tannin/compile/-/compile-1.1.0.tgz#1e4d1c5364cbfeffa1c20352c053e19ef20ffe93" @@ -3883,6 +3987,18 @@ resolved "https://registry.yarnpkg.com/@tannin/postfix/-/postfix-1.1.0.tgz#6071f4204ae26c2e885cf3a3f1203a9f71e3f291" integrity sha512-oocsqY7g0cR+Gur5jRQLSrX2OtpMLMse1I10JQBm8CdGMrDkh1Mg2gjsiquMHRtBs4Qwu5wgEp5GgIYHk4SNPw== +"@tanstack/react-virtual@^3.8.1": + version "3.11.2" + resolved "https://registry.yarnpkg.com/@tanstack/react-virtual/-/react-virtual-3.11.2.tgz#d6b9bd999c181f0a2edce270c87a2febead04322" + integrity sha512-OuFzMXPF4+xZgx8UzJha0AieuMihhhaWG0tCqpp6tDzlFwOmNBPYMuLOtMJ1Tr4pXLHmgjcWhG6RlknY2oNTdQ== + dependencies: + "@tanstack/virtual-core" "3.11.2" + +"@tanstack/virtual-core@3.11.2": + version "3.11.2" + resolved "https://registry.yarnpkg.com/@tanstack/virtual-core/-/virtual-core-3.11.2.tgz#00409e743ac4eea9afe5b7708594d5fcebb00212" + integrity sha512-vTtpNt7mKCiZ1pwU9hfKPhpdVO2sVzFQsxoVBGtOSHxlrRRzYr8iQ2TlwbAcRYCcEiZ9ECAM8kBzH0v2+VzfKw== + "@tootallnate/once@1": version "1.1.2" resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-1.1.2.tgz#ccb91445360179a04e7fe6aff78c00ffc1eeaf82" @@ -7902,6 +8018,11 @@ clone@^1.0.2: resolved "https://registry.yarnpkg.com/clone/-/clone-1.0.4.tgz#da309cc263df15994c688ca902179ca3c7cd7c7e" integrity sha1-2jCcwmPfFZlMaIypAheco8fNfH4= +clsx@^2.0.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/clsx/-/clsx-2.1.1.tgz#eed397c9fd8bd882bfb18deab7102049a2f32999" + integrity sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA== + cmd-shim@^3.0.0, cmd-shim@^3.0.3: version "3.0.3" resolved "https://registry.yarnpkg.com/cmd-shim/-/cmd-shim-3.0.3.tgz#2c35238d3df37d98ecdd7d5f6b8dc6b21cadc7cb" @@ -14860,6 +14981,11 @@ lru-cache@^7.7.1: resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-7.18.3.tgz#f793896e0fd0e954a59dfdd82f0773808df6aa89" integrity sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA== +lucide-react@^0.468.0: + version "0.468.0" + resolved "https://registry.yarnpkg.com/lucide-react/-/lucide-react-0.468.0.tgz#830c1bfd905575ddd23b832baa420c87db166910" + integrity sha512-6koYRhnM2N0GGZIdXzSeiNwguv1gt/FAjZOiPl76roBi3xKEXa4WmfpxgQwTTL4KipXjefrnf3oV4IsYhi4JFA== + magic-string@^0.25.2: version "0.25.7" resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.25.7.tgz#3f497d6fd34c669c6798dcb821f2ef31f5445051" @@ -18313,6 +18439,15 @@ prop-types@^15.0.0, prop-types@^15.5.10, prop-types@^15.5.6, prop-types@^15.5.8, object-assign "^4.1.1" react-is "^16.8.1" +prop-types@^15.8.1: + version "15.8.1" + resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5" + integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg== + dependencies: + loose-envify "^1.4.0" + object-assign "^4.1.1" + react-is "^16.13.1" + property-information@^5.0.0, property-information@^5.3.0: version "5.6.0" resolved "https://registry.yarnpkg.com/property-information/-/property-information-5.6.0.tgz#61675545fb23002f245c6540ec46077d4da3ed69" @@ -21328,6 +21463,11 @@ symbol.prototype.description@^1.0.0: has-symbols "^1.0.1" object.getownpropertydescriptors "^2.1.2" +tabbable@^6.0.0: + version "6.2.0" + resolved "https://registry.yarnpkg.com/tabbable/-/tabbable-6.2.0.tgz#732fb62bc0175cfcec257330be187dcfba1f3b97" + integrity sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew== + table@^6.0.4, table@^6.0.9: version "6.7.2" resolved "https://registry.yarnpkg.com/table/-/table-6.7.2.tgz#a8d39b9f5966693ca8b0feba270a78722cbaf3b0" @@ -21931,6 +22071,11 @@ tslib@^2.1.0, tslib@^2.3.0: resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.3.1.tgz#e8a335add5ceae51aa261d32a490158ef042ef01" integrity sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw== +tslib@^2.8.0: + version "2.8.1" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.8.1.tgz#612efe4ed235d567e8aba5f2a5fab70280ade83f" + integrity sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w== + tsutils@^3.17.1, tsutils@^3.21.0: version "3.21.0" resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-3.21.0.tgz#b48717d394cea6c1e096983eed58e9d61715b623" From 8258a9588961d0ee80e95c4822de6f3fb1a907df Mon Sep 17 00:00:00 2001 From: abaicus Date: Wed, 8 Jan 2025 22:09:16 +0200 Subject: [PATCH 04/24] 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 e5846395ea..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', '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'; } From 12c26f6e648a692e9e6256d73adabe8dd8ae9782 Mon Sep 17 00:00:00 2001 From: abaicus Date: Wed, 15 Jan 2025 17:48:15 +0200 Subject: [PATCH 05/24] feat: new backend dashboard [wip][ref Codeinwp/neve-pro-addon#2914] --- .github/workflows/playwright.yml | 4 +- assets/apps/dashboard/src/Components/App.js | 36 +- .../dashboard/src/Components/Common/Button.js | 8 +- .../src/Components/Common/Multiselect.js | 122 +++++++ .../src/Components/Common/Notification.js | 2 +- .../dashboard/src/Components/Common/Pill.js | 2 +- .../dashboard/src/Components/Common/Select.js | 92 +++++ .../src/Components/Common/TextInput.js | 52 +++ .../src/Components/{ => Common}/Toast.js | 18 +- .../dashboard/src/Components/Common/Toggle.js | 56 ++- .../Components/Common/TransitionWrapper.js | 43 +++ .../src/Components/Content/Changelog.js | 5 +- .../src/Components/Content/FreePro.js | 49 +-- .../src/Components/Content/ModuleGrid.js | 162 ++++++++- .../Content/ModuleGridPlaceholder.js | 62 ---- .../dashboard/src/Components/Content/Pro.js | 23 -- .../src/Components/Content/Settings.js | 139 ++++++++ .../Content/Settings/AccessRestriction.js | 195 +++++++++++ .../Content/Settings/GeneralTabContent.js | 154 +++++++++ .../Settings/ManageModulesTabContent.js | 5 + .../Content/Settings/OptionGroup.js | 123 +++++++ .../Content/Settings/PerformanceTabContent.js | 100 ++++++ .../Content/Settings/WhiteLabelTabContent.js | 318 ++++++++++++++++++ .../{ => Content/Sidebar}/LicenseCard.js | 30 +- .../{ => Content/Sidebar}/PluginsCard.js | 15 +- .../{ => Content/Sidebar}/Sidebar.js | 55 +-- .../{ => Content/Sidebar}/SupportCard.js | 4 +- .../Content/StarterSitesUnavailable.js | 76 +++-- .../src/Components/Content/Welcome.js | 33 +- .../src/Components/Controls/ControlWrap.js | 52 +++ .../Components/Controls/MultiselectControl.js | 80 +++++ .../src/Components/Controls/SelectControl.js | 66 ++++ .../src/Components/Controls/TextControl.js | 90 +++++ .../src/Components/Controls/ToggleControl.js | 76 +++++ .../apps/dashboard/src/Components/Header.js | 104 +++--- .../dashboard/src/Components/Notifications.js | 14 +- .../dashboard/src/Components/PluginCard.js | 179 ---------- .../{Loading.js => SkeletonLoader.js} | 8 +- .../apps/dashboard/src/Components/Snackbar.js | 77 +++-- .../dashboard/src/Components/TabsContent.js | 7 - .../dashboard/src/Hooks/useLicenseData.js | 19 ++ assets/apps/dashboard/src/Layout/Container.js | 2 +- assets/apps/dashboard/src/store/reducer.js | 1 - assets/apps/dashboard/src/tailwind.config.js | 1 - assets/apps/dashboard/src/utils/common.js | 35 +- assets/apps/dashboard/src/utils/constants.js | 18 +- assets/apps/dashboard/src/utils/rest.js | 9 +- inc/admin/dashboard/main.php | 31 +- 48 files changed, 2251 insertions(+), 601 deletions(-) create mode 100644 assets/apps/dashboard/src/Components/Common/Multiselect.js create mode 100644 assets/apps/dashboard/src/Components/Common/Select.js create mode 100644 assets/apps/dashboard/src/Components/Common/TextInput.js rename assets/apps/dashboard/src/Components/{ => Common}/Toast.js (71%) create mode 100644 assets/apps/dashboard/src/Components/Common/TransitionWrapper.js delete mode 100644 assets/apps/dashboard/src/Components/Content/ModuleGridPlaceholder.js delete mode 100644 assets/apps/dashboard/src/Components/Content/Pro.js create mode 100644 assets/apps/dashboard/src/Components/Content/Settings.js create mode 100644 assets/apps/dashboard/src/Components/Content/Settings/AccessRestriction.js create mode 100644 assets/apps/dashboard/src/Components/Content/Settings/GeneralTabContent.js create mode 100644 assets/apps/dashboard/src/Components/Content/Settings/ManageModulesTabContent.js create mode 100644 assets/apps/dashboard/src/Components/Content/Settings/OptionGroup.js create mode 100644 assets/apps/dashboard/src/Components/Content/Settings/PerformanceTabContent.js create mode 100644 assets/apps/dashboard/src/Components/Content/Settings/WhiteLabelTabContent.js rename assets/apps/dashboard/src/Components/{ => Content/Sidebar}/LicenseCard.js (85%) rename assets/apps/dashboard/src/Components/{ => Content/Sidebar}/PluginsCard.js (89%) rename assets/apps/dashboard/src/Components/{ => Content/Sidebar}/Sidebar.js (68%) rename assets/apps/dashboard/src/Components/{ => Content/Sidebar}/SupportCard.js (88%) create mode 100644 assets/apps/dashboard/src/Components/Controls/ControlWrap.js create mode 100644 assets/apps/dashboard/src/Components/Controls/MultiselectControl.js create mode 100644 assets/apps/dashboard/src/Components/Controls/SelectControl.js create mode 100644 assets/apps/dashboard/src/Components/Controls/TextControl.js create mode 100644 assets/apps/dashboard/src/Components/Controls/ToggleControl.js delete mode 100644 assets/apps/dashboard/src/Components/PluginCard.js rename assets/apps/dashboard/src/Components/{Loading.js => SkeletonLoader.js} (94%) delete mode 100644 assets/apps/dashboard/src/Components/TabsContent.js create mode 100644 assets/apps/dashboard/src/Hooks/useLicenseData.js diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml index c0c31e96cc..60a282ee2e 100644 --- a/.github/workflows/playwright.yml +++ b/.github/workflows/playwright.yml @@ -27,7 +27,7 @@ jobs: - name: Get yarn cache directory path id: yarn-cache-dir-path run: echo "::set-output name=dir::$(yarn cache dir)" - - uses: actions/cache@v2 + - uses: actions/cache@v3 id: yarn-cache with: path: ${{ steps.yarn-cache-dir-path.outputs.dir }} @@ -39,7 +39,7 @@ jobs: run: | echo "::set-output name=dir::$(composer config cache-files-dir)" - name: Configure Composer cache - uses: actions/cache@v1 + uses: actions/cache@v3 with: path: ${{ steps.composer-cache.outputs.dir }} key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} diff --git a/assets/apps/dashboard/src/Components/App.js b/assets/apps/dashboard/src/Components/App.js index 2bd5178632..e64ca42f4f 100644 --- a/assets/apps/dashboard/src/Components/App.js +++ b/assets/apps/dashboard/src/Components/App.js @@ -1,25 +1,25 @@ +import Container from '../Layout/Container'; +import { fetchOptions } from '../utils/rest'; +import Sidebar from './Content/Sidebar/Sidebar'; import Header from './Header'; import Notifications from './Notifications'; -import TabsContent from './TabsContent'; -import Sidebar from './Sidebar'; -import Loading from './Loading'; +import SkeletonLoader from './SkeletonLoader'; import Snackbar from './Snackbar'; -import Container from '../Layout/Container'; -import { fetchOptions } from '../utils/rest'; import { useDispatch, useSelect } from '@wordpress/data'; -import { useState, useEffect } from '@wordpress/element'; -import Deal from './Deal'; +import { useEffect, useState } from '@wordpress/element'; +import { tabs } from '../utils/common'; +import { TransitionWrapper } from './Common/TransitionWrapper'; +import { NEVE_STORE } from '../utils/constants'; const App = () => { const [loading, setLoading] = useState(true); - const { setSettings, setTab } = useDispatch('neve-dashboard'); + const { setSettings, setTab } = useDispatch(NEVE_STORE); - const { toast, currentTab } = useSelect((select) => { - const { getToast, getTab } = select('neve-dashboard'); + const { currentTab } = useSelect((select) => { + const { getTab } = select(NEVE_STORE); return { - toast: getToast(), currentTab: getTab(), }; }); @@ -32,7 +32,7 @@ const App = () => { }, []); if (loading) { - return ; + return ; } return (
@@ -42,18 +42,16 @@ const App = () => { {'starter-sites' !== currentTab && } -
- -
+
{tabs[currentTab].render(setTab)}
- {'starter-sites' !== currentTab && ( -
+ {!['starter-sites', 'settings'].includes(currentTab) && ( + -
+ )}
- {toast && } +
); }; diff --git a/assets/apps/dashboard/src/Components/Common/Button.js b/assets/apps/dashboard/src/Components/Common/Button.js index cd06b52110..414c35cb46 100644 --- a/assets/apps/dashboard/src/Components/Common/Button.js +++ b/assets/apps/dashboard/src/Components/Common/Button.js @@ -6,7 +6,7 @@ const Button = (props) => { const { href, onClick, - className, + className = '', isSubmit, isPrimary, isSecondary, @@ -17,20 +17,22 @@ const Button = (props) => { } = props; const classNames = cn([ - 'flex items-center px-3 py-2 transition-colors duration-150 rounded text-sm border gap-2', + 'flex items-center px-3 py-2 transition-colors duration-150 text-sm border gap-2', { + rounded: !className.includes('rounded'), 'border-transparent bg-blue-600 text-white hover:bg-blue-700 hover:text-white': isPrimary, 'border-blue-600 text-blue-600 hover:bg-blue-600 hover:text-white': isSecondary, 'border-transparent text-gray-600 hover:text-gray-900': isLink, - 'cursor-not-allowed opacity-50 pointer-events-none': disabled, + 'cursor-not-allowed opacity-50': disabled, }, className, ]); const passedProps = { className: classNames, + disabled, onClick, }; diff --git a/assets/apps/dashboard/src/Components/Common/Multiselect.js b/assets/apps/dashboard/src/Components/Common/Multiselect.js new file mode 100644 index 0000000000..ea4c9582da --- /dev/null +++ b/assets/apps/dashboard/src/Components/Common/Multiselect.js @@ -0,0 +1,122 @@ +import { useRef, useState } from '@wordpress/element'; +import { __ } from '@wordpress/i18n'; +import cn from 'classnames'; +import { Check, ChevronDown } from 'lucide-react'; +import { useEffect } from 'react'; +const MultiSelect = ({ value, label, disabled, choices = {}, onChange }) => { + const [isOpen, setIsOpen] = useState(false); + + const dropdownRef = useRef(null); + + const closeDropdown = (e) => { + if (dropdownRef.current && !dropdownRef.current.contains(e.target)) { + setIsOpen(false); + } + }; + + useEffect(() => { + if (isOpen) { + document.addEventListener('click', closeDropdown); + } else { + document.removeEventListener('click', closeDropdown); + } + + return () => { + document.removeEventListener('click', closeDropdown); + }; + }, [isOpen]); + + const handleChange = (optionValue) => { + const nextValues = value.includes(optionValue) + ? value.filter((v) => v !== optionValue) + : [...value, optionValue]; + onChange(nextValues); + }; + + return ( +
+ {label && ( + + {label} + + )} +
+ + + {isOpen && ( +
+
+ {Object.entries(choices).map( + ([optionValue, optionLabel]) => ( + + ) + )} +
+
+ )} +
+
+ ); +}; + +export default MultiSelect; diff --git a/assets/apps/dashboard/src/Components/Common/Notification.js b/assets/apps/dashboard/src/Components/Common/Notification.js index 27a7a8d28c..8811fe691a 100644 --- a/assets/apps/dashboard/src/Components/Common/Notification.js +++ b/assets/apps/dashboard/src/Components/Common/Notification.js @@ -15,7 +15,7 @@ import Tooltip from './Tooltip'; import TransitionInOut from './TransitionInOut'; import { useEffect } from 'react'; -const Notification = ({ data, slug }) => { +const Notification = ({ data }) => { const [hidden, setHidden] = useState(false); const { text, cta, type, update, url, targetBlank } = data; const { canInstallPlugins } = neveDash; diff --git a/assets/apps/dashboard/src/Components/Common/Pill.js b/assets/apps/dashboard/src/Components/Common/Pill.js index d732a7e1fd..6d4137c8c8 100644 --- a/assets/apps/dashboard/src/Components/Common/Pill.js +++ b/assets/apps/dashboard/src/Components/Common/Pill.js @@ -8,7 +8,7 @@ export default ({ children, type = 'primary', className }) => { const typeClasses = { primary: 'bg-blue-100 text-blue-700', secondary: 'bg-gray-100 text-gray-700', - success: 'bg-green-100 text-green-700', + success: 'bg-lime-100 text-lime-700', error: 'bg-red-100 text-red-700', warning: 'bg-yellow-100 text-yellow-700', }; diff --git a/assets/apps/dashboard/src/Components/Common/Select.js b/assets/apps/dashboard/src/Components/Common/Select.js new file mode 100644 index 0000000000..9f3e3b7e00 --- /dev/null +++ b/assets/apps/dashboard/src/Components/Common/Select.js @@ -0,0 +1,92 @@ +import cn from 'classnames'; +import { LoaderCircle, LucideChevronDown } from 'lucide-react'; + +import { + Field, + Label, + Listbox, + ListboxButton, + ListboxOption, + ListboxOptions, +} from '@headlessui/react'; +import { __ } from '@wordpress/i18n'; + +export default ({ + label, + value, + onChange, + disabled = false, + loading, + choices, +}) => { + return ( + + {label && ( + + )} +
+ {loading && ( + + )} + + {({ open }) => ( + <> + + {choices[value] || + __('Select an option', 'neve')} + + + + + {Object.entries(choices).map( + ([optionValue, optionLabel]) => ( + + {optionLabel} + + ) + )} + + + )} + +
+
+ ); +}; diff --git a/assets/apps/dashboard/src/Components/Common/TextInput.js b/assets/apps/dashboard/src/Components/Common/TextInput.js new file mode 100644 index 0000000000..6c178baa1a --- /dev/null +++ b/assets/apps/dashboard/src/Components/Common/TextInput.js @@ -0,0 +1,52 @@ +import { Description, Field, Input, Label } from '@headlessui/react'; +import { Fragment } from '@wordpress/element'; +import cn from 'classnames'; + +const TextInput = ({ + value, + label, + disabled, + onChange, + name, + className = '', + type = 'text', + description, +}) => { + let TagName = 'input'; + + if (type === 'textarea') { + TagName = 'textarea'; + } + + return ( + + {label && ( + + )} + {description && ( + + {description} + + )} + + + + + ); +}; + +export default TextInput; diff --git a/assets/apps/dashboard/src/Components/Toast.js b/assets/apps/dashboard/src/Components/Common/Toast.js similarity index 71% rename from assets/apps/dashboard/src/Components/Toast.js rename to assets/apps/dashboard/src/Components/Common/Toast.js index ca6851c000..009b11beab 100644 --- a/assets/apps/dashboard/src/Components/Toast.js +++ b/assets/apps/dashboard/src/Components/Common/Toast.js @@ -7,9 +7,9 @@ import { LucideCircleX, LucideInfo, } from 'lucide-react'; -import TransitionInOut from './Common/TransitionInOut'; +import TransitionInOut from './TransitionInOut'; -const Toast = ({ message, dismiss, time, type = 'info' }) => { +const Toast = ({ message, dismiss, time, type = 'info', className }) => { const [show, setShow] = useState(false); useEffect(() => { @@ -23,7 +23,7 @@ const Toast = ({ message, dismiss, time, type = 'info' }) => { }, timeBeforeHide); const dismissTimeout = setTimeout(() => { - dismiss(''); + if (dismiss) dismiss(''); }, timeBeforeDismiss); return () => { @@ -42,12 +42,12 @@ const Toast = ({ message, dismiss, time, type = 'info' }) => { const classes = cn( 'px-2 py-1.5 flex items-center text-sm border rounded gap-2', { - 'border-sky-300 bg-sky-50 text-sky-800': type === 'info', - 'border-red-300 bg-red-50 text-red-800': type === 'error', - 'border-lime-300 bg-lime-50 text-lime-800': type === 'success', - 'border-orange-300 bg-orange-50 text-orange-800': - type === 'warning', - } + 'bg-sky-50 text-sky-800': type === 'info', + 'bg-red-50 text-red-800': type === 'error', + 'bg-lime-50 text-lime-800': type === 'success', + 'bg-orange-50 text-orange-800': type === 'warning', + }, + className ); const ICON = iconMap[type]; diff --git a/assets/apps/dashboard/src/Components/Common/Toggle.js b/assets/apps/dashboard/src/Components/Common/Toggle.js index 0a82469904..d5e94a462b 100644 --- a/assets/apps/dashboard/src/Components/Common/Toggle.js +++ b/assets/apps/dashboard/src/Components/Common/Toggle.js @@ -1,7 +1,16 @@ -import { Switch, Label, Field } from '@headlessui/react'; +import { Switch, Label, Field, Description } from '@headlessui/react'; import cn from 'classnames'; -export default ({ checked, onToggle, label, disabled = false, className }) => { +export default ({ + checked, + onToggle, + label, + disabled = false, + className, + labelBefore = false, + labelClassName = '', + description, +}) => { const switchClasses = cn( 'group inline-flex h-6 w-11 items-center rounded-full bg-gray-300 transition data-[checked]:bg-blue-600', { @@ -11,18 +20,41 @@ export default ({ checked, onToggle, label, disabled = false, className }) => { const wrapClasses = cn('flex items-center gap-3', className); + const labelClasses = cn( + { + 'font-medium': !labelClassName.includes('font-'), + 'text-sm': !labelClassName.includes('text-'), + 'text-gray-600': !labelClassName.includes('text-'), + }, + labelClassName + ); + return ( - - - - + +
+ {label && labelBefore && ( + + )} + + + + + + {label && !labelBefore && ( + + )} +
- + {description && ( + + {description} + + )}
); }; diff --git a/assets/apps/dashboard/src/Components/Common/TransitionWrapper.js b/assets/apps/dashboard/src/Components/Common/TransitionWrapper.js new file mode 100644 index 0000000000..eb89938b58 --- /dev/null +++ b/assets/apps/dashboard/src/Components/Common/TransitionWrapper.js @@ -0,0 +1,43 @@ +import { Transition } from '@headlessui/react'; +import cn from 'classnames'; + +export const TransitionWrapper = ({ children, from = 'bottom', className }) => { + const directionClasses = { + left: { + enterFrom: '-translate-x-2', + enterTo: 'translate-x-0', + }, + right: { + enterFrom: 'translate-x-2', + enterTo: 'translate-x-0', + }, + top: { + enterFrom: '-translate-y-2', + enterTo: 'translate-y-0', + }, + bottom: { + enterFrom: 'translate-y-2', + enterTo: 'translate-y-0', + }, + }; + + const animationData = directionClasses[from] || directionClasses.bottom; + + const transitionClasses = { + enter: 'ease-out duration-150', + enterFrom: `opacity-0 ${animationData.enterFrom}`, + enterTo: `opacity-100 ${animationData.enterTo}`, + }; + + return ( + + {children} + + ); +}; diff --git a/assets/apps/dashboard/src/Components/Content/Changelog.js b/assets/apps/dashboard/src/Components/Content/Changelog.js index 6f9033c725..6cfefdac64 100644 --- a/assets/apps/dashboard/src/Components/Content/Changelog.js +++ b/assets/apps/dashboard/src/Components/Content/Changelog.js @@ -7,6 +7,7 @@ import { useState } from '@wordpress/element'; import Card from '../../Layout/Card'; import Button from '../Common/Button'; import Pill from '../Common/Pill'; +import { TransitionWrapper } from '../Common/TransitionWrapper'; const TAB_CHOICES = { FREE: 'free', @@ -112,7 +113,7 @@ const Changelog = () => { activeTab === TAB_CHOICES.FREE ? changelog : changelogPro; return ( -
+ {changelogPro && (
@@ -165,7 +166,7 @@ const Changelog = () => {
)} -
+ ); }; diff --git a/assets/apps/dashboard/src/Components/Content/FreePro.js b/assets/apps/dashboard/src/Components/Content/FreePro.js index 9c629d9623..43346990c9 100644 --- a/assets/apps/dashboard/src/Components/Content/FreePro.js +++ b/assets/apps/dashboard/src/Components/Content/FreePro.js @@ -13,42 +13,7 @@ import { import Card from '../../Layout/Card'; import Tooltip from '../Common/Tooltip'; import Button from '../Common/Button'; - -const IntroCard = () => { - const upgradeList = [ - __('Need advanced header/footer customization options', 'neve'), - __( - 'Run an online store and want enhanced WooCommerce features', - 'neve' - ), - __('Build multilingual or RTL websites', 'neve'), - __('Create websites for clients and need white-labeling', 'neve'), - ]; - - return ( - -
-

- {__( - "While Neve's free version includes everything you need to build a great website, Neve Pro adds advanced customization options and specific features for e-commerce, multilingual sites, and client projects. Here's a detailed comparison to help you decide if Pro is right for your needs.", - 'neve' - )} -

-
- - {__('Considering an upgrade?', 'neve')} - - {__('Pro features are most helpful if you:', 'neve')} -
    - {upgradeList.map((item, index) => ( -
  • • {item}
  • - ))} -
-
-
-
- ); -}; +import { TransitionWrapper } from '../Common/TransitionWrapper'; const FreeProCard = () => ( @@ -122,14 +87,14 @@ const UpsellCard = () => {

{__('Need help deciding?', 'neve')}

-
+

{__( 'Our support team is happy to answer your questions about specific Pro features and help you determine if they match your needs.', 'neve' )}

-
+
{__( 'Average response time: ~8 hours during business days', @@ -166,8 +131,12 @@ const UpsellCard = () => { export default () => { return (
- - + + + + + +
); }; diff --git a/assets/apps/dashboard/src/Components/Content/ModuleGrid.js b/assets/apps/dashboard/src/Components/Content/ModuleGrid.js index bb7a63a6e1..754bd26ef7 100644 --- a/assets/apps/dashboard/src/Components/Content/ModuleGrid.js +++ b/assets/apps/dashboard/src/Components/Content/ModuleGrid.js @@ -1,6 +1,164 @@ +/* global neveDash */ +import { useDispatch, useSelect } from '@wordpress/data'; +import { useState } from '@wordpress/element'; import { __ } from '@wordpress/i18n'; -import { NEVE_HAS_VALID_PRO } from '../../utils/constants'; +import { LoaderCircle, LucideSettings } from 'lucide-react'; + +import useLicenseData from '../../Hooks/useLicenseData'; +import Card from '../../Layout/Card'; +import { + NEVE_HAS_PRO, + NEVE_MODULE_ICON_MAP, + NEVE_STORE, +} from '../../utils/constants'; +import Link from '../Common/Link'; +import Pill from '../Common/Pill'; +import Toggle from '../Common/Toggle'; +import Tooltip from '../Common/Tooltip'; +import { changeOption } from '../../utils/rest'; +import Button from '../Common/Button'; + +const ModuleToggle = ({ slug, moduleData }) => { + const [loading, setLoading] = useState(false); + + const { licenseTier, isLicenseValid } = useLicenseData(); + const { changeModuleStatus, setToast } = useDispatch(NEVE_STORE); + const { moduleStatus } = useSelect((select) => { + const { getModuleStatus } = select(NEVE_STORE); + + return { + moduleStatus: getModuleStatus(slug) || false, + }; + }); + + if (!NEVE_HAS_PRO) { + return ( + + + {__('Pro', 'neve')} + + + ); + } + + const { nicename, availabilityLevel } = moduleData; + const { upgradeLinks } = neveDash; + + if (!isLicenseValid || licenseTier < availabilityLevel) { + return ( + + ); + } + + const handleToggle = (value) => { + setLoading(true); + changeModuleStatus(slug, value); + + changeOption(slug, value, true).then((r) => { + if (r.success) { + setLoading(false); + setToast( + (value + ? __('Module Activated', 'neve') + : __('Module Deactivated.', 'neve')) + ` (${nicename})` + ); + return; + } + changeModuleStatus(slug, !value); + setLoading(false); + setToast( + __('Could not activate module. Please try again.', 'neve') + ); + }); + }; + + return ( +
+ {loading && } + +
+ ); +}; + +const ModuleCard = ({ moduleData, slug }) => { + const { nicename, description, documentation, hide } = moduleData; + const CardIcon = NEVE_MODULE_ICON_MAP[slug] || LucideSettings; + + if (hide) { + return null; + } + + return ( + } + title={nicename} + className="bg-white p-6 rounded-lg shadow-sm" + afterTitle={} + > +

+ {description}{' '} + {documentation && documentation.url && ( + + )} +

+
+ ); +}; + +const ModulesHeader = () => { + const { isLicenseValid } = useLicenseData(); + + return ( +
+

+ {__('Neve Pro Modules', 'neve')} +

+ {!isLicenseValid && ( + + )} +
+ ); +}; export default () => { - return
; + const unorderedModuels = Object.entries(neveDash.modules); + + const orderedModules = unorderedModuels.sort((a, b) => { + if (a[1].order && b[1].order) { + return a[1].order - b[1].order; + } + return 0; + }); + + return ( + <> + +
+ {orderedModules.map(([slug, moduleData]) => ( + + ))} +
+ + ); }; diff --git a/assets/apps/dashboard/src/Components/Content/ModuleGridPlaceholder.js b/assets/apps/dashboard/src/Components/Content/ModuleGridPlaceholder.js deleted file mode 100644 index 2b53eeca3c..0000000000 --- a/assets/apps/dashboard/src/Components/Content/ModuleGridPlaceholder.js +++ /dev/null @@ -1,62 +0,0 @@ -/* global neveDash */ -import { __ } from '@wordpress/i18n'; -import { LucideSettings } from 'lucide-react'; -import Card from '../../Layout/Card'; -import { - NEVE_HAS_VALID_PRO, - NEVE_MODULE_ICON_MAP, -} from '../../utils/constants'; -import Tooltip from '../Common/Tooltip'; -import Pill from '../Common/Pill'; -import Link from '../Common/Link'; - -const ModuleCardPlaceholder = ({ slug, title, description }) => { - const CardIcon = NEVE_MODULE_ICON_MAP[slug] || LucideSettings; - - const ProBadge = ( - - {__('Pro', 'neve')} - - ); - - return ( - } - title={title} - className="bg-white p-6 rounded-lg shadow-sm" - afterTitle={ProBadge} - > -

- {description} -

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

- {__('Neve Pro Modules', 'neve')} -

- -
- {Object.entries(neveDash.modules).map( - ([slug, { nicename, description }]) => ( - - ) - )} -
- ); -}; diff --git a/assets/apps/dashboard/src/Components/Content/Pro.js b/assets/apps/dashboard/src/Components/Content/Pro.js deleted file mode 100644 index ad358d15fd..0000000000 --- a/assets/apps/dashboard/src/Components/Content/Pro.js +++ /dev/null @@ -1,23 +0,0 @@ -/* global neveDash */ -import { CircleFadingArrowUp } from 'lucide-react'; -import Notice from '../Common/Notice'; -import ModuleCard from '../ModuleCard'; -const Pro = () => { - const { modules, hasOldPro, strings } = neveDash; - - if (hasOldPro) { - return ( - {strings.updateOldPro} - ); - } - - return ( -
- {Object.keys(modules).map((id, index) => { - return ; - })} -
- ); -}; - -export default Pro; diff --git a/assets/apps/dashboard/src/Components/Content/Settings.js b/assets/apps/dashboard/src/Components/Content/Settings.js new file mode 100644 index 0000000000..ab8ce0cfe4 --- /dev/null +++ b/assets/apps/dashboard/src/Components/Content/Settings.js @@ -0,0 +1,139 @@ +/* global neveDash */ +import { useState } from '@wordpress/element'; +import { __ } from '@wordpress/i18n'; +import cn from 'classnames'; +import { + CircleFadingArrowUp, + LucideBriefcase, + LucideGauge, + LucidePuzzle, + LucideSettings, +} from 'lucide-react'; + +import { useSelect } from '@wordpress/data'; +import Card from '../../Layout/Card'; +import { + NEVE_HAS_PRO, + NEVE_IS_WHITELABEL, + NEVE_STORE, +} from '../../utils/constants'; +import Notice from '../Common/Notice'; +import { TransitionWrapper } from '../Common/TransitionWrapper'; +import GeneralTabContent from './Settings/GeneralTabContent'; +import ManageModulesTabContent from './Settings/ManageModulesTabContent'; +import PerformanceTabContent from './Settings/PerformanceTabContent'; +import WhiteLabelTabContent from './Settings/WhiteLabelTabContent'; + +const NAV_ITEMS = [ + { + id: 'general', + label: __('General', 'neve'), + icon: LucideSettings, + }, + { + id: 'performance', + label: __('Performance', 'neve'), + icon: LucideGauge, + }, + { + id: 'white-label', + label: __('White Label', 'neve'), + icon: LucideBriefcase, + }, + { + id: 'manage-modules', + label: __('Manage Modules', 'neve'), + icon: LucidePuzzle, + }, +]; + +const Menu = ({ tab, setTab }) => { + const { whiteLabelStatus } = useSelect((select) => { + const { getModuleStatus } = select(NEVE_STORE); + + return { + whiteLabelStatus: getModuleStatus('white_label') || false, + }; + }); + + const menuItems = NAV_ITEMS.filter(({ id }) => { + if (id === 'manage-modules') return NEVE_HAS_PRO; + + if (id === 'white-label') + return whiteLabelStatus && !NEVE_IS_WHITELABEL; + + return true; + }); + + return ( + + + {menuItems.map(({ id, label, icon }) => { + const Icon = icon; + const classes = cn( + 'w-full flex items-center px-4 py-3 text-left', + { + 'text-gray-600 hover:bg-gray-50': tab !== id, + 'bg-blue-50 text-blue-600': tab === id, + } + ); + + return ( + + ); + })} + + + ); +}; + +const Settings = () => { + const { hasOldPro, strings } = neveDash; + + const [tab, setTab] = useState(NAV_ITEMS[0].id); + + if (hasOldPro) { + return ( + {strings.updateOldPro} + ); + } + + return ( +
+
+ +
+ + {tab === 'general' && ( + + + + )} + {tab === 'performance' && ( + + + + )} + {tab === 'white-label' && ( + + + + )} + {tab === 'manage-modules' && ( + + + + )} + +
+ ); +}; + +export default Settings; diff --git a/assets/apps/dashboard/src/Components/Content/Settings/AccessRestriction.js b/assets/apps/dashboard/src/Components/Content/Settings/AccessRestriction.js new file mode 100644 index 0000000000..cba6a517bc --- /dev/null +++ b/assets/apps/dashboard/src/Components/Content/Settings/AccessRestriction.js @@ -0,0 +1,195 @@ +/* global neveAccessRestriction */ + +import apiFetch from '@wordpress/api-fetch'; +import { useState } from '@wordpress/element'; +import { __ } from '@wordpress/i18n'; +import { LucideLoaderCircle } from 'lucide-react'; +import Notice from '../../Common/Notice'; +import Select from '../../Common/Select'; +import Toggle from '../../Common/Toggle'; +import ControlWrap from '../../Controls/ControlWrap'; +import { NEVE_STORE } from '../../../utils/constants'; +import { useDispatch } from '@wordpress/data'; + +export const saveOption = (value) => { + return new Promise((resolve) => { + apiFetch({ + path: neveAccessRestriction.settingsRoute, + method: 'POST', + data: { settings: value }, + }) + .then((responseRaw) => { + const response = JSON.parse(responseRaw); + const status = response.status === 'success'; + resolve({ success: status }); + }) + .catch(() => { + resolve({ success: false }); + }); + }); +}; + +const AccessRestriction = ({ optionData }) => { + const [settings, setSettings] = useState(neveAccessRestriction.options); + const [saving, setSaving] = useState(false); + const [error, setError] = useState(''); + + const { setToast } = useDispatch(NEVE_STORE); + + const updateContentTypeStatus = (slug, status) => { + const newSettings = { ...settings }; + + newSettings.content_types[slug].enabled = status; + + setSettings(newSettings); + saveAsync(newSettings); + }; + + const updateSetting = (slug, value) => { + const newSettings = { + ...settings, + [slug]: value, + }; + + setSettings(newSettings); + saveAsync(newSettings); + }; + + const saveAsync = (newSettings = null) => { + const settingsToSave = newSettings || settings; + setSaving(true); + setError(''); + saveOption(JSON.stringify(settingsToSave)) + .then((r) => { + if (!r.success) { + setError( + __('An error occurred. Please try again.', 'neve') + ); + setToast(false); + return; + } + setToast(true); + neveAccessRestriction.options = newSettings; + }) + .finally(() => { + setSaving(false); + }); + }; + + return ( + + + {__('Saving', 'neve')}... +
+ ) : null + } + > +
+ { + return callbackSettings[callbackKey].enabled; + }} + settings={neveAccessRestriction.options.content_types} + /> +
+ +
+ +
+ + {'' !== error && ( + + {error} + + )} + + ); +}; + +const defaultValueCallback = (settings, key) => settings[key]; + +const Fields = ({ + type, + updateSetting, + settings, + valueCallback = defaultValueCallback, +}) => { + const { fields } = neveAccessRestriction.fields[type]; + + return ( + <> + {Object.keys(fields).map((key, index) => { + const { type: fieldType, label, description } = fields[key]; + + if (fields[key].parent) { + const parent = fields[key].parent; + + if (settings[parent.fieldKey] !== parent.fieldValue) { + return null; + } + } + + const value = valueCallback(settings, key); + + return ( +
+ {'toggle' === fieldType && ( + <> + { + const status = newValue ? 'yes' : 'no'; + updateSetting(key, status); + }} + /> + {value && description && ( +

{description}

+ )} + + )} + {'select' === fieldType && ( + <> + - )} - {(('multi_select' === type && - undefined === dependsOn) || - ('multi_select' === type && - isToggleEnabled( - dependsOn - ))) && ( - - )} - {(('react' === type && - undefined === dependsOn) || - ('react' === type && - isToggleEnabled( - dependsOn - ))) && ( - - )} - - ); - } - )} -
- - ); - }); - }; - - return ( -
-
-

{nicename}

-
- {tier < availabilityLevel ? ( - - ) : ( - - {required_actions && !manageableDependentPlugins && ( - - )} - {loading && ( - - )} - {manageableDependentPlugins && ( - - )} - {!required_actions && - 'block_editor_booster' !== slug && ( - { - setLoading(true); - changeOption( - slug, - value, - true - ).then((r) => { - if (r.success) { - changeModuleStatus( - slug, - value - ); - setLoading(false); - setToast( - (value - ? __( - 'Module Activated', - 'neve' - ) - : __( - 'Module Deactivated.', - 'neve' - )) + - ` (${nicename})` - ); - return false; - } - setLoading(false); - setToast( - __( - 'Could not activate module. Please try again.', - 'neve' - ) - ); - }); - }} - /> - )} - - )} -
-
-
-

- {description + ' '} - {documentation?.url && ( - - {__('Learn More', 'neve')} - - )} -

- {links && getModuleStatus(slug) && ( -
- {links.map((link, index) => ( - - ))} -
- )} - {0 < options?.length && - true === getModuleStatus(slug) && - -1 < tier && ( -
- {renderOptionsAccordions()} -
- )} -
-
- ); -}; - -export default compose( - withSelect((select) => { - const { getModuleStatus, getLicenseTier, getProOption } = - select('neve-dashboard'); - return { - getOption: (slug) => getProOption(slug), - getModuleStatus: (slug) => getModuleStatus(slug), - tier: getLicenseTier(), - }; - }), - withDispatch((dispatch) => { - const { changeModuleStatus, setToast } = dispatch('neve-dashboard'); - return { - changeModuleStatus: (slug, value) => - changeModuleStatus(slug, value), - setToast: (message) => setToast(message), - }; - }) -)(ModuleCard); From ac52dce700686c69dda65314fe188c0c82aeb236 Mon Sep 17 00:00:00 2001 From: abaicus Date: Mon, 27 Jan 2025 16:07:05 +0200 Subject: [PATCH 13/24] feat: allow dependend modules for notifications --- .../dashboard/src/Components/Notifications.js | 30 ++++++------------- assets/apps/dashboard/src/store/reducer.js | 1 + assets/apps/dashboard/src/store/selectors.js | 17 +++++++++++ 3 files changed, 27 insertions(+), 21 deletions(-) diff --git a/assets/apps/dashboard/src/Components/Notifications.js b/assets/apps/dashboard/src/Components/Notifications.js index aaaeffef8a..d3d251ab2c 100644 --- a/assets/apps/dashboard/src/Components/Notifications.js +++ b/assets/apps/dashboard/src/Components/Notifications.js @@ -1,32 +1,20 @@ -/* global neveDash */ import Notification from './Common/Notification'; import Container from '../Layout/Container'; import TransitionWrapper from './Common/TransitionWrapper'; +import { useSelect } from '@wordpress/data'; const Notifications = () => { - if (!neveDash.notifications) { - return null; - } - if (1 > neveDash.notifications.length) { - return null; - } + const notifications = useSelect((select) => { + return select('neve-dashboard').getNotifications(); + }); return ( - {Object.keys(neveDash.notifications).map((slug, index) => { - return ( - - - - ); - })} + {Object.entries(notifications).map(([slug, data]) => ( + + + + ))} ); }; diff --git a/assets/apps/dashboard/src/store/reducer.js b/assets/apps/dashboard/src/store/reducer.js index 375854f26c..d2cd911b7c 100644 --- a/assets/apps/dashboard/src/store/reducer.js +++ b/assets/apps/dashboard/src/store/reducer.js @@ -7,6 +7,7 @@ const initialState = { toast: null, currentTab: 'start', license: neveDash.pro ? neveDash.license : {}, + notifications: neveDash.notifications || {}, }; const hash = getTabHash(); diff --git a/assets/apps/dashboard/src/store/selectors.js b/assets/apps/dashboard/src/store/selectors.js index 4f2cf76db9..cb576148e4 100644 --- a/assets/apps/dashboard/src/store/selectors.js +++ b/assets/apps/dashboard/src/store/selectors.js @@ -8,4 +8,21 @@ export default { getLicense: (state) => state.license, getToast: (state) => state.toast, getTab: (state) => state.currentTab, + getNotifications: (state) => { + const shownNotifications = { ...state.notifications }; + + Object.entries(state.notifications).forEach(([slug, data]) => { + if (!data.dependentModule) { + return; + } + + const option = `nv_pro_${data.dependentModule}_status`; + + if (!state.settings[option]) { + delete shownNotifications[slug]; + } + }); + + return shownNotifications; + }, }; From 0daea906dd747282f32bb939e2b1eb3fc1c5b31d Mon Sep 17 00:00:00 2001 From: abaicus Date: Mon, 27 Jan 2025 17:27:58 +0200 Subject: [PATCH 14/24] fix: remove modules that relate to other plugins when those are not active --- inc/admin/dashboard/main.php | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/inc/admin/dashboard/main.php b/inc/admin/dashboard/main.php index fd03c6c674..b746e43351 100755 --- a/inc/admin/dashboard/main.php +++ b/inc/admin/dashboard/main.php @@ -636,7 +636,7 @@ private function get_free_pro_features() { * @return array[] */ private function get_modules() { - return array( + $plugins = array( 'hfg_module' => array( 'nicename' => __( 'Header Booster', 'neve' ), 'description' => __( 'Extend your header with more components and settings, build sticky/transparent headers or display them conditionally.', 'neve' ), @@ -644,10 +644,12 @@ private function get_modules() { 'woocommerce_booster' => array( 'nicename' => __( 'WooCommerce Booster', 'neve' ), 'description' => __( 'Empower your online store with awesome new features, specially designed for a smooth WooCommerce integration.', 'neve' ), + 'condition' => class_exists( 'WooCommerce' ), ), 'easy_digital_downloads' => array( 'nicename' => __( 'Easy Digital Downloads Booster', 'neve' ), 'description' => __( 'Enhance your Easy Digital Downloads store with additional customization settings.', 'neve' ), + 'condition' => class_exists( 'Easy_Digital_Downloads' ), ), 'blog_pro' => array( 'nicename' => __( 'Blog Booster', 'neve' ), @@ -680,10 +682,12 @@ private function get_modules() { 'elementor_booster' => array( 'nicename' => __( 'Elementor Booster', 'neve' ), 'description' => __( 'Leverage the true flexibility of Elementor with powerful addons and templates that you can import with just one click.', 'neve' ), + 'condition' => defined( 'ELEMENTOR_VERSION' ), ), 'lifterlms_booster' => array( 'nicename' => __( 'LifterLMS Booster', 'neve' ), 'description' => __( 'Boost your users learning process with cool new features designed to work smoothly with LifterLMS.', 'neve' ), + 'condition' => class_exists( 'LifterLMS' ), ), 'typekit_fonts' => array( 'nicename' => __( 'Typekit Fonts', 'neve' ), @@ -698,6 +702,13 @@ private function get_modules() { 'description' => __( 'Optionally restrict access to specific parts of your website to certain users, user roles, or require a password to access.', 'neve' ), ), ); + + return array_filter( + $plugins, + function ( $module ) { + return ! isset( $module['condition'] ) || $module['condition'] === true; + } + ); } /** From d17126518d97caef5fcd79e83c3ff2cf240b7136 Mon Sep 17 00:00:00 2001 From: abaicus Date: Mon, 27 Jan 2025 17:28:48 +0200 Subject: [PATCH 15/24] chore: move plugins grid in pro version to the main column --- .../src/Components/Content/Settings.js | 2 +- .../Components/Content/Sidebar/PluginsCard.js | 17 ++++++++++++----- .../dashboard/src/Components/Content/Welcome.js | 2 ++ 3 files changed, 15 insertions(+), 6 deletions(-) diff --git a/assets/apps/dashboard/src/Components/Content/Settings.js b/assets/apps/dashboard/src/Components/Content/Settings.js index e77e76ddae..0d95b1e749 100644 --- a/assets/apps/dashboard/src/Components/Content/Settings.js +++ b/assets/apps/dashboard/src/Components/Content/Settings.js @@ -60,7 +60,7 @@ const Menu = ({ tab, setTab }) => { if (id === 'manage-modules') return NEVE_HAS_PRO; if (id === 'white-label') { - return whiteLabelStatus && NEVE_SHOW_WHITELABEL; + return !NEVE_HAS_PRO || (whiteLabelStatus && NEVE_SHOW_WHITELABEL); } return true; diff --git a/assets/apps/dashboard/src/Components/Content/Sidebar/PluginsCard.js b/assets/apps/dashboard/src/Components/Content/Sidebar/PluginsCard.js index d9050de60a..83e8625b16 100644 --- a/assets/apps/dashboard/src/Components/Content/Sidebar/PluginsCard.js +++ b/assets/apps/dashboard/src/Components/Content/Sidebar/PluginsCard.js @@ -113,18 +113,25 @@ const PluginCard = ({ slug, data }) => { ); }; -const PluginsCard = () => { +const PluginsCard = ({ grid = false }) => { const { plugins } = neveDash; if (NEVE_HIDE_PLUGINS || plugins.length < 1) { return null; } + const contentClasses = cn({ + 'space-y-3': !grid, + 'grid gap-4 grid-cols-2': grid, + }); + return ( - - {Object.entries(plugins).map(([slug, args]) => ( - - ))} + +
+ {Object.entries(plugins).map(([slug, args]) => ( + + ))} +
); }; diff --git a/assets/apps/dashboard/src/Components/Content/Welcome.js b/assets/apps/dashboard/src/Components/Content/Welcome.js index 0597ff660c..e3663742db 100644 --- a/assets/apps/dashboard/src/Components/Content/Welcome.js +++ b/assets/apps/dashboard/src/Components/Content/Welcome.js @@ -7,11 +7,13 @@ import { NEVE_HAS_PRO } from '../../utils/constants'; import Link from '../Common/Link'; import TransitionWrapper from '../Common/TransitionWrapper'; import ModuleGrid from './ModuleGrid'; +import PluginsCard from './Sidebar/PluginsCard'; export default () => ( {!NEVE_HAS_PRO && } + {NEVE_HAS_PRO && } ); From 69d6fee3eb15d0abb163714e8bd7dc1b0463d3ec Mon Sep 17 00:00:00 2001 From: abaicus Date: Mon, 27 Jan 2025 17:29:35 +0200 Subject: [PATCH 16/24] chore: add community card to sidebar, change cards order --- .../src/Components/Content/Sidebar/Sidebar.js | 28 ++++++++++++++++--- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/assets/apps/dashboard/src/Components/Content/Sidebar/Sidebar.js b/assets/apps/dashboard/src/Components/Content/Sidebar/Sidebar.js index bef25a3c36..e6d8eb3844 100644 --- a/assets/apps/dashboard/src/Components/Content/Sidebar/Sidebar.js +++ b/assets/apps/dashboard/src/Components/Content/Sidebar/Sidebar.js @@ -6,7 +6,6 @@ import { useState } from '@wordpress/element'; import { useDispatch, useSelect } from '@wordpress/data'; import { NEVE_HAS_PRO, - NEVE_HIDE_PLUGINS, NEVE_IS_WHITELABEL, NEVE_STORE, } from '../../../utils/constants'; @@ -108,6 +107,25 @@ const ContributingCard = () => { ); }; +const CommunityCard = () => { + return ( + +

+ {__( + 'Share opinions, ask questions and help each other on our Neve community!', + 'neve' + )} +

+ + +
+ ); +}; + const Sidebar = () => { return (
@@ -115,14 +133,16 @@ const Sidebar = () => { {NEVE_HAS_PRO && } + {!NEVE_IS_WHITELABEL && } + + {!NEVE_HAS_PRO && } + {!NEVE_IS_WHITELABEL && ( <> - + )} - - {!NEVE_HIDE_PLUGINS && }
); }; From 15b14a28e13a495ad75fc90b6b6eb31c55dcbc78 Mon Sep 17 00:00:00 2001 From: abaicus Date: Mon, 27 Jan 2025 17:29:54 +0200 Subject: [PATCH 17/24] chore: add link to whitelabel tab placeholder --- .../src/Components/Content/Settings/WhiteLabelTabContent.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/assets/apps/dashboard/src/Components/Content/Settings/WhiteLabelTabContent.js b/assets/apps/dashboard/src/Components/Content/Settings/WhiteLabelTabContent.js index 6978e93c34..63d0fcdc29 100644 --- a/assets/apps/dashboard/src/Components/Content/Settings/WhiteLabelTabContent.js +++ b/assets/apps/dashboard/src/Components/Content/Settings/WhiteLabelTabContent.js @@ -276,8 +276,6 @@ export default () => { const showPlaceholder = !isLicenseValid || licenseTier < 3; - const [showTierInfo, setShowTierInfo] = useState(false); - return ( <>
@@ -287,9 +285,10 @@ export default () => { {showPlaceholder && (