Fixes woocommerce/woocommerce-admin#5294 and Automattic/woocommerce-paymentswoocommerce/woocommerce-admin#810 .

Changes:

* Update UsageModal UI to have two action buttons instead of 'Count me in' checkbox.
* Make UsageModal configurable with custom title, message and buttons text.
* Add customized modal to request site usage tracking after WC Payments KYC flow is completed.
This commit is contained in:
Vasily Belolapotkov 2020-11-26 03:27:37 +03:00 committed by GitHub
parent cc5e500085
commit 5dab25382f
9 changed files with 232 additions and 101 deletions

View File

@ -9,7 +9,7 @@ import PropTypes from 'prop-types';
import { get, isFunction, identity } from 'lodash'; import { get, isFunction, identity } from 'lodash';
import { parse } from 'qs'; import { parse } from 'qs';
import { Spinner } from '@woocommerce/components'; import { Spinner } from '@woocommerce/components';
import { getHistory } from '@woocommerce/navigation'; import { getHistory, getQuery } from '@woocommerce/navigation';
import { getSetting } from '@woocommerce/wc-admin-settings'; import { getSetting } from '@woocommerce/wc-admin-settings';
import { import {
PLUGINS_STORE_NAME, PLUGINS_STORE_NAME,
@ -32,6 +32,12 @@ const StoreAlerts = lazy( () =>
import( /* webpackChunkName: "store-alerts" */ './store-alerts' ) import( /* webpackChunkName: "store-alerts" */ './store-alerts' )
); );
const WCPayUsageModal = lazy( () =>
import(
/* webpackChunkName: "wcpay-usage-modal" */ '../task-list/tasks/payments/wcpay-usage-modal'
)
);
export class PrimaryLayout extends Component { export class PrimaryLayout extends Component {
render() { render() {
const { children } = this.props; const { children } = this.props;
@ -121,6 +127,15 @@ class _Layout extends Component {
return parse( search ); return parse( search );
} }
isWCPaySettingsPage() {
const { page, section, tab } = getQuery();
return (
page === 'wc-settings' &&
tab === 'checkout' &&
section === 'woocommerce_payments'
);
}
render() { render() {
const { isEmbedded, ...restProps } = this.props; const { isEmbedded, ...restProps } = this.props;
const { location, page } = this.props; const { location, page } = this.props;
@ -146,6 +161,12 @@ class _Layout extends Component {
</div> </div>
</PrimaryLayout> </PrimaryLayout>
) } ) }
{ isEmbedded && this.isWCPaySettingsPage() && (
<Suspense fallback={ null }>
<WCPayUsageModal />
</Suspense>
) }
</div> </div>
); );
} }

View File

@ -141,6 +141,69 @@
} }
} }
.components-modal__frame.woocommerce-usage-modal {
width: 600px;
max-width: 100%;
.components-modal__header {
border-bottom: 0;
margin-bottom: 0;
}
.woocommerce-usage-modal__wrapper {
flex-grow: 1;
display: flex;
flex-direction: column;
a {
color: $studio-gray-60;
}
button.is-primary {
align-self: flex-end;
}
}
.woocommerce-usage-modal__actions {
display: flex;
justify-content: flex-end;
margin-top: $gap;
button {
margin-left: $gap;
}
}
}
.woocommerce-payments__usage-modal {
.components-modal__header {
height: auto;
padding: 24px 24px 0 24px;
.components-modal__header-heading {
font-size: 24px;
line-height: 32px;
margin: 0 0 24px 0;
}
}
.woocommerce-payments__usage-modal-message {
padding: $gap 0;
font-size: 16px;
line-height: 24px;
}
.woocommerce-payments__usage-footer {
display: flex;
justify-content: flex-end;
padding: $gap 0;
button {
margin-left: $gap;
}
}
}
.components-modal__frame.woocommerce-cart-modal { .components-modal__frame.woocommerce-cart-modal {
width: 600px; width: 600px;
max-width: 100%; max-width: 100%;

View File

@ -6,12 +6,7 @@ import { Component } from '@wordpress/element';
import { compose } from '@wordpress/compose'; import { compose } from '@wordpress/compose';
import { withDispatch, withSelect } from '@wordpress/data'; import { withDispatch, withSelect } from '@wordpress/data';
import interpolateComponents from 'interpolate-components'; import interpolateComponents from 'interpolate-components';
import { import { Button, Modal } from '@wordpress/components';
Button,
CheckboxControl,
FormToggle,
Modal,
} from '@wordpress/components';
import { Link } from '@woocommerce/components'; import { Link } from '@woocommerce/components';
import { OPTIONS_STORE_NAME } from '@woocommerce/data'; import { OPTIONS_STORE_NAME } from '@woocommerce/data';
@ -19,17 +14,9 @@ class UsageModal extends Component {
constructor( props ) { constructor( props ) {
super( props ); super( props );
this.state = { this.state = {
allowTracking: props.allowTracking,
isLoadingScripts: false, isLoadingScripts: false,
isRequestStarted: false,
}; };
this.onTrackingChange = this.onTrackingChange.bind( this );
}
onTrackingChange() {
this.setState( {
allowTracking: ! this.state.allowTracking,
} );
} }
async componentDidUpdate( prevProps, prevState ) { async componentDidUpdate( prevProps, prevState ) {
@ -40,7 +27,13 @@ class UsageModal extends Component {
onContinue, onContinue,
createNotice, createNotice,
} = this.props; } = this.props;
const { isLoadingScripts } = this.state; const { isLoadingScripts, isRequestStarted } = this.state;
// We can't rely on isRequesting props only because option update might be triggered by other component.
if ( ! isRequestStarted ) {
return;
}
const isRequestSuccessful = const isRequestSuccessful =
! isRequesting && ! isRequesting &&
! isLoadingScripts && ! isLoadingScripts &&
@ -66,13 +59,17 @@ class UsageModal extends Component {
} }
} }
updateTracking() { updateTracking( { allowTracking } ) {
const { allowTracking } = this.state;
const { updateOptions } = this.props; const { updateOptions } = this.props;
if ( allowTracking && typeof window.wcTracks.enable === 'function' ) { if ( allowTracking && typeof window.wcTracks.enable === 'function' ) {
this.setState( { isLoadingScripts: true } ); this.setState( { isLoadingScripts: true } );
window.wcTracks.enable( () => { window.wcTracks.enable( () => {
// Don't update state if component is unmounted already
if ( ! this._isMounted ) {
return;
}
this.setState( { isLoadingScripts: false } ); this.setState( { isLoadingScripts: false } );
} ); } );
} else if ( ! allowTracking ) { } else if ( ! allowTracking ) {
@ -80,11 +77,20 @@ class UsageModal extends Component {
} }
const trackingValue = allowTracking ? 'yes' : 'no'; const trackingValue = allowTracking ? 'yes' : 'no';
this.setState( { isRequestStarted: true } );
updateOptions( { updateOptions( {
woocommerce_allow_tracking: trackingValue, woocommerce_allow_tracking: trackingValue,
} ); } );
} }
componentDidMount() {
this._isMounted = true;
}
componentWillUnmount() {
this._isMounted = false;
}
render() { render() {
// Bail if site has already opted in to tracking // Bail if site has already opted in to tracking
if ( this.props.allowTracking ) { if ( this.props.allowTracking ) {
@ -94,64 +100,63 @@ class UsageModal extends Component {
return null; return null;
} }
const { allowTracking } = this.state; const {
const { isRequesting } = this.props; isRequesting,
const trackingMessage = interpolateComponents( { title = __( 'Build a better WooCommerce', 'woocommerce-admin' ),
mixedString: __( message = interpolateComponents( {
'Get improved features and faster fixes by sharing non-sensitive data via {{link}}usage tracking{{/link}} ' + mixedString: __(
'that shows us how WooCommerce is used. No personal data is tracked or stored.', 'Get improved features and faster fixes by sharing non-sensitive data via {{link}}usage tracking{{/link}} ' +
'woocommerce-admin' 'that shows us how WooCommerce is used. No personal data is tracked or stored.',
), 'woocommerce-admin'
components: {
link: (
<Link
href="https://woocommerce.com/usage-tracking"
target="_blank"
type="external"
/>
), ),
}, components: {
} ); link: (
<Link
href="https://woocommerce.com/usage-tracking"
target="_blank"
type="external"
/>
),
},
} ),
dismissActionText = __( 'No thanks', 'woocommerce-admin' ),
acceptActionText = __( 'Yes, count me in!', 'woocommerce-admin' ),
} = this.props;
const { isRequestStarted } = this.state;
const isBusy = isRequestStarted && isRequesting;
return ( return (
<Modal <Modal
title={ __( title={ title }
'Build a better WooCommerce', isDismissible={ this.props.isDismissible }
'woocommerce-admin'
) }
onRequestClose={ () => this.props.onClose() } onRequestClose={ () => this.props.onClose() }
className="woocommerce-profile-wizard__usage-modal" className="woocommerce-usage-modal"
> >
<div className="woocommerce-profile-wizard__usage-wrapper"> <div className="woocommerce-usage-modal__wrapper">
<div className="woocommerce-profile-wizard__usage-modal-message"> <div className="woocommerce-usage-modal__message">
{ trackingMessage } { message }
</div> </div>
<div className="woocommerce-profile-wizard__tracking"> <div className="woocommerce-usage-modal__actions">
<CheckboxControl <Button
className="woocommerce-profile-wizard__tracking-checkbox" isSecondary
checked={ allowTracking } isBusy={ isBusy }
label={ __( onClick={ () =>
'Yes, count me in!', this.updateTracking( { allowTracking: false } )
'woocommerce-admin' }
) } >
onChange={ this.onTrackingChange } { dismissActionText }
/> </Button>
<Button
<FormToggle isPrimary
aria-hidden="true" isBusy={ isBusy }
checked={ allowTracking } onClick={ () =>
onChange={ this.onTrackingChange } this.updateTracking( { allowTracking: true } )
onClick={ ( e ) => e.stopPropagation() } }
tabIndex="-1" >
/> { acceptActionText }
</Button>
</div> </div>
<Button
isPrimary
isBusy={ isRequesting }
onClick={ () => this.updateTracking() }
>
{ __( 'Continue', 'woocommerce-admin' ) }
</Button>
</div> </div>
</Modal> </Modal>
); );

View File

@ -474,30 +474,6 @@
cursor: help; cursor: help;
} }
.components-modal__frame.woocommerce-profile-wizard__usage-modal {
width: 600px;
max-width: 100%;
.components-modal__header {
border-bottom: 0;
margin-bottom: 0;
}
.woocommerce-profile-wizard__usage-wrapper {
flex-grow: 1;
display: flex;
flex-direction: column;
a {
color: $studio-gray-60;
}
button.is-primary {
align-self: flex-end;
}
}
}
.woocommerce-business-extensions { .woocommerce-business-extensions {
margin-left: -$gap; margin-left: -$gap;
margin-right: -$gap; margin-right: -$gap;

View File

@ -65,7 +65,7 @@ class Payments extends Component {
: 'stripe'; : 'stripe';
} }
markConfigured( method ) { markConfigured( method, queryParams = {} ) {
const { clearTaskStatusCache } = this.props; const { clearTaskStatusCache } = this.props;
const { enabledMethods } = this.state; const { enabledMethods } = this.state;
@ -82,7 +82,9 @@ class Payments extends Component {
payment_method: method, payment_method: method,
} ); } );
getHistory().push( getNewPath( { task: 'payments' }, '/', {} ) ); getHistory().push(
getNewPath( { ...queryParams, task: 'payments' }, '/', {} )
);
} }
getCurrentMethod() { getCurrentMethod() {

View File

@ -28,6 +28,7 @@ import PayPal from './paypal';
import Klarna from './klarna'; import Klarna from './klarna';
import PayFast from './payfast'; import PayFast from './payfast';
import EWay from './eway'; import EWay from './eway';
import WCPayUsageModal from './wcpay-usage-modal';
export function installActivateAndConnectWcpay( export function installActivateAndConnectWcpay(
resolve, resolve,
@ -147,6 +148,7 @@ export function getPaymentMethods( {
{ wcPayIsConnected && wcPaySettingsLink } { wcPayIsConnected && wcPaySettingsLink }
{ ! wcPayIsConnected && <p>{ tosPrompt }</p> } { ! wcPayIsConnected && <p>{ tosPrompt }</p> }
{ profileItems.setup_client && <p>{ wcPayDocPrompt }</p> } { profileItems.setup_client && <p>{ wcPayDocPrompt }</p> }
<WCPayUsageModal />
</Fragment> </Fragment>
), ),
before: <WCPayIcon />, before: <WCPayIcon />,

View File

@ -0,0 +1,62 @@
/**
* External dependencies
*/
import { __ } from '@wordpress/i18n';
import { useState } from '@wordpress/element';
import { getQuery, updateQueryString } from '@woocommerce/navigation';
import interpolateComponents from 'interpolate-components';
import { Link } from '@woocommerce/components';
/**
* Internal dependencies
*/
import UsageModal from '../../../profile-wizard/steps/usage-modal';
const WCPayUsageModal = () => {
const query = getQuery();
const shouldDisplayModal = query[ 'wcpay-connection-success' ] === '1';
const [ isOpen, setIsOpen ] = useState( shouldDisplayModal );
if ( ! isOpen ) {
return null;
}
const closeModal = () => {
setIsOpen( false );
updateQueryString( { 'wcpay-connection-success': undefined } );
};
const title = __(
'Help us build a better WooCommerce Payments experience',
'woocommerce-admin'
);
const trackingMessage = interpolateComponents( {
mixedString: __(
'By agreeing to share non-sensitive {{link}}usage data{{/link}}, youll help us improve features and optimize the WooCommerce Payments experience. You can opt out at any time.',
'woocommerce-admin'
),
components: {
link: (
<Link
href="https://woocommerce.com/usage-tracking"
target="_blank"
type="external"
/>
),
},
} );
return (
<UsageModal
isDismissible={ false }
title={ title }
message={ trackingMessage }
acceptActionText={ __( 'I agree', 'woocommerce-admin' ) }
dismissActionText={ __( 'No thanks', 'woocommerce-admin' ) }
onContinue={ closeModal }
onClose={ closeModal }
/>
);
};
export default WCPayUsageModal;

View File

@ -19,7 +19,7 @@ class WCPay extends Component {
'woocommerce-admin' 'woocommerce-admin'
) )
); );
markConfigured( 'wcpay' ); markConfigured( 'wcpay', { 'wcpay-connection-success': '1' } );
} }
} }

View File

@ -73,13 +73,13 @@ export async function completeStoreDetailsSection( storeDetails = {} ) {
text: 'Build a better WooCommerce', text: 'Build a better WooCommerce',
} ); } );
// Query for "Continue" buttons // Query for primary buttons: "Continue" and "Yes, count me in"
const continueButtons = await page.$$( 'button.is-primary' ); const primaryButtons = await page.$$( 'button.is-primary' );
expect( continueButtons ).toHaveLength( 2 ); expect( primaryButtons ).toHaveLength( 2 );
await Promise.all( [ await Promise.all( [
// Click on "Continue" button of the usage pop-up window to move to the next step // Click on "No thanks" button of the usage pop-up window to move to the next step
continueButtons[ 1 ].click(), await page.click( 'button.is-secondary', { text: 'No thanks' } ),
// Wait for "In which industry does the store operate?" section to load // Wait for "In which industry does the store operate?" section to load
page.waitForNavigation( { waitUntil: 'networkidle0' } ), page.waitForNavigation( { waitUntil: 'networkidle0' } ),