diff --git a/assets/js/components/notifications/FirstPartyModeSetupBanner.js b/assets/js/components/notifications/FirstPartyModeSetupBanner.js
index 18be396df31..d43a994ff76 100644
--- a/assets/js/components/notifications/FirstPartyModeSetupBanner.js
+++ b/assets/js/components/notifications/FirstPartyModeSetupBanner.js
@@ -75,12 +75,12 @@ export default function FirstPartyModeSetupBanner( { id, Notification } ) {
select( CORE_NOTIFICATIONS ).isNotificationDismissed( id )
);
+ const { invalidateResolution } = useDispatch( CORE_NOTIFICATIONS );
+
const isDismissing = useSelect( ( select ) =>
select( CORE_USER ).isDismissingItem( id )
);
- const { dismissNotification, invalidateResolution } =
- useDispatch( CORE_NOTIFICATIONS );
const { setValue } = useDispatch( CORE_UI );
const learnMoreURL = useSelect( ( select ) => {
@@ -97,15 +97,17 @@ export default function FirstPartyModeSetupBanner( { id, Notification } ) {
const onCTAClick = async () => {
setFirstPartyModeEnabled( true );
- await saveFirstPartyModeSettings();
+ const { error } = await saveFirstPartyModeSettings();
+
+ if ( error ) {
+ return { error };
+ }
setValue( FPM_SHOW_SETUP_SUCCESS_NOTIFICATION, true );
invalidateResolution( 'getQueuedNotifications', [
viewContext,
NOTIFICATION_GROUPS.DEFAULT,
] );
-
- dismissNotification( id );
};
const { triggerSurvey } = useDispatch( CORE_USER );
@@ -189,6 +191,9 @@ export default function FirstPartyModeSetupBanner( { id, Notification } ) {
'google-site-kit'
) }
onCTAClick={ onCTAClick }
+ ctaDismissOptions={ {
+ skipHidingFromQueue: false,
+ } }
dismissLabel={ __( 'Maybe later', 'google-site-kit' ) }
onDismiss={ onDismiss }
dismissOptions={ {
diff --git a/assets/js/components/notifications/FirstPartyModeSetupBanner.stories.js b/assets/js/components/notifications/FirstPartyModeSetupBanner.stories.js
index 6487d580b30..7788c23e7d8 100644
--- a/assets/js/components/notifications/FirstPartyModeSetupBanner.stories.js
+++ b/assets/js/components/notifications/FirstPartyModeSetupBanner.stories.js
@@ -26,6 +26,7 @@ import fetchMock from 'fetch-mock';
*/
import { provideModules } from '../../../../tests/js/utils';
import WithRegistrySetup from '../../../../tests/js/WithRegistrySetup';
+import { CORE_SITE } from '../../googlesitekit/datastore/site/constants';
import { CORE_USER } from '../../googlesitekit/datastore/user/constants';
import { withNotificationComponentProps } from '../../googlesitekit/notifications/util/component-props';
import { WEEK_IN_SECONDS } from '../../util';
@@ -49,10 +50,27 @@ export const Default = Template.bind();
Default.storyName = 'FirstPartyModeSetupBanner';
Default.scenario = {};
+export const ErrorOnCTAClick = Template.bind();
+ErrorOnCTAClick.storyName = 'ErrorOnCTAClick';
+ErrorOnCTAClick.scenario = {};
+ErrorOnCTAClick.args = {
+ setupRegistry: ( registry ) => {
+ registry.dispatch( CORE_SITE ).receiveError(
+ {
+ code: 'test_error',
+ message: 'Test Error',
+ data: {},
+ },
+ 'notificationAction',
+ [ FPM_SETUP_CTA_BANNER_NOTIFICATION ]
+ );
+ },
+};
+
export default {
title: 'Modules/FirstPartyMode/Dashboard/FirstPartyModeSetupBanner',
decorators: [
- ( Story ) => {
+ ( Story, { args } ) => {
const setupRegistry = ( registry ) => {
provideModules( registry, [
{
@@ -87,6 +105,8 @@ export default {
status: 200,
}
);
+
+ args.setupRegistry?.( registry );
};
return (
diff --git a/assets/js/components/notifications/FirstPartyModeSetupBanner.test.js b/assets/js/components/notifications/FirstPartyModeSetupBanner.test.js
index f89fc038a9d..2675daa19c2 100644
--- a/assets/js/components/notifications/FirstPartyModeSetupBanner.test.js
+++ b/assets/js/components/notifications/FirstPartyModeSetupBanner.test.js
@@ -247,6 +247,47 @@ describe( 'FirstPartyModeSetupBanner', () => {
} );
} );
+ it( 'should display the error message when the CTA button is clicked and the request fails', async () => {
+ fetchMock.postOnce( fpmSettingsEndpoint, {
+ body: JSON.stringify( {
+ code: 'test_error',
+ message: 'Test Error',
+ data: {
+ reason: 'test_reason',
+ },
+ } ),
+ status: 500,
+ } );
+
+ const { getByRole, getByText, waitForRegistry } = render(
+ ,
+ {
+ registry,
+ viewContext: VIEW_CONTEXT_MAIN_DASHBOARD,
+ }
+ );
+
+ await waitForRegistry();
+
+ fetchMock.post( dismissItemEndpoint, {
+ body: JSON.stringify( [ FPM_SETUP_CTA_BANNER_NOTIFICATION ] ),
+ status: 200,
+ } );
+
+ fireEvent.click(
+ getByRole( 'button', {
+ name: 'Enable First-party mode',
+ } )
+ );
+
+ await waitFor( () => {
+ expect( fetchMock ).toHaveFetched( fpmSettingsEndpoint );
+ expect( fetchMock ).not.toHaveFetched( dismissItemEndpoint );
+ } );
+
+ expect( getByText( 'Error: Test Error' ) ).toBeInTheDocument();
+ } );
+
it( 'should set FPM_SHOW_SETUP_SUCCESS_NOTIFICATION to true and invalidate the notifications queue resolution when the CTA button is clicked', async () => {
const { getByRole, waitForRegistry } = render( , {
registry,
diff --git a/assets/js/googlesitekit/notifications/components/common/ActionsCTALinkDismiss.js b/assets/js/googlesitekit/notifications/components/common/ActionsCTALinkDismiss.js
index 0cc4c2e66ee..023a02a633f 100644
--- a/assets/js/googlesitekit/notifications/components/common/ActionsCTALinkDismiss.js
+++ b/assets/js/googlesitekit/notifications/components/common/ActionsCTALinkDismiss.js
@@ -14,10 +14,16 @@
* limitations under the License.
*/
+/**
+ * External dependencies
+ */
+import PropTypes from 'prop-types';
+
/**
* WordPress dependencies
*/
import { __ } from '@wordpress/i18n';
+import { Fragment } from '@wordpress/element';
/**
* Internal dependencies
@@ -33,8 +39,10 @@ export default function ActionsCTALinkDismiss( {
ctaLink,
ctaLabel,
onCTAClick,
+ ctaDismissOptions,
onDismiss = () => {},
dismissLabel = __( 'OK, Got it!', 'google-site-kit' ),
+ dismissOnCTAClick = true,
dismissExpires = 0,
dismissOptions = {},
} ) {
@@ -45,24 +53,42 @@ export default function ActionsCTALinkDismiss( {
} );
return (
-
+
);
}
+
+ActionsCTALinkDismiss.propTypes = {
+ id: PropTypes.string,
+ className: PropTypes.string,
+ ctaLink: PropTypes.string,
+ ctaLabel: PropTypes.string,
+ onCTAClick: PropTypes.func,
+ onDismiss: PropTypes.func,
+ ctaDismissOptions: PropTypes.object,
+ dismissLabel: PropTypes.string,
+ dismissOnCTAClick: PropTypes.bool,
+ dismissExpires: PropTypes.number,
+ dismissOptions: PropTypes.object,
+};
diff --git a/assets/js/googlesitekit/notifications/components/common/CTALink.js b/assets/js/googlesitekit/notifications/components/common/CTALink.js
index 5a13586157c..1440a8241a8 100644
--- a/assets/js/googlesitekit/notifications/components/common/CTALink.js
+++ b/assets/js/googlesitekit/notifications/components/common/CTALink.js
@@ -31,6 +31,7 @@ import { useState } from '@wordpress/element';
import { useDispatch, useSelect } from 'googlesitekit-data';
import { CORE_NOTIFICATIONS } from '../../datastore/constants';
import { CORE_LOCATION } from '../../../datastore/location/constants';
+import { CORE_SITE } from '../../../datastore/site/constants';
import useNotificationEvents from '../../hooks/useNotificationEvents';
import { SpinnerButton } from 'googlesitekit-components';
@@ -39,7 +40,9 @@ export default function CTALink( {
ctaLink,
ctaLabel,
onCTAClick,
- dismissExpires = -1,
+ dismissOnCTAClick = false,
+ dismissExpires = 0,
+ dismissOptions = { skipHidingFromQueue: true },
} ) {
const [ isAwaitingCTAResponse, setIsAwaitingCTAResponse ] =
useState( false );
@@ -53,28 +56,39 @@ export default function CTALink( {
: false;
} );
+ const { clearError, receiveError } = useDispatch( CORE_SITE );
+
const { dismissNotification } = useDispatch( CORE_NOTIFICATIONS );
const { navigateTo } = useDispatch( CORE_LOCATION );
const handleCTAClick = async ( event ) => {
+ clearError( 'notificationAction', [ id ] );
+
event.persist();
if ( ! event.defaultPrevented && ctaLink ) {
event.preventDefault();
}
setIsAwaitingCTAResponse( true );
- await onCTAClick?.( event );
+
+ const { error } = ( await onCTAClick?.( event ) ) || {};
+
if ( isMounted() ) {
setIsAwaitingCTAResponse( false );
}
+ if ( error ) {
+ receiveError( error, 'notificationAction', [ id ] );
+ return;
+ }
+
const ctaClickActions = [ trackEvents.confirm() ];
- if ( dismissExpires >= 0 ) {
+ if ( dismissOnCTAClick ) {
ctaClickActions.push(
dismissNotification( id, {
+ ...dismissOptions,
expiresInSeconds: dismissExpires,
- skipHidingFromQueue: true,
} )
);
}
@@ -105,5 +119,7 @@ CTALink.propTypes = {
ctaLink: PropTypes.string,
ctaLabel: PropTypes.string,
onCTAClick: PropTypes.func,
+ dismissOnCTAClick: PropTypes.bool,
dismissExpires: PropTypes.number,
+ dismissOptions: PropTypes.object,
};
diff --git a/assets/js/googlesitekit/notifications/components/common/Error.js b/assets/js/googlesitekit/notifications/components/common/Error.js
new file mode 100644
index 00000000000..89a5bc7d0cc
--- /dev/null
+++ b/assets/js/googlesitekit/notifications/components/common/Error.js
@@ -0,0 +1,53 @@
+/**
+ * Site Kit by Google, Copyright 2025 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * External dependencies
+ */
+import PropTypes from 'prop-types';
+
+/*
+ * WordPress dependencies
+ */
+import { useEffect } from '@wordpress/element';
+
+/**
+ * Internal dependencies
+ */
+import { useDispatch, useSelect } from 'googlesitekit-data';
+import { CORE_SITE } from '../../../datastore/site/constants';
+import ErrorText from '../../../../components/ErrorText';
+
+export default function Error( { id } ) {
+ const ctaError = useSelect( ( select ) => {
+ return select( CORE_SITE ).getError( 'notificationAction', [ id ] );
+ } );
+
+ const { clearError } = useDispatch( CORE_SITE );
+
+ useEffect( () => {
+ return () => {
+ clearError( 'notificationAction', [ id ] );
+ };
+ }, [ clearError, id ] );
+
+ return ctaError ? : null;
+}
+
+// eslint-disable-next-line sitekit/acronym-case
+Error.propTypes = {
+ id: PropTypes.string,
+};
diff --git a/assets/js/googlesitekit/notifications/components/layout/NotificationWithSVG.js b/assets/js/googlesitekit/notifications/components/layout/NotificationWithSVG.js
index f35af34fa5b..c1e91c69b01 100644
--- a/assets/js/googlesitekit/notifications/components/layout/NotificationWithSVG.js
+++ b/assets/js/googlesitekit/notifications/components/layout/NotificationWithSVG.js
@@ -28,6 +28,7 @@ import {
useBreakpoint,
} from '../../../../hooks/useBreakpoint';
import { Cell, Grid, Row } from '../../../../material-components';
+import Error from '../common/Error';
export default function NotificationWithSVG( {
id,
@@ -77,6 +78,7 @@ export default function NotificationWithSVG( {
{ description }
+
{ actions }
|