From 6817cc6c671a21c192b429f9d99f0b9b0bf79b3d Mon Sep 17 00:00:00 2001 From: asvinb Date: Fri, 25 Oct 2024 22:41:09 +0400 Subject: [PATCH 01/51] Rename hook to useStoreAddressSynced. --- .../contact-information/store-address-card.js | 12 +++++++--- .../connected-google-combo-account-card.js | 23 ++++++++++++------- .../sync-store-address.js | 17 ++++++++++++++ js/src/hooks/useStoreAddressSynced.js | 18 +++++++++++++++ .../setup-stepper/setup-accounts/index.js | 5 +++- 5 files changed, 63 insertions(+), 12 deletions(-) create mode 100644 js/src/components/google-combo-account-card/sync-store-address.js create mode 100644 js/src/hooks/useStoreAddressSynced.js diff --git a/js/src/components/contact-information/store-address-card.js b/js/src/components/contact-information/store-address-card.js index 482603b4c7..f757af9085 100644 --- a/js/src/components/contact-information/store-address-card.js +++ b/js/src/components/contact-information/store-address-card.js @@ -2,7 +2,7 @@ * External dependencies */ import { __ } from '@wordpress/i18n'; -import { useRef, createInterpolateElement } from '@wordpress/element'; +import { useRef, createInterpolateElement, useState } from '@wordpress/element'; import { CardDivider } from '@wordpress/components'; import { Spinner } from '@woocommerce/components'; import { update as updateIcon } from '@wordpress/icons'; @@ -11,6 +11,7 @@ import { getPath, getQuery } from '@woocommerce/navigation'; /** * Internal dependencies */ +import { useAppDispatch } from '.~/data'; import useStoreAddress from '.~/hooks/useStoreAddress'; import Section from '.~/wcdl/section'; import Subsection from '.~/wcdl/subsection'; @@ -55,6 +56,8 @@ import './store-address-card.scss'; */ const StoreAddressCard = ( { showValidation = false } ) => { const { loaded, data, refetch } = useStoreAddress(); + const [ isSaving, setSaving ] = useState( false ); + const { updateGoogleMCContactInformation } = useAppDispatch(); const path = getPath(); const { subpath } = getQuery(); @@ -66,7 +69,10 @@ const StoreAddressCard = ( { showValidation = false } ) => { } const handleRefreshClick = () => { - refetch(); + setSaving( true ); + updateGoogleMCContactInformation() + .then( () => refetch() ) + .catch( () => setSaving( false ) ); refetchedCallbackRef.current = ( storeAddress ) => { const eventProps = { @@ -88,7 +94,7 @@ const StoreAddressCard = ( { showValidation = false } ) => { iconPosition="right" text={ __( 'Refresh to sync', 'google-listings-and-ads' ) } onClick={ handleRefreshClick } - disabled={ ! loaded } + disabled={ ! loaded || isSaving } /> ); diff --git a/js/src/components/google-combo-account-card/connected-google-combo-account-card.js b/js/src/components/google-combo-account-card/connected-google-combo-account-card.js index 5d981edf80..181a79463a 100644 --- a/js/src/components/google-combo-account-card/connected-google-combo-account-card.js +++ b/js/src/components/google-combo-account-card/connected-google-combo-account-card.js @@ -6,6 +6,7 @@ import AccountDetails from './account-details'; import Indicator from './indicator'; import getAccountCreationTexts from './getAccountCreationTexts'; import SpinnerCard from '.~/components/spinner-card'; +import SyncStoreAddress from './sync-store-address'; import useAutoCreateAdsMCAccounts from '.~/hooks/useAutoCreateAdsMCAccounts'; import './connected-google-combo-account-card.scss'; @@ -22,14 +23,20 @@ const ConnectedGoogleComboAccountCard = () => { } return ( - } - helper={ subText } - indicator={ } - /> +
+ } + helper={ subText } + indicator={ + + } + /> + + +
); }; diff --git a/js/src/components/google-combo-account-card/sync-store-address.js b/js/src/components/google-combo-account-card/sync-store-address.js new file mode 100644 index 0000000000..43d1142854 --- /dev/null +++ b/js/src/components/google-combo-account-card/sync-store-address.js @@ -0,0 +1,17 @@ +/** + * Internal dependencies + */ +import StoreAddressCard from '.~/components/contact-information/store-address-card'; +import useStoreAddressSynced from '.~/hooks/useStoreAddressSynced'; + +const SyncStoreAddress = () => { + const storeAddressSynced = useStoreAddressSynced(); + + if ( ! storeAddressSynced ) { + return ; + } + + return null; +}; + +export default SyncStoreAddress; diff --git a/js/src/hooks/useStoreAddressSynced.js b/js/src/hooks/useStoreAddressSynced.js new file mode 100644 index 0000000000..43d393d0f6 --- /dev/null +++ b/js/src/hooks/useStoreAddressSynced.js @@ -0,0 +1,18 @@ +/** + * Internal dependencies + */ +import useGoogleMCAccount from '.~/hooks/useGoogleMCAccount'; +import useStoreAddress from '.~/hooks/useStoreAddress'; + +/** + * Checks if the store address is synchronized with the Merchant Center (GMC) account address. + * + * @return {boolean} Returns `true` if the store address matches the GMC account address, + * and both data sources are ready and loaded; otherwise, returns `false`. + */ +export default function useStoreAddressSynced() { + const { isReady } = useGoogleMCAccount(); + const { data, loaded } = useStoreAddress(); + + return isReady && data.address && ! data.isMCAddressDifferent && loaded; +} diff --git a/js/src/setup-mc/setup-stepper/setup-accounts/index.js b/js/src/setup-mc/setup-stepper/setup-accounts/index.js index cf200ed4df..b659c2e95c 100644 --- a/js/src/setup-mc/setup-stepper/setup-accounts/index.js +++ b/js/src/setup-mc/setup-stepper/setup-accounts/index.js @@ -25,6 +25,7 @@ import Faqs from './faqs'; import './index.scss'; import useGoogleAdsAccount from '.~/hooks/useGoogleAdsAccount'; import useGoogleAdsAccountReady from '.~/hooks/useGoogleAdsAccountReady'; +import useStoreAddressSynced from '.~/hooks/useStoreAddressSynced'; /** * Renders the disclaimer of Comparison Shopping Service (CSS). @@ -89,6 +90,7 @@ const SetupAccounts = ( props ) => { isReady: isGoogleMCReady, } = useGoogleMCAccount(); const { hasFinishedResolution } = useGoogleAdsAccount(); + const storeAddressSynced = useStoreAddressSynced(); const isGoogleAdsReady = useGoogleAdsAccountReady(); /** @@ -115,7 +117,8 @@ const SetupAccounts = ( props ) => { const isContinueButtonDisabled = ! ( hasFinishedResolution && isGoogleAdsReady && - isGoogleMCReady + isGoogleMCReady && + storeAddressSynced ); return ( From bd6c4fef03d23ec8992f9baacabe360a468dc641 Mon Sep 17 00:00:00 2001 From: asvinb Date: Mon, 28 Oct 2024 20:19:36 +0400 Subject: [PATCH 02/51] Add E2E tests. --- .../specs/setup-mc/step-1-accounts.test.js | 30 ++++++++++++++++++- .../pages/setup-mc/step-1-set-up-accounts.js | 21 +++++++++++++ 2 files changed, 50 insertions(+), 1 deletion(-) diff --git a/tests/e2e/specs/setup-mc/step-1-accounts.test.js b/tests/e2e/specs/setup-mc/step-1-accounts.test.js index 04b3f1cc1a..3b9125e9ab 100644 --- a/tests/e2e/specs/setup-mc/step-1-accounts.test.js +++ b/tests/e2e/specs/setup-mc/step-1-accounts.test.js @@ -291,6 +291,9 @@ test.describe( 'Set up accounts', () => { test.beforeAll( async () => { await setUpAccountsPage.mockAdsAccountConnected(); await setUpAccountsPage.mockMCNotConnected(); + await setUpAccountsPage.mockContactInformation( { + streetAddress: 'Automata Road', + } ); await setUpAccountsPage.goto(); } ); @@ -306,6 +309,9 @@ test.describe( 'Set up accounts', () => { test.beforeAll( async () => { await setUpAccountsPage.mockAdsAccountDisconnected(); await setUpAccountsPage.mockMCConnected(); + await setUpAccountsPage.mockContactInformation( { + streetAddress: 'Automata Road', + } ); await setUpAccountsPage.goto(); } ); @@ -317,11 +323,33 @@ test.describe( 'Set up accounts', () => { } ); } ); - test.describe( 'When all accounts are connected', async () => { + test.describe( 'When the store address needs to be synced and accounts are connected', async () => { test.beforeAll( async () => { await setUpAccountsPage.mockAdsAccountConnected(); await setUpAccountsPage.mockMCConnected(); + await setUpAccountsPage.mockContactInformation( { + streetAddress: 'Automata Road', + isMCAddressDifferent: true, + } ); + + await setUpAccountsPage.goto(); + } ); + + test( 'should see "Continue" button disabled when the store address needs to be synced', async () => { + const continueButton = + await setUpAccountsPage.getContinueButton(); + await expect( continueButton ).toBeDisabled(); + } ); + } ); + + test.describe( 'When all accounts are connected and store address is synced', async () => { + test.beforeAll( async () => { await setUpAccountsPage.mockAdsAccountConnected(); + await setUpAccountsPage.mockMCConnected(); + await setUpAccountsPage.mockContactInformation( { + streetAddress: 'Automata Road', + isMCAddressDifferent: false, + } ); await setUpAccountsPage.goto(); } ); diff --git a/tests/e2e/utils/pages/setup-mc/step-1-set-up-accounts.js b/tests/e2e/utils/pages/setup-mc/step-1-set-up-accounts.js index 1876de366c..2f1566edca 100644 --- a/tests/e2e/utils/pages/setup-mc/step-1-set-up-accounts.js +++ b/tests/e2e/utils/pages/setup-mc/step-1-set-up-accounts.js @@ -376,4 +376,25 @@ export default class SetUpAccountsPage extends MockRequests { getTermsCheckbox() { return this.page.getByLabel( /I accept the terms and conditions/ ); } + + /** + * Get store address card. + * + * @return {import('@playwright/test').Locator} Get store address card. + */ + getStoreAddressCard() { + return this.page.locator( '.gla-store-address-card' ); + } + + /** + * Get store address refresh to sync button. + * + * @return {import('@playwright/test').Locator} Get store address refresh to sync button. + */ + getStoreAddressRefreshToSyncButton() { + return this.getStoreAddressCard().getByRole( 'button', { + name: 'Refresh to sync', + exact: true, + } ); + } } From 8dc67406449a339eea84059e6c03c1bdbf12273d Mon Sep 17 00:00:00 2001 From: asvinb Date: Mon, 28 Oct 2024 20:54:37 +0400 Subject: [PATCH 03/51] Fix condition. --- .../google-combo-account-card/sync-store-address.js | 12 +++++++++--- js/src/hooks/useStoreAddressSynced.js | 11 ++++++----- tests/e2e/specs/setup-mc/step-1-accounts.test.js | 2 ++ 3 files changed, 17 insertions(+), 8 deletions(-) diff --git a/js/src/components/google-combo-account-card/sync-store-address.js b/js/src/components/google-combo-account-card/sync-store-address.js index 43d1142854..8fdaa8ff6d 100644 --- a/js/src/components/google-combo-account-card/sync-store-address.js +++ b/js/src/components/google-combo-account-card/sync-store-address.js @@ -3,15 +3,21 @@ */ import StoreAddressCard from '.~/components/contact-information/store-address-card'; import useStoreAddressSynced from '.~/hooks/useStoreAddressSynced'; +import useGoogleMCAccount from '.~/hooks/useGoogleMCAccount'; +/* + * Renders StoreAddressCard to sync the store address if we have a connected MC account and the address needs to be synced. + * If there's no connected account or the store address has been synced, it will return null. + */ const SyncStoreAddress = () => { const storeAddressSynced = useStoreAddressSynced(); + const { isReady } = useGoogleMCAccount(); - if ( ! storeAddressSynced ) { - return ; + if ( ! isReady || storeAddressSynced ) { + return null; } - return null; + return ; }; export default SyncStoreAddress; diff --git a/js/src/hooks/useStoreAddressSynced.js b/js/src/hooks/useStoreAddressSynced.js index 43d393d0f6..4dab93ba73 100644 --- a/js/src/hooks/useStoreAddressSynced.js +++ b/js/src/hooks/useStoreAddressSynced.js @@ -1,18 +1,19 @@ /** * Internal dependencies */ -import useGoogleMCAccount from '.~/hooks/useGoogleMCAccount'; import useStoreAddress from '.~/hooks/useStoreAddress'; /** * Checks if the store address is synchronized with the Merchant Center (GMC) account address. * - * @return {boolean} Returns `true` if the store address matches the GMC account address, - * and both data sources are ready and loaded; otherwise, returns `false`. + * @return {boolean} Returns `true` if the store address matches the GMC account address, otherwise, returns `false`. */ export default function useStoreAddressSynced() { - const { isReady } = useGoogleMCAccount(); const { data, loaded } = useStoreAddress(); - return isReady && data.address && ! data.isMCAddressDifferent && loaded; + if ( ! loaded ) { + return false; + } + + return data.address && ! Boolean( data.isMCAddressDifferent ); } diff --git a/tests/e2e/specs/setup-mc/step-1-accounts.test.js b/tests/e2e/specs/setup-mc/step-1-accounts.test.js index 3b9125e9ab..b131674cd0 100644 --- a/tests/e2e/specs/setup-mc/step-1-accounts.test.js +++ b/tests/e2e/specs/setup-mc/step-1-accounts.test.js @@ -293,6 +293,7 @@ test.describe( 'Set up accounts', () => { await setUpAccountsPage.mockMCNotConnected(); await setUpAccountsPage.mockContactInformation( { streetAddress: 'Automata Road', + isMCAddressDifferent: false, } ); await setUpAccountsPage.goto(); @@ -311,6 +312,7 @@ test.describe( 'Set up accounts', () => { await setUpAccountsPage.mockMCConnected(); await setUpAccountsPage.mockContactInformation( { streetAddress: 'Automata Road', + isMCAddressDifferent: false, } ); await setUpAccountsPage.goto(); From 55b95fbffb52d9ee016a219c6a56726700bcbfd2 Mon Sep 17 00:00:00 2001 From: asvinb Date: Tue, 29 Oct 2024 15:24:56 +0400 Subject: [PATCH 04/51] Adjust hook to return early if there's no MC connected account. --- .../sync-store-address.js | 4 +- js/src/hooks/useStoreAddressSynced.js | 40 +++++++++++++++---- 2 files changed, 34 insertions(+), 10 deletions(-) diff --git a/js/src/components/google-combo-account-card/sync-store-address.js b/js/src/components/google-combo-account-card/sync-store-address.js index 8fdaa8ff6d..bc6385c303 100644 --- a/js/src/components/google-combo-account-card/sync-store-address.js +++ b/js/src/components/google-combo-account-card/sync-store-address.js @@ -3,7 +3,6 @@ */ import StoreAddressCard from '.~/components/contact-information/store-address-card'; import useStoreAddressSynced from '.~/hooks/useStoreAddressSynced'; -import useGoogleMCAccount from '.~/hooks/useGoogleMCAccount'; /* * Renders StoreAddressCard to sync the store address if we have a connected MC account and the address needs to be synced. @@ -11,9 +10,8 @@ import useGoogleMCAccount from '.~/hooks/useGoogleMCAccount'; */ const SyncStoreAddress = () => { const storeAddressSynced = useStoreAddressSynced(); - const { isReady } = useGoogleMCAccount(); - if ( ! isReady || storeAddressSynced ) { + if ( storeAddressSynced === null || storeAddressSynced ) { return null; } diff --git a/js/src/hooks/useStoreAddressSynced.js b/js/src/hooks/useStoreAddressSynced.js index 4dab93ba73..4d1feb4016 100644 --- a/js/src/hooks/useStoreAddressSynced.js +++ b/js/src/hooks/useStoreAddressSynced.js @@ -1,19 +1,45 @@ +/** + * External dependencies + */ +import { useSelect } from '@wordpress/data'; + /** * Internal dependencies */ -import useStoreAddress from '.~/hooks/useStoreAddress'; +import useGoogleMCAccount from '.~/hooks/useGoogleMCAccount'; +import { STORE_KEY } from '.~/data/constants'; /** * Checks if the store address is synchronized with the Merchant Center (GMC) account address. * - * @return {boolean} Returns `true` if the store address matches the GMC account address, otherwise, returns `false`. + * @return {boolean|null} Returns `true` if the store address matches the GMC account address, otherwise, returns `false`. If the GMC account is not connected `null` or if the state is not yet determined., returns `null`. */ export default function useStoreAddressSynced() { - const { data, loaded } = useStoreAddress(); + const { isReady } = useGoogleMCAccount(); + + return useSelect( + ( select ) => { + if ( ! isReady ) { + return null; + } + + const { getGoogleMCContactInformation } = select( STORE_KEY ); + const { data: contact, loaded } = getGoogleMCContactInformation(); + + if ( ! loaded ) { + return null; + } - if ( ! loaded ) { - return false; - } + const { + is_mc_address_different: isMCAddressDifferent, + wc_address_errors: missingRequiredFields, + } = contact; - return data.address && ! Boolean( data.isMCAddressDifferent ); + return ( + ! Boolean( isMCAddressDifferent ) && + ! missingRequiredFields.length + ); + }, + [ isReady ] + ); } From e0b7df9b421595fb609c0f7ef597cb0e554213a0 Mon Sep 17 00:00:00 2001 From: asvinb Date: Tue, 29 Oct 2024 15:48:45 +0400 Subject: [PATCH 05/51] Use correct paramters. --- js/src/components/contact-information/store-address-card.js | 5 ++++- js/src/hooks/useStoreAddressSynced.js | 4 ++-- tests/e2e/specs/setup-mc/step-1-accounts.test.js | 6 +++--- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/js/src/components/contact-information/store-address-card.js b/js/src/components/contact-information/store-address-card.js index f757af9085..f757ada1df 100644 --- a/js/src/components/contact-information/store-address-card.js +++ b/js/src/components/contact-information/store-address-card.js @@ -21,6 +21,7 @@ import ValidationErrors from '.~/components/validation-errors'; import ContactInformationPreviewCard from './contact-information-preview-card'; import TrackableLink from '.~/components/trackable-link'; import mapStoreAddressErrors from './mapStoreAddressErrors'; +import LoadingLabel from '.~/components/loading-label'; import { recordGlaEvent } from '.~/utils/tracks'; import './store-address-card.scss'; @@ -86,7 +87,9 @@ const StoreAddressCard = ( { showValidation = false } ) => { }; }; - const refreshButton = ( + const refreshButton = isSaving ? ( + + ) : ( { await setUpAccountsPage.mockAdsAccountConnected(); await setUpAccountsPage.mockMCNotConnected(); await setUpAccountsPage.mockContactInformation( { - streetAddress: 'Automata Road', + wcAddressErrors: [], isMCAddressDifferent: false, } ); @@ -311,7 +311,7 @@ test.describe( 'Set up accounts', () => { await setUpAccountsPage.mockAdsAccountDisconnected(); await setUpAccountsPage.mockMCConnected(); await setUpAccountsPage.mockContactInformation( { - streetAddress: 'Automata Road', + wcAddressErrors: [], isMCAddressDifferent: false, } ); @@ -330,7 +330,7 @@ test.describe( 'Set up accounts', () => { await setUpAccountsPage.mockAdsAccountConnected(); await setUpAccountsPage.mockMCConnected(); await setUpAccountsPage.mockContactInformation( { - streetAddress: 'Automata Road', + wcAddressErrors: [], isMCAddressDifferent: true, } ); From 850944e6b777f4f9941b28d6a8c5606f91b99042 Mon Sep 17 00:00:00 2001 From: asvinb Date: Tue, 29 Oct 2024 16:15:22 +0400 Subject: [PATCH 06/51] Adjust JSDocs. --- js/src/components/contact-information/store-address-card.js | 4 ++-- js/src/hooks/useStoreAddressSynced.js | 2 +- tests/e2e/specs/setup-mc/step-1-accounts.test.js | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/js/src/components/contact-information/store-address-card.js b/js/src/components/contact-information/store-address-card.js index f757ada1df..c23a52a15a 100644 --- a/js/src/components/contact-information/store-address-card.js +++ b/js/src/components/contact-information/store-address-card.js @@ -72,7 +72,7 @@ const StoreAddressCard = ( { showValidation = false } ) => { const handleRefreshClick = () => { setSaving( true ); updateGoogleMCContactInformation() - .then( () => refetch() ) + .then( refetch ) .catch( () => setSaving( false ) ); refetchedCallbackRef.current = ( storeAddress ) => { @@ -97,7 +97,7 @@ const StoreAddressCard = ( { showValidation = false } ) => { iconPosition="right" text={ __( 'Refresh to sync', 'google-listings-and-ads' ) } onClick={ handleRefreshClick } - disabled={ ! loaded || isSaving } + disabled={ ! loaded } /> ); diff --git a/js/src/hooks/useStoreAddressSynced.js b/js/src/hooks/useStoreAddressSynced.js index 959d992ef6..a53c5bc528 100644 --- a/js/src/hooks/useStoreAddressSynced.js +++ b/js/src/hooks/useStoreAddressSynced.js @@ -12,7 +12,7 @@ import { STORE_KEY } from '.~/data/constants'; /** * Checks if the store address is synchronized with the Merchant Center (GMC) account address. * - * @return {boolean|null} Returns `true` if the store address matches the GMC account address, otherwise, returns `false`. If the GMC account is not connected `null` or if the state is not yet determined., returns `null`. + * @return {boolean|null} Returns `true` if the store address matches the GMC account address, otherwise, returns `false`. If the MC account is not connected or if the state is not yet determined, returns `null`. */ export default function useStoreAddressSynced() { const { isReady } = useGoogleMCAccount(); diff --git a/tests/e2e/specs/setup-mc/step-1-accounts.test.js b/tests/e2e/specs/setup-mc/step-1-accounts.test.js index 932a72a5b3..69c8c593d0 100644 --- a/tests/e2e/specs/setup-mc/step-1-accounts.test.js +++ b/tests/e2e/specs/setup-mc/step-1-accounts.test.js @@ -349,7 +349,7 @@ test.describe( 'Set up accounts', () => { await setUpAccountsPage.mockAdsAccountConnected(); await setUpAccountsPage.mockMCConnected(); await setUpAccountsPage.mockContactInformation( { - streetAddress: 'Automata Road', + wcAddressErrors: [], isMCAddressDifferent: false, } ); From 4ca1f95e456e856e77129a9a225613fde03f3297 Mon Sep 17 00:00:00 2001 From: asvinb Date: Tue, 29 Oct 2024 21:40:49 +0400 Subject: [PATCH 07/51] Add compact styles for store address account card. --- .../contact-information/store-address-card.js | 82 +++++++++++++------ .../store-address-card.scss | 6 ++ .../empty-store-address-card.js | 51 ++++++++++++ .../sync-store-address/index.js | 1 + .../sync-store-address.js | 11 ++- js/src/hooks/useStoreAddressSynced.js | 28 +++++-- .../setup-stepper/setup-accounts/index.js | 4 +- .../specs/setup-mc/step-1-accounts.test.js | 33 ++++++++ 8 files changed, 179 insertions(+), 37 deletions(-) create mode 100644 js/src/components/google-combo-account-card/sync-store-address/empty-store-address-card.js create mode 100644 js/src/components/google-combo-account-card/sync-store-address/index.js rename js/src/components/google-combo-account-card/{ => sync-store-address}/sync-store-address.js (62%) diff --git a/js/src/components/contact-information/store-address-card.js b/js/src/components/contact-information/store-address-card.js index c23a52a15a..0171e141d1 100644 --- a/js/src/components/contact-information/store-address-card.js +++ b/js/src/components/contact-information/store-address-card.js @@ -7,6 +7,7 @@ import { CardDivider } from '@wordpress/components'; import { Spinner } from '@woocommerce/components'; import { update as updateIcon } from '@wordpress/icons'; import { getPath, getQuery } from '@woocommerce/navigation'; +import classNames from 'classnames'; /** * Internal dependencies @@ -52,10 +53,14 @@ import './store-address-card.scss'; * * @param {Object} props React props. * @param {boolean} [props.showValidation=false] Whether to show validation error messages. + * @param {boolean} [props.compactStyles=false] Whether to use compact styles. The address is part of the card description as opposed to the card body and the description is different. * * @return {JSX.Element} Filled AccountCard component. */ -const StoreAddressCard = ( { showValidation = false } ) => { +const StoreAddressCard = ( { + showValidation = false, + compactStyles = false, +} ) => { const { loaded, data, refetch } = useStoreAddress(); const [ isSaving, setSaving ] = useState( false ); const { updateGoogleMCContactInformation } = useAppDispatch(); @@ -102,7 +107,17 @@ const StoreAddressCard = ( { showValidation = false } ) => { ); let addressContent; - const description = ( + const settingsLink = ( + + ); + + let description = ( <>

{ createInterpolateElement( @@ -111,15 +126,7 @@ const StoreAddressCard = ( { showValidation = false } ) => { 'google-listings-and-ads' ), { - link: ( - - ), + link: settingsLink, } ) }

@@ -141,7 +148,7 @@ const StoreAddressCard = ( { showValidation = false } ) => { .join( ', ' ); addressContent = ( -
+
{ address }
{ address2 &&
{ address2 }
}
{ rest }
@@ -151,27 +158,52 @@ const StoreAddressCard = ( { showValidation = false } ) => { addressContent = ; } + if ( compactStyles ) { + description = ( + <> +

+ { createInterpolateElement( + __( + 'We’re using your store address from Woo Commerce settings for Google verification. This information won’t be public. Edit in WooCommerce settings if needed. Then, refresh to sync it to Google.', + 'google-listings-and-ads' + ), + { + link: settingsLink, + } + ) } +

+ { addressContent } + + ); + } + return ( - - - - { __( 'Store address', 'google-listings-and-ads' ) } - - { addressContent } - { showValidation && ( - - ) } - + { ! compactStyles && ( + <> + + + + { __( 'Store address', 'google-listings-and-ads' ) } + + { addressContent } + { showValidation && ( + + ) } + + + ) } ); }; diff --git a/js/src/components/contact-information/store-address-card.scss b/js/src/components/contact-information/store-address-card.scss index c64ae23a55..a325df5d57 100644 --- a/js/src/components/contact-information/store-address-card.scss +++ b/js/src/components/contact-information/store-address-card.scss @@ -8,4 +8,10 @@ .gla-account-card__description { color: $gray-700; } + + &.gla-store-address-card--is-compact { + .gla-store-address-card__address { + color: $gray-900; + } + } } diff --git a/js/src/components/google-combo-account-card/sync-store-address/empty-store-address-card.js b/js/src/components/google-combo-account-card/sync-store-address/empty-store-address-card.js new file mode 100644 index 0000000000..e44de908ee --- /dev/null +++ b/js/src/components/google-combo-account-card/sync-store-address/empty-store-address-card.js @@ -0,0 +1,51 @@ +/** + * External dependencies + */ +import { __ } from '@wordpress/i18n'; +import { createInterpolateElement } from '@wordpress/element'; +import { getPath, getQuery } from '@woocommerce/navigation'; + +/** + * Internal dependencies + */ +import AccountCard, { APPEARANCE } from '.~/components/account-card'; +import TrackableLink from '.~/components/trackable-link'; + +const EmptyStoreAddressCard = () => { + const path = getPath(); + const { subpath } = getQuery(); + + const description = ( +

+ { createInterpolateElement( + __( + 'Your store address is required by Google for verification. This information won’t be public. Complete that in WooCommerce settings.', + 'google-listings-and-ads' + ), + { + link: ( + + ), + } + ) } +

+ ); + + return ( + + ); +}; + +export default EmptyStoreAddressCard; diff --git a/js/src/components/google-combo-account-card/sync-store-address/index.js b/js/src/components/google-combo-account-card/sync-store-address/index.js new file mode 100644 index 0000000000..7017e89ec1 --- /dev/null +++ b/js/src/components/google-combo-account-card/sync-store-address/index.js @@ -0,0 +1 @@ +export { default } from './sync-store-address'; diff --git a/js/src/components/google-combo-account-card/sync-store-address.js b/js/src/components/google-combo-account-card/sync-store-address/sync-store-address.js similarity index 62% rename from js/src/components/google-combo-account-card/sync-store-address.js rename to js/src/components/google-combo-account-card/sync-store-address/sync-store-address.js index bc6385c303..2a44250883 100644 --- a/js/src/components/google-combo-account-card/sync-store-address.js +++ b/js/src/components/google-combo-account-card/sync-store-address/sync-store-address.js @@ -2,6 +2,7 @@ * Internal dependencies */ import StoreAddressCard from '.~/components/contact-information/store-address-card'; +import EmptyStoreAddressCard from './empty-store-address-card'; import useStoreAddressSynced from '.~/hooks/useStoreAddressSynced'; /* @@ -9,13 +10,17 @@ import useStoreAddressSynced from '.~/hooks/useStoreAddressSynced'; * If there's no connected account or the store address has been synced, it will return null. */ const SyncStoreAddress = () => { - const storeAddressSynced = useStoreAddressSynced(); + const { addressSynced, isAddressFilled } = useStoreAddressSynced(); - if ( storeAddressSynced === null || storeAddressSynced ) { + if ( addressSynced === null || addressSynced ) { return null; } - return ; + if ( ! isAddressFilled ) { + return ; + } + + return ; }; export default SyncStoreAddress; diff --git a/js/src/hooks/useStoreAddressSynced.js b/js/src/hooks/useStoreAddressSynced.js index a53c5bc528..326adda3aa 100644 --- a/js/src/hooks/useStoreAddressSynced.js +++ b/js/src/hooks/useStoreAddressSynced.js @@ -9,10 +9,16 @@ import { useSelect } from '@wordpress/data'; import useGoogleMCAccount from '.~/hooks/useGoogleMCAccount'; import { STORE_KEY } from '.~/data/constants'; +/** + * @typedef {Object} StoreAddressSyncedData + * @property {boolean|null} isAddressFilled Whether the `data` is loading. It's equal to `isResolving` state of wp-data selector. + * @property {boolean|null} addressSynced Returns `true` if the store address matches the GMC account address, otherwise, returns `false`. If the MC account is not connected or if the state is not yet determined, returns `null`. + */ + /** * Checks if the store address is synchronized with the Merchant Center (GMC) account address. * - * @return {boolean|null} Returns `true` if the store address matches the GMC account address, otherwise, returns `false`. If the MC account is not connected or if the state is not yet determined, returns `null`. + * @return {StoreAddressSyncedData} The store address synced data. */ export default function useStoreAddressSynced() { const { isReady } = useGoogleMCAccount(); @@ -20,14 +26,20 @@ export default function useStoreAddressSynced() { return useSelect( ( select ) => { if ( ! isReady ) { - return null; + return { + isAddressFilled: null, + addressSynced: null, + }; } const { getGoogleMCContactInformation } = select( STORE_KEY ); const contact = getGoogleMCContactInformation(); if ( ! contact ) { - return null; + return { + isAddressFilled: null, + addressSynced: null, + }; } const { @@ -35,10 +47,12 @@ export default function useStoreAddressSynced() { wc_address_errors: missingRequiredFields, } = contact; - return ( - ! Boolean( isMCAddressDifferent ) && - ! missingRequiredFields.length - ); + return { + isAddressFilled: ! missingRequiredFields.length, + addressSynced: + ! Boolean( isMCAddressDifferent ) && + ! missingRequiredFields.length, + }; }, [ isReady ] ); diff --git a/js/src/setup-mc/setup-stepper/setup-accounts/index.js b/js/src/setup-mc/setup-stepper/setup-accounts/index.js index b659c2e95c..84123f00ad 100644 --- a/js/src/setup-mc/setup-stepper/setup-accounts/index.js +++ b/js/src/setup-mc/setup-stepper/setup-accounts/index.js @@ -90,7 +90,7 @@ const SetupAccounts = ( props ) => { isReady: isGoogleMCReady, } = useGoogleMCAccount(); const { hasFinishedResolution } = useGoogleAdsAccount(); - const storeAddressSynced = useStoreAddressSynced(); + const { addressSynced } = useStoreAddressSynced(); const isGoogleAdsReady = useGoogleAdsAccountReady(); /** @@ -118,7 +118,7 @@ const SetupAccounts = ( props ) => { hasFinishedResolution && isGoogleAdsReady && isGoogleMCReady && - storeAddressSynced + addressSynced ); return ( diff --git a/tests/e2e/specs/setup-mc/step-1-accounts.test.js b/tests/e2e/specs/setup-mc/step-1-accounts.test.js index 69c8c593d0..e790232347 100644 --- a/tests/e2e/specs/setup-mc/step-1-accounts.test.js +++ b/tests/e2e/specs/setup-mc/step-1-accounts.test.js @@ -337,6 +337,12 @@ test.describe( 'Set up accounts', () => { await setUpAccountsPage.goto(); } ); + test( 'should see the Refresh to sync button', async () => { + const refreshToSyncButton = + await setUpAccountsPage.getStoreAddressRefreshToSyncButton(); + await expect( refreshToSyncButton ).toBeVisible(); + } ); + test( 'should see "Continue" button disabled when the store address needs to be synced', async () => { const continueButton = await setUpAccountsPage.getContinueButton(); @@ -344,6 +350,33 @@ test.describe( 'Set up accounts', () => { } ); } ); + test.describe( 'When the store address needs to be completed in WooCommerce settings', async () => { + test.beforeAll( async () => { + await setUpAccountsPage.mockAdsAccountConnected(); + await setUpAccountsPage.mockMCConnected(); + await setUpAccountsPage.mockContactInformation( { + wcAddressErrors: [ 'address_1', 'city', 'postcode' ], + isMCAddressDifferent: true, + } ); + + await setUpAccountsPage.goto(); + } ); + + test( 'should see the notice to complete the store address in WooCommerce settings', async () => { + const storeAddressCard = + setUpAccountsPage.getStoreAddressCard(); + await expect( storeAddressCard ).toContainText( + 'Complete that in WooCommerce settings.' + ); + } ); + + test( 'should see "Continue" button disabled when the store address needs to be completed', async () => { + const continueButton = + await setUpAccountsPage.getContinueButton(); + await expect( continueButton ).toBeDisabled(); + } ); + } ); + test.describe( 'When all accounts are connected and store address is synced', async () => { test.beforeAll( async () => { await setUpAccountsPage.mockAdsAccountConnected(); From 5b81e157fc43be1d928b0de04e30bc5ec9fd6250 Mon Sep 17 00:00:00 2001 From: asvinb Date: Tue, 29 Oct 2024 21:44:22 +0400 Subject: [PATCH 08/51] No need to refetch. --- js/src/components/contact-information/store-address-card.js | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/js/src/components/contact-information/store-address-card.js b/js/src/components/contact-information/store-address-card.js index 0171e141d1..c92a48b8b5 100644 --- a/js/src/components/contact-information/store-address-card.js +++ b/js/src/components/contact-information/store-address-card.js @@ -61,7 +61,7 @@ const StoreAddressCard = ( { showValidation = false, compactStyles = false, } ) => { - const { loaded, data, refetch } = useStoreAddress(); + const { loaded, data } = useStoreAddress(); const [ isSaving, setSaving ] = useState( false ); const { updateGoogleMCContactInformation } = useAppDispatch(); const path = getPath(); @@ -76,9 +76,7 @@ const StoreAddressCard = ( { const handleRefreshClick = () => { setSaving( true ); - updateGoogleMCContactInformation() - .then( refetch ) - .catch( () => setSaving( false ) ); + updateGoogleMCContactInformation().catch( () => setSaving( false ) ); refetchedCallbackRef.current = ( storeAddress ) => { const eventProps = { From 123925dde6466ab87aecb7c6b47db89a2ae5c787 Mon Sep 17 00:00:00 2001 From: asvinb Date: Tue, 29 Oct 2024 22:20:24 +0400 Subject: [PATCH 09/51] Remove store requirements step. --- .../setup-stepper/saved-setup-stepper.js | 18 -- .../setup-mc/setup-stepper/stepNameKeyMap.js | 3 +- .../setup-stepper/store-requirements/index.js | 108 ------- ...st.js => step-3-complete-campaign.test.js} | 2 +- .../step-3-confirm-store-requirements.test.js | 235 -------------- ...ampaign.js => step-3-complete-campaign.js} | 0 .../step-3-confirm-store-requirements.js | 293 ------------------ 7 files changed, 2 insertions(+), 657 deletions(-) delete mode 100644 js/src/setup-mc/setup-stepper/store-requirements/index.js rename tests/e2e/specs/setup-mc/{step-4-complete-campaign.test.js => step-3-complete-campaign.test.js} (99%) delete mode 100644 tests/e2e/specs/setup-mc/step-3-confirm-store-requirements.test.js rename tests/e2e/utils/pages/setup-mc/{step-4-complete-campaign.js => step-3-complete-campaign.js} (100%) delete mode 100644 tests/e2e/utils/pages/setup-mc/step-3-confirm-store-requirements.js diff --git a/js/src/setup-mc/setup-stepper/saved-setup-stepper.js b/js/src/setup-mc/setup-stepper/saved-setup-stepper.js index eb13d3a815..56a8a96995 100644 --- a/js/src/setup-mc/setup-stepper/saved-setup-stepper.js +++ b/js/src/setup-mc/setup-stepper/saved-setup-stepper.js @@ -20,7 +20,6 @@ import useSaveShippingTimes from '.~/hooks/useSaveShippingTimes'; import useDispatchCoreNotices from '.~/hooks/useDispatchCoreNotices'; import SetupAccounts from './setup-accounts'; import SetupFreeListings from '.~/components/free-listings/setup-free-listings'; -import StoreRequirements from './store-requirements'; import SetupPaidAds from './setup-paid-ads'; import stepNameKeyMap from './stepNameKeyMap'; import { @@ -102,10 +101,6 @@ const SavedSetupStepper = ( { savedStep } ) => { }; const handleSetupListingsContinue = () => { - continueStep( stepNameKeyMap.store_requirements ); - }; - - const handleStoreRequirementsContinue = () => { continueStep( stepNameKeyMap.paid_ads ); }; @@ -210,19 +205,6 @@ const SavedSetupStepper = ( { savedStep } ) => { ), onClick: handleStepClick, }, - { - key: stepNameKeyMap.store_requirements, - label: __( - 'Confirm store requirements', - 'google-listings-and-ads' - ), - content: ( - - ), - onClick: handleStepClick, - }, { key: stepNameKeyMap.paid_ads, label: __( 'Create a campaign', 'google-listings-and-ads' ), diff --git a/js/src/setup-mc/setup-stepper/stepNameKeyMap.js b/js/src/setup-mc/setup-stepper/stepNameKeyMap.js index e290c78310..1d234712fb 100644 --- a/js/src/setup-mc/setup-stepper/stepNameKeyMap.js +++ b/js/src/setup-mc/setup-stepper/stepNameKeyMap.js @@ -1,8 +1,7 @@ const stepNameKeyMap = { accounts: '1', product_listings: '2', - store_requirements: '3', - paid_ads: '4', + paid_ads: '3', }; export default stepNameKeyMap; diff --git a/js/src/setup-mc/setup-stepper/store-requirements/index.js b/js/src/setup-mc/setup-stepper/store-requirements/index.js deleted file mode 100644 index ef3f73ffa0..0000000000 --- a/js/src/setup-mc/setup-stepper/store-requirements/index.js +++ /dev/null @@ -1,108 +0,0 @@ -/** - * External dependencies - */ -import { __ } from '@wordpress/i18n'; -import { useState } from '@wordpress/element'; - -/** - * Internal dependencies - */ -import { useAppDispatch } from '.~/data'; -import useStoreAddress from '.~/hooks/useStoreAddress'; -import useDispatchCoreNotices from '.~/hooks/useDispatchCoreNotices'; -import StepContent from '.~/components/stepper/step-content'; -import StepContentHeader from '.~/components/stepper/step-content-header'; -import StepContentActions from '.~/components/stepper/step-content-actions'; -import StepContentFooter from '.~/components/stepper/step-content-footer'; -import AdaptiveForm from '.~/components/adaptive-form'; -import ContactInformation from '.~/components/contact-information'; -import AppButton from '.~/components/app-button'; - -/** - * Step for the store requirements in the onboarding flow. - * - * @param {Object} props React props. - * @param {() => void} props.onContinue Callback called once continue button is clicked. - */ -export default function StoreRequirements( { onContinue } ) { - const { updateGoogleMCContactInformation } = useAppDispatch(); - const { createNotice } = useDispatchCoreNotices(); - const { data: address } = useStoreAddress(); - - /** - * Since it still lacking the phone verification state, - * all onboarding accounts are considered unverified phone numbers. - */ - const [ isPhoneNumberReady, setPhoneNumberReady ] = useState( false ); - - const handleSubmitCallback = async () => { - try { - await updateGoogleMCContactInformation(); - onContinue(); - } catch ( error ) { - createNotice( - 'error', - __( - 'Unable to update your contact information. Please try again later.', - 'google-listings-and-ads' - ) - ); - } - }; - - return ( - - - - { ( formContext ) => { - const { handleSubmit, adapter } = formContext; - - const handleSubmitClick = ( event ) => { - const isReadyToComplete = - isPhoneNumberReady && address.isAddressFilled; - - if ( isReadyToComplete ) { - return handleSubmit( event ); - } - - adapter.showValidation(); - }; - - return ( - <> - - setPhoneNumberReady( true ) - } - /> - - - - - { __( - 'Continue', - 'google-listings-and-ads' - ) } - - - - - ); - } } - - - ); -} diff --git a/tests/e2e/specs/setup-mc/step-4-complete-campaign.test.js b/tests/e2e/specs/setup-mc/step-3-complete-campaign.test.js similarity index 99% rename from tests/e2e/specs/setup-mc/step-4-complete-campaign.test.js rename to tests/e2e/specs/setup-mc/step-3-complete-campaign.test.js index 53b2a4a497..74106538e9 100644 --- a/tests/e2e/specs/setup-mc/step-4-complete-campaign.test.js +++ b/tests/e2e/specs/setup-mc/step-3-complete-campaign.test.js @@ -7,7 +7,7 @@ const { test, expect } = require( '@playwright/test' ); * Internal dependencies */ import SetupBudgetPage from '../../utils/pages/setup-ads/setup-budget'; -import CompleteCampaign from '../../utils/pages/setup-mc/step-4-complete-campaign'; +import CompleteCampaign from '../../utils/pages/setup-mc/step-3-complete-campaign'; import SetupAdsAccountPage from '../../utils/pages/setup-ads/setup-ads-accounts'; import { checkFAQExpandable, diff --git a/tests/e2e/specs/setup-mc/step-3-confirm-store-requirements.test.js b/tests/e2e/specs/setup-mc/step-3-confirm-store-requirements.test.js deleted file mode 100644 index cfa84fc858..0000000000 --- a/tests/e2e/specs/setup-mc/step-3-confirm-store-requirements.test.js +++ /dev/null @@ -1,235 +0,0 @@ -/** - * External dependencies - */ -import { parsePhoneNumberFromString as parsePhoneNumber } from 'libphonenumber-js'; -const { test, expect } = require( '@playwright/test' ); - -/** - * Internal dependencies - */ -import { LOAD_STATE } from '../../utils/constants'; -import StoreRequirements from '../../utils/pages/setup-mc/step-3-confirm-store-requirements'; - -test.use( { storageState: process.env.ADMINSTATE } ); - -test.describe.configure( { mode: 'serial' } ); - -/** - * @type {import('../../utils/pages/setup-mc/step-3-confirm-store-requirements.js').default} storeRequirements - */ -let storeRequirements = null; - -/** - * @type {import('@playwright/test').Page} page - */ -let page = null; - -test.describe( 'Confirm store requirements', () => { - test.beforeAll( async ( { browser } ) => { - page = await browser.newPage(); - storeRequirements = new StoreRequirements( page ); - await Promise.all( [ - // Mock Jetpack as connected - storeRequirements.mockJetpackConnected(), - - // Mock google as connected. - storeRequirements.mockGoogleConnected(), - - // Mock Merchant Center as connected - storeRequirements.mockMCConnected(), - - // Mock MC step as store_requirements - storeRequirements.mockMCSetup( 'incomplete', 'store_requirements' ), - - // Mock MC contact information - storeRequirements.mockContactInformation(), - ] ); - } ); - - test.afterAll( async () => { - await storeRequirements.closePage(); - } ); - - test( 'should see the heading and the texts below', async () => { - await storeRequirements.goto(); - - await expect( - page.getByRole( 'heading', { - name: 'Confirm store requirements', - } ) - ).toBeVisible(); - - await expect( - page.getByText( - 'Review and confirm that your store meets Google Merchant Center requirements.' - ) - ).toBeVisible(); - } ); - - test.describe( 'Phone verification', () => { - test.beforeAll( async () => { - await storeRequirements.fulfillPhoneVerificationRequest( { - verification_id: 'abc-123-def', - } ); - } ); - - test( 'should see the correct texts in phone verification card', async () => { - const phoneNumberDescriptionRow = - storeRequirements.getPhoneNumberDescriptionRow(); - await expect( phoneNumberDescriptionRow ).toContainText( - 'Please enter a phone number to be used for verification.' - ); - } ); - - test( 'should see the "Send verification code" button to be disabled', async () => { - const sendCodeButton = - storeRequirements.getSendVerificationCodeButton(); - await expect( sendCodeButton ).toBeDisabled(); - } ); - - test( 'should see the "Send verification code" button to be disabled when enter a phone number with wrong format', async () => { - const sendCodeButton = - storeRequirements.getSendVerificationCodeButton(); - await storeRequirements.selectCountryCodeOption( - 'United States (US) (+1)' - ); - await expect( sendCodeButton ).toBeDisabled(); - await storeRequirements.fillPhoneNumber( '9999999999' ); - await expect( sendCodeButton ).toBeDisabled(); - } ); - - test( 'should see the "Send verification code" button to be enabled when country code and phone number are entered', async () => { - const sendCodeButton = - storeRequirements.getSendVerificationCodeButton(); - await storeRequirements.selectCountryCodeOption( - 'United States (US) (+1)' - ); - await expect( sendCodeButton ).toBeDisabled(); - await storeRequirements.fillPhoneNumber( '8888888888' ); - await expect( sendCodeButton ).toBeEnabled(); - } ); - - test( 'should see "Verify phone number" button is disabled after clicking "Send verification code" button', async () => { - await storeRequirements.clickSendVerificationCodeButton(); - const verifyNumberButton = - storeRequirements.getVerifyPhoneNumberButton(); - await expect( verifyNumberButton ).toBeDisabled(); - } ); - - test( 'should see "Verify phone number" button is enabled after entering the verification code', async () => { - await storeRequirements.fillVerificationCode(); - const verifyNumberButton = - storeRequirements.getVerifyPhoneNumberButton(); - await expect( verifyNumberButton ).toBeEnabled(); - } ); - - test( 'should see an error notice after entering wrong verification code', async () => { - // Mock phone verification request as failed - await storeRequirements.fulfillPhoneVerificationVerifyRequest( - { - message: '[verificationCode] Wrong code.', - reason: 'badRequest', - }, - 400 - ); - await storeRequirements.clickVerifyPhoneNumberButon(); - - const errorNotice = storeRequirements.getPhoneNumberErrorNotice(); - await expect( errorNotice ).toContainText( - 'Wrong verification code. Please try again.' - ); - } ); - - test( 'should see phone number and the "Edit" button after entering correct verification code', async () => { - // Mock MC contact information - await storeRequirements.mockContactInformation( { - phoneNumber: '+18888888888', - phoneVerificationStatus: 'verified', - } ); - - // Mock phone verification request as successful - await storeRequirements.fulfillPhoneVerificationVerifyRequest( - null, - 204 - ); - - await storeRequirements.fillVerificationCode( '654321' ); - await storeRequirements.clickVerifyPhoneNumberButon(); - - const parsedNumber = parsePhoneNumber( '8888888888', 'US' ); - const expectedNumber = parsedNumber.formatInternational(); - - const phoneNumberDescriptionRow = - storeRequirements.getPhoneNumberDescriptionRow(); - await expect( phoneNumberDescriptionRow ).toContainText( - expectedNumber - ); - - const phoneNumberEditButton = - storeRequirements.getPhoneNumberEditButton(); - await expect( phoneNumberEditButton ).toBeVisible(); - } ); - } ); - - test.describe( 'Store address', () => { - test.beforeAll( async () => { - // Mock MC contact information - await storeRequirements.mockContactInformation( { - phoneNumber: '+18888888888', - phoneVerificationStatus: 'verified', - streetAddress: 'Automata Road', - } ); - await storeRequirements.goto(); - } ); - - test( 'should see "Refresh to sync" button', async () => { - const refreshToSyncButton = - storeRequirements.getStoreAddressRefreshToSyncButton(); - await expect( refreshToSyncButton ).toBeVisible(); - await expect( refreshToSyncButton ).toBeEnabled(); - } ); - - test( 'should see store address contains "Automata Road"', async () => { - const storeAddressCard = storeRequirements.getStoreAddressCard(); - await expect( storeAddressCard ).toContainText( 'Automata Road' ); - } ); - - test( 'should see store address contains "WooCommerce Road" after clicking "Refresh to sync" button', async () => { - // Mock MC contact information - await storeRequirements.mockContactInformation( { - phoneNumber: '+18888888888', - phoneVerificationStatus: 'verified', - streetAddress: 'WooCommerce Road', - } ); - - const refreshToSyncButton = - storeRequirements.getStoreAddressRefreshToSyncButton(); - await refreshToSyncButton.click(); - await page.waitForLoadState( LOAD_STATE.DOM_CONTENT_LOADED ); - const storeAddressCard = storeRequirements.getStoreAddressCard(); - await expect( storeAddressCard ).toContainText( - 'WooCommerce Road' - ); - } ); - } ); - - test.describe( 'Links', () => { - test( 'should contain the correct URL for "Learn more" link', async () => { - const link = storeRequirements.getLearnMoreLink(); - await expect( link ).toBeVisible(); - await expect( link ).toHaveAttribute( - 'href', - 'https://woocommerce.com/document/google-for-woocommerce/get-started/requirements/#contact-information' - ); - } ); - - test( 'should contain the correct URL for "WooCommerce settings" link', async () => { - const link = storeRequirements.getWooCommerceSettingsLink(); - await expect( link ).toBeVisible(); - await expect( link ).toHaveAttribute( - 'href', - 'admin.php?page=wc-settings' - ); - } ); - } ); -} ); diff --git a/tests/e2e/utils/pages/setup-mc/step-4-complete-campaign.js b/tests/e2e/utils/pages/setup-mc/step-3-complete-campaign.js similarity index 100% rename from tests/e2e/utils/pages/setup-mc/step-4-complete-campaign.js rename to tests/e2e/utils/pages/setup-mc/step-3-complete-campaign.js diff --git a/tests/e2e/utils/pages/setup-mc/step-3-confirm-store-requirements.js b/tests/e2e/utils/pages/setup-mc/step-3-confirm-store-requirements.js deleted file mode 100644 index 6bd36508c2..0000000000 --- a/tests/e2e/utils/pages/setup-mc/step-3-confirm-store-requirements.js +++ /dev/null @@ -1,293 +0,0 @@ -/** - * Internal dependencies - */ -import { LOAD_STATE } from '../../constants'; -import MockRequests from '../../mock-requests'; - -/** - * Configure product listings page object class. - */ -export default class StoreRequirements extends MockRequests { - /** - * @param {import('@playwright/test').Page} page - */ - constructor( page ) { - super( page ); - this.page = page; - } - - /** - * Close the current page. - * - * @return {Promise} - */ - async closePage() { - await this.page.close(); - } - - /** - * Go to the set up mc page. - * - * @return {Promise} - */ - async goto() { - await this.page.goto( - '/wp-admin/admin.php?page=wc-admin&path=%2Fgoogle%2Fsetup-mc&google-mc=connected', - { waitUntil: LOAD_STATE.DOM_CONTENT_LOADED } - ); - } - - /** - * Get phone verification card. - * - * @return {import('@playwright/test').Locator} Get phone verification card. - */ - getPhoneVerificationCard() { - return this.page.locator( '.gla-phone-number-card' ); - } - - /** - * Get store address card. - * - * @return {import('@playwright/test').Locator} Get store address card. - */ - getStoreAddressCard() { - return this.page.locator( '.gla-store-address-card' ); - } - - /** - * Get phone number description text. - * - * @return {import('@playwright/test').Locator} Get checklist card. - */ - getPhoneNumberDescriptionRow() { - return this.getPhoneVerificationCard().locator( - '.gla-account-card__description' - ); - } - - /** - * Get "Send verification code" button. - * - * @return {import('@playwright/test').Locator} Get "Send verification code" button. - */ - getSendVerificationCodeButton() { - return this.getPhoneVerificationCard().getByRole( 'button', { - name: 'Send verification code', - exact: true, - } ); - } - - /** - * Get "Verify phone number" button. - * - * @return {import('@playwright/test').Locator} Get "Verify phone number" button. - */ - getVerifyPhoneNumberButton() { - return this.getPhoneVerificationCard().getByRole( 'button', { - name: 'Verify phone number', - exact: true, - } ); - } - - /** - * Get country code input box. - * - * @return {import('@playwright/test').Locator} Get country code input box. - */ - getCountryCodeInputBox() { - return this.getPhoneVerificationCard().locator( - 'input[id*="woocommerce-select-control"]' - ); - } - - /** - * Get phone number input box. - * - * @return {import('@playwright/test').Locator} Get phone number input box. - */ - getPhoneNumberInputBox() { - return this.getPhoneVerificationCard().locator( - 'input[id*="inspector-input-control"]' - ); - } - - /** - * Get verification code input boxes. - * - * @return {import('@playwright/test').Locator} Get verification code input box. - */ - getVerificationCodeInputBoxes() { - return this.getPhoneVerificationCard().locator( - '.wcdl-subsection .app-input-control input' - ); - } - - /** - * Get phone number error notice. - * - * @return {import('@playwright/test').Locator} Get phone number error notice. - */ - getPhoneNumberErrorNotice() { - return this.getPhoneVerificationCard().locator( - '.components-notice.is-error' - ); - } - - /** - * Get phone number edit button. - * - * @return {import('@playwright/test').Locator} Get phone number edit button. - */ - getPhoneNumberEditButton() { - return this.getPhoneVerificationCard().getByRole( 'button', { - name: 'Edit', - exact: true, - } ); - } - - /** - * Get store address refresh to sync button. - * - * @return {import('@playwright/test').Locator} Get store address refresh to sync button. - */ - getStoreAddressRefreshToSyncButton() { - return this.getStoreAddressCard().getByRole( 'button', { - name: 'Refresh to sync', - exact: true, - } ); - } - - /** - * Get Continue button. - * - * @return {import('@playwright/test').Locator} Get Continue button. - */ - getContinueButton() { - return this.page.getByRole( 'button', { - name: 'Continue', - exact: true, - } ); - } - - /** - * Get error message row. - * - * @return {import('@playwright/test').Locator} Get error message row. - */ - getErrorMessageRow() { - return this.page.locator( '.gla-validation-errors' ); - } - - /** - * Get learn more link. - * - * @return {import('@playwright/test').Locator} Get learn more link. - */ - getLearnMoreLink() { - return this.page.getByRole( 'link', { - name: 'Learn more', - exact: true, - } ); - } - - /** - * Get WooCommerce settings link. - * - * @return {import('@playwright/test').Locator} Get WooCommerce settings link. - */ - getWooCommerceSettingsLink() { - return this.page.getByRole( 'link', { - name: 'WooCommerce settings', - exact: true, - } ); - } - - /** - * Fill country code. - * - * @param {string} code - * - * @return {Promise} - */ - async fillCountryCode( code = 'United States (US) (+1)' ) { - const countryCodeInputBox = this.getCountryCodeInputBox(); - await countryCodeInputBox.fill( code ); - await this.page.waitForLoadState( LOAD_STATE.DOM_CONTENT_LOADED ); - } - - /** - * Select country code option. - * - * @param {string} code - * - * @return {Promise} - */ - async selectCountryCodeOption( code = 'United States (US) (+1)' ) { - await this.fillCountryCode( code ); - const countryCodeOption = this.page.getByRole( 'option', { - name: code, - } ); - await countryCodeOption.click(); - await this.page.waitForLoadState( LOAD_STATE.DOM_CONTENT_LOADED ); - } - - /** - * Fill phone number. - * - * @param {string} number - * - * @return {Promise} - */ - async fillPhoneNumber( number = '8888888888' ) { - const phoneNumberInputBox = this.getPhoneNumberInputBox(); - await phoneNumberInputBox.fill( number ); - - // A hack to finish typing in the input box, similar to pressing anywhere in the page. - await phoneNumberInputBox.press( 'Tab' ); - - await this.page.waitForLoadState( LOAD_STATE.DOM_CONTENT_LOADED ); - } - - /** - * Click send verification code button. - * - * @return {Promise} - */ - async clickSendVerificationCodeButton() { - const sendCodeButton = this.getSendVerificationCodeButton(); - await sendCodeButton.click(); - await this.page.waitForLoadState( LOAD_STATE.DOM_CONTENT_LOADED ); - } - - /** - * Fill verification code. - * - * @param {string} code - * - * @return {Promise} - */ - async fillVerificationCode( code = '123456' ) { - const verificationInputBoxes = this.getVerificationCodeInputBoxes(); - const count = await verificationInputBoxes.count(); - - for ( let i = 0; i < count; i++ ) { - const input = await verificationInputBoxes.nth( i ); - const digit = code[ i ]; - await input.fill( digit ); - } - - await this.page.waitForLoadState( LOAD_STATE.DOM_CONTENT_LOADED ); - } - - /** - * Click verify phone number button. - * - * @return {Promise} - */ - async clickVerifyPhoneNumberButon() { - const verifyPhoneNumberButton = this.getVerifyPhoneNumberButton(); - await verifyPhoneNumberButton.click(); - await this.page.waitForLoadState( LOAD_STATE.DOM_CONTENT_LOADED ); - } -} From b3a9ff9bcbb7930d0dcd65e101f594c890823c17 Mon Sep 17 00:00:00 2001 From: asvinb Date: Tue, 29 Oct 2024 22:32:21 +0400 Subject: [PATCH 10/51] Remove unused component. --- .../components/contact-information/index.js | 62 +------------------ 1 file changed, 2 insertions(+), 60 deletions(-) diff --git a/js/src/components/contact-information/index.js b/js/src/components/contact-information/index.js index fb3c01f781..a2dced84e4 100644 --- a/js/src/components/contact-information/index.js +++ b/js/src/components/contact-information/index.js @@ -7,16 +7,11 @@ import { __ } from '@wordpress/i18n'; * Internal dependencies */ import { getEditPhoneNumberUrl, getEditStoreAddressUrl } from '.~/utils/urls'; -import { useAdaptiveFormContext } from '.~/components/adaptive-form'; -import useGoogleMCPhoneNumber from '.~/hooks/useGoogleMCPhoneNumber'; import Section from '.~/wcdl/section'; import VerticalGapLayout from '.~/components/vertical-gap-layout'; import AppDocumentationLink from '.~/components/app-documentation-link'; -import PhoneNumberCard, { PhoneNumberCardPreview } from './phone-number-card'; -import StoreAddressCard, { - StoreAddressCardPreview, -} from './store-address-card'; -import usePhoneNumberCheckTrackEventEffect from './usePhoneNumberCheckTrackEventEffect'; +import { PhoneNumberCardPreview } from './phone-number-card'; +import { StoreAddressCardPreview } from './store-address-card'; const learnMoreLinkId = 'contact-information-read-more'; const learnMoreUrl = @@ -39,7 +34,6 @@ const description = ( ); -const mcTitle = __( 'Verify contact information', 'google-listings-and-ads' ); const settingsTitle = __( 'Contact information', 'google-listings-and-ads' ); /** @@ -78,55 +72,3 @@ export function ContactInformationPreview() { ); } - -/** - * Renders a contact information section with specified initial state and texts. - * - * @param {Object} props React props. - * @param {Function} [props.onPhoneNumberVerified] Called when the phone number is verified or has been verified. - * @fires gla_documentation_link_click with `{ context: 'setup-mc-contact-information', link_id: 'contact-information-read-more', href: 'https://woocommerce.com/document/google-for-woocommerce/get-started/requirements/#contact-information' }` - * @fires gla_documentation_link_click with `{ context: 'settings-no-phone-number-notice', link_id: 'contact-information-read-more', href: 'https://woocommerce.com/document/google-for-woocommerce/get-started/requirements/#contact-information' }` - * @fires gla_documentation_link_click with `{ context: 'settings-no-store-address-notice', link_id: 'contact-information-read-more', href: 'https://woocommerce.com/document/google-for-woocommerce/get-started/requirements/#contact-information' }` - */ -const ContactInformation = ( { onPhoneNumberVerified } ) => { - const { adapter } = useAdaptiveFormContext(); - const phone = useGoogleMCPhoneNumber(); - const title = mcTitle; - const trackContext = 'setup-mc-contact-information'; - - usePhoneNumberCheckTrackEventEffect( phone ); - - return ( -
- { description } -

- - { __( 'Learn more', 'google-listings-and-ads' ) } - -

-
- } - > - - - - - - ); -}; - -export default ContactInformation; From ac7918c937da1cebbcb04b66601248e1236c6fff Mon Sep 17 00:00:00 2001 From: asvinb Date: Tue, 29 Oct 2024 22:56:47 +0400 Subject: [PATCH 11/51] Fix JS tests. --- .../setup-stepper/saved-setup-stepper.test.js | 50 +++++-------------- src/MerchantCenter/MerchantCenterService.php | 6 +-- .../MerchantCenterServiceTest.php | 32 +----------- 3 files changed, 14 insertions(+), 74 deletions(-) diff --git a/js/src/setup-mc/setup-stepper/saved-setup-stepper.test.js b/js/src/setup-mc/setup-stepper/saved-setup-stepper.test.js index 1a0081b5f2..5b92118295 100644 --- a/js/src/setup-mc/setup-stepper/saved-setup-stepper.test.js +++ b/js/src/setup-mc/setup-stepper/saved-setup-stepper.test.js @@ -31,9 +31,6 @@ jest.mock( './setup-accounts', () => jest.fn().mockName( 'SetupAccounts' ) ); jest.mock( '.~/components/free-listings/setup-free-listings', () => jest.fn().mockName( 'SetupFreeListings' ) ); -jest.mock( './store-requirements', () => - jest.fn().mockName( 'StoreRequirements' ) -); jest.mock( './setup-paid-ads', () => jest.fn().mockName( 'SetupPaidAds' ) ); /** @@ -49,13 +46,11 @@ import { recordEvent } from '@woocommerce/tracks'; import SavedSetupStepper from './saved-setup-stepper'; import SetupAccounts from './setup-accounts'; import SetupFreeListings from '.~/components/free-listings/setup-free-listings'; -import StoreRequirements from './store-requirements'; import SetupPaidAds from './setup-paid-ads'; describe( 'SavedSetupStepper', () => { let continueToStep2; let continueToStep3; - let continueToStep4; beforeEach( () => { SetupAccounts.mockImplementation( ( { onContinue } ) => { @@ -68,11 +63,6 @@ describe( 'SavedSetupStepper', () => { return null; } ); - StoreRequirements.mockImplementation( ( { onContinue } ) => { - continueToStep4 = onContinue; - return null; - } ); - SetupPaidAds.mockReturnValue( null ); } ); @@ -80,7 +70,7 @@ describe( 'SavedSetupStepper', () => { jest.clearAllMocks(); } ); - async function continueUntilStep4() { + async function continueUntilStep3() { continueToStep2(); // Wait for stepper content to be rendered. @@ -89,21 +79,15 @@ describe( 'SavedSetupStepper', () => { } ); continueToStep3(); - - await waitFor( () => { - expect( continueToStep4 ).toBeDefined(); - } ); - - continueToStep4(); } describe( 'tracks', () => { it( 'Should record events after calling back to `onContinue`', async () => { render( ); - await continueUntilStep4(); + await continueUntilStep3(); - expect( recordEvent ).toHaveBeenCalledTimes( 3 ); + expect( recordEvent ).toHaveBeenCalledTimes( 2 ); expect( recordEvent ).toHaveBeenNthCalledWith( 1, 'gla_setup_mc', { action: 'go-to-step2', triggered_by: 'step1-continue-button', @@ -112,51 +96,41 @@ describe( 'SavedSetupStepper', () => { action: 'go-to-step3', triggered_by: 'step2-continue-button', } ); - expect( recordEvent ).toHaveBeenNthCalledWith( 3, 'gla_setup_mc', { - action: 'go-to-step4', - triggered_by: 'step3-continue-button', - } ); } ); it( 'Should record events after clicking step navigation buttons', async () => { const user = userEvent.setup(); - render( ); + render( ); const step1 = screen.getByRole( 'button', { name: /accounts/ } ); const step2 = screen.getByRole( 'button', { name: /listings/ } ); - const step3 = screen.getByRole( 'button', { name: /store/ } ); - // Step 4 -> Step 3 -> Step 2 -> Step 1 - await user.click( step3 ); + // Step 3 -> Step 2 -> Step 1 await user.click( step2 ); await user.click( step1 ); - expect( recordEvent ).toHaveBeenCalledTimes( 3 ); + expect( recordEvent ).toHaveBeenCalledTimes( 2 ); expect( recordEvent ).toHaveBeenNthCalledWith( 1, 'gla_setup_mc', { - action: 'go-to-step3', - triggered_by: 'stepper-step3-button', - } ); - expect( recordEvent ).toHaveBeenNthCalledWith( 2, 'gla_setup_mc', { action: 'go-to-step2', triggered_by: 'stepper-step2-button', } ); - expect( recordEvent ).toHaveBeenNthCalledWith( 3, 'gla_setup_mc', { + expect( recordEvent ).toHaveBeenNthCalledWith( 2, 'gla_setup_mc', { action: 'go-to-step1', triggered_by: 'stepper-step1-button', } ); - // Step 4 -> Step 2 - await continueUntilStep4(); + // Step 3 -> Step 1 + await continueUntilStep3(); recordEvent.mockClear(); expect( recordEvent ).toHaveBeenCalledTimes( 0 ); - await user.click( step2 ); + await user.click( step1 ); expect( recordEvent ).toHaveBeenCalledTimes( 1 ); expect( recordEvent ).toHaveBeenNthCalledWith( 1, 'gla_setup_mc', { - action: 'go-to-step2', - triggered_by: 'stepper-step2-button', + action: 'go-to-step1', + triggered_by: 'stepper-step1-button', } ); } ); } ); diff --git a/src/MerchantCenter/MerchantCenterService.php b/src/MerchantCenter/MerchantCenterService.php index 33b4c78529..124b33d1f3 100644 --- a/src/MerchantCenter/MerchantCenterService.php +++ b/src/MerchantCenter/MerchantCenterService.php @@ -235,11 +235,7 @@ public function get_setup_status(): array { $step = 'product_listings'; if ( $this->saved_target_audience() && $this->saved_shipping_and_tax_options() ) { - $step = 'store_requirements'; - - if ( $this->is_mc_contact_information_setup() ) { - $step = 'paid_ads'; - } + $step = 'paid_ads'; } } diff --git a/tests/Unit/MerchantCenter/MerchantCenterServiceTest.php b/tests/Unit/MerchantCenter/MerchantCenterServiceTest.php index dfd45aff29..1310acd314 100644 --- a/tests/Unit/MerchantCenter/MerchantCenterServiceTest.php +++ b/tests/Unit/MerchantCenter/MerchantCenterServiceTest.php @@ -433,36 +433,6 @@ public function test_get_setup_status_step_product_listings() { ); } - public function test_get_setup_status_step_store_requirements() { - $this->options->method( 'get_merchant_id' )->willReturn( 1234 ); - $this->merchant_account_state->method( 'last_incomplete_step' )->willReturn( '' ); - $this->ads_service->method( 'connected_account' )->willReturn( true ); - $this->options->method( 'get' ) - ->withConsecutive( - [ OptionsInterface::MC_SETUP_COMPLETED_AT, false ], - [ OptionsInterface::TARGET_AUDIENCE ], - [ OptionsInterface::MERCHANT_CENTER, [] ] - )->willReturnOnConsecutiveCalls( - false, - [ - 'location' => 'selected', - 'countries' => [ 'US' ], - ], - [ - 'shipping_rate' => 'automatic', - 'shipping_time' => 'manual', - ] - ); - - $this->assertEquals( - [ - 'status' => 'incomplete', - 'step' => 'store_requirements', - ], - $this->mc_service->get_setup_status() - ); - } - public function test_get_setup_status_shipping_selected_rates() { $this->options->method( 'get_merchant_id' )->willReturn( 1234 ); $this->merchant_account_state->method( 'last_incomplete_step' )->willReturn( '' ); @@ -501,7 +471,7 @@ public function test_get_setup_status_shipping_selected_rates() { $this->assertEquals( [ 'status' => 'incomplete', - 'step' => 'store_requirements', + 'step' => 'paid_ads', ], $this->mc_service->get_setup_status() ); From fe7d56517ff7a61071431a02267280ca37d61c83 Mon Sep 17 00:00:00 2001 From: asvinb Date: Wed, 30 Oct 2024 14:07:11 +0400 Subject: [PATCH 12/51] Fix E2E tests. --- .../setup-mc/step-2-product-listings.test.js | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) diff --git a/tests/e2e/specs/setup-mc/step-2-product-listings.test.js b/tests/e2e/specs/setup-mc/step-2-product-listings.test.js index 81bcd4c3ba..15392df3e8 100644 --- a/tests/e2e/specs/setup-mc/step-2-product-listings.test.js +++ b/tests/e2e/specs/setup-mc/step-2-product-listings.test.js @@ -362,27 +362,15 @@ test.describe( 'Configure product listings', () => { await productListingsPage.fillEstimatedShippingTimes( '14' ); } ); - test( 'should see the heading of next step and request for the contact information after clicking "Continue"', async () => { - const requestPromise = - productListingsPage.registerContinueRequest(); + test( 'should see the heading of next step after clicking "Continue"', async () => { await productListingsPage.clickContinueButton(); await expect( page.getByRole( 'heading', { - name: 'Confirm store requirements', + name: 'Create a campaign to advertise your products', exact: true, } ) ).toBeVisible(); - - const request = await requestPromise; - const response = await request.response(); - const responseBody = await response.json(); - - expect( response.status() ).toBe( 200 ); - - expect( responseBody.wc_address.street_address ).toBe( - 'Automata Road' - ); } ); } ); } ); From babf4d60f67db149a63f4e04067166991ed97baa Mon Sep 17 00:00:00 2001 From: Joe McGill Date: Thu, 31 Oct 2024 15:11:34 -0500 Subject: [PATCH 13/51] Update store address card usage --- .../components/contact-information/index.js | 36 +--- .../contact-information/store-address-card.js | 196 +++++------------- .../store-address-card.scss | 8 +- .../connected-google-combo-account-card.js | 4 +- js/src/hooks/useStoreAddressSynced.js | 12 +- .../setup-stepper/setup-accounts/index.js | 4 +- 6 files changed, 65 insertions(+), 195 deletions(-) diff --git a/js/src/components/contact-information/index.js b/js/src/components/contact-information/index.js index a2dced84e4..e27bb6620b 100644 --- a/js/src/components/contact-information/index.js +++ b/js/src/components/contact-information/index.js @@ -6,16 +6,9 @@ import { __ } from '@wordpress/i18n'; /** * Internal dependencies */ -import { getEditPhoneNumberUrl, getEditStoreAddressUrl } from '.~/utils/urls'; import Section from '.~/wcdl/section'; +import StoreAddressCard from './store-address-card'; import VerticalGapLayout from '.~/components/vertical-gap-layout'; -import AppDocumentationLink from '.~/components/app-documentation-link'; -import { PhoneNumberCardPreview } from './phone-number-card'; -import { StoreAddressCardPreview } from './store-address-card'; - -const learnMoreLinkId = 'contact-information-read-more'; -const learnMoreUrl = - 'https://woocommerce.com/document/google-for-woocommerce/get-started/requirements/#contact-information'; const description = ( <> @@ -38,36 +31,13 @@ const settingsTitle = __( 'Contact information', 'google-listings-and-ads' ); /** * Renders a preview of contact information section, - * or a if contact informations are not saved yet. + * or a if contact information are not saved yet. */ export function ContactInformationPreview() { return (
- - { __( 'Learn more', 'google-listings-and-ads' ) } - - } - /> - - { __( 'Learn more', 'google-listings-and-ads' ) } - - } - /> +
); diff --git a/js/src/components/contact-information/store-address-card.js b/js/src/components/contact-information/store-address-card.js index c92a48b8b5..cf858286ac 100644 --- a/js/src/components/contact-information/store-address-card.js +++ b/js/src/components/contact-information/store-address-card.js @@ -3,19 +3,16 @@ */ import { __ } from '@wordpress/i18n'; import { useRef, createInterpolateElement, useState } from '@wordpress/element'; -import { CardDivider } from '@wordpress/components'; import { Spinner } from '@woocommerce/components'; import { update as updateIcon } from '@wordpress/icons'; import { getPath, getQuery } from '@woocommerce/navigation'; -import classNames from 'classnames'; /** * Internal dependencies */ import { useAppDispatch } from '.~/data'; import useStoreAddress from '.~/hooks/useStoreAddress'; -import Section from '.~/wcdl/section'; -import Subsection from '.~/wcdl/subsection'; +import useStoreAddressSynced from '.~/hooks/useStoreAddressSynced'; import AccountCard, { APPEARANCE } from '.~/components/account-card'; import AppButton from '.~/components/app-button'; import ValidationErrors from '.~/components/validation-errors'; @@ -52,16 +49,13 @@ import './store-address-card.scss'; * @fires gla_wc_store_address_validation Whenever the new store address data is fetched after clicking "Refresh to sync" button. * * @param {Object} props React props. - * @param {boolean} [props.showValidation=false] Whether to show validation error messages. - * @param {boolean} [props.compactStyles=false] Whether to use compact styles. The address is part of the card description as opposed to the card body and the description is different. + * @param {boolean} [props.isDescriptionShown=true] Whether the description section is hidden. * * @return {JSX.Element} Filled AccountCard component. */ -const StoreAddressCard = ( { - showValidation = false, - compactStyles = false, -} ) => { +const StoreAddressCard = ( { isDescriptionShown = true }) => { const { loaded, data } = useStoreAddress(); + const { isAddressFilled, isAddressSynced } = useStoreAddressSynced(); const [ isSaving, setSaving ] = useState( false ); const { updateGoogleMCContactInformation } = useAppDispatch(); const path = getPath(); @@ -76,7 +70,9 @@ const StoreAddressCard = ( { const handleRefreshClick = () => { setSaving( true ); - updateGoogleMCContactInformation().catch( () => setSaving( false ) ); + updateGoogleMCContactInformation() + .then( () => setSaving( false ) ) + .catch(); refetchedCallbackRef.current = ( storeAddress ) => { const eventProps = { @@ -90,6 +86,8 @@ const StoreAddressCard = ( { }; }; + const showIndicator = isAddressFilled && ! isAddressSynced; + const refreshButton = isSaving ? ( ) : ( @@ -104,7 +102,6 @@ const StoreAddressCard = ( { /> ); - let addressContent; const settingsLink = ( ); - let description = ( - <> -

- { createInterpolateElement( - __( - 'Edit your store address in your WooCommerce settings.', - 'google-listings-and-ads' - ), - { - link: settingsLink, - } - ) } -

-

- { __( - 'Once you’ve saved your new address there, refresh to sync your new address with Google.', - 'google-listings-and-ads' - ) } -

- - ); + let addressContent; if ( loaded ) { const { address, address2, city, state, country, postcode } = data; @@ -156,124 +133,55 @@ const StoreAddressCard = ( { addressContent = ; } - if ( compactStyles ) { - description = ( - <> -

- { createInterpolateElement( - __( - 'We’re using your store address from Woo Commerce settings for Google verification. This information won’t be public. Edit in WooCommerce settings if needed. Then, refresh to sync it to Google.', - 'google-listings-and-ads' - ), - { - link: settingsLink, - } - ) } -

- { addressContent } - - ); - } + const description = ( +

+ { createInterpolateElement( + __( + 'We’re using your store address from Woo Commerce settings for Google verification. This information won’t be public.', + 'google-listings-and-ads' + ), + { + link: settingsLink, + } + ) } +

+ ); + + const helper = ( + createInterpolateElement( + __( + 'Edit in WooCommerce settings if needed. Then, refresh to sync it to Google.' + ), + { + link: settingsLink, + } + ) + + ); + + const detail = ( + <> + { addressContent } + { ! isAddressFilled && ( + + ) } + + ) return ( - { ! compactStyles && ( - <> - - - - { __( 'Store address', 'google-listings-and-ads' ) } - - { addressContent } - { showValidation && ( - - ) } - - - ) } - + description={ isDescriptionShown && description } + detail={ detail } + helper={ helper } + indicator={ showIndicator && refreshButton } + /> ); }; export default StoreAddressCard; - -/** - * Trigger when store address edit button is clicked. - * Before `1.5.0` this name was used for tracking clicking "Edit in settings" to edit the WC address. As of `>1.5.0`, that event is now tracked as `edit_wc_store_address`. - * - * @event gla_edit_mc_store_address - * @property {string} path The path used in the page from which the link was clicked, e.g. `"/google/settings"`. - * @property {string|undefined} [subpath] The subpath used in the page, e.g. `"/edit-store-address"` or `undefined` when there is no subpath. - */ - -/** - * Renders a component with the store address. - * In preview mode, meaning there will be no refresh button, just the edit link. - * - * @fires gla_edit_mc_store_address Whenever "Edit" is clicked. - * - * @param {Object} props React props - * @param {string} props.editHref URL where Edit button should point to. - * @param {JSX.Element} props.learnMore Link to be shown at the end of missing data message. - * @return {JSX.Element} Filled AccountCard component. - */ -export function StoreAddressCardPreview( { editHref, learnMore } ) { - const { loaded, data } = useStoreAddress( 'mc' ); - let content, warning; - - if ( loaded ) { - const { - isAddressFilled, - isMCAddressDifferent, - address, - address2, - city, - state, - country, - postcode, - } = data; - const stateAndCountry = state ? `${ state } - ${ country }` : country; - - if ( isAddressFilled && ! isMCAddressDifferent ) { - content = [ address, address2, city, stateAndCountry, postcode ] - .filter( Boolean ) - .join( ', ' ); - } else { - warning = __( - 'Please add your store address', - 'google-listings-and-ads' - ); - content = ( - <> - { __( - 'Google requires the store address for all stores using Google Merchant Center. ', - 'google-listings-and-ads' - ) } - { learnMore } - - ); - } - } - - return ( - - ); -} diff --git a/js/src/components/contact-information/store-address-card.scss b/js/src/components/contact-information/store-address-card.scss index a325df5d57..a1b226ea9b 100644 --- a/js/src/components/contact-information/store-address-card.scss +++ b/js/src/components/contact-information/store-address-card.scss @@ -6,12 +6,6 @@ } .gla-account-card__description { - color: $gray-700; - } - - &.gla-store-address-card--is-compact { - .gla-store-address-card__address { - color: $gray-900; - } + color: $gray-900; } } diff --git a/js/src/components/google-combo-account-card/connected-google-combo-account-card.js b/js/src/components/google-combo-account-card/connected-google-combo-account-card.js index 6f4542e2d7..e5e0958039 100644 --- a/js/src/components/google-combo-account-card/connected-google-combo-account-card.js +++ b/js/src/components/google-combo-account-card/connected-google-combo-account-card.js @@ -24,7 +24,7 @@ const ConnectedGoogleComboAccountCard = () => { const { existingAccounts: existingGoogleAdsAccounts } = useExistingGoogleAdsAccounts(); const isConnected = useGoogleAdsAccountReady(); - const { addressSynced } = useStoreAddressSynced(); + const { isAddressSynced } = useStoreAddressSynced(); if ( ! hasDetermined ) { return ; @@ -52,7 +52,7 @@ const ConnectedGoogleComboAccountCard = () => { { showConnectAds && } - { addressSynced === false && } + { isAddressSynced === false && }
); }; diff --git a/js/src/hooks/useStoreAddressSynced.js b/js/src/hooks/useStoreAddressSynced.js index 326adda3aa..a522bff25e 100644 --- a/js/src/hooks/useStoreAddressSynced.js +++ b/js/src/hooks/useStoreAddressSynced.js @@ -11,8 +11,8 @@ import { STORE_KEY } from '.~/data/constants'; /** * @typedef {Object} StoreAddressSyncedData - * @property {boolean|null} isAddressFilled Whether the `data` is loading. It's equal to `isResolving` state of wp-data selector. - * @property {boolean|null} addressSynced Returns `true` if the store address matches the GMC account address, otherwise, returns `false`. If the MC account is not connected or if the state is not yet determined, returns `null`. + * @property {boolean|null} isAddressFilled Whether the address is filled without errors. Returns 'null' if the state is undetermined. + * @property {boolean|null} isAddressSynced Returns `true` if the store address matches the GMC account address, otherwise, returns `false`. If the MC account is not connected or if the state is not yet determined, returns `null`. */ /** @@ -28,7 +28,7 @@ export default function useStoreAddressSynced() { if ( ! isReady ) { return { isAddressFilled: null, - addressSynced: null, + isAddressSynced: null, }; } @@ -38,7 +38,7 @@ export default function useStoreAddressSynced() { if ( ! contact ) { return { isAddressFilled: null, - addressSynced: null, + isAddressSynced: null, }; } @@ -49,9 +49,7 @@ export default function useStoreAddressSynced() { return { isAddressFilled: ! missingRequiredFields.length, - addressSynced: - ! Boolean( isMCAddressDifferent ) && - ! missingRequiredFields.length, + isAddressSynced: ! isMCAddressDifferent }; }, [ isReady ] diff --git a/js/src/setup-mc/setup-stepper/setup-accounts/index.js b/js/src/setup-mc/setup-stepper/setup-accounts/index.js index 84123f00ad..e60f9d5c02 100644 --- a/js/src/setup-mc/setup-stepper/setup-accounts/index.js +++ b/js/src/setup-mc/setup-stepper/setup-accounts/index.js @@ -90,7 +90,7 @@ const SetupAccounts = ( props ) => { isReady: isGoogleMCReady, } = useGoogleMCAccount(); const { hasFinishedResolution } = useGoogleAdsAccount(); - const { addressSynced } = useStoreAddressSynced(); + const { isAddressFilled, isAddressSynced } = useStoreAddressSynced(); const isGoogleAdsReady = useGoogleAdsAccountReady(); /** @@ -118,7 +118,7 @@ const SetupAccounts = ( props ) => { hasFinishedResolution && isGoogleAdsReady && isGoogleMCReady && - addressSynced + isAddressSynced ); return ( From 1f18092dce9baa9840477de4361a46a37e2578d5 Mon Sep 17 00:00:00 2001 From: Joe McGill Date: Thu, 31 Oct 2024 15:24:58 -0500 Subject: [PATCH 14/51] Remove unused components --- .../contact-information-preview-card.js | 87 ----- .../contact-information-preview-card.scss | 21 -- .../phone-number-card/constants.js | 7 - .../edit-phone-number-content.js | 147 -------- .../edit-phone-number-content.test.js | 131 ------- .../phone-number-card/index.js | 2 - .../phone-number-card-preview.js | 82 ----- .../phone-number-card-preview.scss | 15 - .../phone-number-card/phone-number-card.js | 217 ------------ .../phone-number-card/phone-number-card.scss | 55 --- .../phone-number-card.test.js | 261 -------------- .../verification-code-control.js | 164 --------- .../verification-code-control.scss | 7 - .../verification-code-control.test.js | 147 -------- .../verify-phone-number-content.js | 231 ------------- .../verify-phone-number-content.test.js | 325 ------------------ .../contact-information/store-address-card.js | 1 - .../usePhoneNumberCheckTrackEventEffect.js | 45 --- js/src/settings/edit-phone-number.js | 81 ----- js/src/settings/edit-store-address.js | 115 ------- js/src/settings/index.js | 6 - js/src/utils/urls.js | 17 - 22 files changed, 2164 deletions(-) delete mode 100644 js/src/components/contact-information/contact-information-preview-card.js delete mode 100644 js/src/components/contact-information/contact-information-preview-card.scss delete mode 100644 js/src/components/contact-information/phone-number-card/constants.js delete mode 100644 js/src/components/contact-information/phone-number-card/edit-phone-number-content.js delete mode 100644 js/src/components/contact-information/phone-number-card/edit-phone-number-content.test.js delete mode 100644 js/src/components/contact-information/phone-number-card/index.js delete mode 100644 js/src/components/contact-information/phone-number-card/phone-number-card-preview.js delete mode 100644 js/src/components/contact-information/phone-number-card/phone-number-card-preview.scss delete mode 100644 js/src/components/contact-information/phone-number-card/phone-number-card.js delete mode 100644 js/src/components/contact-information/phone-number-card/phone-number-card.scss delete mode 100644 js/src/components/contact-information/phone-number-card/phone-number-card.test.js delete mode 100644 js/src/components/contact-information/phone-number-card/verification-code-control.js delete mode 100644 js/src/components/contact-information/phone-number-card/verification-code-control.scss delete mode 100644 js/src/components/contact-information/phone-number-card/verification-code-control.test.js delete mode 100644 js/src/components/contact-information/phone-number-card/verify-phone-number-content.js delete mode 100644 js/src/components/contact-information/phone-number-card/verify-phone-number-content.test.js delete mode 100644 js/src/components/contact-information/usePhoneNumberCheckTrackEventEffect.js delete mode 100644 js/src/settings/edit-phone-number.js delete mode 100644 js/src/settings/edit-store-address.js diff --git a/js/src/components/contact-information/contact-information-preview-card.js b/js/src/components/contact-information/contact-information-preview-card.js deleted file mode 100644 index b60db4b5cf..0000000000 --- a/js/src/components/contact-information/contact-information-preview-card.js +++ /dev/null @@ -1,87 +0,0 @@ -/** - * External dependencies - */ -import { __ } from '@wordpress/i18n'; -import { Icon, warning as warningIcon } from '@wordpress/icons'; -import { getPath, getQuery } from '@woocommerce/navigation'; - -/** - * Internal dependencies - */ -import AccountCard from '.~/components/account-card'; -import AppButton from '.~/components/app-button'; -import './contact-information-preview-card.scss'; - -/** - * Renders a contact information card component. - * It adds loading & warning state to the regular `AccountCard`, and an edit button link. - * - * @param {Object} props React props - * @param {import('.~/components/account-card').APPEARANCE} props.appearance - * @param {string} props.editHref URL where Edit button should point to. - * @param {string} props.editEventName Tracing event name used when the "Edit" button is clicked. - * @param {boolean} props.loading Set to `true` if the card should be rendered in the loading state. - * @param {JSX.Element} props.content Main content of the card to be rendered once the data is loaded. - * @param {string} [props.warning] Warning title, to be used instead of the default one. - * @return {JSX.Element} Filled AccountCard component. - */ -export default function ContactInformationPreviewCard( { - editHref, - editEventName, - loading, - content, - appearance, - warning, -} ) { - const { subpath } = getQuery(); - const editButton = ( - - ); - let description; - let title; - - if ( loading ) { - description = ( - - ); - } else if ( warning ) { - title = ( - <> - - { warning } - - ); - description = ( - - { content } - - ); - } else { - description = content; - } - - return ( - - ); -} diff --git a/js/src/components/contact-information/contact-information-preview-card.scss b/js/src/components/contact-information/contact-information-preview-card.scss deleted file mode 100644 index 8f938a738c..0000000000 --- a/js/src/components/contact-information/contact-information-preview-card.scss +++ /dev/null @@ -1,21 +0,0 @@ -.gla-contact-info-preview-card { - // Vertically align icon inside the title. - .wcdl-subsection-title { - display: flex; - align-items: center; - } - &__notice-icon { - fill: $alert-red; - margin: calc(var(--main-gap) / -8) 0; - } - &__notice-details { - color: $gray-700; - } - - &__placeholder { - display: inline-block; - width: 18em; - - @include placeholder; - } -} diff --git a/js/src/components/contact-information/phone-number-card/constants.js b/js/src/components/contact-information/phone-number-card/constants.js deleted file mode 100644 index 2d9c5c6aa8..0000000000 --- a/js/src/components/contact-information/phone-number-card/constants.js +++ /dev/null @@ -1,7 +0,0 @@ -/** - * @enum {string} - */ -export const VERIFICATION_METHOD = Object.freeze( { - SMS: 'SMS', - PHONE_CALL: 'PHONE_CALL', -} ); diff --git a/js/src/components/contact-information/phone-number-card/edit-phone-number-content.js b/js/src/components/contact-information/phone-number-card/edit-phone-number-content.js deleted file mode 100644 index 90f5652000..0000000000 --- a/js/src/components/contact-information/phone-number-card/edit-phone-number-content.js +++ /dev/null @@ -1,147 +0,0 @@ -/** - * External dependencies - */ -import { parsePhoneNumberFromString as parsePhoneNumber } from 'libphonenumber-js'; -import { __ } from '@wordpress/i18n'; -import { useState, useEffect } from '@wordpress/element'; -import { Flex, FlexItem, FlexBlock, RadioControl } from '@wordpress/components'; - -/** - * Internal dependencies - */ -import useCountryCallingCodeOptions from '.~/hooks/useCountryCallingCodeOptions'; -import Section from '.~/wcdl/section'; -import Subsection from '.~/wcdl/subsection'; -import SelectControl from '.~/wcdl/select-control'; -import AppInputControl from '.~/components/app-input-control'; -import AppButton from '.~/components/app-button'; -import { VERIFICATION_METHOD } from './constants'; - -/** - * @typedef { import(".~/hooks/useGoogleMCPhoneNumber").PhoneNumberData } PhoneNumberData - */ - -/** - * @typedef {Object} ExtraPhoneNumberData - * @property {string} number The phone number string in E.164 format. Example: '+12133734253'. Available if the input data is parsable. - * @property {string} display The phone number string in international format. Example: '+1 213 373 4253'. - * @property {string} verificationMethod Selected verification method. - * - * @typedef {PhoneNumberData & ExtraPhoneNumberData} CallbackPhoneNumberData - */ - -/** - * @callback onSendVerificationCodeClick - * @param {CallbackPhoneNumberData} phoneNumberData The changed phone number data. - */ - -const verificationOptions = [ - { - label: __( 'Text message', 'google-listings-and-ads' ), - value: VERIFICATION_METHOD.SMS, - }, - { - label: __( 'Phone call', 'google-listings-and-ads' ), - value: VERIFICATION_METHOD.PHONE_CALL, - }, -]; - -/** - * Renders inputs for editing phone number in Card.Body UI. - * - * @param {Object} props React props. - * @param {string} props.initCountry The initial country code for the country selection. Example: 'US'. - * @param {string} props.initNationalNumber The initial national (significant) number for its input field. Example: '2133734253'. - * @param {onSendVerificationCodeClick} props.onSendVerificationCodeClick Called when clicking on the "Send verification code" button. - */ -export default function EditPhoneNumberContent( { - initCountry, - initNationalNumber, - onSendVerificationCodeClick, -} ) { - const countryCallingCodeOptions = useCountryCallingCodeOptions(); - const [ country, setCountry ] = useState( initCountry ); - const [ number, setNumber ] = useState( initNationalNumber ); - const [ verificationMethod, setVerificationMethod ] = useState( - VERIFICATION_METHOD.SMS - ); - const [ phoneNumber, setPhoneNumber ] = useState( null ); - - useEffect( () => { - const parsed = parsePhoneNumber( number, country ); - const isValid = parsed ? parsed.isValid() : false; - - if ( parsed ) { - setPhoneNumber( { - ...parsed, - isValid, - display: parsed.formatInternational(), - verificationMethod, - } ); - } else { - setPhoneNumber( { - isValid, - country, - number: '', - display: '', - verificationMethod, - } ); - } - }, [ number, country, verificationMethod ] ); - - const onSendClick = () => onSendVerificationCodeClick( phoneNumber ); - - return ( - - - - - - - - - - - - { __( - 'Select verification method', - 'google-listings-and-ads' - ) } - - - - - - - - ); -} diff --git a/js/src/components/contact-information/phone-number-card/edit-phone-number-content.test.js b/js/src/components/contact-information/phone-number-card/edit-phone-number-content.test.js deleted file mode 100644 index e08b24f7af..0000000000 --- a/js/src/components/contact-information/phone-number-card/edit-phone-number-content.test.js +++ /dev/null @@ -1,131 +0,0 @@ -/** - * External dependencies - */ -import '@testing-library/jest-dom'; -import { screen, render } from '@testing-library/react'; -import userEvent from '@testing-library/user-event'; - -/** - * Internal dependencies - */ -import EditPhoneNumberContent from './edit-phone-number-content'; - -jest.mock( '.~/hooks/useCountryKeyNameMap', () => - jest - .fn() - .mockReturnValue( { US: 'United States', JP: 'Japan' } ) - .mockName( 'useCountryKeyNameMap' ) -); - -describe( 'PhoneNumberCard', () => { - it( 'Should take the initial country and national number as the initial values of inputs', () => { - render( - - ); - - const country = screen.getByRole( 'combobox' ); - const phone = screen.getByRole( 'textbox' ); - - expect( country.value ).toBe( 'United States (+1)' ); - expect( phone.value ).toBe( '2133734253' ); - } ); - - it( 'When an entered phone number is invalid, should disable "Send verification code" button', async () => { - const user = userEvent.setup(); - - render( - - ); - - const phone = screen.getByRole( 'textbox' ); - const submit = screen.getByRole( 'button' ); - - expect( submit ).toBeEnabled(); - - await user.click( phone ); - await user.keyboard( '{Backspace}' ); - - expect( submit ).toBeDisabled(); - - await user.type( phone, '1' ); - - expect( submit ).toBeEnabled(); - - await user.type( phone, '2' ); - - expect( submit ).toBeDisabled(); - } ); - - it( 'Should call back `onSendVerificationCodeClick` with input values and verification method when clicking on "Send verification code" button', async () => { - const user = userEvent.setup(); - const onSendVerificationCodeClick = jest - .fn() - .mockName( 'onSendVerificationCodeClick' ); - - render( - - ); - - const country = screen.getByRole( 'combobox' ); - const phone = screen.getByRole( 'textbox' ); - const submit = screen.getByRole( 'button' ); - - expect( onSendVerificationCodeClick ).toHaveBeenCalledTimes( 0 ); - - // Select and enter a U.S. phone number - await user.type( country, 'uni' ); - await user.click( await screen.findByRole( 'option' ) ); - await user.clear( phone ); - await user.type( phone, '2133734253' ); - - await user.click( submit ); - - expect( onSendVerificationCodeClick ).toHaveBeenCalledTimes( 1 ); - expect( onSendVerificationCodeClick ).toHaveBeenCalledWith( - expect.objectContaining( { - country: 'US', - countryCallingCode: '1', - nationalNumber: '2133734253', - isValid: true, - display: '+1 213 373 4253', - number: '+12133734253', - verificationMethod: 'SMS', - } ) - ); - - // Select and enter a Japanese phone number - await user.clear( country ); - await user.type( country, 'jap' ); - await user.click( await screen.findByRole( 'option' ) ); - await user.clear( phone ); - await user.type( phone, '570550634' ); - - // Select verification method to PHONE_CALL - await user.click( screen.getByRole( 'radio', { name: 'Phone call' } ) ); - - await user.click( submit ); - - expect( onSendVerificationCodeClick ).toHaveBeenCalledTimes( 2 ); - expect( onSendVerificationCodeClick ).toHaveBeenLastCalledWith( - expect.objectContaining( { - country: 'JP', - countryCallingCode: '81', - nationalNumber: '570550634', - isValid: true, - display: '+81 570 550 634', - number: '+81570550634', - verificationMethod: 'PHONE_CALL', - } ) - ); - } ); -} ); diff --git a/js/src/components/contact-information/phone-number-card/index.js b/js/src/components/contact-information/phone-number-card/index.js deleted file mode 100644 index e907dbddbe..0000000000 --- a/js/src/components/contact-information/phone-number-card/index.js +++ /dev/null @@ -1,2 +0,0 @@ -export { default } from './phone-number-card'; -export { PhoneNumberCardPreview } from './phone-number-card-preview'; diff --git a/js/src/components/contact-information/phone-number-card/phone-number-card-preview.js b/js/src/components/contact-information/phone-number-card/phone-number-card-preview.js deleted file mode 100644 index 6b503780a0..0000000000 --- a/js/src/components/contact-information/phone-number-card/phone-number-card-preview.js +++ /dev/null @@ -1,82 +0,0 @@ -/** - * External dependencies - */ -import { __ } from '@wordpress/i18n'; - -/** - * Internal dependencies - */ -import { APPEARANCE } from '.~/components/account-card'; -import useGoogleMCPhoneNumber from '.~/hooks/useGoogleMCPhoneNumber'; -import ContactInformationPreviewCard from '../contact-information-preview-card'; -import './phone-number-card-preview.scss'; - -/** - * Triggered when phone number edit button is clicked. - * - * @event gla_edit_mc_phone_number - * @property {string} path The path used in the page, e.g. `"/google/settings"`. - * @property {string} subpath The subpath used in the page, or `undefined` when there is no subpath. - */ - -/** - * Renders a component with the MC's phone number. - * In preview mode, meaning there will be no editing features, just the number and edit link. - * - * @fires gla_edit_mc_phone_number Whenever "Edit" is clicked. - * - * @param {Object} props React props - * @param {string} props.editHref URL where Edit button should point to. - * @param {JSX.Element} props.learnMore Link to be shown at the end of missing data message. - * @return {JSX.Element} Filled AccountCard component. - */ -export function PhoneNumberCardPreview( { editHref, learnMore } ) { - const { loaded, data } = useGoogleMCPhoneNumber(); - let content, warning; - - if ( loaded ) { - if ( data.isValid ) { - const verificationStatus = data.isVerified ? ( -
- { __( 'Verified', 'google-listings-and-ads' ) } -
- ) : ( -
- { __( 'Unverified', 'google-listings-and-ads' ) } -
- ); - content = ( - <> - { data.display } - { verificationStatus } - - ); - } else { - warning = __( - 'Please add your phone number', - 'google-listings-and-ads' - ); - content = ( - <> - { __( - 'Google requires the phone number for all stores using Google Merchant Center. ', - 'google-listings-and-ads' - ) } - { learnMore } - - ); - } - } - - return ( - - ); -} diff --git a/js/src/components/contact-information/phone-number-card/phone-number-card-preview.scss b/js/src/components/contact-information/phone-number-card/phone-number-card-preview.scss deleted file mode 100644 index dd46bc1516..0000000000 --- a/js/src/components/contact-information/phone-number-card/phone-number-card-preview.scss +++ /dev/null @@ -1,15 +0,0 @@ -.gla-contact-info-preview-card { - .gla-account-card__description { - gap: $grid-unit-05; - } -} - -.gla-phone-number-card-preview { - &__verified-status { - color: $alert-green; - } - - &__unverified-status { - color: $alert-red; - } -} diff --git a/js/src/components/contact-information/phone-number-card/phone-number-card.js b/js/src/components/contact-information/phone-number-card/phone-number-card.js deleted file mode 100644 index 7cd288fa63..0000000000 --- a/js/src/components/contact-information/phone-number-card/phone-number-card.js +++ /dev/null @@ -1,217 +0,0 @@ -/** - * External dependencies - */ -import { __ } from '@wordpress/i18n'; -import { useState, useEffect, useRef } from '@wordpress/element'; -import { CardDivider } from '@wordpress/components'; -import { Spinner } from '@woocommerce/components'; - -/** - * Internal dependencies - */ -import AccountCard, { APPEARANCE } from '.~/components/account-card'; -import AppButton from '.~/components/app-button'; -import AppSpinner from '.~/components/app-spinner'; -import ValidationErrors from '.~/components/validation-errors'; -import VerifyPhoneNumberContent from './verify-phone-number-content'; -import EditPhoneNumberContent from './edit-phone-number-content'; -import './phone-number-card.scss'; - -const noop = () => {}; - -const basePhoneNumberCardProps = { - className: 'gla-phone-number-card', - appearance: APPEARANCE.PHONE, -}; - -/** - * @typedef { import(".~/hooks/useGoogleMCPhoneNumber").PhoneNumber } PhoneNumber - */ - -function EditPhoneNumberCard( { - phoneNumber, - showValidation, - onPhoneNumberVerified, -} ) { - const { loaded, data } = phoneNumber; - const [ verifying, setVerifying ] = useState( false ); - const [ unverifiedPhoneNumber, setUnverifiedPhoneNumber ] = - useState( null ); - - let cardContent = ; - - if ( loaded ) { - cardContent = unverifiedPhoneNumber ? ( - { - setVerifying( isVerifying ); - - if ( isVerified ) { - onPhoneNumberVerified(); - } - } } - /> - ) : ( - - ); - } - - const description = unverifiedPhoneNumber - ? unverifiedPhoneNumber.display - : __( - 'Please enter a phone number to be used for verification.', - 'google-listings-and-ads' - ); - - const indicator = unverifiedPhoneNumber ? ( - setUnverifiedPhoneNumber( null ) } - /> - ) : null; - - const helper = - showValidation && ! data.isVerified ? ( - - ) : null; - - return ( - - - { cardContent } - - ); -} - -/** - * Clicking on the Merchant Center phone number edit button. - * - * @event gla_mc_phone_number_edit_button_click - * @property {string} view which view the edit button is in. Possible values: `setup-mc`, `settings`. - */ - -/** - * Renders phone number data in Card UI and is able to edit. - * - * @param {Object} props React props. - * @param {string} props.view The view the card is in. - * @param {PhoneNumber} props.phoneNumber Phone number data. - * @param {boolean|null} [props.initEditing=null] Specify the inital UI state. - * `true`: initialize with the editing UI for entering the phone number and proceeding with verification. - * `false`: initialize with the non-editing UI viewing the phone number and a button for switching to the editing UI. - * `null`: determine the initial UI state according to the `data.isVerified` after the `phoneNumber` loaded. - * @param {boolean} [props.showValidation=false] Whether to show validation error messages. - * @param {Function} [props.onEditClick] Called when clicking on "Edit" button. - * If this callback is omitted, it will enter edit mode when clicking on "Edit" button. - * @param {Function} [props.onPhoneNumberVerified] Called when the phone number is verified or has been verified. - * - * @fires gla_mc_phone_number_edit_button_click - */ -const PhoneNumberCard = ( { - view, - phoneNumber, - initEditing = null, - showValidation = false, - onEditClick, - onPhoneNumberVerified = noop, -} ) => { - const { loaded, data } = phoneNumber; - const [ isEditing, setEditing ] = useState( initEditing ); - const onPhoneNumberVerifiedRef = useRef(); - onPhoneNumberVerifiedRef.current = onPhoneNumberVerified; - - const { isVerified } = data; - - // If the initial value of `isEditing` got from `initEditing` is null, then the `isEditing` state - // is determined after the `phoneNumber` is loaded. - useEffect( () => { - if ( loaded && isEditing === null ) { - setEditing( ! isVerified ); - } - }, [ loaded, isVerified, isEditing ] ); - - // If `initEditing` is true, EditPhoneNumberCard takes care of the call of `onPhoneNumberVerified` - // after the phone number is verified. If `initEditing` is false or null, this useEffect handles - // the call of `onPhoneNumberVerified` when the loaded phone number has already been verified. - useEffect( () => { - if ( initEditing !== true && isVerified ) { - onPhoneNumberVerifiedRef.current(); - } - }, [ initEditing, isVerified ] ); - - // Return a simple loading AccountCard since the initial edit state is unknown before loaded. - if ( isEditing === null ) { - return ( - } - /> - ); - } - - if ( isEditing ) { - const handlePhoneNumberVerified = () => { - setEditing( false ); - onPhoneNumberVerified(); - }; - return ( - - ); - } - - let description = null; - let indicator = ; - - if ( loaded ) { - description = data.display; - indicator = ( - { - if ( onEditClick ) { - onEditClick(); - } else { - setEditing( true ); - } - } } - > - { __( 'Edit', 'google-listings-and-ads' ) } - - ); - } - - return ( - - ); -}; - -export default PhoneNumberCard; diff --git a/js/src/components/contact-information/phone-number-card/phone-number-card.scss b/js/src/components/contact-information/phone-number-card/phone-number-card.scss deleted file mode 100644 index 80404bdb7f..0000000000 --- a/js/src/components/contact-information/phone-number-card/phone-number-card.scss +++ /dev/null @@ -1,55 +0,0 @@ -.gla-phone-number-card { - .gla-account-card__description { - color: $gray-700; - } - - .gla-account-card__helper { - font-style: normal; - } - - // Country code selector - .wcdl-select-control { - .wcdl-select-control__input { - margin-bottom: 0; - } - - .woocommerce-select-control { - .components-base-control { - height: $gla-size-control-height; - border-color: $gray-600; - } - - .woocommerce-select-control__control-input { - font-size: $default-font-size; - color: $gray-900; - } - - .woocommerce-select-control__listbox { - top: 37px; - } - - .woocommerce-select-control__option { - min-height: $gla-size-control-height; - font-size: $default-font-size; - } - } - } - - // Phone number input - .components-input-control .components-input-control__container { - .components-input-control__input { - height: $gla-size-control-height; - font-size: $default-font-size; - color: $gray-900; - } - - .components-input-control__backdrop { - border-color: $gray-600; - } - } - - // Verification method radio - .components-radio-control__option:not(:last-child) { - margin-bottom: $grid-unit; - } -} diff --git a/js/src/components/contact-information/phone-number-card/phone-number-card.test.js b/js/src/components/contact-information/phone-number-card/phone-number-card.test.js deleted file mode 100644 index 79967ed952..0000000000 --- a/js/src/components/contact-information/phone-number-card/phone-number-card.test.js +++ /dev/null @@ -1,261 +0,0 @@ -/** - * External dependencies - */ -import '@testing-library/jest-dom'; -import { screen, render } from '@testing-library/react'; -import userEvent from '@testing-library/user-event'; -import { parsePhoneNumberFromString as parsePhoneNumber } from 'libphonenumber-js'; - -/** - * Internal dependencies - */ -import PhoneNumberCard from './phone-number-card'; - -jest.mock( '@woocommerce/components', () => ( { - ...jest.requireActual( '@woocommerce/components' ), - Spinner: jest - .fn( () =>
) - .mockName( 'Spinner' ), -} ) ); - -jest.mock( '.~/hooks/useCountryKeyNameMap', () => - jest - .fn() - .mockReturnValue( { US: 'United States' } ) - .mockName( 'useCountryKeyNameMap' ) -); - -jest.mock( '.~/data', () => ( { - ...jest.requireActual( '.~/data' ), - useAppDispatch() { - return { - requestPhoneVerificationCode: jest - .fn( () => Promise.resolve( { verificationId: 123 } ) ) - .mockName( 'requestPhoneVerificationCode' ), - - verifyPhoneNumber: jest - .fn( () => Promise.resolve( {} ) ) - .mockName( 'verifyPhoneNumber' ), - }; - }, -} ) ); - -describe( 'PhoneNumberCard', () => { - let phoneData; - let phoneNumber; - - function mockPhoneData( fullPhoneNumber ) { - const parsed = parsePhoneNumber( fullPhoneNumber ); - - phoneData = { - ...parsed, - display: parsed.formatInternational(), - isValid: parsed.isValid(), - }; - - phoneNumber = { - ...phoneNumber, - data: phoneData, - }; - } - - function mockVerified( isVerified ) { - phoneNumber = { - ...phoneNumber, - data: { - ...phoneData, - isVerified, - }, - }; - } - - function mockLoaded( loaded ) { - phoneNumber = { - ...phoneNumber, - loaded, - }; - } - - beforeEach( () => { - mockPhoneData( '+12133734253' ); - mockVerified( true ); - mockLoaded( true ); - } ); - - it( 'When not yet loaded, should render a loading spinner', () => { - mockLoaded( false ); - - render( ); - - const spinner = screen.getByRole( 'status', { name: 'spinner' } ); - const display = screen.queryByText( phoneData.display ); - const button = screen.queryByRole( 'button' ); - - expect( spinner ).toBeInTheDocument(); - expect( display ).not.toBeInTheDocument(); - expect( button ).not.toBeInTheDocument(); - } ); - - it( 'When `initEditing` is not specified, should render in non-editing mode after loading a verified phone number', () => { - mockLoaded( false ); - const { rerender } = render( - - ); - - mockLoaded( true ); - rerender( ); - - const button = screen.getByRole( 'button', { name: 'Edit' } ); - - expect( button ).toBeInTheDocument(); - } ); - - it( 'When `initEditing` is not specified, should render in editing mode after loading an unverified phone number', () => { - mockLoaded( false ); - const { rerender } = render( - - ); - - mockVerified( false ); - mockLoaded( true ); - rerender( ); - - const button = screen.getByRole( 'button', { name: /Send/ } ); - - expect( button ).toBeInTheDocument(); - } ); - - it( 'When `initEditing` is true, should directly render in editing mode', () => { - render( ); - - const button = screen.getByRole( 'button', { name: /Send/ } ); - - expect( button ).toBeInTheDocument(); - } ); - - it( 'When `initEditing` is false, should render in non-editing mode regardless of verified or not', () => { - // Start with a verified and valid phone number - const { rerender } = render( - - ); - - expect( screen.getByText( phoneData.display ) ).toBeInTheDocument(); - - // Set to an unverified and invalid phone number - mockPhoneData( '+121' ); - mockVerified( false ); - - rerender( - - ); - - expect( screen.getByText( phoneData.display ) ).toBeInTheDocument(); - } ); - - it( 'When the phone number is loaded but not yet verified, should directly render in editing mode', () => { - mockVerified( false ); - render( ); - - const button = screen.getByRole( 'button', { name: /Send/ } ); - - expect( button ).toBeInTheDocument(); - } ); - - it( 'When `showValidation` is true and the phone number is not yet verified, should show a validation error text', () => { - const text = 'A verified phone number is required.'; - mockVerified( false ); - - const { rerender } = render( - - ); - - expect( screen.queryByText( text ) ).not.toBeInTheDocument(); - - rerender( - - ); - - expect( screen.getByText( text ) ).toBeInTheDocument(); - } ); - - it( 'When `onEditClick` is specified and the Edit button is clicked, should callback `onEditClick`', async () => { - const user = userEvent.setup(); - const onEditClick = jest.fn(); - render( - - ); - - const button = screen.getByRole( 'button', { name: 'Edit' } ); - - expect( button ).toBeInTheDocument(); - expect( onEditClick ).toHaveBeenCalledTimes( 0 ); - - await user.click( button ); - - expect( onEditClick ).toHaveBeenCalledTimes( 1 ); - } ); - - describe( 'Should callback `onPhoneNumberVerified`', () => { - it( 'When `initEditing` is not specified and loaded phone number has been verified', () => { - const onPhoneNumberVerified = jest.fn(); - - render( - - ); - - expect( onPhoneNumberVerified ).toHaveBeenCalledTimes( 1 ); - } ); - - it( 'When `initEditing` is false and loaded phone number has been verified', () => { - const onPhoneNumberVerified = jest.fn(); - render( - - ); - - expect( onPhoneNumberVerified ).toHaveBeenCalledTimes( 1 ); - } ); - - it( 'When an unverified phone number is getting verified', async () => { - const user = userEvent.setup(); - const onPhoneNumberVerified = jest.fn(); - mockVerified( false ); - render( - - ); - - expect( onPhoneNumberVerified ).toHaveBeenCalledTimes( 0 ); - - await user.click( screen.getByRole( 'button', { name: /Send/ } ) ); - - const codeInputs = screen.getAllByRole( 'textbox' ); - for ( const [ i, codeInput ] of codeInputs.entries() ) { - await user.type( codeInput, i.toString() ); - } - - await user.click( - screen.getByRole( 'button', { name: /Verify/ } ) - ); - - expect( onPhoneNumberVerified ).toHaveBeenCalledTimes( 1 ); - } ); - } ); -} ); diff --git a/js/src/components/contact-information/phone-number-card/verification-code-control.js b/js/src/components/contact-information/phone-number-card/verification-code-control.js deleted file mode 100644 index f496c8313a..0000000000 --- a/js/src/components/contact-information/phone-number-card/verification-code-control.js +++ /dev/null @@ -1,164 +0,0 @@ -/** - * External dependencies - */ -import { useState, useEffect, useRef } from '@wordpress/element'; -import { Flex } from '@wordpress/components'; - -/** - * Internal dependencies - */ -import AppInputControl from '.~/components/app-input-control'; -import './verification-code-control.scss'; - -const DIGIT_LENGTH = 6; -const initDigits = Array( DIGIT_LENGTH ).fill( '' ); - -const toCallbackData = ( digits ) => { - const code = digits.join( '' ); - const isFilled = code.length === DIGIT_LENGTH; - return { code, isFilled }; -}; - -/** - * @callback onCodeChange - * @param {Object} verification Data payload. - * @param {string} verification.code The current entered verification code. - * @param {boolean} verification.isFilled Whether all digits of validation code are filled. - */ - -/** - * Renders a row of input elements for entering six-digit verification code. - * - * @param {Object} props React props. - * @param {onCodeChange} props.onCodeChange Called when the verification code are changed. - * @param {string} [props.resetNeedle=''] When the passed value changes, it will trigger internal state resetting for this component. - */ -export default function VerificationCodeControl( { - onCodeChange, - resetNeedle = '', -} ) { - const inputsRef = useRef( [] ); - const cursorRef = useRef( 0 ); - const onCodeChangeRef = useRef(); - const [ digits, setDigits ] = useState( initDigits ); - - onCodeChangeRef.current = onCodeChange; - - /** - * Moves focus to the input at given input - * if it exists. - * - * @param {number} targetIdx Index of the node to move the focus to. - */ - const maybeMoveFocus = ( targetIdx ) => { - const node = inputsRef.current[ targetIdx ]; - if ( node ) { - node.focus(); - } - }; - - const handleKeyDown = ( e ) => { - const { dataset, selectionStart, selectionEnd, value } = e.target; - const idx = Number( dataset.idx ); - - switch ( e.code ) { - case 'ArrowLeft': - case 'Backspace': - if ( selectionStart === 0 && selectionEnd === 0 ) { - maybeMoveFocus( idx - 1 ); - } - break; - - case 'ArrowRight': - if ( selectionStart === 1 || ! value ) { - maybeMoveFocus( idx + 1 ); - } - break; - } - }; - - // Track the cursor's position. - const handleBeforeInput = ( e ) => { - cursorRef.current = e.target.selectionStart; - }; - - const handleInput = ( e ) => { - const { value, dataset } = e.target; - const idx = Number( dataset.idx ); - - // Only keep the first entered char from the starting position of key cursor. - // If that char is not a digit, then clear the input to empty. - const digit = value.substr( cursorRef.current, 1 ).replace( /\D/, '' ); - if ( digit !== value ) { - e.target.value = digit; - } - - if ( digit ) { - maybeMoveFocus( idx + 1 ); - } - - if ( digit !== digits[ idx ] ) { - const nextDigits = [ ...digits ]; - nextDigits[ idx ] = digit; - setDigits( nextDigits ); - - onCodeChange( toCallbackData( nextDigits ) ); - } - }; - - // Update the inputs' values. - useEffect( () => { - inputsRef.current.forEach( ( el ) => ( el.value = '' ) ); - - setDigits( initDigits ); - onCodeChangeRef.current( toCallbackData( initDigits ) ); - }, [ resetNeedle ] ); - - /** - * Set the focus to the first input if the control's value is (back) at the initial state. - * - * Since the has an internal state management that always controls the actual `value` prop of the , - * the is forced the to be a controlled input. - * When using it, it's always necessary to specify `value` prop from the below - * to avoid the warning - A component is changing an uncontrolled input to be controlled. - * - * @see https://github.com/WordPress/gutenberg/blob/%40wordpress/components%4012.0.8/packages/components/src/input-control/input-field.js#L47-L68 - * @see https://github.com/WordPress/gutenberg/blob/%40wordpress/components%4012.0.8/packages/components/src/input-control/input-field.js#L115-L118 - * - * But after specifying the `value` prop, - * the synchronization of external and internal `value` state will depend on whether the input is focused. - * It'd sync external to internal only if the input is not focused. - * So here we await the `digits` is reset back to `initDigits` by above useEffect and sync to internal value, - * then move the focus calling after the synchronization tick finished. - * - * @see https://github.com/WordPress/gutenberg/blob/%40wordpress/components%4012.0.8/packages/components/src/input-control/input-field.js#L73-L90 - */ - useEffect( () => { - if ( digits === initDigits ) { - maybeMoveFocus( 0 ); - } - }, [ resetNeedle, digits ] ); - - return ( - - { digits.map( ( value, idx ) => { - return ( - ( inputsRef.current[ idx ] = el ) } - data-idx={ idx } - value={ value } - onKeyDown={ handleKeyDown } - onBeforeInput={ handleBeforeInput } - onInput={ handleInput } - autoComplete="off" - /> - ); - } ) } - - ); -} diff --git a/js/src/components/contact-information/phone-number-card/verification-code-control.scss b/js/src/components/contact-information/phone-number-card/verification-code-control.scss deleted file mode 100644 index 315f311d93..0000000000 --- a/js/src/components/contact-information/phone-number-card/verification-code-control.scss +++ /dev/null @@ -1,7 +0,0 @@ -.gla-verification-code-control { - // Verification code input - .components-input-control .components-input-control__container .components-input-control__input { - width: $grid-unit-50; - text-align: center; - } -} diff --git a/js/src/components/contact-information/phone-number-card/verification-code-control.test.js b/js/src/components/contact-information/phone-number-card/verification-code-control.test.js deleted file mode 100644 index d826bf11ac..0000000000 --- a/js/src/components/contact-information/phone-number-card/verification-code-control.test.js +++ /dev/null @@ -1,147 +0,0 @@ -/** - * External dependencies - */ -import { fireEvent, render, screen } from '@testing-library/react'; -import userEvent from '@testing-library/user-event'; -import '@testing-library/jest-dom/extend-expect'; - -/** - * Internal dependencies - */ -import VerificationCodeControl from './verification-code-control'; - -describe( 'VerificationCodeControl component', () => { - test( 'digit inputs are visible and empty', () => { - render( {} } /> ); - - const inputs = screen.getAllByRole( 'textbox' ); - expect( inputs?.length ).toBe( 6 ); - inputs.forEach( ( { value } ) => { - expect( value ).toBe( '' ); - } ); - } ); - - test( 'digit typing applies to the input', async () => { - const user = userEvent.setup(); - - render( {} } /> ); - const input = screen.getAllByRole( 'textbox' )[ 0 ]; - - await user.type( input, '2' ); - expect( input.value ).toBe( '2' ); - - await user.clear( input ); - expect( input.value ).toBe( '' ); - - await user.type( input, '5' ); - expect( input.value ).toBe( '5' ); - - fireEvent.input( input, { - target: { value: '3' }, - } ); - expect( input.value ).toBe( '3' ); - } ); - - test( 'clears if a non digit is typed', async () => { - const user = userEvent.setup(); - - render( {} } /> ); - const input = screen.getAllByRole( 'textbox' )[ 0 ]; - - await user.type( input, '2' ); - - fireEvent.input( input, { - target: { value: 'x' }, - } ); - expect( input.value ).toBe( '' ); - } ); - - test( 'moves the focus with cursor keys', async () => { - const user = userEvent.setup(); - - render( {} } /> ); - const inputs = screen.getAllByRole( 'textbox' ); - - await user.keyboard( '{ArrowRight}' ); - expect( inputs[ 1 ] ).toHaveFocus(); - - await user.keyboard( '{ArrowLeft}' ); - expect( inputs[ 0 ] ).toHaveFocus(); - } ); - - test( 'moves the focus with typing and backspace', async () => { - const user = userEvent.setup(); - - render( {} } /> ); - const inputs = screen.getAllByRole( 'textbox' ); - - await user.type( inputs[ 0 ], '2' ); - expect( inputs[ 1 ] ).toHaveFocus(); - - await user.keyboard( '{Backspace}' ); - expect( inputs[ 0 ] ).toHaveFocus(); - } ); - - test( "last input doesn't move the cursor forwards", async () => { - const user = userEvent.setup(); - - render( {} } /> ); - const inputs = screen.getAllByRole( 'textbox' ); - const lastInput = inputs.at( -1 ); - - await user.click( lastInput ); - await user.keyboard( '{ArrowRight}' ); - expect( lastInput ).toHaveFocus(); - - await user.type( lastInput, '2' ); - expect( lastInput ).toHaveFocus(); - } ); - - test( "first input doesn't move the cursor backwards", async () => { - const user = userEvent.setup(); - - render( {} } /> ); - const inputs = screen.getAllByRole( 'textbox' ); - const firstInput = inputs.at( 0 ); - - await user.keyboard( '{ArrowLeft}' ); - expect( firstInput ).toHaveFocus(); - - await user.keyboard( '{Backspace}' ); - expect( firstInput ).toHaveFocus(); - } ); - - test( 'typing calls onChange callback', async () => { - const user = userEvent.setup(); - const onChange = jest.fn().mockName( 'On change callback' ); - render( ); - const inputs = screen.getAllByRole( 'textbox' ); - - // Notice the initial render calls it one time due a useEffect function - expect( onChange ).toHaveBeenCalledTimes( 1 ); - - await user.type( inputs[ 0 ], '1' ); - expect( onChange ).toHaveBeenCalledTimes( 2 ); - - await user.type( inputs[ 1 ], '1' ); - expect( onChange ).toHaveBeenCalledTimes( 3 ); - } ); - - test( 'typing Enter submits enclosing form', async () => { - const user = userEvent.setup(); - const onSubmit = jest - .fn( ( e ) => e.preventDefault() ) - .mockName( 'On form submit callback' ); - - render( -
- {} } /> - - - ); - - await user.keyboard( '{Enter}' ); - - expect( onSubmit ).toHaveBeenCalled(); - } ); -} ); diff --git a/js/src/components/contact-information/phone-number-card/verify-phone-number-content.js b/js/src/components/contact-information/phone-number-card/verify-phone-number-content.js deleted file mode 100644 index f8e68a5e6c..0000000000 --- a/js/src/components/contact-information/phone-number-card/verify-phone-number-content.js +++ /dev/null @@ -1,231 +0,0 @@ -/** - * External dependencies - */ -import { __, sprintf } from '@wordpress/i18n'; -import { - useState, - useEffect, - useCallback, - useRef, - createInterpolateElement, -} from '@wordpress/element'; -import { Notice, Flex } from '@wordpress/components'; - -/** - * Internal dependencies - */ -import { useAppDispatch } from '.~/data'; -import useIsMounted from '.~/hooks/useIsMounted'; -import useCountdown from '.~/hooks/useCountdown'; -import Section from '.~/wcdl/section'; -import Subsection from '.~/wcdl/subsection'; -import AppButton from '.~/components/app-button'; -import VerificationCodeControl from './verification-code-control'; -import { VERIFICATION_METHOD } from './constants'; - -const appearanceDict = { - [ VERIFICATION_METHOD.SMS ]: { - toInstruction( phoneNumber ) { - return createInterpolateElement( - __( - 'A text message with the 6-digit verification code has been sent to .', - 'google-listings-and-ads' - ), - { userPhoneNumber: { phoneNumber } } - ); - }, - textResend: __( 'Resend code', 'google-listings-and-ads' ), - // translators: %d: seconds to wait until the next verification code can be requested via SMS. - textResendCooldown: __( - 'Resend code (in %ds)', - 'google-listings-and-ads' - ), - textSwitch: __( - 'Or, receive a verification code through a phone call', - 'google-listings-and-ads' - ), - }, - [ VERIFICATION_METHOD.PHONE_CALL ]: { - toInstruction( phoneNumber ) { - return createInterpolateElement( - __( - 'You will receive a phone call at with an automated message containing the 6-digit verification code.', - 'google-listings-and-ads' - ), - { userPhoneNumber: { phoneNumber } } - ); - }, - textResend: __( 'Call again', 'google-listings-and-ads' ), - // translators: %d: seconds to wait until the next verification code can be requested via phone call. - textResendCooldown: __( - 'Call again (in %ds)', - 'google-listings-and-ads' - ), - textSwitch: __( - 'Or, receive a verification code through text message', - 'google-listings-and-ads' - ), - }, -}; - -/** - * Renders inputs for verifying phone number in Card.Body UI. - * Please note that this component will send a verification code request if the current method hasn't been requested yet after rendering. - * - * @param {Object} props React props. - * @param {string} props.verificationMethod The initial verification method. - * @param {string} props.country The country code. Example: 'US'. - * @param {string} props.number The phone number string in E.164 format. Example: '+12133734253'. - * @param {string} props.display The phone number string in international format. Example: '+1 213 373 4253'. - * @param {(isVerifying: boolean, isVerified: boolean) => void} props.onVerificationStateChange Called when the verification state is changed. - */ -export default function VerifyPhoneNumberContent( { - verificationMethod, - country, - number, - display, - onVerificationStateChange, -} ) { - const isMounted = useIsMounted(); - const [ method, setMethod ] = useState( verificationMethod ); - const { second, callCount, startCountdown } = useCountdown( method ); - const [ verification, setVerification ] = useState( null ); - const [ verifying, setVerifying ] = useState( false ); - const [ error, setError ] = useState( null ); - const verificationIdRef = useRef( {} ); - const { requestPhoneVerificationCode, verifyPhoneNumber } = - useAppDispatch(); - - const isSMS = method === VERIFICATION_METHOD.SMS; - - const switchMethod = () => { - if ( isSMS ) { - setMethod( VERIFICATION_METHOD.PHONE_CALL ); - } else { - setMethod( VERIFICATION_METHOD.SMS ); - } - }; - - const handleVerificationCodeRequest = useCallback( () => { - setError( null ); - startCountdown( 60 ); - verificationIdRef.current[ method ] = null; - - requestPhoneVerificationCode( country, number, method ) - .then( ( { verificationId } ) => { - verificationIdRef.current[ method ] = verificationId; - } ) - .catch( ( e ) => { - if ( isMounted() ) { - setError( e ); - startCountdown( 0 ); - } - } ); - }, [ - country, - number, - method, - startCountdown, - requestPhoneVerificationCode, - isMounted, - ] ); - - const handleVerifySubmit = ( event ) => { - event.preventDefault(); - setError( null ); - setVerifying( true ); - onVerificationStateChange( true, false ); - - const id = verificationIdRef.current[ method ]; - verifyPhoneNumber( id, verification.code, method ) - .then( () => { - onVerificationStateChange( false, true ); - } ) - .catch( ( e ) => { - if ( isMounted() ) { - setError( e ); - setVerifying( false ); - onVerificationStateChange( false, false ); - } - } ); - }; - - // Trigger a verification code request if the current method hasn't been requested yet. - useEffect( () => { - if ( callCount === 0 ) { - handleVerificationCodeRequest(); - } - }, [ method, callCount, handleVerificationCodeRequest ] ); - - // Render related. - const { toInstruction, textResend, textResendCooldown, textSwitch } = - appearanceDict[ method ]; - - const verificationId = verificationIdRef.current[ method ]; - const disabledVerify = ! ( verification?.isFilled && verificationId ); - - return ( -
- - { error && ( - - - { error.display } - - - ) } - - - { __( - 'Enter verification code', - 'google-listings-and-ads' - ) } - - { toInstruction( display ) } - - - - - - - - 0 || verifying } - text={ - second - ? sprintf( textResendCooldown, second ) - : textResend - } - onClick={ handleVerificationCodeRequest } - /> - - - - - - -
- ); -} diff --git a/js/src/components/contact-information/phone-number-card/verify-phone-number-content.test.js b/js/src/components/contact-information/phone-number-card/verify-phone-number-content.test.js deleted file mode 100644 index ab631ab518..0000000000 --- a/js/src/components/contact-information/phone-number-card/verify-phone-number-content.test.js +++ /dev/null @@ -1,325 +0,0 @@ -/** - * External dependencies - */ -import '@testing-library/jest-dom'; -import { screen, render, act } from '@testing-library/react'; -import userEvent from '@testing-library/user-event'; - -/** - * Internal dependencies - */ -import VerifyPhoneNumberContent from './verify-phone-number-content'; -import { useAppDispatch } from '.~/data'; - -jest.mock( '.~/data', () => ( { - ...jest.requireActual( '.~/data' ), - useAppDispatch: jest.fn(), -} ) ); - -describe( 'VerifyPhoneNumberContent', () => { - let requestPhoneVerificationCode; - let verifyPhoneNumber; - - function getSwitchButton() { - return screen.getByRole( 'button', { - name: 'Switch verification method', - } ); - } - - function getVerifyButton() { - return screen.getByRole( 'button', { name: /Verify/ } ); - } - - beforeEach( () => { - requestPhoneVerificationCode = jest - .fn( () => Promise.resolve( { verificationId: '987654321' } ) ) - .mockName( 'requestPhoneVerificationCode' ); - - verifyPhoneNumber = jest - .fn( () => { - return new Promise( ( resolve, reject ) => { - setTimeout( - () => reject( { display: 'error reason: JS test' } ), - 1000 - ); - } ); - } ) - .mockName( 'verifyPhoneNumber' ); - - useAppDispatch.mockReturnValue( { - requestPhoneVerificationCode, - verifyPhoneNumber, - } ); - } ); - - it( 'Should render the given props with corresponding verification method', async () => { - const user = userEvent.setup(); - - render( - - ); - - const switchButton = getSwitchButton(); - - expect( switchButton ).toBeInTheDocument(); - expect( switchButton.textContent ).toBe( - 'Or, receive a verification code through a phone call' - ); - expect( screen.getByText( '+1 213 373 4253' ) ).toBeInTheDocument(); - expect( - screen.getByText( - /A text message with the 6-digit verification code has been sent/ - ) - ).toBeInTheDocument(); - expect( - screen.getByRole( 'button', { name: /Resend code/ } ) - ).toBeInTheDocument(); - - expect( requestPhoneVerificationCode ).toHaveBeenCalledTimes( 1 ); - - // Switch to the PHONE_CALL method - await user.click( switchButton ); - - expect( switchButton.textContent ).toBe( - 'Or, receive a verification code through text message' - ); - expect( screen.getByText( '+1 213 373 4253' ) ).toBeInTheDocument(); - expect( - screen.getByText( /You will receive a phone call/ ) - ).toBeInTheDocument(); - expect( - screen.getByRole( 'button', { name: /Call again/ } ) - ).toBeInTheDocument(); - - expect( requestPhoneVerificationCode ).toHaveBeenCalledTimes( 2 ); - } ); - - it( 'When not yet entered 6-digit verification code, should disable the "Verify phone number" button', async () => { - const user = userEvent.setup(); - - render( - - ); - - const button = getVerifyButton(); - const codeInputs = screen.getAllByRole( 'textbox' ); - - expect( button ).toBeInTheDocument(); - expect( button ).toBeDisabled(); - - for ( const [ i, codeInput ] of codeInputs.entries() ) { - await user.type( codeInput, i.toString() ); - } - - expect( button ).toBeEnabled(); - } ); - - it( 'When waiting for the countdown of each verification method, should disable the "Resend code/Call again" button', async () => { - const user = userEvent.setup(); - - render( - - ); - - const button = screen.getByRole( 'button', { name: /Resend code/ } ); - - expect( button ).toBeDisabled(); - - // Switch to the PHONE_CALL method - await user.click( getSwitchButton() ); - - expect( button ).toBeDisabled(); - } ); - - it( 'When not waiting for the countdown of each verification method, should call `requestPhoneVerificationCode` with the country code, phone number and verification method', async () => { - const user = userEvent.setup(); - - render( - - ); - - const switchButton = getSwitchButton(); - - expect( requestPhoneVerificationCode ).toHaveBeenCalledTimes( 1 ); - expect( requestPhoneVerificationCode ).toHaveBeenCalledWith( - 'US', - '+12133734253', - 'SMS' - ); - - // Switch to the PHONE_CALL method - await user.click( switchButton ); - - expect( requestPhoneVerificationCode ).toHaveBeenCalledTimes( 2 ); - expect( requestPhoneVerificationCode ).toHaveBeenLastCalledWith( - 'US', - '+12133734253', - 'PHONE_CALL' - ); - - // Switch back to the SMS method but it's still waiting for countdown - await user.click( switchButton ); - - expect( requestPhoneVerificationCode ).toHaveBeenCalledTimes( 2 ); - - // Switch back to the PHONE_CALL method but it's still waiting for countdown - await user.click( switchButton ); - - expect( requestPhoneVerificationCode ).toHaveBeenCalledTimes( 2 ); - } ); - - it( 'When clicking the "Verify phone number" button, should call `verifyPhoneNumber` with the verification id, code and method', async () => { - const user = userEvent.setup(); - - render( - {} } - /> - ); - - const button = getVerifyButton(); - const codeInputs = screen.getAllByRole( 'textbox' ); - - for ( const [ i, codeInput ] of codeInputs.entries() ) { - await user.type( codeInput, i.toString() ); - } - - expect( verifyPhoneNumber ).toHaveBeenCalledTimes( 0 ); - - await user.click( button ); - - expect( verifyPhoneNumber ).toHaveBeenCalledTimes( 1 ); - expect( verifyPhoneNumber ).toHaveBeenLastCalledWith( - '987654321', - '012345', - 'SMS' - ); - } ); - - it( 'Should call back to `onVerificationStateChange` according to the state and result of `verifyPhoneNumber`', async () => { - jest.useFakeTimers(); - - const user = userEvent.setup( { - advanceTimers: jest.advanceTimersByTime, - } ); - const onVerificationStateChange = jest - .fn() - .mockName( 'onVerificationStateChange' ); - - render( - - ); - - const button = getVerifyButton(); - const codeInputs = screen.getAllByRole( 'textbox' ); - - for ( const [ i, codeInput ] of codeInputs.entries() ) { - await user.type( codeInput, i.toString() ); - } - - // ----------------------------------------- - // Failed `verifyPhoneNumber` calling result - // ----------------------------------------- - expect( onVerificationStateChange ).toHaveBeenCalledTimes( 0 ); - - await user.click( button ); - - // First callback for loading state: - // 1. isVerifying: true - // 2. isVerified: false - expect( onVerificationStateChange ).toHaveBeenCalledTimes( 1 ); - expect( onVerificationStateChange ).toHaveBeenCalledWith( true, false ); - - await act( async () => { - jest.runOnlyPendingTimers(); - } ); - - // Second callback for failed result: - // 1. isVerifying: false - // 2. isVerified: false - expect( onVerificationStateChange ).toHaveBeenCalledTimes( 2 ); - expect( onVerificationStateChange ).toHaveBeenLastCalledWith( - false, - false - ); - - // `Notice` component will insert another invisible text element under with the same string. - // So here filter out it. - expect( - screen.getByText( ( content, element ) => { - return ( - element.parentElement.tagName !== 'BODY' && - content === 'error reason: JS test' - ); - } ) - ).toBeInTheDocument(); - - // --------------------------------------------- - // Successful `verifyPhoneNumber` calling result - // --------------------------------------------- - onVerificationStateChange.mockReset(); - - verifyPhoneNumber.mockImplementation( - () => new Promise( ( resolve ) => setTimeout( resolve, 1000 ) ) - ); - - expect( onVerificationStateChange ).toHaveBeenCalledTimes( 0 ); - - await user.click( button ); - - // First callback for loading state: - // 1. isVerifying: true - // 2. isVerified: false - expect( onVerificationStateChange ).toHaveBeenCalledTimes( 1 ); - expect( onVerificationStateChange ).toHaveBeenCalledWith( true, false ); - - await act( async () => { - jest.runOnlyPendingTimers(); - } ); - - // Second callback for successful result: - // 1. isVerifying: false - // 2. isVerified: true - expect( onVerificationStateChange ).toHaveBeenCalledTimes( 2 ); - expect( onVerificationStateChange ).toHaveBeenLastCalledWith( - false, - true - ); - - // The verify button should keep disabled after successfully verified - expect( button ).toBeDisabled(); - - jest.useRealTimers(); - jest.clearAllTimers(); - } ); -} ); diff --git a/js/src/components/contact-information/store-address-card.js b/js/src/components/contact-information/store-address-card.js index cf858286ac..dbdbcc27a8 100644 --- a/js/src/components/contact-information/store-address-card.js +++ b/js/src/components/contact-information/store-address-card.js @@ -16,7 +16,6 @@ import useStoreAddressSynced from '.~/hooks/useStoreAddressSynced'; import AccountCard, { APPEARANCE } from '.~/components/account-card'; import AppButton from '.~/components/app-button'; import ValidationErrors from '.~/components/validation-errors'; -import ContactInformationPreviewCard from './contact-information-preview-card'; import TrackableLink from '.~/components/trackable-link'; import mapStoreAddressErrors from './mapStoreAddressErrors'; import LoadingLabel from '.~/components/loading-label'; diff --git a/js/src/components/contact-information/usePhoneNumberCheckTrackEventEffect.js b/js/src/components/contact-information/usePhoneNumberCheckTrackEventEffect.js deleted file mode 100644 index 4b4dea05a1..0000000000 --- a/js/src/components/contact-information/usePhoneNumberCheckTrackEventEffect.js +++ /dev/null @@ -1,45 +0,0 @@ -/** - * External dependencies - */ -import { useEffect } from '@wordpress/element'; -import { getPath } from '@woocommerce/navigation'; - -/** - * Internal dependencies - */ -import { recordGlaEvent } from '.~/utils/tracks'; - -/** - * Check for whether the phone number for Merchant Center exists or not. - * - * @event gla_mc_phone_number_check - * @property {string} path the path where the check is in. - * @property {string} exist whether the phone number exists or not. - * @property {string} isValid whether the phone number is valid or not. - */ - -/** - * @param {import(".~/hooks/useGoogleMCPhoneNumber").PhoneNumberData} phone - * @fires gla_mc_phone_number_check - */ -const usePhoneNumberCheckTrackEventEffect = ( { - loaded, - data: { display, isValid }, -} ) => { - const exist = !! display; - const path = getPath(); - - useEffect( () => { - if ( ! loaded ) { - return; - } - - recordGlaEvent( 'gla_mc_phone_number_check', { - path, - exist, - isValid, - } ); - }, [ exist, isValid, loaded, path ] ); -}; - -export default usePhoneNumberCheckTrackEventEffect; diff --git a/js/src/settings/edit-phone-number.js b/js/src/settings/edit-phone-number.js deleted file mode 100644 index f038fdef38..0000000000 --- a/js/src/settings/edit-phone-number.js +++ /dev/null @@ -1,81 +0,0 @@ -/** - * External dependencies - */ -import { __ } from '@wordpress/i18n'; - -/** - * Internal dependencies - */ -import { getSettingsUrl } from '.~/utils/urls'; -import TopBar from '.~/components/stepper/top-bar'; -import HelpIconButton from '.~/components/help-icon-button'; -import useLayout from '.~/hooks/useLayout'; -import useGoogleMCPhoneNumber from '.~/hooks/useGoogleMCPhoneNumber'; -import Section from '.~/wcdl/section'; -import AppDocumentationLink from '.~/components/app-documentation-link'; -import PhoneNumberCard from '.~/components/contact-information/phone-number-card'; -import usePhoneNumberCheckTrackEventEffect from '.~/components/contact-information/usePhoneNumberCheckTrackEventEffect'; - -const learnMoreLinkId = 'contact-information-read-more'; -const learnMoreUrl = - 'https://woocommerce.com/document/google-for-woocommerce/get-started/requirements/#contact-information'; - -/** - * Renders the phone number settings page. - * - * @see PhoneNumberCard - * @fires gla_documentation_link_click with `{ context: "settings-phone-number", link_id: "contact-information-read-more", href: "https://woocommerce.com/document/google-for-woocommerce/get-started/requirements/#contact-information" }` - */ -const EditPhoneNumber = () => { - const phone = useGoogleMCPhoneNumber(); - - usePhoneNumberCheckTrackEventEffect( phone ); - useLayout( 'full-content' ); - - return ( - <> - - } - backHref={ getSettingsUrl() } - /> -
-
-

- { __( - 'Your phone number is required by Google for verification purposes. It will be shared with the Google Merchant Center and will not be displayed to customers.', - 'google-listings-and-ads' - ) } -

-

- - { __( - 'Learn more', - 'google-listings-and-ads' - ) } - -

-
- } - > - - -
- - ); -}; - -export default EditPhoneNumber; diff --git a/js/src/settings/edit-store-address.js b/js/src/settings/edit-store-address.js deleted file mode 100644 index 42b8cbac9c..0000000000 --- a/js/src/settings/edit-store-address.js +++ /dev/null @@ -1,115 +0,0 @@ -/** - * External dependencies - */ -import { __ } from '@wordpress/i18n'; -import { getHistory } from '@woocommerce/navigation'; -import { useState } from '@wordpress/element'; -import { Flex } from '@wordpress/components'; - -/** - * Internal dependencies - */ -import { getSettingsUrl } from '.~/utils/urls'; -import { useAppDispatch } from '.~/data'; -import useLayout from '.~/hooks/useLayout'; -import useStoreAddress from '.~/hooks/useStoreAddress'; -import TopBar from '.~/components/stepper/top-bar'; -import HelpIconButton from '.~/components/help-icon-button'; -import Section from '.~/wcdl/section'; -import AppButton from '.~/components/app-button'; - -import AppDocumentationLink from '.~/components/app-documentation-link'; -import StoreAddressCard from '.~/components/contact-information/store-address-card'; - -const learnMoreLinkId = 'contact-information-read-more'; -const learnMoreUrl = - 'https://woocommerce.com/document/google-for-woocommerce/get-started/requirements/#contact-information'; - -/** - * Triggered when the save button in contact information page is clicked. - * - * @event gla_contact_information_save_button_click - */ - -/** - * Renders the store address settings page. - * - * @see StoreAddressCard - * @fires gla_contact_information_save_button_click - * @fires gla_documentation_link_click with `{ context: "settings-store-address", link_id: "contact-information-read-more", href: "https://woocommerce.com/document/google-for-woocommerce/get-started/requirements/#contact-information" }` - */ -const EditStoreAddress = () => { - useLayout( 'full-content' ); - - const { updateGoogleMCContactInformation } = useAppDispatch(); - const { data: address } = useStoreAddress(); - const [ isSaving, setSaving ] = useState( false ); - - const handleSaveClick = () => { - setSaving( true ); - updateGoogleMCContactInformation() - .then( () => getHistory().push( getSettingsUrl() ) ) - .catch( () => setSaving( false ) ); - }; - - const isReadyToSave = - address.isAddressFilled && address.isMCAddressDifferent; - - return ( - <> - - } - backHref={ getSettingsUrl() } - /> -
-
-

- { __( - 'Your store address is required by Google for verification purposes. It will be shared with the Google Merchant Center and will not be displayed to customers.', - 'google-listings-and-ads' - ) } -

-

- - { __( - 'Learn more', - 'google-listings-and-ads' - ) } - -

-
- } - > - - -
- - - { __( 'Save details', 'google-listings-and-ads' ) } - - -
- - - ); -}; - -export default EditStoreAddress; diff --git a/js/src/settings/index.js b/js/src/settings/index.js index 8be37d0fa0..a81032f74c 100644 --- a/js/src/settings/index.js +++ b/js/src/settings/index.js @@ -16,8 +16,6 @@ import { ContactInformationPreview } from '.~/components/contact-information'; import LinkedAccounts from './linked-accounts'; import ReconnectWPComAccount from './reconnect-wpcom-account'; import ReconnectGoogleAccount from './reconnect-google-account'; -import EditStoreAddress from './edit-store-address'; -import EditPhoneNumber from './edit-phone-number'; import EnableNewProductSyncNotice from '.~/components/enable-new-product-sync-notice'; import MainTabNav from '.~/components/main-tab-nav'; import RebrandingTour from '.~/components/tours/rebranding-tour'; @@ -55,10 +53,6 @@ const Settings = () => { ); case subpaths.reconnectGoogleAccount: return ; - case subpaths.editPhoneNumber: - return ; - case subpaths.editStoreAddress: - return ; default: } diff --git a/js/src/utils/urls.js b/js/src/utils/urls.js index 2ee14aebaf..894b74db5f 100644 --- a/js/src/utils/urls.js +++ b/js/src/utils/urls.js @@ -22,8 +22,6 @@ export const subpaths = { editFreeListings: '/free-listings/edit', editCampaign: '/campaigns/edit', createCampaign: '/campaigns/create', - editPhoneNumber: '/edit-phone-number', - editStoreAddress: '/edit-store-address', reconnectWPComAccount: '/reconnect-wpcom-account', reconnectGoogleAccount: '/reconnect-google-account', }; @@ -86,21 +84,6 @@ export const geReportsUrl = () => { return getNewPath( null, reportsPath, null ); }; -export const getEditPhoneNumberUrl = () => { - return getNewPath( - { subpath: subpaths.editPhoneNumber }, - settingsPath, - null - ); -}; -export const getEditStoreAddressUrl = () => { - return getNewPath( - { subpath: subpaths.editStoreAddress }, - settingsPath, - null - ); -}; - /** * Returns the URL of the account re-connecting page. * From bfcc39b1c27cca019d29b9ead50be177a26134c6 Mon Sep 17 00:00:00 2001 From: Joe McGill Date: Thu, 31 Oct 2024 15:28:38 -0500 Subject: [PATCH 15/51] Remove phone account card format --- js/src/components/account-card/index.js | 6 ------ 1 file changed, 6 deletions(-) diff --git a/js/src/components/account-card/index.js b/js/src/components/account-card/index.js index 7b2433366a..49a21e178e 100644 --- a/js/src/components/account-card/index.js +++ b/js/src/components/account-card/index.js @@ -3,7 +3,6 @@ */ import { __ } from '@wordpress/i18n'; import classnames from 'classnames'; -import GridiconPhone from 'gridicons/dist/phone'; import { Icon, store as storeIcon } from '@wordpress/icons'; /** @@ -29,7 +28,6 @@ export const APPEARANCE = { GOOGLE: 'google', GOOGLE_MERCHANT_CENTER: 'google_merchant_center', GOOGLE_ADS: 'google_ads', - PHONE: 'phone', ADDRESS: 'address', FINAL_URL: 'final_url', }; @@ -104,10 +102,6 @@ const appearanceDict = { 'google-listings-and-ads' ), }, - [ APPEARANCE.PHONE ]: { - icon: , - title: __( 'Phone number', 'google-listings-and-ads' ), - }, [ APPEARANCE.ADDRESS ]: { icon: , title: __( 'Store address', 'google-listings-and-ads' ), From d2b2a38b9be91e773b7bb13d3bc5735a0ae7e761 Mon Sep 17 00:00:00 2001 From: Joe McGill Date: Thu, 31 Oct 2024 15:30:12 -0500 Subject: [PATCH 16/51] Remvove unused useCountryCallingCodeOptions --- js/src/hooks/useCountryCallingCodeOptions.js | 33 -------------------- 1 file changed, 33 deletions(-) delete mode 100644 js/src/hooks/useCountryCallingCodeOptions.js diff --git a/js/src/hooks/useCountryCallingCodeOptions.js b/js/src/hooks/useCountryCallingCodeOptions.js deleted file mode 100644 index 1ffe5afab6..0000000000 --- a/js/src/hooks/useCountryCallingCodeOptions.js +++ /dev/null @@ -1,33 +0,0 @@ -/** - * External dependencies - */ -import { useMemo } from '@wordpress/element'; -import { getCountries, getCountryCallingCode } from 'libphonenumber-js'; - -/** - * Internal dependencies - */ -import useCountryKeyNameMap from './useCountryKeyNameMap'; - -const toOption = ( country, countryCallingCode, countryName ) => ( { - key: country, - keywords: [ countryName, countryCallingCode, country ], - label: `${ countryName } (+${ countryCallingCode })`, -} ); - -export default function useCountryCallingCodeOptions() { - const countryNameDict = useCountryKeyNameMap(); - - return useMemo( () => { - return getCountries().reduce( ( acc, country ) => { - const countryName = countryNameDict[ country ]; - if ( countryName ) { - const countryCallingCode = getCountryCallingCode( country ); - acc.push( - toOption( country, countryCallingCode, countryName ) - ); - } - return acc; - }, [] ); - }, [ countryNameDict ] ); -} From a49f43f437cdf8e316247f981666ff8048d128cf Mon Sep 17 00:00:00 2001 From: Joe McGill Date: Fri, 1 Nov 2024 10:24:57 -0500 Subject: [PATCH 17/51] Streamline StoreAddressCard states and display --- .../components/contact-information/index.js | 2 +- .../contact-information/store-address-card.js | 37 +++++++------- .../connected-google-combo-account-card.js | 4 +- .../empty-store-address-card.js | 51 ------------------- .../sync-store-address/sync-store-address.js | 22 -------- 5 files changed, 21 insertions(+), 95 deletions(-) delete mode 100644 js/src/components/google-combo-account-card/sync-store-address/empty-store-address-card.js delete mode 100644 js/src/components/google-combo-account-card/sync-store-address/sync-store-address.js diff --git a/js/src/components/contact-information/index.js b/js/src/components/contact-information/index.js index e27bb6620b..f89ff5cee3 100644 --- a/js/src/components/contact-information/index.js +++ b/js/src/components/contact-information/index.js @@ -37,7 +37,7 @@ export function ContactInformationPreview() { return (
- +
); diff --git a/js/src/components/contact-information/store-address-card.js b/js/src/components/contact-information/store-address-card.js index dbdbcc27a8..bb47255f1f 100644 --- a/js/src/components/contact-information/store-address-card.js +++ b/js/src/components/contact-information/store-address-card.js @@ -48,11 +48,10 @@ import './store-address-card.scss'; * @fires gla_wc_store_address_validation Whenever the new store address data is fetched after clicking "Refresh to sync" button. * * @param {Object} props React props. - * @param {boolean} [props.isDescriptionShown=true] Whether the description section is hidden. * * @return {JSX.Element} Filled AccountCard component. */ -const StoreAddressCard = ( { isDescriptionShown = true }) => { +const StoreAddressCard = () => { const { loaded, data } = useStoreAddress(); const { isAddressFilled, isAddressSynced } = useStoreAddressSynced(); const [ isSaving, setSaving ] = useState( false ); @@ -111,9 +110,9 @@ const StoreAddressCard = ( { isDescriptionShown = true }) => { /> ); - let addressContent; + let addressContent = ; - if ( loaded ) { + if ( loaded && isAddressFilled ) { const { address, address2, city, state, country, postcode } = data; const stateAndCountry = state ? `${ state } - ${ country }` : country; @@ -129,14 +128,14 @@ const StoreAddressCard = ( { isDescriptionShown = true }) => { ); } else { - addressContent = ; + addressContent = null; } - const description = ( + const longDescription = (

{ createInterpolateElement( __( - 'We’re using your store address from Woo Commerce settings for Google verification. This information won’t be public.', + 'We’re using your store address for Google verification. This information won’t be public. Edit in WooCommerce settings if needed. Then, refresh to sync it to Google.', 'google-listings-and-ads' ), { @@ -146,16 +145,17 @@ const StoreAddressCard = ( { isDescriptionShown = true }) => {

); - const helper = ( - createInterpolateElement( - __( - 'Edit in WooCommerce settings if needed. Then, refresh to sync it to Google.' - ), - { - link: settingsLink, - } - ) - + const shortDescription = ( +

+ { createInterpolateElement( + __( + 'Your store address is required by Google for verification. This information won’t be public. Complete that in WooCommerce settings.' + ), + { + link: settingsLink, + } + ) } +

); const detail = ( @@ -175,9 +175,8 @@ const StoreAddressCard = ( { isDescriptionShown = true }) => { appearance={ APPEARANCE.ADDRESS } alignIcon="top" alignIndicator="top" - description={ isDescriptionShown && description } + description={ isAddressFilled ? longDescription : shortDescription } detail={ detail } - helper={ helper } indicator={ showIndicator && refreshButton } /> ); diff --git a/js/src/components/google-combo-account-card/connected-google-combo-account-card.js b/js/src/components/google-combo-account-card/connected-google-combo-account-card.js index e5e0958039..4a92e34d7d 100644 --- a/js/src/components/google-combo-account-card/connected-google-combo-account-card.js +++ b/js/src/components/google-combo-account-card/connected-google-combo-account-card.js @@ -7,7 +7,7 @@ import AccountDetails from './account-details'; import Indicator from './indicator'; import getAccountCreationTexts from './getAccountCreationTexts'; import SpinnerCard from '.~/components/spinner-card'; -import SyncStoreAddress from './sync-store-address'; +import StoreAddressCard from '.~/components/contact-information/store-address-card'; import useAutoCreateAdsMCAccounts from '.~/hooks/useAutoCreateAdsMCAccounts'; import useGoogleAdsAccountReady from '.~/hooks/useGoogleAdsAccountReady'; import useExistingGoogleAdsAccounts from '.~/hooks/useExistingGoogleAdsAccounts'; @@ -52,7 +52,7 @@ const ConnectedGoogleComboAccountCard = () => { { showConnectAds && } - { isAddressSynced === false && } + { isAddressSynced === false && } ); }; diff --git a/js/src/components/google-combo-account-card/sync-store-address/empty-store-address-card.js b/js/src/components/google-combo-account-card/sync-store-address/empty-store-address-card.js deleted file mode 100644 index e44de908ee..0000000000 --- a/js/src/components/google-combo-account-card/sync-store-address/empty-store-address-card.js +++ /dev/null @@ -1,51 +0,0 @@ -/** - * External dependencies - */ -import { __ } from '@wordpress/i18n'; -import { createInterpolateElement } from '@wordpress/element'; -import { getPath, getQuery } from '@woocommerce/navigation'; - -/** - * Internal dependencies - */ -import AccountCard, { APPEARANCE } from '.~/components/account-card'; -import TrackableLink from '.~/components/trackable-link'; - -const EmptyStoreAddressCard = () => { - const path = getPath(); - const { subpath } = getQuery(); - - const description = ( -

- { createInterpolateElement( - __( - 'Your store address is required by Google for verification. This information won’t be public. Complete that in WooCommerce settings.', - 'google-listings-and-ads' - ), - { - link: ( - - ), - } - ) } -

- ); - - return ( - - ); -}; - -export default EmptyStoreAddressCard; diff --git a/js/src/components/google-combo-account-card/sync-store-address/sync-store-address.js b/js/src/components/google-combo-account-card/sync-store-address/sync-store-address.js deleted file mode 100644 index 780fe8555e..0000000000 --- a/js/src/components/google-combo-account-card/sync-store-address/sync-store-address.js +++ /dev/null @@ -1,22 +0,0 @@ -/** - * Internal dependencies - */ -import StoreAddressCard from '.~/components/contact-information/store-address-card'; -import EmptyStoreAddressCard from './empty-store-address-card'; -import useStoreAddressSynced from '.~/hooks/useStoreAddressSynced'; - -/* - * Renders StoreAddressCard to sync the store address if we have a connected MC account and the address needs to be synced. - * If there's no connected account or the store address has been synced, it will return null. - */ -const SyncStoreAddress = () => { - const { isAddressFilled } = useStoreAddressSynced(); - - if ( ! isAddressFilled ) { - return ; - } - - return ; -}; - -export default SyncStoreAddress; From 1e0ed3beef87beac53028b2e19e393ffacd440db Mon Sep 17 00:00:00 2001 From: asvinb Date: Fri, 1 Nov 2024 19:51:04 +0400 Subject: [PATCH 18/51] Fix linting errors. --- .../contact-information/store-address-card.js | 12 ++++-------- .../sync-store-address/index.js | 1 - js/src/hooks/useStoreAddressSynced.js | 2 +- .../setup-mc/setup-stepper/setup-accounts/index.js | 2 +- 4 files changed, 6 insertions(+), 11 deletions(-) delete mode 100644 js/src/components/google-combo-account-card/sync-store-address/index.js diff --git a/js/src/components/contact-information/store-address-card.js b/js/src/components/contact-information/store-address-card.js index bb47255f1f..04fde91b64 100644 --- a/js/src/components/contact-information/store-address-card.js +++ b/js/src/components/contact-information/store-address-card.js @@ -46,9 +46,6 @@ import './store-address-card.scss'; * * @fires gla_edit_wc_store_address Whenever "Edit in WooCommerce Settings" button is clicked. * @fires gla_wc_store_address_validation Whenever the new store address data is fetched after clicking "Refresh to sync" button. - * - * @param {Object} props React props. - * * @return {JSX.Element} Filled AccountCard component. */ const StoreAddressCard = () => { @@ -149,7 +146,8 @@ const StoreAddressCard = () => {

{ createInterpolateElement( __( - 'Your store address is required by Google for verification. This information won’t be public. Complete that in WooCommerce settings.' + 'Your store address is required by Google for verification. This information won’t be public. Complete that in WooCommerce settings.', + 'google-listings-and-ads' ), { link: settingsLink, @@ -162,12 +160,10 @@ const StoreAddressCard = () => { <> { addressContent } { ! isAddressFilled && ( - + ) } - ) + ); return ( { isReady: isGoogleMCReady, } = useGoogleMCAccount(); const { hasFinishedResolution } = useGoogleAdsAccount(); - const { isAddressFilled, isAddressSynced } = useStoreAddressSynced(); + const { isAddressSynced } = useStoreAddressSynced(); const isGoogleAdsReady = useGoogleAdsAccountReady(); /** From f76a955adfc09f3a7741dc6463b7dac68e8179dd Mon Sep 17 00:00:00 2001 From: Joe McGill Date: Fri, 1 Nov 2024 12:16:21 -0500 Subject: [PATCH 19/51] Resolve store address before showing the card --- .../contact-information/store-address-card.js | 11 ++++++++++- .../connected-google-combo-account-card.js | 10 ++++++++-- js/src/hooks/useStoreAddressSynced.js | 16 ++++++++++++---- 3 files changed, 30 insertions(+), 7 deletions(-) diff --git a/js/src/components/contact-information/store-address-card.js b/js/src/components/contact-information/store-address-card.js index 04fde91b64..a97612104e 100644 --- a/js/src/components/contact-information/store-address-card.js +++ b/js/src/components/contact-information/store-address-card.js @@ -16,6 +16,7 @@ import useStoreAddressSynced from '.~/hooks/useStoreAddressSynced'; import AccountCard, { APPEARANCE } from '.~/components/account-card'; import AppButton from '.~/components/app-button'; import ValidationErrors from '.~/components/validation-errors'; +import SpinnerCard from '.~/components/spinner-card'; import TrackableLink from '.~/components/trackable-link'; import mapStoreAddressErrors from './mapStoreAddressErrors'; import LoadingLabel from '.~/components/loading-label'; @@ -50,7 +51,11 @@ import './store-address-card.scss'; */ const StoreAddressCard = () => { const { loaded, data } = useStoreAddress(); - const { isAddressFilled, isAddressSynced } = useStoreAddressSynced(); + const { + isAddressFilled, + isAddressSynced, + hasFinishedResolution: hasFinishedStoreAddressResolution, + } = useStoreAddressSynced(); const [ isSaving, setSaving ] = useState( false ); const { updateGoogleMCContactInformation } = useAppDispatch(); const path = getPath(); @@ -63,6 +68,10 @@ const StoreAddressCard = () => { refetchedCallbackRef.current = null; } + if ( ! hasFinishedStoreAddressResolution ) { + return ; + } + const handleRefreshClick = () => { setSaving( true ); updateGoogleMCContactInformation() diff --git a/js/src/components/google-combo-account-card/connected-google-combo-account-card.js b/js/src/components/google-combo-account-card/connected-google-combo-account-card.js index 4a92e34d7d..e9ce69818b 100644 --- a/js/src/components/google-combo-account-card/connected-google-combo-account-card.js +++ b/js/src/components/google-combo-account-card/connected-google-combo-account-card.js @@ -24,7 +24,13 @@ const ConnectedGoogleComboAccountCard = () => { const { existingAccounts: existingGoogleAdsAccounts } = useExistingGoogleAdsAccounts(); const isConnected = useGoogleAdsAccountReady(); - const { isAddressSynced } = useStoreAddressSynced(); + const { + isAddressSynced, + hasFinishedResolution: hasFinishedStoreAddressResolution, + } = useStoreAddressSynced(); + + const showAddressCard = + hasFinishedStoreAddressResolution && ! isAddressSynced; if ( ! hasDetermined ) { return ; @@ -52,7 +58,7 @@ const ConnectedGoogleComboAccountCard = () => { { showConnectAds && } - { isAddressSynced === false && } + { showAddressCard && } ); }; diff --git a/js/src/hooks/useStoreAddressSynced.js b/js/src/hooks/useStoreAddressSynced.js index a4132f28f6..3a6bc7f802 100644 --- a/js/src/hooks/useStoreAddressSynced.js +++ b/js/src/hooks/useStoreAddressSynced.js @@ -15,13 +15,15 @@ import { STORE_KEY } from '.~/data/constants'; * @property {boolean|null} isAddressSynced Returns `true` if the store address matches the GMC account address, otherwise, returns `false`. If the MC account is not connected or if the state is not yet determined, returns `null`. */ +const googleMCContactInformationSelector = 'getGoogleMCContactInformation'; + /** * Checks if the store address is synchronized with the Merchant Center (GMC) account address. * * @return {StoreAddressSyncedData} The store address synced data. */ export default function useStoreAddressSynced() { - const { isReady } = useGoogleMCAccount(); + const { isReady, hasFinishedResolutionGoogle } = useGoogleMCAccount(); return useSelect( ( select ) => { @@ -29,16 +31,21 @@ export default function useStoreAddressSynced() { return { isAddressFilled: null, isAddressSynced: null, + hasFinishedResolution: hasFinishedResolutionGoogle, }; } - const { getGoogleMCContactInformation } = select( STORE_KEY ); - const contact = getGoogleMCContactInformation(); + const selector = select( STORE_KEY ); + const contact = selector[ googleMCContactInformationSelector ](); + const hasFinishedResolution = selector.hasFinishedResolution( + googleMCContactInformationSelector + ); if ( ! contact ) { return { isAddressFilled: null, isAddressSynced: null, + hasFinishedResolution, }; } @@ -50,8 +57,9 @@ export default function useStoreAddressSynced() { return { isAddressFilled: ! missingRequiredFields.length, isAddressSynced: ! isMCAddressDifferent, + hasFinishedResolution, }; }, - [ isReady ] + [ hasFinishedResolutionGoogle, isReady ] ); } From a9f5530026d5ffc8431532117163db32f7edf34c Mon Sep 17 00:00:00 2001 From: Joe McGill Date: Fri, 1 Nov 2024 13:17:14 -0500 Subject: [PATCH 20/51] Improve updateGoogleMCContactInformation resolution --- .../components/contact-information/store-address-card.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/js/src/components/contact-information/store-address-card.js b/js/src/components/contact-information/store-address-card.js index a97612104e..dba0d65f3a 100644 --- a/js/src/components/contact-information/store-address-card.js +++ b/js/src/components/contact-information/store-address-card.js @@ -74,9 +74,11 @@ const StoreAddressCard = () => { const handleRefreshClick = () => { setSaving( true ); - updateGoogleMCContactInformation() - .then( () => setSaving( false ) ) - .catch(); + updateGoogleMCContactInformation().finally( () => { + // Errors are handled in the dispatched action but + // we change the saving state regardless of success. + setSaving( false ); + } ); refetchedCallbackRef.current = ( storeAddress ) => { const eventProps = { From 7e877614ffbb793dac8fc97297254084e8cd1002 Mon Sep 17 00:00:00 2001 From: Joe McGill Date: Fri, 1 Nov 2024 13:50:29 -0500 Subject: [PATCH 21/51] Check MC address setup when getting setup status and remove phone verification --- src/MerchantCenter/MerchantCenterService.php | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/src/MerchantCenter/MerchantCenterService.php b/src/MerchantCenter/MerchantCenterService.php index 124b33d1f3..0da5d91002 100644 --- a/src/MerchantCenter/MerchantCenterService.php +++ b/src/MerchantCenter/MerchantCenterService.php @@ -230,7 +230,8 @@ public function get_setup_status(): array { if ( $this->connected_account() && - $this->container->get( AdsService::class )->connected_account() + $this->container->get( AdsService::class )->connected_account() && + $this->is_mc_contact_information_setup() ) { $step = 'product_listings'; @@ -309,9 +310,7 @@ protected function maybe_add_contact_info_issue( array $issues, DateTime $cache_ */ protected function is_mc_contact_information_setup(): bool { $is_setup = [ - 'phone_number' => false, - 'phone_number_verified' => false, - 'address' => false, + 'address' => false, ]; try { @@ -327,9 +326,6 @@ protected function is_mc_contact_information_setup(): bool { } if ( $contact_info instanceof AccountBusinessInformation ) { - $is_setup['phone_number'] = ! empty( $contact_info->getPhoneNumber() ); - $is_setup['phone_number_verified'] = 'VERIFIED' === $contact_info->getPhoneVerificationStatus(); - /** @var Settings $settings */ $settings = $this->container->get( Settings::class ); @@ -341,7 +337,7 @@ protected function is_mc_contact_information_setup(): bool { } } - return $is_setup['phone_number'] && $is_setup['phone_number_verified'] && $is_setup['address']; + return $is_setup['address']; } /** From 852dfde9cf4d8b6af75ddee65a3726e3eee1cde1 Mon Sep 17 00:00:00 2001 From: Joe McGill Date: Fri, 1 Nov 2024 14:29:03 -0500 Subject: [PATCH 22/51] Fix MerchantCenterServiceTests tests --- .../MerchantCenter/MerchantCenterServiceTest.php | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/tests/Unit/MerchantCenter/MerchantCenterServiceTest.php b/tests/Unit/MerchantCenter/MerchantCenterServiceTest.php index 1310acd314..f4eb298749 100644 --- a/tests/Unit/MerchantCenter/MerchantCenterServiceTest.php +++ b/tests/Unit/MerchantCenter/MerchantCenterServiceTest.php @@ -423,6 +423,12 @@ public function test_get_setup_status_step_product_listings() { ] ); $this->target_audience->method( 'get_target_countries' )->willReturn( [ 'US' ] ); + $this->contact_information->method( 'get_contact_information' ) + ->willReturn( $this->get_valid_business_info() ); + $this->settings->method( 'get_store_address' ) + ->willReturn( $this->get_sample_address() ); + $this->address_utility->method( 'compare_addresses' ) + ->willReturn( true ); $this->assertEquals( [ @@ -467,6 +473,12 @@ public function test_get_setup_status_shipping_selected_rates() { ] ); $this->target_audience->method( 'get_target_countries' )->willReturn( [ 'GB' ] ); + $this->contact_information->method( 'get_contact_information' ) + ->willReturn( $this->get_valid_business_info() ); + $this->settings->method( 'get_store_address' ) + ->willReturn( $this->get_sample_address() ); + $this->address_utility->method( 'compare_addresses' ) + ->willReturn( true ); $this->assertEquals( [ From ec43e8fa3f80a4f7a97a8bd5eab81a4e822db8d2 Mon Sep 17 00:00:00 2001 From: Joe McGill Date: Tue, 5 Nov 2024 20:10:00 -0600 Subject: [PATCH 23/51] Always show the address card when MC is connected --- .../connected-google-combo-account-card.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/src/components/google-combo-account-card/connected-google-combo-account-card.js b/js/src/components/google-combo-account-card/connected-google-combo-account-card.js index e9ce69818b..c4944674d6 100644 --- a/js/src/components/google-combo-account-card/connected-google-combo-account-card.js +++ b/js/src/components/google-combo-account-card/connected-google-combo-account-card.js @@ -30,7 +30,7 @@ const ConnectedGoogleComboAccountCard = () => { } = useStoreAddressSynced(); const showAddressCard = - hasFinishedStoreAddressResolution && ! isAddressSynced; + hasFinishedStoreAddressResolution && isAddressSynced !== null; if ( ! hasDetermined ) { return ; From 4905228db6517fa94a22b21263e2cf32271421a1 Mon Sep 17 00:00:00 2001 From: Joe McGill Date: Tue, 5 Nov 2024 20:49:17 -0600 Subject: [PATCH 24/51] Revert refresh button to previous behavior --- .../contact-information/store-address-card.js | 40 ++++--------------- .../connected-google-combo-account-card.js | 11 ++--- 2 files changed, 12 insertions(+), 39 deletions(-) diff --git a/js/src/components/contact-information/store-address-card.js b/js/src/components/contact-information/store-address-card.js index dba0d65f3a..65582deb2a 100644 --- a/js/src/components/contact-information/store-address-card.js +++ b/js/src/components/contact-information/store-address-card.js @@ -2,7 +2,7 @@ * External dependencies */ import { __ } from '@wordpress/i18n'; -import { useRef, createInterpolateElement, useState } from '@wordpress/element'; +import { useRef, createInterpolateElement } from '@wordpress/element'; import { Spinner } from '@woocommerce/components'; import { update as updateIcon } from '@wordpress/icons'; import { getPath, getQuery } from '@woocommerce/navigation'; @@ -10,16 +10,13 @@ import { getPath, getQuery } from '@woocommerce/navigation'; /** * Internal dependencies */ -import { useAppDispatch } from '.~/data'; import useStoreAddress from '.~/hooks/useStoreAddress'; import useStoreAddressSynced from '.~/hooks/useStoreAddressSynced'; import AccountCard, { APPEARANCE } from '.~/components/account-card'; import AppButton from '.~/components/app-button'; import ValidationErrors from '.~/components/validation-errors'; -import SpinnerCard from '.~/components/spinner-card'; import TrackableLink from '.~/components/trackable-link'; import mapStoreAddressErrors from './mapStoreAddressErrors'; -import LoadingLabel from '.~/components/loading-label'; import { recordGlaEvent } from '.~/utils/tracks'; import './store-address-card.scss'; @@ -50,14 +47,8 @@ import './store-address-card.scss'; * @return {JSX.Element} Filled AccountCard component. */ const StoreAddressCard = () => { - const { loaded, data } = useStoreAddress(); - const { - isAddressFilled, - isAddressSynced, - hasFinishedResolution: hasFinishedStoreAddressResolution, - } = useStoreAddressSynced(); - const [ isSaving, setSaving ] = useState( false ); - const { updateGoogleMCContactInformation } = useAppDispatch(); + const { loaded, data, refetch } = useStoreAddress(); + const { isAddressFilled } = useStoreAddressSynced(); const path = getPath(); const { subpath } = getQuery(); @@ -68,17 +59,8 @@ const StoreAddressCard = () => { refetchedCallbackRef.current = null; } - if ( ! hasFinishedStoreAddressResolution ) { - return ; - } - const handleRefreshClick = () => { - setSaving( true ); - updateGoogleMCContactInformation().finally( () => { - // Errors are handled in the dispatched action but - // we change the saving state regardless of success. - setSaving( false ); - } ); + refetch(); refetchedCallbackRef.current = ( storeAddress ) => { const eventProps = { @@ -92,17 +74,13 @@ const StoreAddressCard = () => { }; }; - const showIndicator = isAddressFilled && ! isAddressSynced; - - const refreshButton = isSaving ? ( - - ) : ( + const refreshButton = ( @@ -120,7 +98,7 @@ const StoreAddressCard = () => { let addressContent = ; - if ( loaded && isAddressFilled ) { + if ( loaded ) { const { address, address2, city, state, country, postcode } = data; const stateAndCountry = state ? `${ state } - ${ country }` : country; @@ -135,8 +113,6 @@ const StoreAddressCard = () => {

{ rest }
); - } else { - addressContent = null; } const longDescription = ( @@ -184,7 +160,7 @@ const StoreAddressCard = () => { alignIndicator="top" description={ isAddressFilled ? longDescription : shortDescription } detail={ detail } - indicator={ showIndicator && refreshButton } + indicator={ refreshButton } /> ); }; diff --git a/js/src/components/google-combo-account-card/connected-google-combo-account-card.js b/js/src/components/google-combo-account-card/connected-google-combo-account-card.js index c4944674d6..3b45d87d66 100644 --- a/js/src/components/google-combo-account-card/connected-google-combo-account-card.js +++ b/js/src/components/google-combo-account-card/connected-google-combo-account-card.js @@ -11,8 +11,8 @@ import StoreAddressCard from '.~/components/contact-information/store-address-ca import useAutoCreateAdsMCAccounts from '.~/hooks/useAutoCreateAdsMCAccounts'; import useGoogleAdsAccountReady from '.~/hooks/useGoogleAdsAccountReady'; import useExistingGoogleAdsAccounts from '.~/hooks/useExistingGoogleAdsAccounts'; -import useStoreAddressSynced from '.~/hooks/useStoreAddressSynced'; import './connected-google-combo-account-card.scss'; +import useGoogleMCAccount from '.~/hooks/useGoogleMCAccount'; /** * Renders a Google account card UI with connected account information. @@ -24,13 +24,10 @@ const ConnectedGoogleComboAccountCard = () => { const { existingAccounts: existingGoogleAdsAccounts } = useExistingGoogleAdsAccounts(); const isConnected = useGoogleAdsAccountReady(); - const { - isAddressSynced, - hasFinishedResolution: hasFinishedStoreAddressResolution, - } = useStoreAddressSynced(); + const { hasGoogleMCConnection, hasFinishedResolution } = + useGoogleMCAccount(); - const showAddressCard = - hasFinishedStoreAddressResolution && isAddressSynced !== null; + const showAddressCard = hasFinishedResolution && hasGoogleMCConnection; if ( ! hasDetermined ) { return ; From c024b952e7a5a4b13eaa66a44c8919596eb7238d Mon Sep 17 00:00:00 2001 From: Joe McGill Date: Tue, 5 Nov 2024 21:14:27 -0600 Subject: [PATCH 25/51] Sync MC at the completion of step 1 --- .../setup-stepper/setup-accounts/index.js | 25 ++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/js/src/setup-mc/setup-stepper/setup-accounts/index.js b/js/src/setup-mc/setup-stepper/setup-accounts/index.js index 9038cbde9a..8fd5864cf9 100644 --- a/js/src/setup-mc/setup-stepper/setup-accounts/index.js +++ b/js/src/setup-mc/setup-stepper/setup-accounts/index.js @@ -7,6 +7,7 @@ import { createInterpolateElement } from '@wordpress/element'; /** * Internal dependencies */ +import { useAppDispatch } from '.~/data'; import useJetpackAccount from '.~/hooks/useJetpackAccount'; import useGoogleAccount from '.~/hooks/useGoogleAccount'; import useGoogleMCAccount from '.~/hooks/useGoogleMCAccount'; @@ -26,6 +27,7 @@ import './index.scss'; import useGoogleAdsAccount from '.~/hooks/useGoogleAdsAccount'; import useGoogleAdsAccountReady from '.~/hooks/useGoogleAdsAccountReady'; import useStoreAddressSynced from '.~/hooks/useStoreAddressSynced'; +import useDispatchCoreNotices from '.~/hooks/useDispatchCoreNotices'; /** * Renders the disclaimer of Comparison Shopping Service (CSS). @@ -90,8 +92,25 @@ const SetupAccounts = ( props ) => { isReady: isGoogleMCReady, } = useGoogleMCAccount(); const { hasFinishedResolution } = useGoogleAdsAccount(); - const { isAddressSynced } = useStoreAddressSynced(); + const { isAddressFilled } = useStoreAddressSynced(); const isGoogleAdsReady = useGoogleAdsAccountReady(); + const { updateGoogleMCContactInformation } = useAppDispatch(); + const { createNotice } = useDispatchCoreNotices(); + + const handleContinueClick = async () => { + try { + await updateGoogleMCContactInformation(); + onContinue(); + } catch ( error ) { + createNotice( + 'error', + __( + 'Unable to update your contact information. Please try again later.', + 'google-listings-and-ads' + ) + ); + } + }; /** * When jetpack is loading, or when google account is loading, @@ -118,7 +137,7 @@ const SetupAccounts = ( props ) => { hasFinishedResolution && isGoogleAdsReady && isGoogleMCReady && - isAddressSynced + isAddressFilled ); return ( @@ -154,7 +173,7 @@ const SetupAccounts = ( props ) => { { __( 'Continue', 'google-listings-and-ads' ) } From 5098fe82561104993a2b5f6e0a80d57a6a452842 Mon Sep 17 00:00:00 2001 From: Joe McGill Date: Tue, 5 Nov 2024 22:04:09 -0600 Subject: [PATCH 26/51] Reinstate EditStoreAddress flow in settings --- .../contact-information-preview-card.js | 87 +++++++++++++ .../contact-information-preview-card.scss | 21 ++++ .../components/contact-information/index.js | 21 +++- .../contact-information/store-address-card.js | 71 +++++++++++ js/src/settings/edit-store-address.js | 115 ++++++++++++++++++ js/src/settings/index.js | 3 + js/src/utils/urls.js | 8 ++ 7 files changed, 324 insertions(+), 2 deletions(-) create mode 100644 js/src/components/contact-information/contact-information-preview-card.js create mode 100644 js/src/components/contact-information/contact-information-preview-card.scss create mode 100644 js/src/settings/edit-store-address.js diff --git a/js/src/components/contact-information/contact-information-preview-card.js b/js/src/components/contact-information/contact-information-preview-card.js new file mode 100644 index 0000000000..b60db4b5cf --- /dev/null +++ b/js/src/components/contact-information/contact-information-preview-card.js @@ -0,0 +1,87 @@ +/** + * External dependencies + */ +import { __ } from '@wordpress/i18n'; +import { Icon, warning as warningIcon } from '@wordpress/icons'; +import { getPath, getQuery } from '@woocommerce/navigation'; + +/** + * Internal dependencies + */ +import AccountCard from '.~/components/account-card'; +import AppButton from '.~/components/app-button'; +import './contact-information-preview-card.scss'; + +/** + * Renders a contact information card component. + * It adds loading & warning state to the regular `AccountCard`, and an edit button link. + * + * @param {Object} props React props + * @param {import('.~/components/account-card').APPEARANCE} props.appearance + * @param {string} props.editHref URL where Edit button should point to. + * @param {string} props.editEventName Tracing event name used when the "Edit" button is clicked. + * @param {boolean} props.loading Set to `true` if the card should be rendered in the loading state. + * @param {JSX.Element} props.content Main content of the card to be rendered once the data is loaded. + * @param {string} [props.warning] Warning title, to be used instead of the default one. + * @return {JSX.Element} Filled AccountCard component. + */ +export default function ContactInformationPreviewCard( { + editHref, + editEventName, + loading, + content, + appearance, + warning, +} ) { + const { subpath } = getQuery(); + const editButton = ( + + ); + let description; + let title; + + if ( loading ) { + description = ( + + ); + } else if ( warning ) { + title = ( + <> + + { warning } + + ); + description = ( + + { content } + + ); + } else { + description = content; + } + + return ( + + ); +} diff --git a/js/src/components/contact-information/contact-information-preview-card.scss b/js/src/components/contact-information/contact-information-preview-card.scss new file mode 100644 index 0000000000..8f938a738c --- /dev/null +++ b/js/src/components/contact-information/contact-information-preview-card.scss @@ -0,0 +1,21 @@ +.gla-contact-info-preview-card { + // Vertically align icon inside the title. + .wcdl-subsection-title { + display: flex; + align-items: center; + } + &__notice-icon { + fill: $alert-red; + margin: calc(var(--main-gap) / -8) 0; + } + &__notice-details { + color: $gray-700; + } + + &__placeholder { + display: inline-block; + width: 18em; + + @include placeholder; + } +} diff --git a/js/src/components/contact-information/index.js b/js/src/components/contact-information/index.js index f89ff5cee3..4ca9d4abdc 100644 --- a/js/src/components/contact-information/index.js +++ b/js/src/components/contact-information/index.js @@ -6,9 +6,15 @@ import { __ } from '@wordpress/i18n'; /** * Internal dependencies */ +import { getEditStoreAddressUrl } from '.~/utils/urls'; import Section from '.~/wcdl/section'; -import StoreAddressCard from './store-address-card'; import VerticalGapLayout from '.~/components/vertical-gap-layout'; +import AppDocumentationLink from '.~/components/app-documentation-link'; +import { StoreAddressCardPreview } from './store-address-card'; + +const learnMoreLinkId = 'contact-information-read-more'; +const learnMoreUrl = + 'https://woocommerce.com/document/google-for-woocommerce/get-started/requirements/#contact-information'; const description = ( <> @@ -37,7 +43,18 @@ export function ContactInformationPreview() { return (
- + + { __( 'Learn more', 'google-listings-and-ads' ) } + + } + />
); diff --git a/js/src/components/contact-information/store-address-card.js b/js/src/components/contact-information/store-address-card.js index 65582deb2a..d17553655d 100644 --- a/js/src/components/contact-information/store-address-card.js +++ b/js/src/components/contact-information/store-address-card.js @@ -14,6 +14,7 @@ import useStoreAddress from '.~/hooks/useStoreAddress'; import useStoreAddressSynced from '.~/hooks/useStoreAddressSynced'; import AccountCard, { APPEARANCE } from '.~/components/account-card'; import AppButton from '.~/components/app-button'; +import ContactInformationPreviewCard from './contact-information-preview-card'; import ValidationErrors from '.~/components/validation-errors'; import TrackableLink from '.~/components/trackable-link'; import mapStoreAddressErrors from './mapStoreAddressErrors'; @@ -166,3 +167,73 @@ const StoreAddressCard = () => { }; export default StoreAddressCard; + +/** + * Trigger when store address edit button is clicked. + * Before `1.5.0` this name was used for tracking clicking "Edit in settings" to edit the WC address. As of `>1.5.0`, that event is now tracked as `edit_wc_store_address`. + * + * @event gla_edit_mc_store_address + * @property {string} path The path used in the page from which the link was clicked, e.g. `"/google/settings"`. + * @property {string|undefined} [subpath] The subpath used in the page, e.g. `"/edit-store-address"` or `undefined` when there is no subpath. + */ + +/** + * Renders a component with the store address. + * In preview mode, meaning there will be no refresh button, just the edit link. + * + * @fires gla_edit_mc_store_address Whenever "Edit" is clicked. + * + * @param {Object} props React props + * @param {string} props.editHref URL where Edit button should point to. + * @param {JSX.Element} props.learnMore Link to be shown at the end of missing data message. + * @return {JSX.Element} Filled AccountCard component. + */ +export function StoreAddressCardPreview( { editHref, learnMore } ) { + const { loaded, data } = useStoreAddress( 'mc' ); + let content, warning; + + if ( loaded ) { + const { + isAddressFilled, + isMCAddressDifferent, + address, + address2, + city, + state, + country, + postcode, + } = data; + const stateAndCountry = state ? `${ state } - ${ country }` : country; + + if ( isAddressFilled && ! isMCAddressDifferent ) { + content = [ address, address2, city, stateAndCountry, postcode ] + .filter( Boolean ) + .join( ', ' ); + } else { + warning = __( + 'Please add your store address', + 'google-listings-and-ads' + ); + content = ( + <> + { __( + 'Google requires the store address for all stores using Google Merchant Center. ', + 'google-listings-and-ads' + ) } + { learnMore } + + ); + } + } + + return ( + + ); +} diff --git a/js/src/settings/edit-store-address.js b/js/src/settings/edit-store-address.js new file mode 100644 index 0000000000..42b8cbac9c --- /dev/null +++ b/js/src/settings/edit-store-address.js @@ -0,0 +1,115 @@ +/** + * External dependencies + */ +import { __ } from '@wordpress/i18n'; +import { getHistory } from '@woocommerce/navigation'; +import { useState } from '@wordpress/element'; +import { Flex } from '@wordpress/components'; + +/** + * Internal dependencies + */ +import { getSettingsUrl } from '.~/utils/urls'; +import { useAppDispatch } from '.~/data'; +import useLayout from '.~/hooks/useLayout'; +import useStoreAddress from '.~/hooks/useStoreAddress'; +import TopBar from '.~/components/stepper/top-bar'; +import HelpIconButton from '.~/components/help-icon-button'; +import Section from '.~/wcdl/section'; +import AppButton from '.~/components/app-button'; + +import AppDocumentationLink from '.~/components/app-documentation-link'; +import StoreAddressCard from '.~/components/contact-information/store-address-card'; + +const learnMoreLinkId = 'contact-information-read-more'; +const learnMoreUrl = + 'https://woocommerce.com/document/google-for-woocommerce/get-started/requirements/#contact-information'; + +/** + * Triggered when the save button in contact information page is clicked. + * + * @event gla_contact_information_save_button_click + */ + +/** + * Renders the store address settings page. + * + * @see StoreAddressCard + * @fires gla_contact_information_save_button_click + * @fires gla_documentation_link_click with `{ context: "settings-store-address", link_id: "contact-information-read-more", href: "https://woocommerce.com/document/google-for-woocommerce/get-started/requirements/#contact-information" }` + */ +const EditStoreAddress = () => { + useLayout( 'full-content' ); + + const { updateGoogleMCContactInformation } = useAppDispatch(); + const { data: address } = useStoreAddress(); + const [ isSaving, setSaving ] = useState( false ); + + const handleSaveClick = () => { + setSaving( true ); + updateGoogleMCContactInformation() + .then( () => getHistory().push( getSettingsUrl() ) ) + .catch( () => setSaving( false ) ); + }; + + const isReadyToSave = + address.isAddressFilled && address.isMCAddressDifferent; + + return ( + <> + + } + backHref={ getSettingsUrl() } + /> +
+
+

+ { __( + 'Your store address is required by Google for verification purposes. It will be shared with the Google Merchant Center and will not be displayed to customers.', + 'google-listings-and-ads' + ) } +

+

+ + { __( + 'Learn more', + 'google-listings-and-ads' + ) } + +

+
+ } + > + + +
+ + + { __( 'Save details', 'google-listings-and-ads' ) } + + +
+ + + ); +}; + +export default EditStoreAddress; diff --git a/js/src/settings/index.js b/js/src/settings/index.js index a81032f74c..f23e775cb2 100644 --- a/js/src/settings/index.js +++ b/js/src/settings/index.js @@ -16,6 +16,7 @@ import { ContactInformationPreview } from '.~/components/contact-information'; import LinkedAccounts from './linked-accounts'; import ReconnectWPComAccount from './reconnect-wpcom-account'; import ReconnectGoogleAccount from './reconnect-google-account'; +import EditStoreAddress from './edit-store-address'; import EnableNewProductSyncNotice from '.~/components/enable-new-product-sync-notice'; import MainTabNav from '.~/components/main-tab-nav'; import RebrandingTour from '.~/components/tours/rebranding-tour'; @@ -53,6 +54,8 @@ const Settings = () => { ); case subpaths.reconnectGoogleAccount: return ; + case subpaths.editStoreAddress: + return ; default: } diff --git a/js/src/utils/urls.js b/js/src/utils/urls.js index 894b74db5f..66a0550df2 100644 --- a/js/src/utils/urls.js +++ b/js/src/utils/urls.js @@ -22,6 +22,7 @@ export const subpaths = { editFreeListings: '/free-listings/edit', editCampaign: '/campaigns/edit', createCampaign: '/campaigns/create', + editStoreAddress: '/edit-store-address', reconnectWPComAccount: '/reconnect-wpcom-account', reconnectGoogleAccount: '/reconnect-google-account', }; @@ -84,6 +85,13 @@ export const geReportsUrl = () => { return getNewPath( null, reportsPath, null ); }; +export const getEditStoreAddressUrl = () => { + return getNewPath( + { subpath: subpaths.editStoreAddress }, + settingsPath, + null + ); +}; /** * Returns the URL of the account re-connecting page. * From c1d0c256a8d879e947f31ae9802f3395fabea0c8 Mon Sep 17 00:00:00 2001 From: Joe McGill Date: Tue, 5 Nov 2024 22:11:37 -0600 Subject: [PATCH 27/51] Remove showValidation prop --- js/src/settings/edit-store-address.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/js/src/settings/edit-store-address.js b/js/src/settings/edit-store-address.js index 42b8cbac9c..6288dd5df9 100644 --- a/js/src/settings/edit-store-address.js +++ b/js/src/settings/edit-store-address.js @@ -90,9 +90,7 @@ const EditStoreAddress = () => { } > - +
From 49c26f056d1d56f58fd8fbf064318664c8365f3d Mon Sep 17 00:00:00 2001 From: Joe McGill Date: Tue, 5 Nov 2024 22:13:38 -0600 Subject: [PATCH 28/51] Update button text in docs/tests --- js/src/components/contact-information/store-address-card.js | 2 +- tests/e2e/specs/setup-mc/step-1-accounts.test.js | 2 +- tests/e2e/utils/pages/setup-mc/step-1-set-up-accounts.js | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/js/src/components/contact-information/store-address-card.js b/js/src/components/contact-information/store-address-card.js index d17553655d..0698ab0889 100644 --- a/js/src/components/contact-information/store-address-card.js +++ b/js/src/components/contact-information/store-address-card.js @@ -44,7 +44,7 @@ import './store-address-card.scss'; * Renders a component with a given store address. * * @fires gla_edit_wc_store_address Whenever "Edit in WooCommerce Settings" button is clicked. - * @fires gla_wc_store_address_validation Whenever the new store address data is fetched after clicking "Refresh to sync" button. + * @fires gla_wc_store_address_validation Whenever the new store address data is fetched after clicking "Update store address" button. * @return {JSX.Element} Filled AccountCard component. */ const StoreAddressCard = () => { diff --git a/tests/e2e/specs/setup-mc/step-1-accounts.test.js b/tests/e2e/specs/setup-mc/step-1-accounts.test.js index 7500c84d57..c4b219c349 100644 --- a/tests/e2e/specs/setup-mc/step-1-accounts.test.js +++ b/tests/e2e/specs/setup-mc/step-1-accounts.test.js @@ -420,7 +420,7 @@ test.describe( 'Set up accounts', () => { await setUpAccountsPage.goto(); } ); - test( 'should see the Refresh to sync button', async () => { + test( 'should see the Update store address button', async () => { const refreshToSyncButton = await setUpAccountsPage.getStoreAddressRefreshToSyncButton(); await expect( refreshToSyncButton ).toBeVisible(); diff --git a/tests/e2e/utils/pages/setup-mc/step-1-set-up-accounts.js b/tests/e2e/utils/pages/setup-mc/step-1-set-up-accounts.js index 9d1067dbf8..7ec3938ccc 100644 --- a/tests/e2e/utils/pages/setup-mc/step-1-set-up-accounts.js +++ b/tests/e2e/utils/pages/setup-mc/step-1-set-up-accounts.js @@ -391,7 +391,7 @@ export default class SetUpAccountsPage extends MockRequests { */ getStoreAddressRefreshToSyncButton() { return this.getStoreAddressCard().getByRole( 'button', { - name: 'Refresh to sync', + name: 'Update store address', exact: true, } ); } From afdb05461038a9d17080777b797e6e9a2812043a Mon Sep 17 00:00:00 2001 From: asvinb Date: Wed, 6 Nov 2024 20:05:52 +0400 Subject: [PATCH 29/51] Move condition. --- js/src/components/contact-information/store-address-card.js | 2 +- .../connected-google-combo-account-card.js | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/js/src/components/contact-information/store-address-card.js b/js/src/components/contact-information/store-address-card.js index 0698ab0889..7c832a7afc 100644 --- a/js/src/components/contact-information/store-address-card.js +++ b/js/src/components/contact-information/store-address-card.js @@ -155,7 +155,7 @@ const StoreAddressCard = () => { return ( { const { hasGoogleMCConnection, hasFinishedResolution } = useGoogleMCAccount(); - const showAddressCard = hasFinishedResolution && hasGoogleMCConnection; - if ( ! hasDetermined ) { return ; } @@ -40,6 +38,8 @@ const ConnectedGoogleComboAccountCard = () => { ( editMode && hasExistingGoogleAdsAccounts ) || ( ! isConnected && hasExistingGoogleAdsAccounts ); + const showAddressCard = hasFinishedResolution && hasGoogleMCConnection; + return (
Date: Wed, 6 Nov 2024 11:30:56 -0600 Subject: [PATCH 30/51] Update address card copy --- js/src/components/contact-information/store-address-card.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/js/src/components/contact-information/store-address-card.js b/js/src/components/contact-information/store-address-card.js index 7c832a7afc..6705bfe60c 100644 --- a/js/src/components/contact-information/store-address-card.js +++ b/js/src/components/contact-information/store-address-card.js @@ -120,7 +120,7 @@ const StoreAddressCard = () => {

{ createInterpolateElement( __( - 'We’re using your store address for Google verification. This information won’t be public. Edit in WooCommerce settings if needed. Then, refresh to sync it to Google.', + 'We’re using your store address for Google verification. This information won’t be public. Edit in WooCommerce settings if needed and update to review the changes.', 'google-listings-and-ads' ), { @@ -134,7 +134,7 @@ const StoreAddressCard = () => {

{ createInterpolateElement( __( - 'Your store address is required by Google for verification. This information won’t be public. Complete that in WooCommerce settings.', + 'Your store address is required by Google for verification. This information won’t be public. Complete that in WooCommerce settings and update to review the changes.', 'google-listings-and-ads' ), { From 30ac53a716cca086f4ac99ac28caf36339f9ee60 Mon Sep 17 00:00:00 2001 From: Joe McGill Date: Wed, 6 Nov 2024 20:19:11 -0600 Subject: [PATCH 31/51] Add loading state to the Continue button --- .../setup-stepper/setup-accounts/index.js | 34 ++++++++----------- 1 file changed, 14 insertions(+), 20 deletions(-) diff --git a/js/src/setup-mc/setup-stepper/setup-accounts/index.js b/js/src/setup-mc/setup-stepper/setup-accounts/index.js index 8fd5864cf9..383966e2c7 100644 --- a/js/src/setup-mc/setup-stepper/setup-accounts/index.js +++ b/js/src/setup-mc/setup-stepper/setup-accounts/index.js @@ -2,7 +2,7 @@ * External dependencies */ import { __ } from '@wordpress/i18n'; -import { createInterpolateElement } from '@wordpress/element'; +import { createInterpolateElement, useState } from '@wordpress/element'; /** * Internal dependencies @@ -27,7 +27,6 @@ import './index.scss'; import useGoogleAdsAccount from '.~/hooks/useGoogleAdsAccount'; import useGoogleAdsAccountReady from '.~/hooks/useGoogleAdsAccountReady'; import useStoreAddressSynced from '.~/hooks/useStoreAddressSynced'; -import useDispatchCoreNotices from '.~/hooks/useDispatchCoreNotices'; /** * Renders the disclaimer of Comparison Shopping Service (CSS). @@ -95,21 +94,16 @@ const SetupAccounts = ( props ) => { const { isAddressFilled } = useStoreAddressSynced(); const isGoogleAdsReady = useGoogleAdsAccountReady(); const { updateGoogleMCContactInformation } = useAppDispatch(); - const { createNotice } = useDispatchCoreNotices(); + const [ isSubmitting, setIsSubmitting ] = useState( false ); - const handleContinueClick = async () => { - try { - await updateGoogleMCContactInformation(); - onContinue(); - } catch ( error ) { - createNotice( - 'error', - __( - 'Unable to update your contact information. Please try again later.', - 'google-listings-and-ads' - ) - ); - } + const handleSubmitCallback = async () => { + setIsSubmitting( true ); + await updateGoogleMCContactInformation().finally( () => + // Error handling is done in the action. + setIsSubmitting( false ) + ); + + onContinue(); }; /** @@ -173,10 +167,10 @@ const SetupAccounts = ( props ) => { - { __( 'Continue', 'google-listings-and-ads' ) } - + loading={ isSubmitting } + text={ __( 'Continue', 'google-listings-and-ads' ) } + onClick={ handleSubmitCallback } + />

Date: Thu, 7 Nov 2024 14:43:47 -0600 Subject: [PATCH 32/51] Update E2E tests --- .../specs/setup-mc/step-1-accounts.test.js | 111 ++++++++++++------ .../pages/setup-mc/step-1-set-up-accounts.js | 4 +- 2 files changed, 76 insertions(+), 39 deletions(-) diff --git a/tests/e2e/specs/setup-mc/step-1-accounts.test.js b/tests/e2e/specs/setup-mc/step-1-accounts.test.js index 9c51fe4f27..5702f0338c 100644 --- a/tests/e2e/specs/setup-mc/step-1-accounts.test.js +++ b/tests/e2e/specs/setup-mc/step-1-accounts.test.js @@ -616,6 +616,73 @@ test.describe( 'Set up accounts', () => { } ); } ); + test.describe( 'Store address card', () => { + test.beforeAll( async () => { + // Everything is set up except for the MC connection. + await setUpAccountsPage.mockJetpackConnected(); + await setUpAccountsPage.mockGoogleConnected(); + await setUpAccountsPage.fulfillAdsAccounts( ADS_ACCOUNTS ); + await setUpAccountsPage.mockAdsAccountConnected(); + await setUpAccountsPage.mockAdsStatusClaimed(); + await setUpAccountsPage.mockMCHasAccounts(); + + await setUpAccountsPage.goto(); + } ); + + test( 'should not be shown when MC is not connected', async () => { + const googleAccountCard = setUpAccountsPage.getGoogleAccountCard(); + const storeAddressCard = setUpAccountsPage.getStoreAddressCard(); + + // Wait for UI to render before checking for no visibility. + await expect( googleAccountCard ).toBeVisible(); + await expect( storeAddressCard ).not.toBeVisible(); + } ); + + test( 'should be shown when MC is connected', async () => { + await setUpAccountsPage.mockMCConnected(); + await setUpAccountsPage.mockContactInformation(); + + await page.reload(); + + const storeAddressCard = setUpAccountsPage.getStoreAddressCard(); + + await expect( storeAddressCard ).toBeVisible(); + } ); + + test( 'should update the store address when the button is clicked', async () => { + await setUpAccountsPage.mockContactInformation( { + streetAddress: '123 Main St', + } ); + + const storeAddressCard = setUpAccountsPage.getStoreAddressCard(); + const updateButton = setUpAccountsPage.getStoreAddressButton(); + + await updateButton.click(); + + await expect( storeAddressCard ).toContainText( '123 Main St' ); + } ); + + test( 'should show an error message when the store address is not updated', async () => { + await setUpAccountsPage.mockContactInformation( { + streetAddress: '', + wcAddressErrors: [ 'address_1' ], + } ); + + const storeAddressCard = setUpAccountsPage.getStoreAddressCard(); + const updateButton = setUpAccountsPage.getStoreAddressButton(); + + await updateButton.click(); + + await expect( storeAddressCard ).toContainText( + 'Your store address is required by Google for verification.' + ); + + await expect( storeAddressCard ).toContainText( + 'The address line of store address is required.' + ); + } ); + } ); + test.describe( 'Continue button', () => { test.beforeAll( async () => { // Mock Jetpack as connected @@ -664,52 +731,22 @@ test.describe( 'Set up accounts', () => { } ); } ); - test.describe( 'When the store address needs to be synced and accounts are connected', async () => { - test.beforeAll( async () => { - await setUpAccountsPage.mockAdsAccountConnected(); - await setUpAccountsPage.mockMCConnected(); - await setUpAccountsPage.mockContactInformation( { - wcAddressErrors: [], - isMCAddressDifferent: true, - } ); - - await setUpAccountsPage.goto(); - } ); - - test( 'should see the Update store address button', async () => { - const refreshToSyncButton = - await setUpAccountsPage.getStoreAddressRefreshToSyncButton(); - await expect( refreshToSyncButton ).toBeVisible(); - } ); - - test( 'should see "Continue" button disabled when the store address needs to be synced', async () => { - const continueButton = - await setUpAccountsPage.getContinueButton(); - await expect( continueButton ).toBeDisabled(); - } ); - } ); - - test.describe( 'When the store address needs to be completed in WooCommerce settings', async () => { + test.describe( 'When the store address is invalid', async () => { test.beforeAll( async () => { + await setUpAccountsPage.fulfillAdsAccounts( ADS_ACCOUNTS ); await setUpAccountsPage.mockAdsAccountConnected(); + await setUpAccountsPage.mockAdsStatusClaimed(); + await setUpAccountsPage.mockMCHasAccounts(); await setUpAccountsPage.mockMCConnected(); await setUpAccountsPage.mockContactInformation( { - wcAddressErrors: [ 'address_1', 'city', 'postcode' ], - isMCAddressDifferent: true, + streetAddress: '', + wcAddressErrors: [ 'address_1' ], } ); await setUpAccountsPage.goto(); } ); - test( 'should see the notice to complete the store address in WooCommerce settings', async () => { - const storeAddressCard = - setUpAccountsPage.getStoreAddressCard(); - await expect( storeAddressCard ).toContainText( - 'Complete that in WooCommerce settings.' - ); - } ); - - test( 'should see "Continue" button disabled when the store address needs to be completed', async () => { + test( 'should see "Continue" button disabled when the store address needs to be updated', async () => { const continueButton = await setUpAccountsPage.getContinueButton(); await expect( continueButton ).toBeDisabled(); diff --git a/tests/e2e/utils/pages/setup-mc/step-1-set-up-accounts.js b/tests/e2e/utils/pages/setup-mc/step-1-set-up-accounts.js index f22293abf0..0d0a8185e6 100644 --- a/tests/e2e/utils/pages/setup-mc/step-1-set-up-accounts.js +++ b/tests/e2e/utils/pages/setup-mc/step-1-set-up-accounts.js @@ -159,7 +159,7 @@ export default class SetUpAccountsPage extends MockRequests { * @return {import('@playwright/test').Locator} Get Google combo card connected label. */ getGoogleComboConnectedLabel() { - return this.getGoogleComboAccountCard().locator( + return this.getGoogleAccountCard().locator( '.gla-connected-icon-label' ); } @@ -395,7 +395,7 @@ export default class SetUpAccountsPage extends MockRequests { * * @return {import('@playwright/test').Locator} Get store address refresh to sync button. */ - getStoreAddressRefreshToSyncButton() { + getStoreAddressButton() { return this.getStoreAddressCard().getByRole( 'button', { name: 'Update store address', exact: true, From 8b141d1396256f09e1a2eab1aba72a558dc3ab1f Mon Sep 17 00:00:00 2001 From: Joe McGill Date: Wed, 13 Nov 2024 16:15:03 -0600 Subject: [PATCH 33/51] Update E2E tests --- tests/e2e/specs/setup-mc/step-1-accounts.test.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/e2e/specs/setup-mc/step-1-accounts.test.js b/tests/e2e/specs/setup-mc/step-1-accounts.test.js index 9383cba3b0..c531c05ab1 100644 --- a/tests/e2e/specs/setup-mc/step-1-accounts.test.js +++ b/tests/e2e/specs/setup-mc/step-1-accounts.test.js @@ -756,6 +756,9 @@ test.describe( 'Set up accounts', () => { await setUpAccountsPage.mockAdsAccountConnected(); await setUpAccountsPage.mockAdsStatusClaimed(); await setUpAccountsPage.mockMCHasAccounts(); + await setUpAccountsPage.mockMCNotConnected(); + + await setUpAccountsPage.goto(); } ); test( 'should not be shown when MC is not connected', async () => { From 428738c6350974767145a2fa413196f13ccefa08 Mon Sep 17 00:00:00 2001 From: Joe McGill Date: Wed, 13 Nov 2024 16:20:11 -0600 Subject: [PATCH 34/51] Remove phone verification actions --- js/src/data/action-types.js | 1 - js/src/data/actions.js | 124 ------------------------------------ js/src/data/resolvers.js | 4 -- 3 files changed, 129 deletions(-) diff --git a/js/src/data/action-types.js b/js/src/data/action-types.js index 4d27fb8155..fab3fb4f34 100644 --- a/js/src/data/action-types.js +++ b/js/src/data/action-types.js @@ -21,7 +21,6 @@ const TYPES = { RECEIVE_ACCOUNTS_GOOGLE_ADS_EXISTING: 'RECEIVE_ACCOUNTS_GOOGLE_ADS_EXISTING', RECEIVE_MC_CONTACT_INFORMATION: 'RECEIVE_MC_CONTACT_INFORMATION', - VERIFIED_MC_PHONE_NUMBER: 'VERIFIED_MC_PHONE_NUMBER', RECEIVE_MC_COUNTRIES_AND_CONTINENTS: 'RECEIVE_MC_COUNTRIES_AND_CONTINENTS', RECEIVE_TARGET_AUDIENCE: 'RECEIVE_TARGET_AUDIENCE', SAVE_TARGET_AUDIENCE: 'SAVE_TARGET_AUDIENCE', diff --git a/js/src/data/actions.js b/js/src/data/actions.js index a34a63de70..3c659aa4e5 100644 --- a/js/src/data/actions.js +++ b/js/src/data/actions.js @@ -614,130 +614,6 @@ export function* updateGoogleMCContactInformation() { } } -/** - * Requests a phone verification code and returns a `verificationId` which is used for the next verification step. - * - * Important note: - * This action communicates with Google's production API. - * It will REALLY send the verification code to the phone number via SMS/phone call. - * When developing/testing, please make sure the passed number is your own or belongs to someone you know. - * - * @param {CountryCode} country The country code. Example: 'US'. - * @param {string} phoneNumber The phone number string in E.164 format. Example: '+12133734253'. - * @param {'SMS'|'PHONE_CALL'} method The verification method. - * @return { { verificationId: string } } Verification id to be used for another call. - * @throws { { display: string } } Will throws an identifiable error with the next step instruction for users. - */ -export function* requestPhoneVerificationCode( country, phoneNumber, method ) { - try { - const response = yield apiFetch( { - path: `${ API_NAMESPACE }/mc/phone-verification/request`, - method: 'POST', - data: { - phone_region_code: country, - phone_number: phoneNumber, - verification_method: method, - }, - } ); - - return { - verificationId: response.verification_id, - }; - } catch ( error ) { - // Currently, 'badRequest' won't be presented and all error responses return the - // same reason 'backendError'. Maybe someday the error reason can be distinguished - // and then we can recheck if there is a better way to handle errors. - // - // Ref: - // - https://github.com/woocommerce/google-listings-and-ads/issues/1101 - // - https://github.com/woocommerce/google-listings-and-ads/issues/1998 - if ( error.reason === 'backendError' ) { - throw { - display: __( - 'Unable to request the verification code. This may be due to an invalid phone number or the limit of five attempts to verify the same phone number every four hours.', - 'google-listings-and-ads' - ), - }; - } - - if ( error.reason === 'rateLimitExceeded' ) { - throw { - ...error, - display: __( - 'Unable to initiate the verification code request. A maximum of five attempts to verify the same phone number every four hours. Please try again later.', - 'google-listings-and-ads' - ), - }; - } - - handleApiError( - error, - __( - 'Unable to request the phone verification code.', - 'google-listings-and-ads' - ) - ); - } -} - -/** - * Verifies the phone number for users by passing the corresponding data used from the `requestPhoneVerificationCode` action. - * - * @param {string} verificationId The verification ID got from the `requestPhoneVerificationCode` action. - * @param {string} code The six-digit verification code sent/call to the user's phone. - * @param {'SMS'|'PHONE_CALL'} method The verification method. It should correspond with the verification ID got from the `requestPhoneVerificationCode` action. - * @throws { { display: string } } Will throws an identifiable error with the next step instruction for users. - */ -export function* verifyPhoneNumber( verificationId, code, method ) { - try { - yield apiFetch( { - path: `${ API_NAMESPACE }/mc/phone-verification/verify`, - method: 'POST', - data: { - verification_id: verificationId, - verification_code: code, - verification_method: method, - }, - } ); - - return { - type: TYPES.VERIFIED_MC_PHONE_NUMBER, - }; - } catch ( error ) { - const { reason, message = '' } = error; - - if ( reason === 'badRequest' ) { - // Example of message format: '[verificationCode] Wrong code.' - const [ , errorCode ] = message.match( /^\[(\w+)\]/ ) || []; - const displayDict = { - verificationCode: __( - 'Wrong verification code. Please try again.', - 'google-listings-and-ads' - ), - verificationId: __( - 'The verification code has expired. Please initiate a new verification request by the resend button.', - 'google-listings-and-ads' - ), - }; - - if ( errorCode in displayDict ) { - throw { - ...error, - display: displayDict[ errorCode ], - }; - } - } - - handleApiError( - error, - __( - 'Unable to verify your phone number.', - 'google-listings-and-ads' - ) - ); - } -} - export function* fetchTargetAudience() { try { const response = yield apiFetch( { diff --git a/js/src/data/resolvers.js b/js/src/data/resolvers.js index c29cb52bdd..1046b079c9 100644 --- a/js/src/data/resolvers.js +++ b/js/src/data/resolvers.js @@ -145,10 +145,6 @@ export function* getGoogleMCContactInformation() { } } -getGoogleMCContactInformation.shouldInvalidate = ( action ) => { - return action.type === TYPES.VERIFIED_MC_PHONE_NUMBER; -}; - export function* getMCCountriesAndContinents() { try { const query = { continents: true }; From 5c129a4882863a6e2ed7f304df6b3c3f5c447374 Mon Sep 17 00:00:00 2001 From: Joe McGill Date: Wed, 13 Nov 2024 16:22:52 -0600 Subject: [PATCH 35/51] Remove useGoogleMCPhoneNumber and related selector and deps --- js/src/data/selectors.js | 23 --------- js/src/hooks/useGoogleMCPhoneNumber.js | 68 -------------------------- package.json | 1 - 3 files changed, 92 deletions(-) delete mode 100644 js/src/hooks/useGoogleMCPhoneNumber.js diff --git a/js/src/data/selectors.js b/js/src/data/selectors.js index 123b09f7d5..778bb4cadb 100644 --- a/js/src/data/selectors.js +++ b/js/src/data/selectors.js @@ -124,29 +124,6 @@ export const getGoogleMCContactInformation = ( state ) => { return state.mc.contact; }; -/** - * Select the state of phone number associated with the Google Merchant Center account. - * - * Create another selector to separate the `hasFinishedResolution` state with `getGoogleMCContactInformation`. - * - * @param {Object} state The current store state will be injected by `wp.data`. - * @return {{ data: ContactInformation|null, loaded: boolean }} The payload of contact information associated with the Google Merchant Center account and its loaded state. - */ -export const getGoogleMCPhoneNumber = createRegistrySelector( - ( select ) => ( state ) => { - const selector = select( STORE_KEY ); - - const loaded = - !! getGoogleMCContactInformation( state ) || - selector.hasFinishedResolution( 'getGoogleMCContactInformation' ); - - return { - loaded, - data: selector.getGoogleMCContactInformation(), - }; - } -); - export const getMCCountriesAndContinents = createSelector( ( state ) => { const { countries, continents } = state.mc; diff --git a/js/src/hooks/useGoogleMCPhoneNumber.js b/js/src/hooks/useGoogleMCPhoneNumber.js deleted file mode 100644 index 1be1a8ce26..0000000000 --- a/js/src/hooks/useGoogleMCPhoneNumber.js +++ /dev/null @@ -1,68 +0,0 @@ -/** - * External dependencies - */ -import { useSelect } from '@wordpress/data'; -import { parsePhoneNumberFromString as parsePhoneNumber } from 'libphonenumber-js'; - -/** - * Internal dependencies - */ -import { STORE_KEY } from '.~/data/constants'; - -const emptyData = { - country: '', - countryCallingCode: '', - nationalNumber: '', - isValid: false, - isVerified: false, - display: '', -}; - -/** - * @typedef {Object} PhoneNumber - * @property {boolean} loaded Whether the data have been loaded. - * @property {PhoneNumberData} data User's phone number data fetched from Google Merchant Center. - */ - -/** - * @typedef {Object} PhoneNumberData - * @property {string} country The country code. Example: 'US'. - * @property {string} countryCallingCode The country calling code. Example: '1'. - * @property {string} nationalNumber The national (significant) number. Example: '2133734253'. - * @property {boolean} isValid Whether the phone number is valid. - * @property {boolean} isVerified Whether the phone number is verified. - * @property {string} display The phone number string in international format. Example: '+1 213 373 4253'. - */ - -/** - * A hook to load user's phone number data from Google Merchant Center. - * - * @return {PhoneNumber} The payload of parsed phone number associated with the Google Merchant Center account and its loaded state. - */ -export default function useGoogleMCPhoneNumber() { - return useSelect( ( select ) => { - const { getGoogleMCPhoneNumber } = select( STORE_KEY ); - const { data: contact, loaded } = getGoogleMCPhoneNumber(); - let data = emptyData; - - if ( contact ) { - // Prevent to call parsePhoneNumber with null. - const parsed = parsePhoneNumber( contact.phone_number || '' ); - if ( parsed ) { - data = { - ...parsed, - isValid: parsed.isValid(), - isVerified: - contact.phone_verification_status === 'verified', - display: parsed.formatInternational(), - }; - delete data.metadata; - } - } - - return { - loaded, - data, - }; - }, [] ); -} diff --git a/package.json b/package.json index 79a7884e6b..d48530e89f 100644 --- a/package.json +++ b/package.json @@ -43,7 +43,6 @@ "@wordpress/url": "^3.43.13", "classnames": "^2.5.1", "gridicons": "^3.4.2", - "libphonenumber-js": "1.9.22", "lodash": "^4.17.21", "prop-types": "^15.8.1", "rememo": "^4.0.2" From d4c0daa9099c07887beb994d19bead13be9a12a1 Mon Sep 17 00:00:00 2001 From: Joe McGill Date: Wed, 13 Nov 2024 16:28:47 -0600 Subject: [PATCH 36/51] Tweak address card description color --- js/src/components/contact-information/store-address-card.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/src/components/contact-information/store-address-card.scss b/js/src/components/contact-information/store-address-card.scss index a1b226ea9b..c64ae23a55 100644 --- a/js/src/components/contact-information/store-address-card.scss +++ b/js/src/components/contact-information/store-address-card.scss @@ -6,6 +6,6 @@ } .gla-account-card__description { - color: $gray-900; + color: $gray-700; } } From 059fd9905baff116d92c4a0ad1e6a2151410c60b Mon Sep 17 00:00:00 2001 From: Joe McGill <801097+joemcgill@users.noreply.github.com> Date: Wed, 13 Nov 2024 16:31:11 -0600 Subject: [PATCH 37/51] Correct inline docs Co-authored-by: Eason --- js/src/components/contact-information/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/src/components/contact-information/index.js b/js/src/components/contact-information/index.js index 4ca9d4abdc..2432b1a1c6 100644 --- a/js/src/components/contact-information/index.js +++ b/js/src/components/contact-information/index.js @@ -37,7 +37,7 @@ const settingsTitle = __( 'Contact information', 'google-listings-and-ads' ); /** * Renders a preview of contact information section, - * or a if contact information are not saved yet. + * or a notice if contact information is outdated. */ export function ContactInformationPreview() { return ( From 49f8f8223b91b546d56e64c32c12b9e0851892fe Mon Sep 17 00:00:00 2001 From: Joe McGill Date: Wed, 13 Nov 2024 16:47:21 -0600 Subject: [PATCH 38/51] Update ContactInformationPreview and export as default --- ...ndex.js => contact-information-preview.js} | 29 +++++++++---------- js/src/settings/index.js | 2 +- 2 files changed, 14 insertions(+), 17 deletions(-) rename js/src/components/contact-information/{index.js => contact-information-preview.js} (69%) diff --git a/js/src/components/contact-information/index.js b/js/src/components/contact-information/contact-information-preview.js similarity index 69% rename from js/src/components/contact-information/index.js rename to js/src/components/contact-information/contact-information-preview.js index 2432b1a1c6..319f2f1b42 100644 --- a/js/src/components/contact-information/index.js +++ b/js/src/components/contact-information/contact-information-preview.js @@ -8,7 +8,6 @@ import { __ } from '@wordpress/i18n'; */ import { getEditStoreAddressUrl } from '.~/utils/urls'; import Section from '.~/wcdl/section'; -import VerticalGapLayout from '.~/components/vertical-gap-layout'; import AppDocumentationLink from '.~/components/app-documentation-link'; import { StoreAddressCardPreview } from './store-address-card'; @@ -39,23 +38,21 @@ const settingsTitle = __( 'Contact information', 'google-listings-and-ads' ); * Renders a preview of contact information section, * or a notice if contact information is outdated. */ -export function ContactInformationPreview() { +export default function ContactInformationPreview() { return (
- - - { __( 'Learn more', 'google-listings-and-ads' ) } - - } - /> - + + { __( 'Learn more', 'google-listings-and-ads' ) } + + } + />
); } diff --git a/js/src/settings/index.js b/js/src/settings/index.js index f23e775cb2..631265ff2b 100644 --- a/js/src/settings/index.js +++ b/js/src/settings/index.js @@ -12,7 +12,7 @@ import useMenuEffect from '.~/hooks/useMenuEffect'; import useGoogleAccount from '.~/hooks/useGoogleAccount'; import useUpdateRestAPIAuthorizeStatusByUrlQuery from '.~/hooks/useUpdateRestAPIAuthorizeStatusByUrlQuery'; import { subpaths, getReconnectAccountUrl } from '.~/utils/urls'; -import { ContactInformationPreview } from '.~/components/contact-information'; +import ContactInformationPreview from '.~/components/contact-information/contact-information-preview'; import LinkedAccounts from './linked-accounts'; import ReconnectWPComAccount from './reconnect-wpcom-account'; import ReconnectGoogleAccount from './reconnect-google-account'; From 1372d3ea5eb7279edfbbca6a955cb025bc05ae89 Mon Sep 17 00:00:00 2001 From: Joe McGill Date: Wed, 13 Nov 2024 16:48:19 -0600 Subject: [PATCH 39/51] Remove redundant div wrapper for addressContent --- js/src/components/contact-information/store-address-card.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/js/src/components/contact-information/store-address-card.js b/js/src/components/contact-information/store-address-card.js index 6705bfe60c..a0cd063aca 100644 --- a/js/src/components/contact-information/store-address-card.js +++ b/js/src/components/contact-information/store-address-card.js @@ -108,11 +108,11 @@ const StoreAddressCard = () => { .join( ', ' ); addressContent = ( -
-
{ address }
+ <> + { address &&
{ address }
} { address2 &&
{ address2 }
}
{ rest }
-
+ ); } From e49ca66a2bfb23c6fa9d114a39ccce398f423d92 Mon Sep 17 00:00:00 2001 From: Joe McGill Date: Wed, 13 Nov 2024 17:07:49 -0600 Subject: [PATCH 40/51] Simplify StoreAddressCard description --- .../contact-information/store-address-card.js | 46 +++++++++---------- 1 file changed, 21 insertions(+), 25 deletions(-) diff --git a/js/src/components/contact-information/store-address-card.js b/js/src/components/contact-information/store-address-card.js index a0cd063aca..c0d3c52163 100644 --- a/js/src/components/contact-information/store-address-card.js +++ b/js/src/components/contact-information/store-address-card.js @@ -116,31 +116,27 @@ const StoreAddressCard = () => { ); } - const longDescription = ( + const description = (

- { createInterpolateElement( - __( - 'We’re using your store address for Google verification. This information won’t be public. Edit in WooCommerce settings if needed and update to review the changes.', - 'google-listings-and-ads' - ), - { - link: settingsLink, - } - ) } -

- ); - - const shortDescription = ( -

- { createInterpolateElement( - __( - 'Your store address is required by Google for verification. This information won’t be public. Complete that in WooCommerce settings and update to review the changes.', - 'google-listings-and-ads' - ), - { - link: settingsLink, - } - ) } + { isAddressFilled + ? createInterpolateElement( + __( + 'We’re using your store address for Google verification. This information won’t be public. Edit in WooCommerce settings if needed and update to review the changes.', + 'google-listings-and-ads' + ), + { + link: settingsLink, + } + ) + : createInterpolateElement( + __( + 'Your store address is required by Google for verification. This information won’t be public. Complete that in WooCommerce settings and update to review the changes.', + 'google-listings-and-ads' + ), + { + link: settingsLink, + } + ) }

); @@ -159,7 +155,7 @@ const StoreAddressCard = () => { appearance={ APPEARANCE.ADDRESS } alignIcon="top" alignIndicator="top" - description={ isAddressFilled ? longDescription : shortDescription } + description={ description } detail={ detail } indicator={ refreshButton } /> From 299fad7532e8ce46fd3c2cf5fe43c6406e385b7b Mon Sep 17 00:00:00 2001 From: Joe McGill Date: Wed, 13 Nov 2024 17:19:45 -0600 Subject: [PATCH 41/51] Use isAddressFilled from useStoreAddress hook --- js/src/components/contact-information/store-address-card.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/js/src/components/contact-information/store-address-card.js b/js/src/components/contact-information/store-address-card.js index c0d3c52163..a1e9d6cfb9 100644 --- a/js/src/components/contact-information/store-address-card.js +++ b/js/src/components/contact-information/store-address-card.js @@ -11,7 +11,6 @@ import { getPath, getQuery } from '@woocommerce/navigation'; * Internal dependencies */ import useStoreAddress from '.~/hooks/useStoreAddress'; -import useStoreAddressSynced from '.~/hooks/useStoreAddressSynced'; import AccountCard, { APPEARANCE } from '.~/components/account-card'; import AppButton from '.~/components/app-button'; import ContactInformationPreviewCard from './contact-information-preview-card'; @@ -49,7 +48,7 @@ import './store-address-card.scss'; */ const StoreAddressCard = () => { const { loaded, data, refetch } = useStoreAddress(); - const { isAddressFilled } = useStoreAddressSynced(); + const { isAddressFilled } = data; const path = getPath(); const { subpath } = getQuery(); From 6ad177b9976aa9a4ef3f11acd4d55853000dcdb2 Mon Sep 17 00:00:00 2001 From: Joe McGill Date: Wed, 13 Nov 2024 17:26:11 -0600 Subject: [PATCH 42/51] Add blank line --- js/src/utils/urls.js | 1 + 1 file changed, 1 insertion(+) diff --git a/js/src/utils/urls.js b/js/src/utils/urls.js index 66a0550df2..f785e50a6e 100644 --- a/js/src/utils/urls.js +++ b/js/src/utils/urls.js @@ -92,6 +92,7 @@ export const getEditStoreAddressUrl = () => { null ); }; + /** * Returns the URL of the account re-connecting page. * From c0d25ec03f11def2cf248532fed91eb65a8b2c15 Mon Sep 17 00:00:00 2001 From: Joe McGill Date: Wed, 13 Nov 2024 18:02:28 -0600 Subject: [PATCH 43/51] Update fires tags --- js/src/setup-mc/setup-stepper/saved-setup-stepper.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/js/src/setup-mc/setup-stepper/saved-setup-stepper.js b/js/src/setup-mc/setup-stepper/saved-setup-stepper.js index 56a8a96995..afc64377cd 100644 --- a/js/src/setup-mc/setup-stepper/saved-setup-stepper.js +++ b/js/src/setup-mc/setup-stepper/saved-setup-stepper.js @@ -32,8 +32,8 @@ import { /** * @param {Object} props React props * @param {string} [props.savedStep] A saved step overriding the current step - * @fires gla_setup_mc with `{ triggered_by: 'step1-continue-button' | 'step2-continue-button', 'step3-continue-button', action: 'go-to-step2' | 'go-to-step3' | 'go-to-step4' }`. - * @fires gla_setup_mc with `{ triggered_by: 'stepper-step1-button' | 'stepper-step2-button' | 'stepper-step3-button', action: 'go-to-step1' | 'go-to-step2' | 'go-to-step3' }`. + * @fires gla_setup_mc with `{ triggered_by: 'step1-continue-button' | 'step2-continue-button', action: 'go-to-step2' | 'go-to-step3' }`. + * @fires gla_setup_mc with `{ triggered_by: 'stepper-step1-button' | 'stepper-step2-button', action: 'go-to-step1' | 'go-to-step2' }`. */ const SavedSetupStepper = ( { savedStep } ) => { const [ step, setStep ] = useState( savedStep ); From eeb596bda63dfe0cbf8c107f3c49e6e314c9cd6e Mon Sep 17 00:00:00 2001 From: Joe McGill Date: Wed, 13 Nov 2024 18:33:25 -0600 Subject: [PATCH 44/51] Rename and simplify useStoreAddressSynced hook --- js/src/hooks/useStoreAddressReady.js | 36 ++++++++++ js/src/hooks/useStoreAddressSynced.js | 65 ------------------- .../setup-stepper/setup-accounts/index.js | 6 +- 3 files changed, 39 insertions(+), 68 deletions(-) create mode 100644 js/src/hooks/useStoreAddressReady.js delete mode 100644 js/src/hooks/useStoreAddressSynced.js diff --git a/js/src/hooks/useStoreAddressReady.js b/js/src/hooks/useStoreAddressReady.js new file mode 100644 index 0000000000..d449bb735d --- /dev/null +++ b/js/src/hooks/useStoreAddressReady.js @@ -0,0 +1,36 @@ +/** + * External dependencies + */ +import { useSelect } from '@wordpress/data'; + +/** + * Internal dependencies + */ +import useGoogleMCAccount from '.~/hooks/useGoogleMCAccount'; +import { STORE_KEY } from '.~/data/constants'; + +/** + * Checks if the store address is synchronized with the Merchant Center (GMC) account address. + * + * @return {boolean} Whether the store address is ready to by synced to MC. + */ +export default function useStoreAddressReady() { + const { hasGoogleMCConnection } = useGoogleMCAccount(); + + return useSelect( + ( select ) => { + if ( ! hasGoogleMCConnection ) { + return false; + } + + const contact = select( STORE_KEY ).getGoogleMCContactInformation(); + + if ( ! contact ) { + return false; + } + + return ! contact.wc_address_errors.length; + }, + [ hasGoogleMCConnection ] + ); +} diff --git a/js/src/hooks/useStoreAddressSynced.js b/js/src/hooks/useStoreAddressSynced.js deleted file mode 100644 index 3a6bc7f802..0000000000 --- a/js/src/hooks/useStoreAddressSynced.js +++ /dev/null @@ -1,65 +0,0 @@ -/** - * External dependencies - */ -import { useSelect } from '@wordpress/data'; - -/** - * Internal dependencies - */ -import useGoogleMCAccount from '.~/hooks/useGoogleMCAccount'; -import { STORE_KEY } from '.~/data/constants'; - -/** - * @typedef {Object} StoreAddressSyncedData - * @property {boolean|null} isAddressFilled Whether the address is filled without errors. Returns 'null' if the state is undetermined. - * @property {boolean|null} isAddressSynced Returns `true` if the store address matches the GMC account address, otherwise, returns `false`. If the MC account is not connected or if the state is not yet determined, returns `null`. - */ - -const googleMCContactInformationSelector = 'getGoogleMCContactInformation'; - -/** - * Checks if the store address is synchronized with the Merchant Center (GMC) account address. - * - * @return {StoreAddressSyncedData} The store address synced data. - */ -export default function useStoreAddressSynced() { - const { isReady, hasFinishedResolutionGoogle } = useGoogleMCAccount(); - - return useSelect( - ( select ) => { - if ( ! isReady ) { - return { - isAddressFilled: null, - isAddressSynced: null, - hasFinishedResolution: hasFinishedResolutionGoogle, - }; - } - - const selector = select( STORE_KEY ); - const contact = selector[ googleMCContactInformationSelector ](); - const hasFinishedResolution = selector.hasFinishedResolution( - googleMCContactInformationSelector - ); - - if ( ! contact ) { - return { - isAddressFilled: null, - isAddressSynced: null, - hasFinishedResolution, - }; - } - - const { - is_mc_address_different: isMCAddressDifferent, - wc_address_errors: missingRequiredFields, - } = contact; - - return { - isAddressFilled: ! missingRequiredFields.length, - isAddressSynced: ! isMCAddressDifferent, - hasFinishedResolution, - }; - }, - [ hasFinishedResolutionGoogle, isReady ] - ); -} diff --git a/js/src/setup-mc/setup-stepper/setup-accounts/index.js b/js/src/setup-mc/setup-stepper/setup-accounts/index.js index 383966e2c7..bcb871659f 100644 --- a/js/src/setup-mc/setup-stepper/setup-accounts/index.js +++ b/js/src/setup-mc/setup-stepper/setup-accounts/index.js @@ -26,7 +26,7 @@ import Faqs from './faqs'; import './index.scss'; import useGoogleAdsAccount from '.~/hooks/useGoogleAdsAccount'; import useGoogleAdsAccountReady from '.~/hooks/useGoogleAdsAccountReady'; -import useStoreAddressSynced from '.~/hooks/useStoreAddressSynced'; +import useStoreAddressReady from '.~/hooks/useStoreAddressReady'; /** * Renders the disclaimer of Comparison Shopping Service (CSS). @@ -91,7 +91,7 @@ const SetupAccounts = ( props ) => { isReady: isGoogleMCReady, } = useGoogleMCAccount(); const { hasFinishedResolution } = useGoogleAdsAccount(); - const { isAddressFilled } = useStoreAddressSynced(); + const isStoreAddressReady = useStoreAddressReady(); const isGoogleAdsReady = useGoogleAdsAccountReady(); const { updateGoogleMCContactInformation } = useAppDispatch(); const [ isSubmitting, setIsSubmitting ] = useState( false ); @@ -131,7 +131,7 @@ const SetupAccounts = ( props ) => { hasFinishedResolution && isGoogleAdsReady && isGoogleMCReady && - isAddressFilled + isStoreAddressReady ); return ( From a1360bf83dc0e8ad84114a98c0eec91426717a51 Mon Sep 17 00:00:00 2001 From: Joe McGill Date: Wed, 13 Nov 2024 18:52:15 -0600 Subject: [PATCH 45/51] Fix handleSubmitCallback error handling --- js/src/setup-mc/setup-stepper/setup-accounts/index.js | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/js/src/setup-mc/setup-stepper/setup-accounts/index.js b/js/src/setup-mc/setup-stepper/setup-accounts/index.js index bcb871659f..047c9ee143 100644 --- a/js/src/setup-mc/setup-stepper/setup-accounts/index.js +++ b/js/src/setup-mc/setup-stepper/setup-accounts/index.js @@ -96,14 +96,11 @@ const SetupAccounts = ( props ) => { const { updateGoogleMCContactInformation } = useAppDispatch(); const [ isSubmitting, setIsSubmitting ] = useState( false ); - const handleSubmitCallback = async () => { + const handleSubmitCallback = () => { setIsSubmitting( true ); - await updateGoogleMCContactInformation().finally( () => - // Error handling is done in the action. - setIsSubmitting( false ) - ); - - onContinue(); + updateGoogleMCContactInformation() + .then( () => onContinue() ) + .catch( () => setIsSubmitting( false ) ); }; /** From e55f65eb29968fc7d0d5e9e45fe50401bbb12f1a Mon Sep 17 00:00:00 2001 From: Joe McGill <801097+joemcgill@users.noreply.github.com> Date: Wed, 13 Nov 2024 18:55:45 -0600 Subject: [PATCH 46/51] Update E2E test docs Co-authored-by: Eason --- tests/e2e/utils/pages/setup-mc/step-1-set-up-accounts.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/e2e/utils/pages/setup-mc/step-1-set-up-accounts.js b/tests/e2e/utils/pages/setup-mc/step-1-set-up-accounts.js index 73832ffb79..43266ab10f 100644 --- a/tests/e2e/utils/pages/setup-mc/step-1-set-up-accounts.js +++ b/tests/e2e/utils/pages/setup-mc/step-1-set-up-accounts.js @@ -391,9 +391,9 @@ export default class SetUpAccountsPage extends MockRequests { } /** - * Get store address refresh to sync button. + * Get the "Update store address" button on the store address card. * - * @return {import('@playwright/test').Locator} Get store address refresh to sync button. + * @return {import('@playwright/test').Locator} Get the "Update store address" button on the store address card. */ getStoreAddressButton() { return this.getStoreAddressCard().getByRole( 'button', { From ddbb900ebc94bffc1430dfbbe2fb38630246024d Mon Sep 17 00:00:00 2001 From: Joe McGill Date: Wed, 13 Nov 2024 20:51:51 -0600 Subject: [PATCH 47/51] Improve E2E tests --- .../specs/setup-mc/step-1-accounts.test.js | 33 ++++++++++++++----- .../pages/setup-mc/step-1-set-up-accounts.js | 26 ++++++++++++++- .../pages/setup-mc/step-2-product-listings.js | 13 -------- 3 files changed, 49 insertions(+), 23 deletions(-) diff --git a/tests/e2e/specs/setup-mc/step-1-accounts.test.js b/tests/e2e/specs/setup-mc/step-1-accounts.test.js index c531c05ab1..a8bc132a12 100644 --- a/tests/e2e/specs/setup-mc/step-1-accounts.test.js +++ b/tests/e2e/specs/setup-mc/step-1-accounts.test.js @@ -848,10 +848,7 @@ test.describe( 'Set up accounts', () => { await setUpAccountsPage.mockAdsAccountDisconnected(); await setUpAccountsPage.fulfillAdsAccounts( ADS_ACCOUNTS ); await setUpAccountsPage.mockMCConnected(); - await setUpAccountsPage.mockContactInformation( { - wcAddressErrors: [], - isMCAddressDifferent: false, - } ); + await setUpAccountsPage.mockContactInformation(); await setUpAccountsPage.goto(); } ); @@ -885,14 +882,11 @@ test.describe( 'Set up accounts', () => { } ); } ); - test.describe( 'When all accounts are connected and store address is synced', async () => { + test.describe( 'When all accounts are connected and store address is fulfilled', async () => { test.beforeAll( async () => { await setUpAccountsPage.mockAdsAccountConnected(); await setUpAccountsPage.mockMCConnected(); - await setUpAccountsPage.mockContactInformation( { - wcAddressErrors: [], - isMCAddressDifferent: false, - } ); + await setUpAccountsPage.mockContactInformation( {} ); await setUpAccountsPage.goto(); } ); @@ -903,6 +897,27 @@ test.describe( 'Set up accounts', () => { await expect( continueButton ).toBeEnabled(); } ); + + test( 'should sync the address and show the heading of the next step when clicked', async () => { + const requestPromise = + setUpAccountsPage.registerContactInformationSyncRequest(); + + await setUpAccountsPage.clickContinueButton(); + + const request = await requestPromise; + const response = await request.response(); + const responseBody = await response.json(); + + expect( response.status() ).toBe( 200 ); + expect( responseBody.wc_address_errors ).toStrictEqual( [] ); + + await expect( + page.getByRole( 'heading', { + name: 'Configure your product listings', + exact: true, + } ) + ).toBeVisible(); + } ); } ); } ); diff --git a/tests/e2e/utils/pages/setup-mc/step-1-set-up-accounts.js b/tests/e2e/utils/pages/setup-mc/step-1-set-up-accounts.js index 43266ab10f..4411e96f63 100644 --- a/tests/e2e/utils/pages/setup-mc/step-1-set-up-accounts.js +++ b/tests/e2e/utils/pages/setup-mc/step-1-set-up-accounts.js @@ -311,6 +311,17 @@ export default class SetUpAccountsPage extends MockRequests { } ); } + /** + * Click "Continue" button. + * + * @return {Promise} + */ + async clickContinueButton() { + const continueButton = this.getContinueButton(); + await continueButton.click(); + await this.page.waitForLoadState( LOAD_STATE.DOM_CONTENT_LOADED ); + } + /** * Get link of Google Merchant Center Help. * @@ -403,7 +414,7 @@ export default class SetUpAccountsPage extends MockRequests { } /** - * Register the response when connecting an Ads account + * Register the response when connecting an Ads account. * * @return {Promise} The response. */ @@ -414,4 +425,17 @@ export default class SetUpAccountsPage extends MockRequests { response.status() === 200 ); } + + /** + * Register the response when syncing the store address. + * + * @return {Promise} The response. + */ + registerContactInformationSyncRequest() { + return this.page.waitForRequest( + ( request ) => + request.url().includes( '/gla/mc/contact-information' ) && + request.method() === 'POST' + ); + } } diff --git a/tests/e2e/utils/pages/setup-mc/step-2-product-listings.js b/tests/e2e/utils/pages/setup-mc/step-2-product-listings.js index 47770a1ebc..d6c3c4de5d 100644 --- a/tests/e2e/utils/pages/setup-mc/step-2-product-listings.js +++ b/tests/e2e/utils/pages/setup-mc/step-2-product-listings.js @@ -280,19 +280,6 @@ export default class ProductListingsPage extends MockRequests { } ); } - /** - * Register the requests when the continue button is clicked. - * - * @return {Promise} The requests. - */ - registerContinueRequest() { - return this.page.waitForRequest( - ( request ) => - request.url().includes( '/gla/mc/contact-information' ) && - request.method() === 'GET' - ); - } - /** * Register settings request when the shipping rate radio button is checked. * From 866e7b6389b18c5fae80209810d512248d402509 Mon Sep 17 00:00:00 2001 From: Joe McGill Date: Wed, 13 Nov 2024 20:56:34 -0600 Subject: [PATCH 48/51] Remove phone number validation from MerchantTrait and ContactInformationTest --- tests/Tools/HelperTrait/MerchantTrait.php | 2 -- tests/Unit/MerchantCenter/ContactInformationTest.php | 5 ----- 2 files changed, 7 deletions(-) diff --git a/tests/Tools/HelperTrait/MerchantTrait.php b/tests/Tools/HelperTrait/MerchantTrait.php index 797ad96c0e..835cb3d6e9 100644 --- a/tests/Tools/HelperTrait/MerchantTrait.php +++ b/tests/Tools/HelperTrait/MerchantTrait.php @@ -77,8 +77,6 @@ public function get_valid_account(): Account { public function get_valid_business_info(): AccountBusinessInformation { $business_info = new AccountBusinessInformation(); - $business_info->setPhoneNumber( $this->valid_account_phone_number ); - $business_info->setPhoneVerificationStatus( 'VERIFIED' ); $business_info->setAddress( $this->get_sample_address() ); return $business_info; diff --git a/tests/Unit/MerchantCenter/ContactInformationTest.php b/tests/Unit/MerchantCenter/ContactInformationTest.php index 35ddd47849..ee7ba2a8b5 100644 --- a/tests/Unit/MerchantCenter/ContactInformationTest.php +++ b/tests/Unit/MerchantCenter/ContactInformationTest.php @@ -55,11 +55,6 @@ public function test_get_valid_contact_information() { $contact_information = $this->contact_information->get_contact_information(); - $this->assertEquals( - $this->valid_account_phone_number, - $contact_information->getPhoneNumber() - ); - $this->assertEquals( $this->get_sample_address()->getPostalCode(), $contact_information->getAddress()->getPostalCode() From 7d49d69424fb0fff8b47ac944a43e4499bd3b902 Mon Sep 17 00:00:00 2001 From: Eason Su Date: Thu, 14 Nov 2024 15:58:48 +0800 Subject: [PATCH 49/51] Update the package-lock.json file to reflect the revmoval of the `libphonenumber-js` npm dependency. Ref: https://github.com/woocommerce/google-listings-and-ads/pull/2653#discussion_r1830316085 --- package-lock.json | 7 ------- 1 file changed, 7 deletions(-) diff --git a/package-lock.json b/package-lock.json index 646f4851a0..db9454b7f1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -34,7 +34,6 @@ "@wordpress/url": "^3.43.13", "classnames": "^2.5.1", "gridicons": "^3.4.2", - "libphonenumber-js": "1.9.22", "lodash": "^4.17.21", "prop-types": "^15.8.1", "rememo": "^4.0.2" @@ -23962,12 +23961,6 @@ "node": ">= 0.8.0" } }, - "node_modules/libphonenumber-js": { - "version": "1.9.22", - "resolved": "https://registry.npmjs.org/libphonenumber-js/-/libphonenumber-js-1.9.22.tgz", - "integrity": "sha512-nE0aF0wrNq09ewF36s9FVqRW73hmpw6cobVDlbexmsu1432LEfuN24BCudNuRx4t2rElSeK/N0JbedzRW/TC4A==", - "license": "MIT" - }, "node_modules/lighthouse": { "version": "10.4.0", "resolved": "https://registry.npmjs.org/lighthouse/-/lighthouse-10.4.0.tgz", From 6fff925a766fb739c381dc47333d5da65a6774c4 Mon Sep 17 00:00:00 2001 From: Joe McGill <801097+joemcgill@users.noreply.github.com> Date: Thu, 14 Nov 2024 07:53:32 -0600 Subject: [PATCH 50/51] Fix typo in JSdoc Co-authored-by: Eason --- js/src/hooks/useStoreAddressReady.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/src/hooks/useStoreAddressReady.js b/js/src/hooks/useStoreAddressReady.js index d449bb735d..7f83cf280f 100644 --- a/js/src/hooks/useStoreAddressReady.js +++ b/js/src/hooks/useStoreAddressReady.js @@ -12,7 +12,7 @@ import { STORE_KEY } from '.~/data/constants'; /** * Checks if the store address is synchronized with the Merchant Center (GMC) account address. * - * @return {boolean} Whether the store address is ready to by synced to MC. + * @return {boolean} Whether the store address is ready to be synced to MC. */ export default function useStoreAddressReady() { const { hasGoogleMCConnection } = useGoogleMCAccount(); From c77748cc178648297460a45c66bd2e66c134409d Mon Sep 17 00:00:00 2001 From: Joe McGill Date: Thu, 14 Nov 2024 08:25:26 -0600 Subject: [PATCH 51/51] Remove unused property --- tests/Tools/HelperTrait/MerchantTrait.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/Tools/HelperTrait/MerchantTrait.php b/tests/Tools/HelperTrait/MerchantTrait.php index 835cb3d6e9..208befc11d 100644 --- a/tests/Tools/HelperTrait/MerchantTrait.php +++ b/tests/Tools/HelperTrait/MerchantTrait.php @@ -17,8 +17,7 @@ */ trait MerchantTrait { - protected $valid_account_phone_number = '+18008675309'; - protected $valid_account_id = '123581321'; + protected $valid_account_id = '123581321'; /** * Get a mocked instance of GoogleException that occurs within the runtime of