Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enhancement/10172 add woo commerce redirect modal #10232

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
66ceff0
Add getCacheItem selector.
zutigrm Feb 14, 2025
c006ea3
Add receiveCacheItem action.
zutigrm Feb 14, 2025
3199e88
Include initial state.
zutigrm Feb 14, 2025
ccdbdfd
Add reducer.
zutigrm Feb 14, 2025
a076024
Add CACHE_GET_ITEM control.
zutigrm Feb 14, 2025
a6b06c2
Add getCacheItem resolver.
zutigrm Feb 14, 2025
257346c
Add getCacheItem tests.
zutigrm Feb 14, 2025
04f90ec
Add WooCommerceRedirectModal.js file.
zutigrm Feb 14, 2025
900c811
Add basic markup.
zutigrm Feb 14, 2025
b9d21bb
Add woo logo SVG.
zutigrm Feb 18, 2025
d1492ba
Inlcude WooCommerceRedirectModal to the default exports.
zutigrm Feb 18, 2025
0374199
Add additional styles for the modal.
zutigrm Feb 18, 2025
ade904a
Add ADS_WOOCOMMERCE_REDIRECT_MODAL_CACHE_KEY constant.
zutigrm Feb 18, 2025
310480d
Implement secondary button callback and modal dismissal.
zutigrm Feb 18, 2025
809ac2b
Add WooCommerceRedirectModal stories.
zutigrm Feb 18, 2025
e7535d7
Merge remote-tracking branch 'origin/develop' into enhancement/10172-…
zutigrm Feb 19, 2025
fe38153
Merge remote-tracking branch 'origin/enhancement/10170-expand-ads-inl…
zutigrm Feb 19, 2025
517074c
Merge remote-tracking branch 'origin/develop' into enhancement/10172-…
zutigrm Feb 20, 2025
7fb7359
Add getGoogleForWooCommerceRedirectURI callback and quick refactoring.
zutigrm Feb 20, 2025
a870da3
Add WooCommerceRedirectModal tests.
zutigrm Feb 20, 2025
061a01d
Test dismissal in last test.
zutigrm Feb 20, 2025
a236a37
Improve button alignement on small devices.
zutigrm Feb 20, 2025
60e2af6
Revert cache.js.
zutigrm Feb 20, 2025
e623d5a
Switch to dismissItem.
zutigrm Feb 20, 2025
c373cac
Change constant name to ADS_WOOCOMMERCE_REDIRECT_MODAL_DISMISS_KEY.
zutigrm Feb 20, 2025
1086f3c
Update tests.
zutigrm Feb 20, 2025
a745d2a
Revert cache tests.
zutigrm Feb 20, 2025
9ab7dab
Update tests.
zutigrm Feb 20, 2025
6cd8e41
Improve buttons on small screens.
zutigrm Feb 20, 2025
edea6e4
Fix button spacing on tablets.
zutigrm Feb 20, 2025
845e114
Update VRT.
zutigrm Feb 20, 2025
18217e4
Include null output when modal is dismissed.
zutigrm Feb 21, 2025
39e2bd0
Leave buttons with auto width on small screens.
zutigrm Feb 21, 2025
152505f
Update tests.
zutigrm Feb 21, 2025
aa4ae4e
Update VRT.
zutigrm Feb 21, 2025
ec447c3
Add prop types.
zutigrm Feb 21, 2025
0a6d274
Include ttl.
zutigrm Feb 24, 2025
904e667
Merge remote-tracking branch 'origin/develop' into enhancement/10172-…
zutigrm Feb 24, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
159 changes: 159 additions & 0 deletions assets/js/modules/ads/components/common/WooCommerceRedirectModal.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
/**
* WooCommerce Redirect Modal component.
*
* 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 { __ } from '@wordpress/i18n';
import { useCallback, useMemo } from '@wordpress/element';
import { addQueryArgs } from '@wordpress/url';

/**
* Internal dependencies
*/
import { useDispatch, useSelect } from 'googlesitekit-data';
import {
Button,
Dialog,
DialogContent,
DialogFooter,
DialogTitle,
} from 'googlesitekit-components';
import { ADS_WOOCOMMERCE_REDIRECT_MODAL_DISMISS_KEY } from '../../datastore/constants';
import { CORE_SITE } from '../../../../googlesitekit/datastore/site/constants';
import { CORE_USER } from '../../../../googlesitekit/datastore/user/constants';
import { CORE_LOCATION } from '../../../../googlesitekit/datastore/location/constants';
import { HOUR_IN_SECONDS } from '../../../../util';
import WooLogoIcon from '../../../../../svg/graphics/woo-logo.svg';
import ExternalIcon from '../../../../../svg/icons/external.svg';
import useActivateModuleCallback from '../../../../hooks/useActivateModuleCallback';

export default function WooCommerceRedirectModal( {
dialogActive,
onDismiss,
} ) {
const adminURL = useSelect( ( select ) =>
select( CORE_SITE ).getAdminURL()
);
const isWooCommerceActive = useSelect( ( select ) =>
select( CORE_SITE ).isWooCommerceActivated()
);
const isGoogleForWooCommerceActive = useSelect( ( select ) =>
select( CORE_SITE ).isGoogleForWooCommerceActivated()
);
const isModalDismissed = useSelect( ( select ) =>
select( CORE_USER ).isItemDismissed(
ADS_WOOCOMMERCE_REDIRECT_MODAL_DISMISS_KEY
)
);

const googleForWooCommerceRedirectURI = useMemo( () => {
if ( ! adminURL || ! isWooCommerceActive ) {
return undefined;
}

if ( isGoogleForWooCommerceActive === false ) {
return addQueryArgs( `${ adminURL }/plugin-install.php`, {
s: 'google-listings-and-ads',
tab: 'search',
type: 'term',
} );
}

const googleDashboardPath = encodeURIComponent( '/google/dashboard' );
return `${ adminURL }/admin.php?page=wc-admin&path=${ googleDashboardPath }`;
}, [ adminURL, isWooCommerceActive, isGoogleForWooCommerceActive ] );

const { dismissItem } = useDispatch( CORE_USER );

const markModalDismissed = useCallback( () => {
onDismiss?.();
dismissItem( ADS_WOOCOMMERCE_REDIRECT_MODAL_DISMISS_KEY, {
expiresInSeconds: HOUR_IN_SECONDS,
} );
}, [ onDismiss, dismissItem ] );

const { navigateTo } = useDispatch( CORE_LOCATION );

const getGoogleForWooCommerceRedirectURI = useCallback( () => {
markModalDismissed();

navigateTo( googleForWooCommerceRedirectURI );
}, [ markModalDismissed, navigateTo, googleForWooCommerceRedirectURI ] );

const onSetupCallback = useActivateModuleCallback( 'ads' );

const onContinueWithSiteKit = useCallback( () => {
markModalDismissed();
onSetupCallback();
}, [ markModalDismissed, onSetupCallback ] );

if ( isModalDismissed ) {
return null;
}

return (
<Dialog
open={ dialogActive }
aria-describedby={ undefined }
tabIndex="-1"
className="googlesitekit-dialog-woocommerce-redirect"
onClose={ markModalDismissed }
>
<div className="googlesitekit-dialog-woocommerce-redirect__svg-wrapper">
<WooLogoIcon width={ 110 } height={ 46 } />
</div>
<DialogTitle>
{ __( 'Using the WooCommerce plugin?', 'google-site-kit' ) }
</DialogTitle>
<DialogContent>
<p>
{ __(
'The Google for WooCommerce plugin can utilize your provided business information for advertising on Google and may be more suitable for your business.',
'google-site-kit'
) }
</p>
</DialogContent>
<DialogFooter>
<Button
className="mdc-dialog__cancel-button"
tertiary
onClick={ onContinueWithSiteKit }
>
{ __( 'Continue with Site Kit', 'google-site-kit' ) }
</Button>
<Button
trailingIcon={ <ExternalIcon width={ 13 } height={ 13 } /> }
onClick={ getGoogleForWooCommerceRedirectURI }
>
{ __( 'Use Google for WooCommerce', 'google-site-kit' ) }
</Button>
</DialogFooter>
</Dialog>
);
}

WooCommerceRedirectModal.propTypes = {
dialogActive: PropTypes.bool.isRequired,
onDismiss: PropTypes.func,
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/**
* Ads WooCommerceRedirectModal component stories.
*
* 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.
*/

/**
* Internal dependencies
*/
import WooCommerceRedirectModal from './WooCommerceRedirectModal';

function Template() {
return <WooCommerceRedirectModal dialogActive />;
}

export const Default = Template.bind( null );
Default.storyName = 'Default';
Default.scenario = {};
Default.parameters = {
features: [ 'adsPax' ],
};

export default {
title: 'Modules/Ads/WooCommerceRedirectModal',
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,201 @@
/**
* WooCommerceRedirectModal tests.
*
* 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.
*/

/**
* Internal dependencies
*/
import { mockLocation } from '../../../../../../tests/js/mock-browser-utils';
import {
render,
createTestRegistry,
fireEvent,
provideSiteInfo,
provideModules,
provideUserCapabilities,
provideModuleRegistrations,
} from '../../../../../../tests/js/test-utils';
import { CORE_USER } from '../../../../googlesitekit/datastore/user/constants';
import { CORE_MODULES } from '../../../../googlesitekit/modules/datastore/constants';
import { ADS_WOOCOMMERCE_REDIRECT_MODAL_DISMISS_KEY } from '../../datastore/constants';
import WooCommerceRedirectModal from './WooCommerceRedirectModal';

describe( 'WooCommerceRedirectModal', () => {
mockLocation();
let registry;

const dismissItemEndpoint = RegExp(
'^/google-site-kit/v1/core/user/data/dismiss-item'
);
const moduleActivationEndpoint = RegExp(
'google-site-kit/v1/core/modules/data/activation'
);
const userAuthenticationEndpoint = RegExp(
'^/google-site-kit/v1/core/user/data/authentication'
);

beforeEach( () => {
registry = createTestRegistry();

provideSiteInfo( registry );
provideModules( registry );
provideModuleRegistrations( registry );
provideUserCapabilities( registry );
registry.dispatch( CORE_USER ).receiveGetDismissedItems( [] );
} );

it( 'does not render when dismissed', async () => {
registry
.dispatch( CORE_USER )
.receiveGetDismissedItems( [
ADS_WOOCOMMERCE_REDIRECT_MODAL_DISMISS_KEY,
] );

const { queryByText, waitForRegistry } = render(
<WooCommerceRedirectModal dialogActive onDismiss={ () => null } />,
{ registry }
);

await waitForRegistry();

expect(
queryByText( /continue with site kit/i )
).not.toBeInTheDocument();
} );

it( 'clicking "Continue with Site Kit" should trigger ads module activation and dismiss the modal', async () => {
fetchMock.postOnce( moduleActivationEndpoint, {
body: { success: true },
} );
fetchMock.getOnce( userAuthenticationEndpoint, {
body: { needsReauthentication: false },
} );
fetchMock.postOnce( dismissItemEndpoint, {
body: JSON.stringify( [
ADS_WOOCOMMERCE_REDIRECT_MODAL_DISMISS_KEY,
] ),
} );

const { getByText, waitForRegistry } = render(
<WooCommerceRedirectModal dialogActive onDismiss={ () => null } />,
{ registry }
);
await waitForRegistry();

const continueWithSiteKitButton = getByText(
/continue with site kit/i
);
fireEvent.click( continueWithSiteKitButton );

await waitForRegistry();

expect(
registry.select( CORE_MODULES ).isDoingSetModuleActivation( 'ads' )
).toBe( true );

// Modal should be dismissed.
expect( fetchMock ).toHaveFetched( dismissItemEndpoint );
} );

it( 'clicking "Use Google for WooCommerce" should link to the install plugin page with Google for WooCommerce search term when Google for WooCommerce is not active', async () => {
fetchMock.postOnce( dismissItemEndpoint, {
body: JSON.stringify( [
ADS_WOOCOMMERCE_REDIRECT_MODAL_DISMISS_KEY,
] ),
} );

provideSiteInfo( registry, {
plugins: {
wooCommerce: {
active: true,
installed: true,
},
googleForWooCommerce: {
active: false,
installed: false,
},
},
} );
const { getByText, waitForRegistry } = render(
<WooCommerceRedirectModal dialogActive onDismiss={ () => null } />,
{ registry }
);
await waitForRegistry();

const useGoogleForWooCommerceButton = getByText(
/use google for woocommerce/i
);
fireEvent.click( useGoogleForWooCommerceButton );

await waitForRegistry();

expect( global.location.assign ).toHaveBeenCalledWith(
expect.stringMatching( /plugin-install\.php/ )
);
expect( global.location.assign ).toHaveBeenCalledWith(
expect.stringMatching( /s=google-listings-and-ads/ )
);
expect( global.location.assign ).toHaveBeenCalledWith(
expect.stringMatching( /tab=search/ )
);

// Modal should be dismissed.
expect( fetchMock ).toHaveFetched( dismissItemEndpoint );
} );

it( 'clicking "Use Google for WooCommerce" should link to the google dashboard of the Google for WooCommerce when Google for WooCommerce is active', async () => {
fetchMock.postOnce( dismissItemEndpoint, {
body: JSON.stringify( [
ADS_WOOCOMMERCE_REDIRECT_MODAL_DISMISS_KEY,
] ),
} );

provideSiteInfo( registry, {
plugins: {
wooCommerce: {
active: true,
installed: true,
},
googleForWooCommerce: {
active: true,
installed: true,
},
},
} );
const { getByText, waitForRegistry } = render(
<WooCommerceRedirectModal dialogActive onDismiss={ () => null } />,
{ registry }
);
await waitForRegistry();

const useGoogleForWooCommerceButton = getByText(
/use google for woocommerce/i
);
fireEvent.click( useGoogleForWooCommerceButton );

await waitForRegistry();

expect( global.location.assign ).toHaveBeenCalledWith(
expect.stringMatching( /page=wc-admin/ )
);
expect( global.location.assign ).toHaveBeenCalledWith(
expect.stringMatching( /path=%2Fgoogle%2Fdashboard/ )
);

expect( fetchMock ).toHaveFetched( dismissItemEndpoint );
} );
} );
Loading
Loading