diff --git a/js/src/components/account-card/index.js b/js/src/components/account-card/index.js index 7b2433366a..3330f33249 100644 --- a/js/src/components/account-card/index.js +++ b/js/src/components/account-card/index.js @@ -121,7 +121,7 @@ const appearanceDict = { // The `center` is the default alignment, and no need to append any additional class name. const alignStyleName = { center: false, - top: `gla-account-card__styled--align-top`, + top: 'gla-account-card__styled--align-top', }; const indicatorAlignStyleName = { diff --git a/js/src/components/google-combo-account-card/connect-ads/confirm-create-modal.js b/js/src/components/google-combo-account-card/connect-ads/confirm-create-modal.js new file mode 100644 index 0000000000..c03656f0a2 --- /dev/null +++ b/js/src/components/google-combo-account-card/connect-ads/confirm-create-modal.js @@ -0,0 +1,69 @@ +/** + * External dependencies + */ +import { __ } from '@wordpress/i18n'; + +/** + * Internal dependencies + */ +import AppModal from '.~/components/app-modal'; +import AppButton from '.~/components/app-button'; +import WarningIcon from '.~/components/warning-icon'; +import './confirm-create-modal.scss'; + +/** + * Google Ads account creation confirmation modal. + * This modal is shown when the user tries to create a new Google Ads account. + * + * @param {Object} props Component props. + * @param {Function} props.onContinue Callback to continue with account creation. + * @param {Function} props.onRequestClose Callback to close the modal. + * @return {JSX.Element} Confirmation modal. + */ +const ConfirmCreateModal = ( { onContinue, onRequestClose } ) => { + return ( + + { __( + 'Yes, I want a new account', + 'google-listings-and-ads' + ) } + , + + { __( 'Cancel', 'google-listings-and-ads' ) } + , + ] } + onRequestClose={ onRequestClose } + > +

+ + + { __( + 'Are you sure you want to create a new Google Ads account?', + 'google-listings-and-ads' + ) } + +

+

+ { __( + 'You already have another Ads account associated with this Google account.', + 'google-listings-and-ads' + ) } +

+

+ { __( + 'If you create a new Google Ads account, you will need to accept an invite to the account before it can be used.', + 'google-listings-and-ads' + ) } +

+
+ ); +}; + +export default ConfirmCreateModal; diff --git a/js/src/components/google-combo-account-card/connect-ads/confirm-create-modal.scss b/js/src/components/google-combo-account-card/connect-ads/confirm-create-modal.scss new file mode 100644 index 0000000000..fc15d2235d --- /dev/null +++ b/js/src/components/google-combo-account-card/connect-ads/confirm-create-modal.scss @@ -0,0 +1,8 @@ +.gla-ads-warning-modal { + + .gla-ads-warning-modal__warning-text { + display: flex; + align-items: center; + gap: calc(var(--main-gap) / 3); + } +} diff --git a/js/src/components/google-combo-account-card/connect-ads/connect-ads-footer.js b/js/src/components/google-combo-account-card/connect-ads/connect-ads-footer.js index 9405e43857..62306fe7d0 100644 --- a/js/src/components/google-combo-account-card/connect-ads/connect-ads-footer.js +++ b/js/src/components/google-combo-account-card/connect-ads/connect-ads-footer.js @@ -14,17 +14,17 @@ import DisconnectAccount from '.~/components/google-ads-account-card/disconnect- * * @param {Object} props Props. * @param {boolean} props.isConnected Whether the account is connected. + * @param {Function} props.onCreateNew Callback to create a new account. * @param {Object} props.restProps Rest props. Passed to AppButton. * @return {JSX.Element} Footer component. */ -const ConnectAdsFooter = ( { isConnected, ...restProps } ) => { - // If the account is connected, show the disconnect button. +const ConnectAdsFooter = ( { isConnected, onCreateNew, ...restProps } ) => { if ( isConnected ) { return ; } return ( - + { __( 'Or, create a new Google Ads account', 'google-listings-and-ads' diff --git a/js/src/components/google-combo-account-card/connect-ads/connect-ads.js b/js/src/components/google-combo-account-card/connect-ads/connect-ads.js index ec672fbc5b..aa6471438a 100644 --- a/js/src/components/google-combo-account-card/connect-ads/connect-ads.js +++ b/js/src/components/google-combo-account-card/connect-ads/connect-ads.js @@ -7,37 +7,62 @@ import { __ } from '@wordpress/i18n'; /** * Internal dependencies */ +import AccountCard from '.~/components/account-card'; +import ConnectAdsFooter from './connect-ads-footer'; +import ConfirmCreateModal from './confirm-create-modal'; +import LoadingLabel from '.~/components/loading-label'; import useApiFetchCallback from '.~/hooks/useApiFetchCallback'; import useDispatchCoreNotices from '.~/hooks/useDispatchCoreNotices'; +import useExistingGoogleAdsAccounts from '.~/hooks/useExistingGoogleAdsAccounts'; import useGoogleAdsAccount from '.~/hooks/useGoogleAdsAccount'; import { useAppDispatch } from '.~/data'; import useGoogleAdsAccountReady from '.~/hooks/useGoogleAdsAccountReady'; -import AccountCard from '.~/components/account-card'; import AdsAccountSelectControl from '.~/components/ads-account-select-control'; import ConnectedIconLabel from '.~/components/connected-icon-label'; -import ConnectAdsFooter from './connect-ads-footer'; -import LoadingLabel from '.~/components/loading-label'; import ConnectButton from '.~/components/google-ads-account-card/connect-ads/connect-button'; /** * ConnectAds component renders an account card to connect to an existing Google Ads account. * + * @param {Object} props Component props. + * @param {boolean} props.isConnecting Whether the user is in the process of finalizing the Ads account creation, i.e after the user has claimed the account and the step is conversion_action. + * @param {boolean} props.isCreating Whether the user is in the process of creating a new Ads account. + * @param {Function} props.onCreate A callback to fire when creating a new account. * @return {JSX.Element} {@link AccountCard} filled with content. */ -const ConnectAds = () => { +const ConnectAds = ( { isConnecting, isCreating, onCreate } ) => { const [ value, setValue ] = useState(); const [ isLoading, setLoading ] = useState( false ); - const { refetchGoogleAdsAccount } = useGoogleAdsAccount(); const { createNotice } = useDispatchCoreNotices(); const { fetchGoogleAdsAccountStatus } = useAppDispatch(); const isConnected = useGoogleAdsAccountReady(); - const { googleAdsAccount, hasFinishedResolution } = useGoogleAdsAccount(); + const [ showCreateNewModal, setShowCreateNewModal ] = useState( false ); + const { existingAccounts: accounts, hasFinishedResolution } = + useExistingGoogleAdsAccounts(); + const { + googleAdsAccount, + refetchGoogleAdsAccount, + hasFinishedResolution: hasResolvedGoogleAdsAccount, + } = useGoogleAdsAccount(); const [ connectGoogleAdsAccount ] = useApiFetchCallback( { path: '/wc/gla/ads/accounts', method: 'POST', data: { id: value }, } ); + const onCreateNew = () => { + setShowCreateNewModal( true ); + }; + + const handleOnRequestClose = () => { + setShowCreateNewModal( false ); + }; + + const handleOnContinue = async () => { + setShowCreateNewModal( false ); + await onCreate(); + }; + useEffect( () => { if ( isConnected ) { setValue( googleAdsAccount.id ); @@ -53,10 +78,8 @@ const ConnectAds = () => { try { await connectGoogleAdsAccount(); await fetchGoogleAdsAccountStatus(); - await refetchGoogleAdsAccount(); - setLoading( false ); + refetchGoogleAdsAccount(); } catch ( error ) { - setLoading( false ); createNotice( 'error', __( @@ -64,11 +87,17 @@ const ConnectAds = () => { 'google-listings-and-ads' ) ); + } finally { + setLoading( false ); } }; + if ( ! accounts?.length ) { + return null; + } + const getIndicator = () => { - if ( ! hasFinishedResolution ) { + if ( ! hasFinishedResolution || ! hasResolvedGoogleAdsAccount ) { return ; } @@ -89,34 +118,73 @@ const ConnectAds = () => { ); }; - return ( - - } - actions={ - } + /> + ); + } + + return ( + <> + + } + actions={ + + } + /> + + { showCreateNewModal && ( + - } - /> + ) } + ); }; 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 9f8a9651e8..1897f1b088 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 @@ -53,7 +53,7 @@ const ConnectedGoogleComboAccountCard = () => { const { invalidateResolution } = useAppDispatch(); const { googleAdsAccount } = useGoogleAdsAccount(); const { hasAccess, step } = useGoogleAdsAccountStatus(); - const [ upsertAdsAccount ] = useUpsertAdsAccount(); + const [ upsertAdsAccount, { action } ] = useUpsertAdsAccount(); const finalizeAdsAccountCreation = hasAccess === true && step === 'conversion_action'; @@ -104,8 +104,9 @@ const ConnectedGoogleComboAccountCard = () => { const hasExistingGoogleAdsAccounts = existingGoogleAdsAccounts?.length > 0; const showConnectAds = - ( editMode && hasExistingGoogleAdsAccounts ) || - ( ! isConnected && hasExistingGoogleAdsAccounts ); + ( ( editMode && hasExistingGoogleAdsAccounts ) || + ( ! isConnected && hasExistingGoogleAdsAccounts ) ) && + ! shouldClaimGoogleAdsAccount; // Show the spinner if there's an account creation in progress and account should not be claimed. // If we are not showing the ConnectMC screen, for e.g when we are creating the first account, @@ -132,7 +133,13 @@ const ConnectedGoogleComboAccountCard = () => { /> - { showConnectAds && } + { showConnectAds && ( + + ) } { showConnectMC && ( { ).toBeEnabled(); } ); - test( 'should display the correct Google Ads ID when connected', async () => { - const googleAccountCard = - setUpAccountsPage.getGoogleAccountCard(); + test( 'should send an API request to connect existing Google Ads account', async () => { const googleAdsAccountCard = setUpAccountsPage.getGoogleAdsAccountCard(); const once = setUpAccountsPage.fulfillTimes( 1 ); - await once.fulfillAdsAccounts( { id: 12345 } ); - await once.mockAdsAccountConnected(); + await once.mockAdsStatusClaimed(); + await once.fulfillAdsAccounts( { + id: 111111, + } ); await googleAdsAccountCard .getByRole( 'button', { name: 'Connect' } ) .click(); + } ); + } ); + + test.describe( 'When new Google Ads account is created', () => { + test.beforeAll( async () => { + await setUpAccountsPage.mockAdsAccountDisconnected(); + } ); + + test( 'should see the Create new Google Ads account link', async () => { + const googleAdsAccountCard = + setUpAccountsPage.getGoogleAdsAccountCard(); - await expect( googleAccountCard ).toContainText( - 'Google Ads ID: 12345' + await expect( + googleAdsAccountCard.getByText( + 'Or, create a new Google Ads account', + { exact: true } + ) + ).toBeVisible(); + } ); + + test( 'clicking the "Create new Google Ads account" link should open the modal', async () => { + const googleAdsAccountCard = + setUpAccountsPage.getGoogleAdsAccountCard(); + + await googleAdsAccountCard + .getByText( 'Or, create a new Google Ads account' ) + .click(); + + await expect( setUpAccountsPage.getModal() ).toBeVisible(); + await expect( setUpAccountsPage.getModalHeader() ).toHaveText( + 'Create Google Ads Account' + ); + + // "Yes, I want a new account" button should be disabled and secondary. + const yesButton = setUpAccountsPage.getModalSecondaryButton(); + const cancelButton = setUpAccountsPage.getModalPrimaryButton(); + await expect( yesButton ).toHaveText( + 'Yes, I want a new account' ); + + await expect( cancelButton ).toHaveText( 'Cancel' ); + + // Click the cancel button to close the modal. + cancelButton.click(); + await expect( setUpAccountsPage.getModal() ).not.toBeVisible(); + } ); + + test( 'clicking the "Yes, I want a new account" button should create a new Google Ads account', async () => { + const googleAccountCard = + setUpAccountsPage.getGoogleAccountCard(); + const googleAdsAccountCard = + setUpAccountsPage.getGoogleAdsAccountCard(); + + await setUpAccountsPage.fulfillAdsAccounts( [ + { + id: 111111, + }, + ] ); + + await setUpAccountsPage.mockAdsStatusNotClaimed(); + await setUpAccountsPage.mockAdsAccountIncomplete(); + + await googleAdsAccountCard + .getByText( 'Or, create a new Google Ads account' ) + .click(); + + await expect( setUpAccountsPage.getModal() ).toBeVisible(); + + const yesButton = setUpAccountsPage.getModalSecondaryButton(); + await yesButton.click(); + + await expect( setUpAccountsPage.getModal() ).not.toBeVisible(); + + // Google Ads ID should be displayed. + await expect( + googleAccountCard.getByText( 'Google Ads ID: 12345' ) + ).toBeVisible(); } ); } ); } );