diff --git a/assets/js/components/DashboardMainApp.js b/assets/js/components/DashboardMainApp.js index ab55d90873a..73dabe63f71 100644 --- a/assets/js/components/DashboardMainApp.js +++ b/assets/js/components/DashboardMainApp.js @@ -30,6 +30,7 @@ import { Fragment, useEffect, useState } from '@wordpress/element'; /** * Internal dependencies */ +import { Button } from 'googlesitekit-components'; import { useSelect } from 'googlesitekit-data'; import { WELCOME_TOUR } from '@/js/feature-tours/constants'; import { @@ -61,6 +62,7 @@ import { CONTEXT_MAIN_DASHBOARD_SPEED, CONTEXT_MAIN_DASHBOARD_TRAFFIC, } from '@/js/googlesitekit/widgets/default-contexts'; +import useActivateModuleCallback from '@/js/hooks/useActivateModuleCallback'; import { useBreakpoint } from '@/js/hooks/useBreakpoint'; import { useFeature } from '@/js/hooks/useFeature'; import useFormValue from '@/js/hooks/useFormValue'; @@ -72,6 +74,8 @@ import { AudienceSelectionPanel } from '@/js/modules/analytics-4/components/audi import SiteGoalsIntroModalBanner from '@/js/modules/analytics-4/components/site-goals/notifications/IntroModalBanner'; import SiteGoalsSelectionPanel from '@/js/modules/analytics-4/components/site-goals/selection-panel'; import { MODULE_SLUG_ANALYTICS_4 } from '@/js/modules/analytics-4/constants'; +import { MODULE_SLUG_READER_REVENUE_MANAGER } from '@/js/modules/reader-revenue-manager/constants'; +import { MANAGE_SCOPE } from '@/js/modules/reader-revenue-manager/datastore/constants'; import { DAY_IN_SECONDS } from '@/js/util'; import { getNavigationalScrollTop } from '@/js/util/scroll'; import { isInitialWelcomeModalActive } from '@/js/util/welcome-modal'; @@ -361,6 +365,17 @@ export default function DashboardMainApp() { isKeyMetricsActive, } ); + const onSetupActivate = useActivateModuleCallback( + MODULE_SLUG_READER_REVENUE_MANAGER, + { + redirectQueryArgs: { + expressSetup: true, + cta: 'newsletter-signup', + }, + additionalScopes: [ MANAGE_SCOPE ], + } + ); + return ( @@ -377,6 +392,7 @@ export default function DashboardMainApp() {
+ { /* These notifications are rendered at the top of the dashboard, but are not attached to the header. The first component renders the diff --git a/assets/js/googlesitekit/modules/create-info-store.js b/assets/js/googlesitekit/modules/create-info-store.js index 35f54863c8c..ad5000d9bfb 100644 --- a/assets/js/googlesitekit/modules/create-info-store.js +++ b/assets/js/googlesitekit/modules/create-info-store.js @@ -112,14 +112,23 @@ export function createInfoStore( * * @since 1.10.0 * - * @param {Object} options Options object. - * @param {boolean} options.reAuth The module activation status. Default is true. + * @param {Object} options Options object. + * @param {boolean} options.reAuth The module activation status. Default is true. + * @param {Object} [options.redirectQueryArgs] Optional. Additional query arguments to add to the redirect URL. + * @param {string[]} [options.additionalScopes] Optional. Additional OAuth scopes to request if not yet granted. * @return {(string|undefined)} The admin reauthentication URL, or * undefined if not loaded yet. */ getAdminReauthURL: createRegistrySelector( ( select ) => - ( state, { reAuth = true } = {} ) => { + ( + state, + { + reAuth = true, + redirectQueryArgs = {}, + additionalScopes = [], + } = {} + ) => { const needsReauthentication = select( CORE_USER ).needsReauthentication(); if ( needsReauthentication === undefined ) { @@ -137,17 +146,27 @@ export function createInfoStore( slug, reAuth, ...noSetupQueryArgs, + ...redirectQueryArgs, } ); if ( redirectURL === undefined ) { return undefined; } - if ( ! needsReauthentication ) { + const unsatisfiedAdditionalScopes = additionalScopes.filter( + ( scope ) => ! select( CORE_USER ).hasScope( scope ) + ); + + const shouldUseConnectURL = + needsReauthentication || + unsatisfiedAdditionalScopes.length > 0; + + if ( ! shouldUseConnectURL ) { return redirectURL; } const connectURL = select( CORE_USER ).getConnectURL( { redirectURL, + additionalScopes: unsatisfiedAdditionalScopes, } ); return addQueryArgs( connectURL, { status: reAuth } ); diff --git a/assets/js/googlesitekit/modules/datastore/modules.js b/assets/js/googlesitekit/modules/datastore/modules.js index 527e13eeec3..2e2d293ad39 100644 --- a/assets/js/googlesitekit/modules/datastore/modules.js +++ b/assets/js/googlesitekit/modules/datastore/modules.js @@ -237,12 +237,15 @@ const baseActions = { * * @since 1.8.0 * - * @param {string} slug Slug of the module to activate. - * @return {Object} Object with `{response, error}`. On success, `response.moduleReauthURL` - * is set to redirect the user to the corresponding module setup or OAuth - * consent screen. + * @param {string} slug Slug of the module to activate. + * @param {Object} [options] Optional. Activation options with `redirectQueryArgs` and `additionalScopes`. + * @return {Object} Object with `{response, error}`. On success, `response.moduleReauthURL` + * is set to redirect the user to the corresponding module setup or OAuth + * consent screen. */ - *activateModule( slug ) { + *activateModule( slug, options = {} ) { + const { redirectQueryArgs = {}, additionalScopes = [] } = options; + const { response, error } = yield baseActions.setModuleActivation( { slug, active: true, @@ -250,7 +253,7 @@ const baseActions = { if ( response?.success === true ) { const moduleReauthURL = yield { - payload: { slug }, + payload: { slug, redirectQueryArgs, additionalScopes }, type: SELECT_MODULE_REAUTH_URL, }; return { @@ -613,7 +616,11 @@ export const baseControls = { [ SELECT_MODULE_REAUTH_URL ]: createRegistryControl( ( { select, resolveSelect } ) => async ( { payload } ) => { - const { slug } = payload; + const { + slug, + redirectQueryArgs = {}, + additionalScopes = [], + } = payload; // Ensure the module is loaded before selecting the store name. await resolveSelect( CORE_MODULES ).getModule( slug ); @@ -626,7 +633,10 @@ export const baseControls = { } if ( select( storeName )?.getAdminReauthURL ) { - return await resolveSelect( storeName ).getAdminReauthURL(); + return await resolveSelect( storeName ).getAdminReauthURL( { + redirectQueryArgs, + additionalScopes, + } ); } return select( CORE_SITE ).getAdminURL( 'googlesitekit-dashboard' diff --git a/assets/js/googlesitekit/modules/index.js b/assets/js/googlesitekit/modules/index.js index 81f573c31dc..fa7daaa662b 100644 --- a/assets/js/googlesitekit/modules/index.js +++ b/assets/js/googlesitekit/modules/index.js @@ -45,11 +45,12 @@ export function createModules( { dispatch } ) { * * @since 1.8.0 * - * @param {string} slug Slug of the module to activate. + * @param {string} slug Slug of the module to activate. + * @param {Object} [options] Optional. Activation options. * @return {Promise} The dispatched action. */ - activateModule: ( slug ) => { - return dispatch( CORE_MODULES ).activateModule( slug ); + activateModule: ( slug, options = {} ) => { + return dispatch( CORE_MODULES ).activateModule( slug, options ); }, /** * Deactivates a module on the server. diff --git a/assets/js/hooks/useActivateModuleCallback.js b/assets/js/hooks/useActivateModuleCallback.js index 230045f5f85..b4ebdfe7140 100644 --- a/assets/js/hooks/useActivateModuleCallback.js +++ b/assets/js/hooks/useActivateModuleCallback.js @@ -43,9 +43,10 @@ import useViewContext from './useViewContext'; * @since 1.70.0 * * @param {string} moduleSlug Module slug. + * @param {Object} [options] Optional. Activation options. * @return {Function|null} Callback to activate module, null if the module doesn't exist or the user can't manage options. */ -export default function useActivateModuleCallback( moduleSlug ) { +export default function useActivateModuleCallback( moduleSlug, options = {} ) { const viewContext = useViewContext(); const module = useSelect( ( select ) => select( CORE_MODULES ).getModule( moduleSlug ) @@ -59,7 +60,7 @@ export default function useActivateModuleCallback( moduleSlug ) { const { setInternalServerError } = useDispatch( CORE_SITE ); const activateModuleCallback = useCallback( async () => { - const { error, response } = await activateModule( moduleSlug ); + const { error, response } = await activateModule( moduleSlug, options ); if ( ! error ) { await trackEvent( @@ -80,6 +81,7 @@ export default function useActivateModuleCallback( moduleSlug ) { }, [ activateModule, moduleSlug, + options, navigateTo, setInternalServerError, viewContext, diff --git a/assets/js/modules/reader-revenue-manager/datastore/constants.js b/assets/js/modules/reader-revenue-manager/datastore/constants.js index 3418c3590eb..0c8aa0e791f 100644 --- a/assets/js/modules/reader-revenue-manager/datastore/constants.js +++ b/assets/js/modules/reader-revenue-manager/datastore/constants.js @@ -20,6 +20,11 @@ export const ERROR_CODE_NON_HTTPS_SITE = 'non_https_site'; export const MODULES_READER_REVENUE_MANAGER = 'modules/reader-revenue-manager'; +export const READONLY_SCOPE = + 'https://www.googleapis.com/auth/subscribewithgoogle.publications.readonly'; +export const MANAGE_SCOPE = + 'https://www.googleapis.com/auth/subscribewithgoogle.publications.entitlements.manage'; + export const PUBLICATION_ONBOARDING_STATES = { ONBOARDING_COMPLETE: 'ONBOARDING_COMPLETE', ONBOARDING_ACTION_REQUIRED: 'ONBOARDING_ACTION_REQUIRED',