Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

2504: Add skip paid ads confirmation modal. #2563

Merged
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions js/src/setup-mc/setup-stepper/setup-paid-ads/constants.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export const ACTION_COMPLETE = 'complete-ads';
export const ACTION_SKIP = 'skip-ads';
32 changes: 28 additions & 4 deletions js/src/setup-mc/setup-stepper/setup-paid-ads/setup-paid-ads.js
eason9487 marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,12 @@ import FaqsSection from '.~/components/paid-ads/faqs-section';
import AppButton from '.~/components/app-button';
import PaidAdsFeaturesSection from './paid-ads-features-section';
import PaidAdsSetupSections from './paid-ads-setup-sections';
import SkipPaidAdsConfirmationModal from './skip-paid-ads-confirmation-modal';
import { getProductFeedUrl } from '.~/utils/urls';
import clientSession from './clientSession';
import { API_NAMESPACE, STORE_KEY } from '.~/data/constants';
import { GUIDE_NAMES } from '.~/constants';

const ACTION_COMPLETE = 'complete-ads';
const ACTION_SKIP = 'skip-ads';
import { ACTION_COMPLETE, ACTION_SKIP } from './constants';

/**
* Clicking on the "Create a paid ad campaign" button to open the paid ads setup in the onboarding flow.
Expand Down Expand Up @@ -76,6 +75,10 @@ export default function SetupPaidAds() {
);
const [ paidAds, setPaidAds ] = useState( {} );
const [ completing, setCompleting ] = useState( null );
const [
showSkipPaidAdsConfirmationModal,
setShowSkipPaidAdsConfirmationModal,
] = useState( false );

const handleContinuePaidAdsSetupClick = () => {
setShowPaidAdsSetup( true );
Expand Down Expand Up @@ -117,6 +120,14 @@ export default function SetupPaidAds() {
await finishOnboardingSetup( event, onBeforeFinish );
};

const handleShowSkipPaidAdsConfirmationModal = () => {
setShowSkipPaidAdsConfirmationModal( true );
};

const handleCancelSkipPaidAdsClick = () => {
setShowSkipPaidAdsConfirmationModal( false );
};

// The status check of Google Ads account connection is included in `paidAds.isReady`,
// because when there is no connected account, it will disable the budget section and set the `amount` to `undefined`.
const disabledComplete = completing === ACTION_SKIP || ! paidAds.isReady;
Expand Down Expand Up @@ -150,7 +161,9 @@ export default function SetupPaidAds() {
text={ text }
loading={ completing === ACTION_SKIP }
disabled={ disabledSkip }
onClick={ finishOnboardingSetup }
onClick={ handleShowSkipPaidAdsConfirmationModal }
// TODO: Review eventName and eventProps
// The same eventName and eventProps has been copied over to the "Confirm" button in the SkipPaidAdsConfirmationModal component.
eason9487 marked this conversation as resolved.
Show resolved Hide resolved
eventName="gla_onboarding_complete_button_click"
eventProps={ eventProps }
/>
Expand Down Expand Up @@ -194,6 +207,17 @@ export default function SetupPaidAds() {
<PaidAdsSetupSections onStatesReceived={ setPaidAds } />
) }
<FaqsSection />

{ showSkipPaidAdsConfirmationModal && (
<SkipPaidAdsConfirmationModal
onRequestClose={ handleCancelSkipPaidAdsClick }
onSkipConfirmation={ finishOnboardingSetup }
isProcessing={ !! completing }
showPaidAdsSetup={ showPaidAdsSetup }
paidAds={ paidAds }
/>
) }

<StepContentFooter hidden={ ! showPaidAdsSetup }>
<Flex justify="right" gap={ 4 }>
{ createSkipButton(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
/**
* External dependencies
*/
import { __ } from '@wordpress/i18n';
import { select } from '@wordpress/data';
import { createInterpolateElement } from '@wordpress/element';
import { noop, merge } from 'lodash';

/**
* Internal dependencies
*/
import AppModal from '.~/components/app-modal';
import AppButton from '.~/components/app-button';
import AppDocumentationLink from '.~/components/app-documentation-link';
import useGoogleAdsAccount from '.~/hooks/useGoogleAdsAccount';
import { ACTION_SKIP } from './constants';
import { STORE_KEY } from '.~/data/constants';

/**
* Triggered when the skip button is clicked
* // TODO: to review
*
* @event gla_onboarding_complete_button_click
*/

/**
eason9487 marked this conversation as resolved.
Show resolved Hide resolved
* Renders a modal dialog that confirms whether the user wants to skip setting up paid ads.
* It provides information about the benefits of enabling Performance Max and includes a link to learn more.
*
* @param {Object} props React props.
* @param {Function} props.onRequestClose Function to be called when the modal should be closed. Defaults to a no-op function.
* @param {Function} props.onSkipConfirmation Function to be called when the user confirms skipping the paid ads setup. Defaults to a no-op function.
* @param {boolean} [props.isProcessing=false] Indicates whether a process is currently running (e.g., the confirmation is being processed). If true, the confirmation button will show a loading state.
* @param {boolean} [props.showPaidAdsSetup=false] Indicates whether the paid ads setup is currently shown. If true, additional event properties will be included in the eventProps.
* @param {Object} [props.paidAds={}] The paid ads data, including the campaign form data and validation status.
*/
const SkipPaidAdsConfirmationModal = ( {
onRequestClose = noop,
onSkipConfirmation = noop,
eason9487 marked this conversation as resolved.
Show resolved Hide resolved
isProcessing = false,
eason9487 marked this conversation as resolved.
Show resolved Hide resolved
showPaidAdsSetup = false,
paidAds = {},
eason9487 marked this conversation as resolved.
Show resolved Hide resolved
} ) => {
const { googleAdsAccount } = useGoogleAdsAccount();

const eventProps = {
opened_paid_ads_setup: 'no',
google_ads_account_status: googleAdsAccount?.status,
billing_method_status: 'unknown',
campaign_form_validation: 'unknown',
};

// TODO: Review once https://github.com/woocommerce/google-listings-and-ads/issues/2500 is merged
if ( showPaidAdsSetup ) {
const selector = select( STORE_KEY );
const billing = selector.getGoogleAdsAccountBillingStatus();

merge( eventProps, {
opened_paid_ads_setup: 'yes',
billing_method_status: billing?.status,
campaign_form_validation: paidAds.isValid ? 'valid' : 'invalid',
} );
}

return (
<AppModal
className="gla-ads-skip-paid-ads-modal"
eason9487 marked this conversation as resolved.
Show resolved Hide resolved
title={ __( 'Skip setting up ads?', 'google-listings-and-ads' ) }
buttons={ [
<AppButton key="cancel" isSecondary onClick={ onRequestClose }>
{ __( 'Cancel', 'google-listings-and-ads' ) }
</AppButton>,
<AppButton
key="complete-setup"
// TODO: confirm the eventName
eventName="gla_onboarding_complete_button_click"
eason9487 marked this conversation as resolved.
Show resolved Hide resolved
onClick={ onSkipConfirmation }
loading={ isProcessing }
data-action={ ACTION_SKIP }
eventProps={ eventProps }
isPrimary
>
{ __( 'Complete setup', 'google-listings-and-ads' ) }
</AppButton>,
] }
onRequestClose={ onRequestClose }
>
<p>
{ __(
'Enabling Performance Max is highly recommended to drive more sales and reach new audiences across Google channels like Search, YouTube and Discover.',
'google-listings-and-ads'
) }
</p>
<p>
{ __(
'Performance Max uses the best of Google’s AI to show the most impactful ads for your products at the right time and place. Google will use your product data to create ads for this campaign.',
'google-listings-and-ads'
) }
</p>
<p>
{ createInterpolateElement(
eason9487 marked this conversation as resolved.
Show resolved Hide resolved
__(
'<Link>Learn more about Performance Max.</Link>',
'google-listings-and-ads'
),
{
Link: (
<AppDocumentationLink
href="https://support.google.com/google-ads/answer/10724817"
// TODO: review context and linkId values
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@joemcgill Same here.

context="skip-paid-ads-modal"
linkId="skip-paid-ads-modal-learn-more-performance-max"
eason9487 marked this conversation as resolved.
Show resolved Hide resolved
/>
),
}
) }
</p>
</AppModal>
);
};

export default SkipPaidAdsConfirmationModal;
141 changes: 85 additions & 56 deletions tests/e2e/specs/setup-mc/step-4-complete-campaign.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -370,77 +370,106 @@ test.describe( 'Complete your campaign', () => {
await completeCampaign.clickCompleteSetupButton();
await requestsPromises;

const setupSuccessModal = page
.locator( '.components-modal__content' )
.filter( {
hasText:
'You’ve successfully set up Google for WooCommerce!',
} );
const setupSuccessModal =
completeCampaign.getSetupSuccessModal();
await expect( setupSuccessModal ).toBeVisible();
} );
} );
} );
} );

test.describe( 'Complete onboarding by "Skip this step for now"', () => {
test.beforeAll( async () => {
// Reset the showing status for the "Set up paid ads" section.
await page.evaluate( () => window.sessionStorage.clear() );
await setupAdsAccountPage.mockAdsAccountIncomplete();
await completeCampaign.goto();
await completeCampaign.clickSkipStepButton();
} );
test.describe(
'Ask user for confirmation when clicking "Skip this step for now"',
() => {
test.describe( 'User skips paid ads creation', () => {
test.beforeAll( async () => {
// Reset the showing status for the "Set up paid ads" section.
await page.evaluate( () => window.sessionStorage.clear() );
await setupAdsAccountPage.mockAdsAccountIncomplete();
await completeCampaign.goto();
await completeCampaign.clickSkipStepButton();
} );

test( 'should see the setup success modal', async () => {
const setupSuccessModal = page
.locator( '.components-modal__content' )
.filter( {
hasText:
'You’ve successfully set up Google for WooCommerce!',
test( 'should see the modal', async () => {
const skipPaidAdsModal =
completeCampaign.getSkipPaidAdsCreationModal();
await expect( skipPaidAdsModal ).toBeVisible();
} );
await expect( setupSuccessModal ).toBeVisible();
} );

test( 'should see the url contains product-feed', async () => {
expect( page.url() ).toMatch( /path=%2Fgoogle%2Fproduct-feed/ );
} );
} );
test( 'should see the url contains product-feed if the user skips', async () => {
await completeCampaign.clickCompleteSetupButton();
await page.waitForURL( /path=%2Fgoogle%2Fproduct-feed/ );
expect( page.url() ).toMatch(
/path=%2Fgoogle%2Fproduct-feed/
);
} );

test.describe( 'Complete onboarding by "Skip paid ads creation"', () => {
test.beforeAll( async () => {
await setupAdsAccountPage.mockAdsAccountIncomplete();
await completeCampaign.goto();
await completeCampaign.clickCreatePaidAdButton();
await completeCampaign.clickSkipPaidAdsCreationButon();
} );
test( 'should see the setup success modal', async () => {
const setupSuccessModal =
completeCampaign.getSetupSuccessModal();
await expect( setupSuccessModal ).toBeVisible();
} );

test( 'should see buttons on Dashboard for Google Ads onboarding', async () => {
await page.keyboard.press( 'Escape' );
await page
.getByRole( 'tab', { name: 'Dashboard' } )
.click();

test( 'should also see the setup success modal', async () => {
const setupSuccessModal = page
.locator( '.components-modal__content' )
.filter( {
hasText:
'You’ve successfully set up Google for WooCommerce!',
const buttons = page.getByRole( 'button', {
name: 'Add paid campaign',
} );

await expect( buttons ).toHaveCount( 2 );
for ( const button of await buttons.all() ) {
await expect( button ).toBeVisible();
await expect( button ).toBeEnabled();
}
} );
await expect( setupSuccessModal ).toBeVisible();
} );
} );

test( 'should also see the url contains product-feed', async () => {
expect( page.url() ).toMatch( /path=%2Fgoogle%2Fproduct-feed/ );
} );
test.describe( 'User does not skip paid ads creation', () => {
test.beforeAll( async () => {
// Reset the showing status for the "Set up paid ads" section.
await page.evaluate( () => window.sessionStorage.clear() );
await setupAdsAccountPage.mockAdsAccountIncomplete();
await completeCampaign.goto();
await completeCampaign.clickSkipStepButton();
} );

test( 'should no longer see the confirmation modal', async () => {
await completeCampaign.clickCancelModalButton();

test( 'should see buttons on Dashboard for Google Ads onboarding', async () => {
await page.keyboard.press( 'Escape' );
await page.getByRole( 'tab', { name: 'Dashboard' } ).click();
const skipPaidAdsModal =
completeCampaign.getSkipPaidAdsCreationModal();
await expect( skipPaidAdsModal ).not.toBeVisible();
} );

const buttons = page.getByRole( 'button', {
name: 'Add paid campaign',
test( 'user should stay on the same page', async () => {
await expect( page.url() ).toMatch(
/path=%2Fgoogle%2Fsetup-mc&google-mc=connected/
);
} );
} );
}
);

await expect( buttons ).toHaveCount( 2 );
for ( const button of await buttons.all() ) {
await expect( button ).toBeVisible();
await expect( button ).toBeEnabled();
}
} );
} );
// TODO: Should no longer be needed once https://github.com/woocommerce/google-listings-and-ads/issues/2500 is merged.
test.describe(
'Ask user for confirmation when clicking the "Skip paid ads creation"',
() => {
test.beforeAll( async () => {
await setupAdsAccountPage.mockAdsAccountIncomplete();
await completeCampaign.goto();
await completeCampaign.clickCreatePaidAdButton();
await completeCampaign.clickSkipPaidAdsCreationButon();
} );

test( 'should see the confirmation modal', async () => {
const skipPaidAdsModal =
completeCampaign.getSkipPaidAdsCreationModal();
await expect( skipPaidAdsModal ).toBeVisible();
} );
}
);
} );
Loading