diff --git a/packages/js/images/yoast-connect-google-site-kit.svg b/packages/js/images/yoast-connect-google-site-kit.svg new file mode 100644 index 00000000000..ad3bfb2f879 --- /dev/null +++ b/packages/js/images/yoast-connect-google-site-kit.svg @@ -0,0 +1,90 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/js/src/dashboard/components/dashboard.js b/packages/js/src/dashboard/components/dashboard.js index 62f58e02edc..4ac86adc195 100644 --- a/packages/js/src/dashboard/components/dashboard.js +++ b/packages/js/src/dashboard/components/dashboard.js @@ -1,5 +1,10 @@ import { Scores } from "../scores/components/scores"; import { PageTitle } from "./page-title"; +import { SiteKitSetupWidget } from "./site-kit-setup-widget"; +import { get } from "lodash"; +import { useCallback } from "@wordpress/element"; +import { useToggleState } from "@yoast/ui-library"; +import { useSelect } from "@wordpress/data"; /** * @type {import("../index").ContentType} ContentType @@ -15,13 +20,40 @@ import { PageTitle } from "./page-title"; * @param {Endpoints} endpoints The endpoints. * @param {Object} headers The headers for the score requests. * @param {Links} links The links. + * * @returns {JSX.Element} The element. */ +// The complexity is cause by the google site kit feature flag which is temporary. +// eslint-disable-next-line complexity export const Dashboard = ( { contentTypes, userName, features, endpoints, headers, links } ) => { + const siteKitConfiguration = get( window, "wpseoScriptData.dashboard.siteKitConfiguration", { + isInstalled: false, + isActive: false, + isSetupCompleted: false, + isConnected: false, + installUrl: "", + activateUrl: "", + setupUrl: "", + isFeatureEnabled: false, + } ); + const [ showGoogleSiteKit, , , , setRemoveGoogleSiteKit ] = useToggleState( true ); + const learnMorelink = useSelect( select => select( "@yoast/general" ).selectLink( "https://yoa.st/google-site-kit-learn-more" ), [] ); + const handleRemovePermanently = useCallback( ()=>{ + /* eslint-disable-next-line */ + // TODO: Implement the remove permanently functionality. + setRemoveGoogleSiteKit(); + }, [ setRemoveGoogleSiteKit ] ); + return ( <>
+ { showGoogleSiteKit && siteKitConfiguration.isFeatureEnabled && } { features.indexables && features.seoAnalysis && ( ) } diff --git a/packages/js/src/dashboard/components/site-kit-setup-widget.js b/packages/js/src/dashboard/components/site-kit-setup-widget.js new file mode 100644 index 00000000000..b7dc072140f --- /dev/null +++ b/packages/js/src/dashboard/components/site-kit-setup-widget.js @@ -0,0 +1,158 @@ +import { Button, Paper, Stepper, Title, DropdownMenu } from "@yoast/ui-library"; +import { ReactComponent as YoastConnectSiteKit } from "../../../images/yoast-connect-google-site-kit.svg"; +import { __ } from "@wordpress/i18n"; +import { CheckCircleIcon } from "@heroicons/react/solid"; +import { ArrowRightIcon, XIcon, TrashIcon } from "@heroicons/react/outline"; + +/** + * Get the button and stepper props based on the current state. + * + * @param {boolean} isInstalled Whether the plugin is isInstalled. + * @param {boolean} isActive Whether the feature is active. + * @param {boolean} isSetupCompleted Whether the setup is complete. + * @param {boolean} isConnected Whether the connection is active. + * @param {string} installUrl The URL to install Site Kit. + * @param {string} activateUrl The URL to activate Site Kit. + * @param {string} setupUrl The URL to setup Site Kit. + * + * @returns {Object} The button and stepper props. + */ +const getButtonAndStepperProps = ( isInstalled, isActive, isSetupCompleted, isConnected, installUrl, activateUrl, setupUrl ) => { + let buttonProps; + let currentStep; + let isComplete = false; + + switch ( true ) { + case ( ! isInstalled ): + currentStep = 1; + buttonProps = { + children: __( "Install Site Kit by Google", "wordpress-seo" ), + as: "a", + href: installUrl, + }; + break; + case ( ! isActive ): + currentStep = 2; + buttonProps = { + children: __( "Activate Site Kit by Google", "wordpress-seo" ), + as: "a", + href: activateUrl, + }; + break; + case ( ! isSetupCompleted ): + currentStep = 3; + buttonProps = { + children: __( "Set up Site Kit by Google", "wordpress-seo" ), + as: "a", + href: setupUrl, + }; + break; + case ( ! isConnected ): + currentStep = 4; + buttonProps = { children: __( "Connect Site Kit by Google", "wordpress-seo" ) }; + break; + case isConnected: + isComplete = true; + currentStep = 4; + buttonProps = { children: "Take a quick tour" }; + break; + } + return { buttonProps, currentStep, isComplete }; +}; + +const steps = [ + __( "INSTALL", "wordpress-seo" ), + __( "ACTIVATE", "wordpress-seo" ), + __( "SET UP", "wordpress-seo" ), + __( "CONNECT", "wordpress-seo" ), +]; + +/** + * The google site kit connection guide widget. + * + * @param {boolean} isInstalled Whether the plugin is installed. + * @param {boolean} isActive Whether the feature is active. + * @param {boolean} isSetupCompleted Whether the setup is complete. + * @param {boolean} isConnected Whether the connection is active. + * @param {string} installUrl The URL to install Site Kit. + * @param {string} activateUrl The URL to activate Site Kit. + * @param {string} setupUrl The URL to setup Site Kit. + * @param {function} onRemove The function to call when the widget is removed. + * @param {function} onRemovePermanently The function to call when the widget is removed permanently. + * @param {string} learnMorelink The URL to learn more about the feature. + * + * @returns {JSX.Element} The widget. + */ +export const SiteKitSetupWidget = ( { + installUrl, + activateUrl, + setupUrl, + isConnected, + isActive, + isSetupCompleted, + isInstalled, + onRemove, + onRemovePermanently, + learnMorelink, +} ) => { + const { buttonProps, currentStep, isComplete } = getButtonAndStepperProps( + isInstalled, isActive, isSetupCompleted, isConnected, installUrl, activateUrl, setupUrl ); + return + + + + + + { __( "Remove until next visit", "wordpress-seo" ) } + + + + { __( "Remove permanently", "wordpress-seo" ) } + + + + +
+ + { steps.map( ( label, index ) => ( index + 1 || isComplete } + /> ) ) } + +
+ { __( "Expand your dashboard with insights from Google!", "wordpress-seo" ) } +

{ __( "Bring together powerful tools like Google Analytics and Search Console for a complete overview of your website's performance, all in one seamless dashboard.", "wordpress-seo" ) }

+ + { __( "What you'll get:", "wordpress-seo" ) } +
    +
  • + + { __( "Actionable insights into traffic, SEO, and user behavior to grow your audience.", "wordpress-seo" ) } +
  • +
  • + + { __( "Key performance metrics to fine-tune your website and optimize like a pro.", "wordpress-seo" ) } +
  • +
+
+ + : + } +
+
; +}; + + diff --git a/packages/js/src/integrations-page/recommended-integrations.js b/packages/js/src/integrations-page/recommended-integrations.js index fa8ddd68b43..4ecd436bdf9 100644 --- a/packages/js/src/integrations-page/recommended-integrations.js +++ b/packages/js/src/integrations-page/recommended-integrations.js @@ -81,14 +81,14 @@ const RecommendedIntegrations = [ const siteKitProps = { isInstalled: get( window, "wpseoIntegrationsData.site_kit_configuration.isInstalled", false ), isActive: get( window, "wpseoIntegrationsData.site_kit_configuration.isActive", false ), - afterSetup: get( window, "wpseoIntegrationsData.site_kit_configuration.setup_completed", false ), + afterSetup: get( window, "wpseoIntegrationsData.site_kit_configuration.isSetupCompleted", false ), isConnected: get( window, "wpseoIntegrationsData.site_kit_configuration.isConnected", false ), - installUrl: get( window, "wpseoIntegrationsData.site_kit_configuration.install_url", "" ), - activateUrl: get( window, "wpseoIntegrationsData.site_kit_configuration.activate_url", "" ), - setupUrl: get( window, "wpseoIntegrationsData.site_kit_configuration.setup_url", "" ), + installUrl: get( window, "wpseoIntegrationsData.site_kit_configuration.installUrl", "" ), + activateUrl: get( window, "wpseoIntegrationsData.site_kit_configuration.activateUrl", "" ), + setupUrl: get( window, "wpseoIntegrationsData.site_kit_configuration.setupUrl", "" ), }; -const isSiteKitFeatureEnabled = get( window, "wpseoIntegrationsData.site_kit_configuration.feature_enabled", false ); +const isSiteKitFeatureEnabled = get( window, "wpseoIntegrationsData.site_kit_configuration.isFeatureEnabled", false ); if ( isSiteKitFeatureEnabled ) { RecommendedIntegrations.push( ); } diff --git a/packages/js/src/integrations-page/site-kit-integration.js b/packages/js/src/integrations-page/site-kit-integration.js index c62ef14313d..b77556bab03 100644 --- a/packages/js/src/integrations-page/site-kit-integration.js +++ b/packages/js/src/integrations-page/site-kit-integration.js @@ -49,7 +49,7 @@ const SuccessfullyConnected = () => { * The Site Kit integration component. * * @param {boolean} isActive Whether the integration is active. - * @param {boolean} afterSetup Whether the integration has been set up. + * @param {boolean} isSetupCompleted Whether the integration has been set up. * @param {boolean} isInstalled Whether the integration is installed. * @param {boolean} isConnected Whether the integration is connected. * @param {string} installUrl The installation url. @@ -58,7 +58,7 @@ const SuccessfullyConnected = () => { * * @returns {WPElement} The Site Kit integration component. */ -export const SiteKitIntegration = ( { isActive, afterSetup, isInstalled, isConnected, installUrl, activateUrl, setupUrl } ) => { +export const SiteKitIntegration = ( { isActive, isSetupCompleted, isInstalled, isConnected, installUrl, activateUrl, setupUrl } ) => { const [ isModalOpen, toggleModal ] = useToggleState( false ); const [ isDisconnectModalOpen, toggleDisconnectModal ] = useToggleState( false ); @@ -77,7 +77,7 @@ export const SiteKitIntegration = ( { isActive, afterSetup, isInstalled, isConne href: activateUrl, }; } - if ( ! afterSetup ) { + if ( ! isSetupCompleted ) { return { children: __( "Set up Site Kit by Google", "wordpress-seo" ), as: "a", @@ -98,10 +98,10 @@ export const SiteKitIntegration = ( { isActive, afterSetup, isInstalled, isConne variant: "secondary", onClick: toggleDisconnectModal, }; - }, [ isInstalled, isActive, afterSetup, isConnected, installUrl, activateUrl, toggleModal ] ); + }, [ isInstalled, isActive, isSetupCompleted, isConnected, installUrl, activateUrl, toggleModal ] ); - const successfullyConnected = isInstalled && isActive && afterSetup && isConnected; + const successfullyConnected = isInstalled && isActive && isSetupCompleted && isConnected; return ( <> { successfullyConnected && } - + ; + }, +}; + +export default { + title: "2) Components/Stepper", + component: Stepper, + argTypes: { + className: { control: "text" }, + numberOfSteps: { control: "number" }, + currentStep: { control: "number" }, + }, + parameters: { + docs: { + description: { component }, + }, + }, + args: { + className: "yst-mb-5", + numberOfSteps: 4, + currentStep: 1, + }, +}; diff --git a/packages/ui-library/src/components/stepper/style.css b/packages/ui-library/src/components/stepper/style.css new file mode 100644 index 00000000000..139e0a3eea8 --- /dev/null +++ b/packages/ui-library/src/components/stepper/style.css @@ -0,0 +1,57 @@ +@layer components { + .yst-root { + + .yst-stepper { + @apply yst-relative yst-flex yst-justify-between yst-items-center; + + .yst-progress-bar__progress { + @apply yst-duration-500; + } + + } + + .yst-step { + @apply yst-flex yst-flex-col yst-items-center; + } + + .yst-step__circle { + @apply yst-bg-white + yst-ring-slate-300 + yst-w-6 + yst-h-6 + yst-ring-2 + yst-rounded-full + yst-z-10 + yst-relative; + } + + .yst-step__icon { + @apply yst-absolute yst-top-1/2 yst-left-1/2 yst-transform -yst-translate-x-1/2 -yst-translate-y-1/2; + } + + .yst-step--active { + @apply yst-text-primary-500; + + .yst-step__circle { + @apply + yst-bg-white + yst-text-white + yst-ring-primary-500 + yst-transition-all + yst-ease-in + yst-delay-500; + } + + .yst-step__icon{ + @apply yst-transition-all yst-ease-in; + } + } + + .yst-step--complete { + @apply yst-text-slate-900; + .yst-step__circle { + @apply yst-bg-primary-500 yst-text-white yst-ring-primary-500 yst-transition-none yst-delay-0; + } + } + } +} diff --git a/packages/ui-library/src/index.js b/packages/ui-library/src/index.js index 830ac850945..1c4c9c962a8 100644 --- a/packages/ui-library/src/index.js +++ b/packages/ui-library/src/index.js @@ -41,6 +41,8 @@ export { default as TextField } from "./components/text-field"; export { default as TextareaField } from "./components/textarea-field"; export { default as ToggleField } from "./components/toggle-field"; export { TooltipContainer, TooltipTrigger, TooltipWithContext, useTooltipContext } from "./components/tooltip-container"; +export { DropdownMenu } from "./components/dropdown-menu"; +export { Stepper } from "./components/stepper"; export * from "./hooks"; export * from "./constants"; diff --git a/src/dashboard/application/configuration/dashboard-configuration.php b/src/dashboard/application/configuration/dashboard-configuration.php index eae738da4a0..9ad97d3c2e0 100644 --- a/src/dashboard/application/configuration/dashboard-configuration.php +++ b/src/dashboard/application/configuration/dashboard-configuration.php @@ -4,13 +4,16 @@ // phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong namespace Yoast\WP\SEO\Dashboard\Application\Configuration; +use Yoast\WP\SEO\Conditionals\Google_Site_Kit_Feature_Conditional; use Yoast\WP\SEO\Dashboard\Application\Content_Types\Content_Types_Repository; use Yoast\WP\SEO\Dashboard\Application\Endpoints\Endpoints_Repository; +use Yoast\WP\SEO\Dashboard\Infrastructure\Integrations\Site_Kit; use Yoast\WP\SEO\Dashboard\Infrastructure\Nonces\Nonce_Repository; use Yoast\WP\SEO\Editors\Application\Analysis_Features\Enabled_Analysis_Features_Repository; use Yoast\WP\SEO\Editors\Framework\Keyphrase_Analysis; use Yoast\WP\SEO\Editors\Framework\Readability_Analysis; use Yoast\WP\SEO\Helpers\Indexable_Helper; +use Yoast\WP\SEO\Helpers\Options_Helper; use Yoast\WP\SEO\Helpers\User_Helper; /** @@ -60,6 +63,27 @@ class Dashboard_Configuration { */ private $nonce_repository; + /** + * The Google Site Kit conditional. + * + * @var Google_Site_Kit_Feature_Conditional + */ + private $google_site_kit_conditional; + + /** + * The options helper. + * + * @var Options_Helper + */ + private $options_helper; + + /** + * The site kit integration configuration data. + * + * @var Site_Kit + */ + private $site_kit_integration_data; + /** * The constructor. * @@ -67,10 +91,13 @@ class Dashboard_Configuration { * @param Indexable_Helper $indexable_helper The indexable helper * repository. * @param User_Helper $user_helper The user helper. - * @param Enabled_Analysis_Features_Repository $enabled_analysis_features_repository The analysis feature - * repository. + * @param Enabled_Analysis_Features_Repository $enabled_analysis_features_repository The analysis feature. + * repository. * @param Endpoints_Repository $endpoints_repository The endpoints repository. * @param Nonce_Repository $nonce_repository The nonce repository. + * @param Google_Site_Kit_Feature_Conditional $google_site_kit_conditional The Google Site Kit conditional. + * @param Options_Helper $options_helper The options helper. + * @param Site_Kit $site_kit_integration_data The site kit integration configuration data. */ public function __construct( Content_Types_Repository $content_types_repository, @@ -78,7 +105,10 @@ public function __construct( User_Helper $user_helper, Enabled_Analysis_Features_Repository $enabled_analysis_features_repository, Endpoints_Repository $endpoints_repository, - Nonce_Repository $nonce_repository + Nonce_Repository $nonce_repository, + Google_Site_Kit_Feature_Conditional $google_site_kit_conditional, + Options_Helper $options_helper, + Site_Kit $site_kit_integration_data ) { $this->content_types_repository = $content_types_repository; $this->indexable_helper = $indexable_helper; @@ -86,6 +116,9 @@ public function __construct( $this->enabled_analysis_features_repository = $enabled_analysis_features_repository; $this->endpoints_repository = $endpoints_repository; $this->nonce_repository = $nonce_repository; + $this->google_site_kit_conditional = $google_site_kit_conditional; + $this->options_helper = $options_helper; + $this->site_kit_integration_data = $site_kit_integration_data; } /** @@ -106,6 +139,7 @@ public function get_configuration(): array { )->to_array(), 'endpoints' => $this->endpoints_repository->get_all_endpoints()->to_array(), 'nonce' => $this->nonce_repository->get_rest_nonce(), + 'siteKitConfiguration' => $this->site_kit_integration_data->to_array(), ]; } } diff --git a/src/dashboard/infrastructure/integrations/site-kit.php b/src/dashboard/infrastructure/integrations/site-kit.php index 33291a16cb7..bf7b93920a1 100644 --- a/src/dashboard/infrastructure/integrations/site-kit.php +++ b/src/dashboard/infrastructure/integrations/site-kit.php @@ -61,14 +61,14 @@ public function to_array(): array { $site_kit_setup_url = \self_admin_url( 'admin.php?page=googlesitekit-splash' ); return [ - 'isInstalled' => \file_exists( \WP_PLUGIN_DIR . '/' . self::SITE_KIT_FILE ), - 'isActive' => \is_plugin_active( self::SITE_KIT_FILE ), - 'setup_completed' => \get_option( 'googlesitekit_has_connected_admins', false ) === '1', - 'isConnected' => $this->options_helper->get( 'google_site_kit_connected', false ), - 'feature_enabled' => ( new Google_Site_Kit_Feature_Conditional() )->is_met(), - 'install_url' => $site_kit_install_url, - 'activate_url' => $site_kit_activate_url, - 'setup_url' => $site_kit_setup_url, + 'isInstalled' => \file_exists( \WP_PLUGIN_DIR . '/' . self::SITE_KIT_FILE ), + 'isActive' => \is_plugin_active( self::SITE_KIT_FILE ), + 'isSetupCompleted' => \get_option( 'googlesitekit_has_connected_admins', false ) === '1', + 'isConnected' => $this->options_helper->get( 'google_site_kit_connected', false ), + 'isFeatureEnabled' => ( new Google_Site_Kit_Feature_Conditional() )->is_met(), + 'installUrl' => $site_kit_install_url, + 'activateUrl' => $site_kit_activate_url, + 'setupUrl' => $site_kit_setup_url, ]; } diff --git a/tests/Unit/Integrations/Admin/Integrations_Page_Integration_Test.php b/tests/Unit/Integrations/Admin/Integrations_Page_Integration_Test.php index ddcfd212669..73668200bca 100644 --- a/tests/Unit/Integrations/Admin/Integrations_Page_Integration_Test.php +++ b/tests/Unit/Integrations/Admin/Integrations_Page_Integration_Test.php @@ -160,14 +160,14 @@ public function test_enqueue_assets() { $this->elementor_conditional->expects( 'is_met' )->andReturnFalse(); $this->jetpack_conditional->expects( 'is_met' )->andReturnFalse(); $site_kit_config = [ - 'isInstalled' => false, - 'isActive' => false, - 'setup_completed' => false, - 'isConnected' => false, - 'feature_enabled' => false, - 'install_url' => 'example.com', - 'activate_url' => 'example.com', - 'setup_url' => 'example.com', + 'isInstalled' => false, + 'isActive' => false, + 'isSetupCompleted' => false, + 'isConnected' => false, + 'isFeatureEnabled' => false, + 'installUrl' => 'example.com', + 'activateUrl' => 'example.com', + 'setupUrl' => 'example.com', ]; $this->site_kit_configuration->expects( 'to_array' )->andReturn( $site_kit_config );