Clean up 'Profile Wizard' code (#51190)

* Remove plugins/woocommerce-admin/client/profile-wizard

* Clean up styles

* Remove setup wizard hook reference

* Add changelog

* Remove e2e utils related to old setup wizard

* Fix lint

* Add changelog

* Remove unused usage modal
This commit is contained in:
Chi-Hsuan Huang 2024-09-10 11:29:14 +08:00 committed by GitHub
parent e85869f3a4
commit 7dc26a8c66
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
46 changed files with 38 additions and 7008 deletions

View File

@ -12,7 +12,8 @@ npm install @woocommerce/e2e-utils --save
## Usage ## Usage
Example: Example:
~~~js
```js
import { import {
shopper, shopper,
merchant, merchant,
@ -29,7 +30,7 @@ describe( 'Cart page', () => {
await expect( page ).toMatchElement( '.cart-empty', { text: 'Your cart is currently empty.' } ); await expect( page ).toMatchElement( '.cart-empty', { text: 'Your cart is currently empty.' } );
} ); } );
} ); } );
~~~ ```
### Retries ### Retries
@ -86,7 +87,6 @@ This package provides support for enabling retries in tests:
|----------|-------------|------------| |----------|-------------|------------|
| `addDownloadableProductPermission` | `productName` | Add a downloadable permission for product in order | | `addDownloadableProductPermission` | `productName` | Add a downloadable permission for product in order |
| `collapseAdminMenu` | `collapse` | Collapse or expand the WP admin menu | | `collapseAdminMenu` | `collapse` | Collapse or expand the WP admin menu |
| `dismissOnboardingWizard` | | Dismiss the onboarding wizard if present |
| `goToOrder` | `orderId` | Go to view a single order | | `goToOrder` | `orderId` | Go to view a single order |
| `goToProduct` | `productId` | Go to view a single product | | `goToProduct` | `productId` | Go to view a single product |
| `login` | | Log in as merchant | | `login` | | Log in as merchant |
@ -100,7 +100,6 @@ This package provides support for enabling retries in tests:
| `openPermalinkSettings` | | Go to Settings -> Permalinks | | `openPermalinkSettings` | | Go to Settings -> Permalinks |
| `openPlugins` | | Go to the Plugins screen | | `openPlugins` | | Go to the Plugins screen |
| `openSettings` | | Go to WooCommerce -> Settings | | `openSettings` | | Go to WooCommerce -> Settings |
| `runSetupWizard` | | Open the onboarding profiler |
| `updateOrderStatus` | `orderId, status` | Update the status of an order | | `updateOrderStatus` | `orderId, status` | Update the status of an order |
| `openEmailLog` | | Open the WP Mail Log page | | `openEmailLog` | | Open the WP Mail Log page |
| `openAnalyticsPage` | | Open any Analytics page | | `openAnalyticsPage` | | Open any Analytics page |
@ -210,7 +209,6 @@ There is a general utilities object `utils` with the following functions:
| `clickFilter` | `selector` | helper method that clicks on a list page filter | | `clickFilter` | `selector` | helper method that clicks on a list page filter |
| `clickTab` | `tabName` | Click on a WooCommerce -> Settings tab | | `clickTab` | `tabName` | Click on a WooCommerce -> Settings tab |
| `clickUpdateOrder` | `noticeText`, `waitForSave` | Helper method to click the Update button on the order details page | | `clickUpdateOrder` | `noticeText`, `waitForSave` | Helper method to click the Update button on the order details page |
| `completeOnboardingWizard` | | completes the onboarding wizard with some default settings |
| `createCoupon` | `couponAmount`, `couponType` | creates a basic coupon. Default amount is 5. Default coupon type is fixed discount. Returns the generated coupon code. | | `createCoupon` | `couponAmount`, `couponType` | creates a basic coupon. Default amount is 5. Default coupon type is fixed discount. Returns the generated coupon code. |
| `createGroupedProduct` | | creates a grouped product for the grouped product tests. Returns the product id. | | `createGroupedProduct` | | creates a grouped product for the grouped product tests. Returns the product id. |
| `createSimpleDownloadableProduct` | `name, downloadLimit, downloadName, price` | Create a simple downloadable product | | `createSimpleDownloadableProduct` | `name, downloadLimit, downloadName, price` | Create a simple downloadable product |

View File

@ -0,0 +1,4 @@
Significance: patch
Type: dev
Remove Profile Wizard releated utils

View File

@ -60,151 +60,6 @@ const waitAndClickPrimary = async ( waitForNetworkIdle = true ) => {
await page.waitForNavigation( { waitUntil: 'networkidle0' } ); await page.waitForNavigation( { waitUntil: 'networkidle0' } );
} }
}; };
/**
* Complete onboarding wizard.
*/
const completeOnboardingWizard = async () => {
// Store Details section
await merchant.runSetupWizard();
// Fill store's address - first line
await expect( page ).toFill(
'#inspector-text-control-0',
config.get( 'addresses.admin.store.addressfirstline' )
);
// Fill store's address - second line
await expect( page ).toFill(
'#inspector-text-control-1',
config.get( 'addresses.admin.store.addresssecondline' )
);
// Fill country and state where the store is located
await expect( page ).toFill(
'.woocommerce-select-control__control-input',
config.get( 'addresses.admin.store.countryandstate' )
);
// Fill the city where the store is located
await expect( page ).toFill(
'#inspector-text-control-2',
config.get( 'addresses.admin.store.city' )
);
// Fill postcode of the store
await expect( page ).toFill(
'#inspector-text-control-3',
config.get( 'addresses.admin.store.postcode' )
);
// Verify that checkbox next to "I'm setting up a store for a client" is not selected
await verifyCheckboxIsUnset( '.components-checkbox-control__input' );
// Wait for "Continue" button to become active
await page.waitForSelector( 'button.is-primary:not(:disabled)' );
// Click on "Continue" button to move to the next step
await page.click( 'button.is-primary', { text: 'Continue' } );
// Wait for usage tracking pop-up window to appear on a new site
const usageTrackingHeader = await page.$(
'.components-modal__header-heading'
);
if ( usageTrackingHeader ) {
await expect( page ).toMatchElement(
'.components-modal__header-heading',
{
text: 'Build a better WooCommerce',
}
);
// Query for "No Thanks" buttons
const continueButtons = await page.$$(
'.woocommerce-usage-modal__actions button.is-secondary'
);
expect( continueButtons ).toHaveLength( 1 );
await continueButtons[ 0 ].click();
await expect( page ).toMatchElement(
'.woocommerce-usage-modal__actions button.is-secondary.is-busy'
);
await expect( page ).not.toMatchElement(
'.woocommerce-usage-modal__actions button.is-primary:disabled'
);
}
await page.waitForNavigation( { waitUntil: 'networkidle0' } );
// Industry section
// Query for the industries checkboxes
const industryCheckboxes = await page.$$(
'.components-checkbox-control__input'
);
expect( industryCheckboxes ).toHaveLength( 8 );
// Select all industries including "Other"
for ( let i = 0; i < 8; i++ ) {
await industryCheckboxes[ i ].click();
}
// Fill "Other" industry
await expect( page ).toFill(
'.components-text-control__input',
config.get( 'onboardingwizard.industry' )
);
// Wait for "Continue" button to become active
await waitAndClickPrimary();
// Product types section
// Query for the product types checkboxes
const productTypesCheckboxes = await page.$$(
'.components-checkbox-control__input'
);
expect( productTypesCheckboxes ).toHaveLength( 7 );
// Select Physical and Downloadable products
for ( let i = 1; i < 2; i++ ) {
await productTypesCheckboxes[ i ].click();
}
// Wait for "Continue" button to become active
await waitAndClickPrimary();
// Business Details section
// Temporarily add delay to reduce test flakiness
await page.waitFor( 2000 );
// Query for the <SelectControl>s
const selectControls = await page.$$( '.woocommerce-select-control' );
expect( selectControls ).toHaveLength( 2 );
// Fill the number of products you plan to sell
await selectControls[ 0 ].click();
await page.waitForSelector( '.woocommerce-select-control__control' );
await expect( page ).toClick( '.woocommerce-select-control__option', {
text: config.get( 'onboardingwizard.numberofproducts' ),
} );
// Fill currently selling elsewhere
await selectControls[ 1 ].click();
await page.waitForSelector( '.woocommerce-select-control__control' );
await expect( page ).toClick( '.woocommerce-select-control__option', {
text: config.get( 'onboardingwizard.sellingelsewhere' ),
} );
// Wait for "Continue" button to become active
await waitAndClickPrimary( false );
// Skip installing extensions
await unsetCheckbox( '.components-checkbox-control__input' );
await verifyCheckboxIsUnset( '.components-checkbox-control__input' );
await waitAndClickPrimary();
// End of onboarding wizard
};
/** /**
* Create simple product. * Create simple product.
@ -668,7 +523,6 @@ const deleteAllShippingZones = async () => {
}; };
export { export {
completeOnboardingWizard,
createSimpleProduct, createSimpleProduct,
createVariableProduct, createVariableProduct,
createGroupedProduct, createGroupedProduct,

View File

@ -31,8 +31,6 @@ const {
WP_ADMIN_NEW_PRODUCT, WP_ADMIN_NEW_PRODUCT,
WP_ADMIN_PERMALINK_SETTINGS, WP_ADMIN_PERMALINK_SETTINGS,
WP_ADMIN_PLUGINS, WP_ADMIN_PLUGINS,
WP_ADMIN_SETUP_WIZARD,
WP_ADMIN_WC_HOME,
WP_ADMIN_WC_SETTINGS, WP_ADMIN_WC_SETTINGS,
WP_ADMIN_WC_EXTENSIONS, WP_ADMIN_WC_EXTENSIONS,
WP_ADMIN_WC_HELPER, WP_ADMIN_WC_HELPER,
@ -42,7 +40,6 @@ const {
WP_ADMIN_IMPORT_PRODUCTS, WP_ADMIN_IMPORT_PRODUCTS,
WP_ADMIN_PLUGIN_INSTALL, WP_ADMIN_PLUGIN_INSTALL,
WP_ADMIN_WP_UPDATES, WP_ADMIN_WP_UPDATES,
IS_RETEST_MODE,
} = require( './constants' ); } = require( './constants' );
const { getSlug, waitForTimeout } = require( './utils' ); const { getSlug, waitForTimeout } = require( './utils' );
@ -83,7 +80,6 @@ const merchant = {
await Promise.all( [ await Promise.all( [
page.click( 'input[type=submit]' ), page.click( 'input[type=submit]' ),
page.waitForNavigation( { waitUntil: 'networkidle0' } ), page.waitForNavigation( { waitUntil: 'networkidle0' } ),
merchant.dismissOnboardingWizard(),
] ); ] );
}, },
@ -173,15 +169,6 @@ const merchant = {
} ); } );
}, },
runSetupWizard: async () => {
const setupWizard = IS_RETEST_MODE
? WP_ADMIN_SETUP_WIZARD
: WP_ADMIN_WC_HOME;
await page.goto( setupWizard, {
waitUntil: 'networkidle0',
} );
},
goToOrder: async ( orderId ) => { goToOrder: async ( orderId ) => {
await page.goto( WP_ADMIN_SINGLE_CPT_VIEW( orderId ), { await page.goto( WP_ADMIN_SINGLE_CPT_VIEW( orderId ), {
waitUntil: 'networkidle0', waitUntil: 'networkidle0',
@ -290,9 +277,10 @@ const merchant = {
revokeDownloadableProductPermission: async ( productName ) => { revokeDownloadableProductPermission: async ( productName ) => {
// Revoke downloadable product permission // Revoke downloadable product permission
const permission = await expect( const permission = await expect( page ).toMatchElement(
page 'div.wc-metabox > h3',
).toMatchElement( 'div.wc-metabox > h3', { text: productName } ); { text: productName }
);
await expect( permission ).toClick( 'button.revoke_access' ); await expect( permission ).toClick( 'button.revoke_access' );
// Wait for auto save // Wait for auto save
@ -625,10 +613,10 @@ const merchant = {
}, },
/* Uploads and activates a plugin located at the provided file path. This will also deactivate and delete the plugin if it exists. /* Uploads and activates a plugin located at the provided file path. This will also deactivate and delete the plugin if it exists.
* *
* @param {string} pluginFilePath The location of the plugin zip file to upload. * @param {string} pluginFilePath The location of the plugin zip file to upload.
* @param {string} pluginName The name of the plugin. For example, `WooCommerce`. * @param {string} pluginName The name of the plugin. For example, `WooCommerce`.
*/ */
uploadAndActivatePlugin: async ( pluginFilePath, pluginName ) => { uploadAndActivatePlugin: async ( pluginFilePath, pluginName ) => {
await merchant.openPlugins(); await merchant.openPlugins();
@ -647,8 +635,7 @@ const merchant = {
await page.click( 'a.upload-view-toggle' ); await page.click( 'a.upload-view-toggle' );
await expect( page ).toMatchElement( 'p.install-help', { await expect( page ).toMatchElement( 'p.install-help', {
text: text: 'If you have a plugin in a .zip format, you may install or update it by uploading it here.',
'If you have a plugin in a .zip format, you may install or update it by uploading it here.',
} ); } );
const uploader = await page.$( 'input[type=file]' ); const uploader = await page.$( 'input[type=file]' );
@ -753,33 +740,6 @@ const merchant = {
} }
}, },
/**
* Dismiss the onboarding wizard if it is open.
*/
dismissOnboardingWizard: async () => {
let waitForNav = false;
const skipButton = await page.$(
'.woocommerce-profile-wizard__footer-link'
);
if ( skipButton ) {
await skipButton.click();
waitForNav = true;
}
// Dismiss usage tracking pop-up window if it appears on a new site
const usageTrackingHeader = await page.$(
'.woocommerce-usage-modal button.is-secondary'
);
if ( usageTrackingHeader ) {
await usageTrackingHeader.click();
waitForNav = true;
}
if ( waitForNav ) {
await page.waitForNavigation( { waitUntil: 'networkidle0' } );
}
},
/** /**
* Expand or collapse the WP admin menu. * Expand or collapse the WP admin menu.
* *

View File

@ -172,11 +172,6 @@ const StoreOwnerFlow = {
StoreOwnerFlowDeprecated(); StoreOwnerFlowDeprecated();
await merchant.openSettings( tab, section ); await merchant.openSettings( tab, section );
}, },
runSetupWizard: async () => {
StoreOwnerFlowDeprecated();
await merchant.runSetupWizard();
},
}; };
export { CustomerFlow, StoreOwnerFlow }; export { CustomerFlow, StoreOwnerFlow };

View File

@ -810,19 +810,6 @@
} }
] ]
}, },
{
"description": "Filter for Onboarding steps configuration.",
"sourceFile": "https://github.com/woocommerce/woocommerce-admin/blob/46c8304c425749dfc715b38e59f56198b05e7b46/client/profile-wizard/index.js#L140-L145",
"name": "woocommerce_admin_profile_wizard_steps",
"type": "filter",
"params": [
{
"name": "steps",
"type": "Array.<Object>",
"description": "Array of steps for Onboarding Wizard."
}
]
},
{ {
"description": "Store Management extensions links", "description": "Store Management extensions links",
"sourceFile": "https://github.com/woocommerce/woocommerce-admin/blob/46c8304c425749dfc715b38e59f56198b05e7b46/client/store-management-links/index.js#L171-L176", "sourceFile": "https://github.com/woocommerce/woocommerce-admin/blob/46c8304c425749dfc715b38e59f56198b05e7b46/client/store-management-links/index.js#L171-L176",

View File

@ -60,16 +60,13 @@ const MarketingOverviewMultichannel = lazy( () =>
const Marketplace = lazy( () => const Marketplace = lazy( () =>
import( /* webpackChunkName: "marketplace" */ '../marketplace' ) import( /* webpackChunkName: "marketplace" */ '../marketplace' )
); );
const ProfileWizard = lazy( () =>
import( /* webpackChunkName: "profile-wizard" */ '../profile-wizard' )
);
const CoreProfiler = lazy( () => const CoreProfiler = lazy( () =>
import( /* webpackChunkName: "core-profiler" */ '../core-profiler' ) import( /* webpackChunkName: "core-profiler" */ '../core-profiler' )
); );
const SettingsGroup = lazy( () => const SettingsGroup = lazy( () =>
import( /* webpackChunkName: "profile-wizard" */ '../settings' ) import( /* webpackChunkName: "settings" */ '../settings' )
); );
const WCPaymentsWelcomePage = lazy( () => const WCPaymentsWelcomePage = lazy( () =>
import( import(
@ -256,34 +253,22 @@ export const getPages = () => {
} ); } );
if ( window.wcAdminFeatures.onboarding ) { if ( window.wcAdminFeatures.onboarding ) {
if ( ! window.wcAdminFeatures[ 'core-profiler' ] ) { pages.push( {
pages.push( { container: CoreProfiler,
container: ProfileWizard, path: '/setup-wizard',
path: '/setup-wizard', breadcrumbs: [
breadcrumbs: [ ...initialBreadcrumbs,
...initialBreadcrumbs, __( 'Profiler', 'woocommerce' ),
__( 'Setup Wizard', 'woocommerce' ), ],
], capability: 'manage_woocommerce',
capability: 'manage_woocommerce', layout: {
} ); header: false,
} else { footer: false,
pages.push( { showNotices: true,
container: CoreProfiler, showStoreAlerts: false,
path: '/setup-wizard', showPluginArea: false,
breadcrumbs: [ },
...initialBreadcrumbs, } );
__( 'Profiler', 'woocommerce' ),
],
capability: 'manage_woocommerce',
layout: {
header: false,
footer: false,
showNotices: true,
showStoreAlerts: false,
showPluginArea: false,
},
} );
}
} }
if ( window.wcAdminFeatures[ 'core-profiler' ] ) { if ( window.wcAdminFeatures[ 'core-profiler' ] ) {

View File

@ -9,10 +9,6 @@
bottom: -1px; /* to hide the border when no visible children */ bottom: -1px; /* to hide the border when no visible children */
z-index: 1001; /* on top of #wp-content-editor-tools */ z-index: 1001; /* on top of #wp-content-editor-tools */
.woocommerce-profile-wizard__body & {
width: 100%;
}
@include breakpoint("782px-960px") { @include breakpoint("782px-960px") {
width: calc(100% - 36px); width: calc(100% - 36px);
} }

View File

@ -58,12 +58,6 @@ const StoreAlerts = lazy( () =>
import( /* webpackChunkName: "store-alerts" */ './store-alerts' ) import( /* webpackChunkName: "store-alerts" */ './store-alerts' )
); );
const WCPayUsageModal = lazy( () =>
import(
/* webpackChunkName: "wcpay-usage-modal" */ '../task-lists/fills/PaymentGatewaySuggestions/components/WCPay/UsageModal'
)
);
export class PrimaryLayout extends Component { export class PrimaryLayout extends Component {
render() { render() {
const { const {
@ -163,15 +157,6 @@ function _Layout( {
}, 0 ); }, 0 );
}, [ location?.pathname ] ); }, [ location?.pathname ] );
function isWCPaySettingsPage() {
const { page: queryPage, section, tab } = getQuery();
return (
queryPage === 'wc-settings' &&
tab === 'checkout' &&
section === 'woocommerce_payments'
);
}
const { breadcrumbs, layout = { header: true, footer: true } } = page; const { breadcrumbs, layout = { header: true, footer: true } } = page;
const { const {
header: showHeader = true, header: showHeader = true,
@ -237,12 +222,6 @@ function _Layout( {
</div> </div>
</PrimaryLayout> </PrimaryLayout>
) } ) }
{ isEmbedded && isWCPaySettingsPage() && (
<Suspense fallback={ null }>
<WCPayUsageModal />
</Suspense>
) }
{ showFooter && <Footer /> } { showFooter && <Footer /> }
<CustomerEffortScoreModalContainer /> <CustomerEffortScoreModalContainer />
</div> </div>

View File

@ -5,16 +5,6 @@
margin-bottom: $gap-small; margin-bottom: $gap-small;
z-index: calc(z-index(".components-snackbar-list") + 1); z-index: calc(z-index(".components-snackbar-list") + 1);
width: auto; width: auto;
.woocommerce-profile-wizard__body & {
left: unset;
width: 100%;
.components-snackbar {
margin-left: auto;
margin-right: auto;
}
}
} }
.components-snackbar { .components-snackbar {

View File

@ -1,125 +0,0 @@
/**
* External dependencies
*/
import { Component } from '@wordpress/element';
import { filter, isEqual } from 'lodash';
import { Stepper } from '@woocommerce/components';
import { updateQueryString } from '@woocommerce/navigation';
/**
* Internal dependencies
*/
import UnsavedChangesModal from './unsaved-changes-modal';
export default class ProfileWizardHeader extends Component {
constructor( props ) {
super( props );
this.state = {
showUnsavedChangesModal: false,
};
this.lastClickedStepKey = null;
this.onStepClick = this.onStepClick.bind( this );
}
shouldWarnForUnsavedChanges( step ) {
if ( typeof this.props.stepValueChanges[ step ] !== 'undefined' ) {
const initialValues =
this.props.stepValueChanges[ step ].initialValues;
const currentValues =
this.props.stepValueChanges[ step ].currentValues;
if (
Array.isArray( initialValues ) &&
Array.isArray( currentValues )
) {
initialValues.sort();
currentValues.sort();
}
return ! isEqual( initialValues, currentValues );
}
return false;
}
findCurrentStep() {
return this.props.steps.find(
( s ) => s.key === this.props.currentStep
);
}
moveToLastClickedStep() {
if ( this.lastClickedStepKey ) {
updateQueryString( { step: this.lastClickedStepKey } );
this.lastClickedStepKey = null;
}
}
saveCurrentStepChanges() {
const currentStep = this.findCurrentStep();
if ( ! currentStep ) {
return null;
}
const stepValueChanges = this.props.stepValueChanges[ currentStep.key ];
if ( typeof stepValueChanges.onSave === 'function' ) {
stepValueChanges.onSave();
}
}
renderStepper() {
const { currentStep, steps } = this.props;
const visibleSteps = filter( steps, ( step ) => !! step.label );
const currentStepIndex = visibleSteps.findIndex(
( step ) => step.key === currentStep
);
visibleSteps.forEach( ( step, index ) => {
const previousStep = visibleSteps[ index - 1 ];
step.isComplete = step.isComplete || index < currentStepIndex;
const canClickStepLabel =
! previousStep || previousStep.isComplete || step.isComplete;
if ( canClickStepLabel ) {
step.onClick = this.onStepClick;
}
} );
return <Stepper steps={ visibleSteps } currentStep={ currentStep } />;
}
onStepClick( key ) {
const { currentStep } = this.props;
if ( this.shouldWarnForUnsavedChanges( currentStep ) ) {
this.setState( { showUnsavedChangesModal: true } );
this.lastClickedStepKey = key;
} else {
updateQueryString( { step: key } );
}
}
render() {
const currentStep = this.findCurrentStep();
if ( ! currentStep || ! currentStep.label ) {
return null;
}
return (
<div className="woocommerce-profile-wizard__header">
{ this.state.showUnsavedChangesModal && (
<UnsavedChangesModal
onClose={ () => {
this.setState( { showUnsavedChangesModal: false } );
this.moveToLastClickedStep();
} }
onSave={ () => {
this.saveCurrentStepChanges();
this.setState( { showUnsavedChangesModal: false } );
this.moveToLastClickedStep();
} }
/>
) }
{ this.renderStepper() }
</div>
);
}
}

View File

@ -1,362 +0,0 @@
/**
* External dependencies
*/
import { __ } from '@wordpress/i18n';
import { applyFilters } from '@wordpress/hooks';
import { Component, createElement } from '@wordpress/element';
import { compose } from '@wordpress/compose';
import { identity, pick } from 'lodash';
import { withDispatch, withSelect } from '@wordpress/data';
import {
getHistory,
getNewPath,
updateQueryString,
} from '@woocommerce/navigation';
import {
NOTES_STORE_NAME,
ONBOARDING_STORE_NAME,
OPTIONS_STORE_NAME,
PLUGINS_STORE_NAME,
withPluginsHydration,
QUERY_DEFAULTS,
} from '@woocommerce/data';
import { recordEvent } from '@woocommerce/tracks';
import { getAdminLink, getSetting } from '@woocommerce/settings';
/**
* Internal dependencies
*/
import { BusinessDetailsStep } from './steps/business-details';
import Industry from './steps/industry';
import ProductTypes from './steps/product-types';
import ProfileWizardHeader from './header';
import StoreDetails from './steps/store-details';
import { getAdminSetting } from '~/utils/admin-settings';
import './style.scss';
const STEPS_FILTER = 'woocommerce_admin_profile_wizard_steps';
class ProfileWizard extends Component {
constructor( props ) {
super( props );
this.cachedActivePlugins = props.activePlugins;
this.goToNextStep = this.goToNextStep.bind( this );
this.trackStepValueChanges = this.trackStepValueChanges.bind( this );
this.updateCurrentStepValues =
this.updateCurrentStepValues.bind( this );
this.stepValueChanges = {};
}
componentDidUpdate( prevProps ) {
const { step: prevStep } = prevProps.query;
const { step } = this.props.query;
const { isError, isGetProfileItemsRequesting, createNotice } =
this.props;
const isRequestError =
! isGetProfileItemsRequesting && prevProps.isRequesting && isError;
if ( isRequestError ) {
createNotice(
'error',
__(
'There was a problem finishing the setup wizard',
'woocommerce'
)
);
}
if ( prevStep !== step ) {
window.document.documentElement.scrollTop = 0;
recordEvent( 'storeprofiler_step_view', {
step: this.getCurrentStep().key,
wc_version: getSetting( 'wcVersion' ),
} );
}
}
componentDidMount() {
document.body.classList.remove( 'woocommerce-admin-is-loading' );
document.body.classList.add( 'woocommerce-profile-wizard__body' );
document.body.classList.add( 'woocommerce-admin-full-screen' );
document.body.classList.add( 'is-wp-toolbar-disabled' );
recordEvent( 'storeprofiler_step_view', {
step: this.getCurrentStep().key,
wc_version: getSetting( 'wcVersion' ),
} );
}
componentWillUnmount() {
document.body.classList.remove( 'woocommerce-profile-wizard__body' );
document.body.classList.remove( 'woocommerce-admin-full-screen' );
document.body.classList.remove( 'is-wp-toolbar-disabled' );
}
/**
* Set the initial and current values of a step to track the state of the step.
* This is used to determine if the step has been changes or not.
*
* @param {string} step key of the step
* @param {*} initialValues the initial values of the step
* @param {*} currentValues the current values of the step
* @param {Function} onSave a function to call when the step is saved
*/
trackStepValueChanges( step, initialValues, currentValues, onSave ) {
this.stepValueChanges[ step ] = {
initialValues,
currentValues,
onSave,
};
}
/**
* Update currentValues of the given step.
*
* @param {string} step key of the step
* @param {*} currentValues the current values of the step
*/
updateCurrentStepValues( step, currentValues ) {
if ( ! this.stepValueChanges[ step ] ) {
return;
}
this.stepValueChanges[ step ].currentValues = currentValues;
}
getSteps() {
const { profileItems } = this.props;
const steps = [];
steps.push( {
key: 'store-details',
container: StoreDetails,
label: __( 'Store Details', 'woocommerce' ),
isComplete:
profileItems.hasOwnProperty( 'setup_client' ) &&
profileItems.setup_client !== null,
} );
steps.push( {
key: 'industry',
container: Industry,
label: __( 'Industry', 'woocommerce' ),
isComplete:
profileItems.hasOwnProperty( 'industry' ) &&
profileItems.industry !== null,
} );
steps.push( {
key: 'product-types',
container: ProductTypes,
label: __( 'Product Types', 'woocommerce' ),
isComplete:
profileItems.hasOwnProperty( 'product_types' ) &&
profileItems.product_types !== null,
} );
steps.push( {
key: 'business-details',
container: BusinessDetailsStep,
label: __( 'Business Details', 'woocommerce' ),
isComplete:
profileItems.hasOwnProperty( 'product_count' ) &&
profileItems.product_count !== null,
} );
/**
* Filter for Onboarding steps configuration.
*
* @filter woocommerce_admin_profile_wizard_steps
* @param {Array.<Object>} steps Array of steps for Onboarding Wizard.
*/
return applyFilters( STEPS_FILTER, steps );
}
getCurrentStep() {
const { step } = this.props.query;
const currentStep = this.getSteps().find( ( s ) => s.key === step );
if ( ! currentStep ) {
return this.getSteps()[ 0 ];
}
return currentStep;
}
/**
* @param {Object} tracksArgs optional track arguments for the storeprofiler_step_complete track.
*/
async goToNextStep( tracksArgs = {} ) {
const { activePlugins } = this.props;
const currentStep = this.getCurrentStep();
const currentStepIndex = this.getSteps().findIndex(
( s ) => s.key === currentStep.key
);
recordEvent( 'storeprofiler_step_complete', {
step: currentStep.key,
wc_version: getSetting( 'wcVersion' ),
...tracksArgs,
} );
// Update the activePlugins cache in case plugins were installed
// in the current step that affect the visibility of the next step.
this.cachedActivePlugins = activePlugins;
const nextStep = this.getSteps()[ currentStepIndex + 1 ];
if ( typeof nextStep === 'undefined' ) {
this.completeProfiler();
return;
}
return updateQueryString( { step: nextStep.key } );
}
completeProfiler() {
const {
activePlugins,
isJetpackConnected,
notes,
updateNote,
updateProfileItems,
connectToJetpack,
} = this.props;
recordEvent( 'storeprofiler_complete' );
const shouldConnectJetpack =
activePlugins.includes( 'jetpack' ) && ! isJetpackConnected;
const profilerNote = notes.find(
( note ) => note.name === 'wc-admin-onboarding-profiler-reminder'
);
if ( profilerNote ) {
updateNote( profilerNote.id, { status: 'actioned' } );
}
updateProfileItems( { completed: true } ).then( () => {
const homescreenUrl = new URL(
getNewPath( {}, '/', {} ),
window.location.href
).href;
if ( shouldConnectJetpack ) {
document.body.classList.add( 'woocommerce-admin-is-loading' );
connectToJetpack( () => {
return homescreenUrl;
} );
} else {
window.location.href = homescreenUrl;
}
} );
}
skipProfiler() {
const { createNotice, updateProfileItems } = this.props;
updateProfileItems( { skipped: true } )
.then( () => {
recordEvent( 'storeprofiler_store_details_skip' );
getHistory().push( getNewPath( {}, '/', {} ) );
} )
.catch( () => {
createNotice(
'error',
__(
'There was a problem skipping the setup wizard',
'woocommerce'
)
);
} );
}
render() {
const { query } = this.props;
const step = this.getCurrentStep();
const stepKey = step.key;
const container = createElement( step.container, {
query,
step,
goToNextStep: this.goToNextStep,
skipProfiler: () => {
this.skipProfiler();
},
trackStepValueChanges: this.trackStepValueChanges,
updateCurrentStepValues: this.updateCurrentStepValues,
} );
const steps = this.getSteps().map( ( _step ) =>
pick( _step, [ 'key', 'label', 'isComplete' ] )
);
const classNames = `woocommerce-profile-wizard__container ${ stepKey }`;
return (
<>
<ProfileWizardHeader
currentStep={ stepKey }
steps={ steps }
stepValueChanges={ this.stepValueChanges }
/>
<div className={ classNames }>{ container }</div>
</>
);
}
}
export default compose(
withSelect( ( select ) => {
const { getNotes } = select( NOTES_STORE_NAME );
const { getProfileItems, getOnboardingError } = select(
ONBOARDING_STORE_NAME
);
const { getActivePlugins, getPluginsError, isJetpackConnected } =
select( PLUGINS_STORE_NAME );
const profileItems = getProfileItems();
const notesQuery = {
page: 1,
per_page: QUERY_DEFAULTS.pageSize,
type: 'update',
status: 'unactioned',
};
const notes = getNotes( notesQuery );
const activePlugins = getActivePlugins();
return {
getPluginsError,
isError: Boolean( getOnboardingError( 'updateProfileItems' ) ),
isJetpackConnected: isJetpackConnected(),
notes,
profileItems,
activePlugins,
};
} ),
withDispatch( ( dispatch ) => {
const { connectToJetpackWithFailureRedirect, createErrorNotice } =
dispatch( PLUGINS_STORE_NAME );
const { updateNote } = dispatch( NOTES_STORE_NAME );
const { updateOptions } = dispatch( OPTIONS_STORE_NAME );
const { updateProfileItems } = dispatch( ONBOARDING_STORE_NAME );
const { createNotice } = dispatch( 'core/notices' );
const connectToJetpack = ( failureRedirect ) => {
connectToJetpackWithFailureRedirect(
failureRedirect,
createErrorNotice,
getAdminLink
);
};
return {
connectToJetpack,
createNotice,
updateNote,
updateOptions,
updateProfileItems,
};
} ),
getAdminSetting( 'plugins' )
? withPluginsHydration( {
...getAdminSetting( 'plugins' ),
jetpackStatus: getAdminSetting( 'dataEndpoints', {} )
.jetpackStatus,
} )
: identity
)( ProfileWizard );

View File

@ -1,31 +0,0 @@
/**
* External dependencies
*/
import { __ } from '@wordpress/i18n';
export const employeeOptions = [
{
key: '1',
label: __( "It's just me", 'woocommerce' ),
},
{
key: '<10',
label: __( '< 10', 'woocommerce' ),
},
{
key: '10-50',
label: '10 - 50',
},
{
key: '50-250',
label: '50 - 250',
},
{
key: '+250',
label: __( '+250', 'woocommerce' ),
},
{
key: 'not specified',
label: __( "I'd rather not say", 'woocommerce' ),
},
];

View File

@ -1,51 +0,0 @@
/**
* External dependencies
*/
import { __ } from '@wordpress/i18n';
export const platformOptions = [
{
key: 'shopify',
label: __( 'Shopify', 'woocommerce' ),
},
{
key: 'bigcommerce',
label: __( 'BigCommerce', 'woocommerce' ),
},
{
key: 'magento',
label: __( 'Magento', 'woocommerce' ),
},
{
key: 'wix',
label: __( 'Wix', 'woocommerce' ),
},
{
key: 'amazon',
label: __( 'Amazon', 'woocommerce' ),
},
{
key: 'ebay',
label: __( 'eBay', 'woocommerce' ),
},
{
key: 'etsy',
label: __( 'Etsy', 'woocommerce' ),
},
{
key: 'squarespace',
label: __( 'Squarespace', 'woocommerce' ),
},
{
key: 'wish',
label: __( 'Wish', 'woocommerce' ),
},
{
key: 'walmart',
label: __( 'Walmart', 'woocommerce' ),
},
{
key: 'other',
label: __( 'Other', 'woocommerce' ),
},
];

View File

@ -1,58 +0,0 @@
/**
* External dependencies
*/
import { formatValue } from '@woocommerce/number';
import { __, sprintf, _x } from '@wordpress/i18n';
const formatNumber = ( numberConfig, value ) => {
return formatValue( numberConfig, 'number', value );
};
export const getNumberRangeString = (
numberConfig,
min,
max = false,
formatAmount = formatNumber
) => {
if ( ! max ) {
return sprintf(
/* translators: a formatted number string, for the minimum value */
_x( '%s+', 'store product count or revenue', 'woocommerce' ),
formatAmount( numberConfig, min )
);
}
return sprintf(
/* translators: a formatted number string, for the minimum and maximum values */
_x(
'%1$s - %2$s',
'store product count or revenue range',
'woocommerce'
),
formatAmount( numberConfig, min ),
formatAmount( numberConfig, max )
);
};
export const getProductCountOptions = ( numberConfig ) => [
{
key: '0',
label: __( "I don't have any products yet.", 'woocommerce' ),
},
{
key: '1-10',
label: getNumberRangeString( numberConfig, 1, 10 ),
},
{
key: '11-100',
label: getNumberRangeString( numberConfig, 11, 100 ),
},
{
key: '101-1000',
label: getNumberRangeString( numberConfig, 101, 1000 ),
},
{
key: '1000+',
label: getNumberRangeString( numberConfig, 1000 ),
},
];

View File

@ -1,100 +0,0 @@
/**
* External dependencies
*/
import { __, sprintf } from '@wordpress/i18n';
/**
* Internal dependencies
*/
import { getCurrencyRegion } from '~/dashboard/utils';
import { getNumberRangeString } from './product-options';
// These are rough exchange rates from USD. Precision is not paramount.
// The keys here should match the keys in `getCurrencyData`.
const exchangeRates = {
US: 1,
EU: 0.9,
IN: 71.24,
GB: 0.76,
BR: 4.19,
VN: 23172.5,
ID: 14031.0,
BD: 84.87,
PK: 154.8,
RU: 63.74,
TR: 5.75,
MX: 19.37,
CA: 1.32,
};
const convertCurrency = ( value, country ) => {
const region = getCurrencyRegion( country );
if ( region === 'US' ) {
return value;
}
const exchangeRate = exchangeRates[ region ] || exchangeRates.US;
const digits = exchangeRate.toString().split( '.' )[ 0 ].length;
const multiplier = Math.pow( 10, 2 + digits );
return Math.round( ( value * exchangeRate ) / multiplier ) * multiplier;
};
export const getRevenueOptions = ( numberConfig, country, formatAmount ) => [
{
key: 'none',
label: sprintf(
/* translators: %s: $0 revenue amount */
__( "%s (I'm just getting started)", 'woocommerce' ),
formatAmount( 0 )
),
},
{
key: 'up-to-2500',
label: sprintf(
/* translators: %s: A given revenue amount, e.g., $2500 */
__( 'Up to %s', 'woocommerce' ),
formatAmount( convertCurrency( 2500, country ) )
),
},
{
key: '2500-10000',
label: getNumberRangeString(
numberConfig,
convertCurrency( 2500, country ),
convertCurrency( 10000, country ),
( _, amount ) => formatAmount( amount )
),
},
{
key: '10000-50000',
label: getNumberRangeString(
numberConfig,
convertCurrency( 10000, country ),
convertCurrency( 50000, country ),
( _, amount ) => formatAmount( amount )
),
},
{
key: '50000-250000',
label: getNumberRangeString(
numberConfig,
convertCurrency( 50000, country ),
convertCurrency( 250000, country ),
( _, amount ) => formatAmount( amount )
),
},
{
key: 'more-than-250000',
label: sprintf(
/* translators: %s: A given revenue amount, e.g., $250000 */
__( 'More than %s', 'woocommerce' ),
formatAmount( convertCurrency( 250000, country ) )
),
},
{
key: 'rather-not-say',
label: __( "I'd rather not say", 'woocommerce' ),
},
];

View File

@ -1,36 +0,0 @@
/**
* External dependencies
*/
import { __ } from '@wordpress/i18n';
export const sellingVenueOptions = [
{
key: 'no',
label: __( 'No', 'woocommerce' ),
},
{
key: 'other',
label: __( 'Yes, on another platform', 'woocommerce' ),
},
{
key: 'other-woocommerce',
label: __(
'Yes, I own a different store powered by WooCommerce',
'woocommerce'
),
},
{
key: 'brick-mortar',
label: __(
'Yes, in person at physical stores and/or events',
'woocommerce'
),
},
{
key: 'brick-mortar-other',
label: __(
'Yes, on another platform and in person at physical stores and/or events',
'woocommerce'
),
},
];

View File

@ -1,140 +0,0 @@
export const AppIllustration = () => {
return (
<svg
width="200"
height="148"
viewBox="0 0 200 148"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<g clipPath="url(#clip0)">
<path
d="M197.563 2.53875e-09H62.909C62.3961 0.000450584 61.9043 0.205742 61.5416 0.570805C61.179 0.935868 60.975 1.43087 60.9746 1.94714V50.9404H93.5623C94.4445 50.9415 95.2902 51.2947 95.9141 51.9226C96.5379 52.5505 96.8888 53.4019 96.8899 54.2899V95.7402H197.563C197.843 95.7402 198.119 95.6791 198.373 95.5612C198.627 95.4432 198.853 95.2712 199.034 95.0569C199.05 95.0402 199.064 95.0222 199.076 95.0033C199.192 94.8612 199.285 94.7024 199.354 94.5322C199.451 94.2981 199.501 94.0468 199.5 93.7931V1.94714C199.499 1.43051 199.295 0.935241 198.932 0.57014C198.569 0.20504 198.077 -2.63458e-05 197.563 2.53875e-09Z"
fill="#F2F2F2"
/>
<path
d="M199.222 7.80469H61.25V8.36132H199.222V7.80469Z"
fill="#CCCCCC"
/>
<path
d="M65.95 5.84371C66.8662 5.84371 67.609 5.09607 67.609 4.17381C67.609 3.25155 66.8662 2.50391 65.95 2.50391C65.0338 2.50391 64.291 3.25155 64.291 4.17381C64.291 5.09607 65.0338 5.84371 65.95 5.84371Z"
fill="#CCCCCC"
/>
<path
d="M70.72 5.84371C71.6363 5.84371 72.379 5.09607 72.379 4.17381C72.379 3.25155 71.6363 2.50391 70.72 2.50391C69.8038 2.50391 69.061 3.25155 69.061 4.17381C69.061 5.09607 69.8038 5.84371 70.72 5.84371Z"
fill="#CCCCCC"
/>
<path
d="M75.4896 5.84371C76.4058 5.84371 77.1486 5.09607 77.1486 4.17381C77.1486 3.25155 76.4058 2.50391 75.4896 2.50391C74.5733 2.50391 73.8306 3.25155 73.8306 4.17381C73.8306 5.09607 74.5733 5.84371 75.4896 5.84371Z"
fill="#CCCCCC"
/>
<path
d="M164.842 19.957H95.6295C94.8646 19.957 94.1311 20.2629 93.5903 20.8073C93.0494 21.3516 92.7456 22.09 92.7456 22.8599C92.7456 23.6298 93.0494 24.3681 93.5903 24.9125C94.1311 25.4569 94.8646 25.7627 95.6295 25.7627H164.842C165.607 25.7627 166.341 25.4569 166.882 24.9125C167.422 24.3681 167.726 23.6298 167.726 22.8599C167.726 22.09 167.422 21.3516 166.882 20.8073C166.341 20.2629 165.607 19.957 164.842 19.957ZM164.842 25.3161H95.6295C94.9823 25.3161 94.3616 25.0573 93.904 24.5967C93.4464 24.1361 93.1893 23.5113 93.1893 22.8599C93.1893 22.2084 93.4464 21.5837 93.904 21.123C94.3616 20.6624 94.9823 20.4036 95.6295 20.4036H164.842C165.489 20.4036 166.11 20.6624 166.568 21.123C167.025 21.5837 167.283 22.2084 167.283 22.8599C167.283 23.5113 167.025 24.1361 166.568 24.5967C166.11 25.0573 165.489 25.3161 164.842 25.3161Z"
fill="#CCCCCC"
/>
<path
d="M186.022 43.0859H116.809C116.044 43.0859 115.31 43.3918 114.769 43.9362C114.229 44.4806 113.925 45.2189 113.925 45.9888C113.925 46.7587 114.229 47.497 114.769 48.0414C115.31 48.5858 116.044 48.8916 116.809 48.8916H186.022C186.786 48.8916 187.52 48.5858 188.061 48.0414C188.602 47.497 188.905 46.7587 188.905 45.9888C188.905 45.2189 188.602 44.4806 188.061 43.9362C187.52 43.3918 186.786 43.0859 186.022 43.0859Z"
fill="white"
/>
<path
d="M186.022 53.8047H116.809C116.044 53.8047 115.31 54.1105 114.769 54.6549C114.229 55.1993 113.925 55.9376 113.925 56.7075C113.925 57.4774 114.229 58.2158 114.769 58.7601C115.31 59.3045 116.044 59.6104 116.809 59.6104H186.022C186.786 59.6104 187.52 59.3045 188.061 58.7601C188.602 58.2158 188.905 57.4774 188.905 56.7075C188.905 55.9376 188.602 55.1993 188.061 54.6549C187.52 54.1105 186.786 53.8047 186.022 53.8047Z"
fill="white"
/>
<path
d="M186.022 64.5195H116.809C116.044 64.5195 115.31 64.8254 114.769 65.3698C114.229 65.9141 113.925 66.6525 113.925 67.4224C113.925 68.1923 114.229 68.9306 114.769 69.475C115.31 70.0194 116.044 70.3252 116.809 70.3252H186.022C186.786 70.3252 187.52 70.0194 188.061 69.475C188.602 68.9306 188.905 68.1923 188.905 67.4224C188.905 66.6525 188.602 65.9141 188.061 65.3698C187.52 64.8254 186.786 64.5195 186.022 64.5195Z"
fill="white"
/>
<path
d="M105.623 38.2852H74.1183C73.4425 38.286 72.7947 38.5565 72.3168 39.0375C71.839 39.5185 71.5702 40.1706 71.5693 40.8508V50.9416H72.013V40.8508C72.0139 40.2891 72.2359 39.7506 72.6306 39.3533C73.0252 38.9561 73.5602 38.7326 74.1183 38.7317H105.623C106.182 38.7322 106.717 38.9556 107.112 39.3529C107.506 39.7502 107.728 40.289 107.729 40.8508V72.5633C107.728 73.1251 107.506 73.6638 107.112 74.0611C106.717 74.4585 106.182 74.6819 105.623 74.6824H96.8897V75.1289H105.623C106.299 75.1285 106.947 74.858 107.425 74.377C107.903 73.8959 108.172 73.2436 108.172 72.5633V40.8508C108.172 40.1705 107.903 39.5182 107.425 39.0371C106.947 38.556 106.299 38.2856 105.623 38.2852Z"
fill="#CCCCCC"
/>
<path
d="M23.9309 70.9116C23.8195 70.9162 19.0705 70.5847 18.9492 70.5806L19.3758 66.294L22.0808 66.212L27.2495 56.5756C26.5327 55.1996 27.4148 53.3739 28.9355 53.0925C32 52.3914 33.0526 57.2443 29.9789 57.8901L25.7036 69.6652C25.5695 70.03 25.3278 70.3449 25.011 70.5676C24.6942 70.7904 24.3174 70.9104 23.9309 70.9116Z"
fill="#FFB8B8"
/>
<path
d="M11.4107 73.118C6.89154 73.1291 6.49482 66.2544 11.024 65.7699C23.0006 65.0415 21.485 62.0137 22.3945 70.9448C22.4224 71.2097 22.3448 71.475 22.1787 71.6824C22.0126 71.8898 21.7715 72.0223 21.5084 72.051L11.803 73.0968C11.6727 73.1109 11.5417 73.1179 11.4107 73.118Z"
className="fill-theme-color"
/>
<path
d="M10.3793 51.3852C16.605 54.9512 11.494 64.3601 5.15222 61.0097C-1.0733 57.4438 4.03771 48.0349 10.3793 51.3852Z"
fill="#FFB8B8"
/>
<path
d="M16.0395 132.376L18.759 132.376L20.053 121.816L16.0391 121.817L16.0395 132.376Z"
fill="#FFB8B8"
/>
<path
d="M15.4567 134.915L24.0042 134.915C23.9716 130.476 18.2546 131.755 15.4565 131.591L15.4567 134.915Z"
fill="#2F2E41"
/>
<path
d="M4.28218 132.376L7.00167 132.376L8.29564 121.816L4.28174 121.817L4.28218 132.376Z"
fill="#FFB8B8"
/>
<path
d="M3.69937 134.915L12.2469 134.915C12.2142 130.476 6.49728 131.755 3.69922 131.591L3.69937 134.915Z"
fill="#2F2E41"
/>
<path
d="M7.37266 128.688C6.71536 128.507 3.14362 129.056 2.72209 128.335C1.24999 113.483 1.57722 98.9486 4.845 90.9619L16.0806 90.2695C18.5931 94.8863 24.3684 125.522 20.8847 127.385L16.4048 127.546C16.1493 127.554 15.8988 127.474 15.6952 127.318C15.4916 127.163 15.3475 126.941 15.2869 126.691L11.539 105.229C10.6057 103.916 8.77111 127.832 8.46815 127.742C8.42864 128.006 8.29626 128.247 8.09515 128.42C7.89404 128.594 7.63762 128.689 7.37266 128.688Z"
fill="#2F2E41"
/>
<path
d="M4.48843 92.8373C-1.18427 86.8634 2.43414 70.8475 2.25101 71.1881C2.26655 70.7958 3.55141 64.7536 6.87506 63.762C9.51196 62.886 12.4305 65.5063 12.7906 68.1566L16.7406 91.6368C16.7611 91.7646 16.7514 91.8955 16.7123 92.0188C16.6733 92.1422 16.6059 92.2546 16.5158 92.3469C16.71 92.9556 4.76613 92.7153 4.48843 92.8373Z"
className="fill-theme-color"
/>
<path
d="M6.32941 55.5845C6.82759 55.2675 7.07312 54.7238 7.36309 54.2285C8.89053 53.3522 10.6464 55.472 12.262 54.6809C16.8302 50.2665 12.1346 48.9642 7.92721 48.7314C6.9333 48.6081 6.08781 48.963 5.33637 49.5492C-3.06805 48.9031 0.962591 60.1519 6.26679 61.6376C7.2033 62.0505 7.90582 61.2148 7.07994 60.4551C5.93728 59.2493 4.52181 56.8221 6.32941 55.5845Z"
fill="#2F2E41"
/>
<path
d="M93.5621 50.4922H32.779C31.7793 50.4936 30.8209 50.894 30.1139 51.6056C29.407 52.3172 29.0092 53.2819 29.0078 54.2882V131.548C29.0092 132.555 29.407 133.52 30.1139 134.231C30.8209 134.943 31.7793 135.343 32.779 135.345H93.5621C94.5619 135.343 95.5202 134.943 96.2272 134.231C96.9341 133.52 97.3319 132.555 97.3333 131.548V54.2882C97.3319 53.2819 96.9341 52.3172 96.2272 51.6056C95.5202 50.894 94.5619 50.4936 93.5621 50.4922ZM96.8896 131.548C96.8886 132.436 96.5376 133.288 95.9138 133.916C95.29 134.544 94.4443 134.897 93.5621 134.898H32.779C31.8968 134.897 31.0511 134.544 30.4273 133.916C29.8035 133.288 29.4526 132.436 29.4515 131.548V54.2882C29.4526 53.4002 29.8035 52.5489 30.4273 51.921C31.0511 51.2931 31.8968 50.9399 32.779 50.9388H93.5621C94.4443 50.9399 95.29 51.2931 95.9138 51.921C96.5376 52.5489 96.8886 53.4002 96.8896 54.2882V131.548Z"
fill="#3F3D56"
/>
<path
d="M15.6527 83.0043C13.6494 83.2327 12.7698 78.5402 12.0039 77.3445L15.9936 75.7852L17.2918 78.1751L28.168 78.4472C28.2326 78.3591 28.3032 78.2755 28.3793 78.1971C30.5362 75.9238 34.0488 79.33 31.9 81.5984C31.663 81.8519 31.3752 82.0521 31.0557 82.1855C30.7362 82.319 30.3923 82.3828 30.0465 82.3727C29.7007 82.3627 29.361 82.2789 29.0497 82.1271C28.7384 81.9753 28.4626 81.7588 28.2405 81.4918C27.9719 81.5111 15.9153 83.0114 15.6527 83.0043Z"
fill="#FFB8B8"
/>
<path
d="M12.6985 80.7664C12.0314 81.4133 6.91061 72.3318 6.5365 72.0751C3.94893 68.0267 10.0405 64.0298 12.6727 68.0562L17.9961 76.2911C18.1402 76.5145 18.1904 76.7864 18.1356 77.047C18.0808 77.3076 17.9255 77.5357 17.7039 77.6812C17.4613 77.7956 12.8697 81.0123 12.6985 80.7664Z"
className="fill-theme-color"
/>
<path
d="M81.583 103.974H44.7583C40.9966 103.955 40.9809 98.1877 44.7584 98.168H81.583C85.3421 98.1857 85.3624 103.954 81.583 103.974Z"
fill="#CCCCCC"
/>
<path
d="M81.583 114.692H44.7583C40.9966 114.674 40.9809 108.906 44.7584 108.887H81.583C85.3421 108.904 85.3624 114.673 81.583 114.692Z"
fill="#CCCCCC"
/>
<path
d="M81.583 125.411H44.7583C40.9966 125.393 40.9809 119.625 44.7584 119.605H81.583C85.3421 119.623 85.3624 125.391 81.583 125.411Z"
fill="#CCCCCC"
/>
<path
d="M95.3371 57.6387C94.1963 57.6387 93.0812 57.2982 92.1327 56.6603C91.1842 56.0223 90.4449 55.1156 90.0084 54.0548C89.5718 52.9939 89.4576 51.8266 89.6802 50.7004C89.9027 49.5742 90.452 48.5397 91.2587 47.7278C92.0653 46.9159 93.093 46.3629 94.2118 46.1389C95.3307 45.9149 96.4904 46.0299 97.5443 46.4693C98.5982 46.9087 99.499 47.6528 100.133 48.6076C100.767 49.5623 101.105 50.6848 101.105 51.833C101.103 53.3723 100.495 54.8479 99.4136 55.9363C98.3323 57.0247 96.8662 57.637 95.3371 57.6387Z"
className="fill-theme-color"
/>
<path
d="M97.999 51.6121H95.5588V48.821C95.5588 48.7617 95.5355 48.7049 95.4939 48.6631C95.4523 48.6212 95.3958 48.5977 95.337 48.5977C95.2782 48.5977 95.2217 48.6212 95.1801 48.6631C95.1385 48.7049 95.1152 48.7617 95.1152 48.821V51.6121H92.675C92.6161 51.6121 92.5597 51.6357 92.5181 51.6775C92.4765 51.7194 92.4531 51.7762 92.4531 51.8354C92.4531 51.8947 92.4765 51.9515 92.5181 51.9933C92.5597 52.0352 92.6161 52.0587 92.675 52.0587H95.1152V54.8499C95.1152 54.9091 95.1385 54.9659 95.1801 55.0078C95.2217 55.0497 95.2782 55.0732 95.337 55.0732C95.3958 55.0732 95.4523 55.0497 95.4939 55.0078C95.5355 54.9659 95.5588 54.9091 95.5588 54.8499V52.0587H97.999C98.0579 52.0587 98.1143 52.0352 98.1559 51.9933C98.1975 51.9515 98.2209 51.8947 98.2209 51.8354C98.2209 51.7762 98.1975 51.7194 98.1559 51.6775C98.1143 51.6357 98.0579 51.6121 97.999 51.6121Z"
fill="white"
/>
<path
d="M80.9177 91.2002H45.424C44.4535 91.1991 43.5232 90.8105 42.837 90.1198C42.1508 89.4291 41.7648 88.4926 41.7637 87.5158V67.5086C41.7648 66.5318 42.1508 65.5953 42.837 64.9046C43.5232 64.2139 44.4535 63.8253 45.424 63.8242H80.9177C81.8882 63.8253 82.8185 64.2139 83.5047 64.9046C84.1909 65.5953 84.5769 66.5318 84.578 67.5086V87.5158C84.5769 88.4926 84.1909 89.4291 83.5047 90.1198C82.8185 90.8105 81.8882 91.1991 80.9177 91.2002Z"
className="fill-theme-color"
/>
</g>
<defs>
<clipPath id="clip0">
<rect
width="199"
height="148"
fill="white"
transform="translate(0.5)"
/>
</clipPath>
</defs>
</svg>
);
};

View File

@ -1,901 +0,0 @@
/**
* External dependencies
*/
import { __ } from '@wordpress/i18n';
import { Component } from '@wordpress/element';
import { compose } from '@wordpress/compose';
import {
Button,
Card,
CardBody,
CardFooter,
TabPanel,
__experimentalText as Text,
FlexItem,
CheckboxControl,
Spinner,
Notice,
} from '@wordpress/components';
import { withDispatch, withSelect } from '@wordpress/data';
import { Form, TextControl, SelectControl } from '@woocommerce/components';
import {
ONBOARDING_STORE_NAME,
PLUGINS_STORE_NAME,
SETTINGS_STORE_NAME,
} from '@woocommerce/data';
import { getSetting } from '@woocommerce/settings';
import { recordEvent } from '@woocommerce/tracks';
import clsx from 'clsx';
import { CurrencyContext } from '@woocommerce/currency';
/**
* Internal dependencies
*/
import { createNoticesFromResponse } from '~/lib/notices';
import { platformOptions } from '../../data/platform-options';
import { employeeOptions } from '../../data/employee-options';
import { sellingVenueOptions } from '../../data/selling-venue-options';
import { getRevenueOptions } from '../../data/revenue-options';
import { getProductCountOptions } from '../../data/product-options';
import {
SelectiveExtensionsBundle,
getInstallableExtensions,
} from './selective-extensions-bundle';
import { getPluginSlug, getPluginTrackKey, getTimeFrame } from '~/utils';
import './style.scss';
const BUSINESS_DETAILS_TAB_NAME = 'business-details';
const BUSINESS_FEATURES_TAB_NAME = 'business-features';
export const filterBusinessExtensions = (
extensionInstallationOptions,
alreadyActivatedExtensions = []
) => {
return (
Object.keys( extensionInstallationOptions )
.filter(
( key ) =>
extensionInstallationOptions[ key ] &&
key !== 'install_extensions' &&
! alreadyActivatedExtensions.includes( key )
)
.map( getPluginSlug )
// remove duplicate
.filter( ( item, index, arr ) => arr.indexOf( item ) === index )
);
};
export const prepareExtensionTrackingData = (
extensionInstallationOptions
) => {
const installedExtensions = {};
for ( let [ fieldKey, value ] of Object.entries(
extensionInstallationOptions
) ) {
fieldKey = getPluginSlug( fieldKey );
const key = getPluginTrackKey( fieldKey );
if (
fieldKey !== 'install_extensions' &&
! installedExtensions[ `install_${ key }` ]
) {
installedExtensions[ `install_${ key }` ] = value;
}
}
return installedExtensions;
};
export const prepareExtensionTrackingInstallationData = (
extensionInstallationOptions,
installationData
) => {
const installed = [];
const data = {};
for ( let [ fieldKey ] of Object.entries( extensionInstallationOptions ) ) {
fieldKey = getPluginSlug( fieldKey );
const key = getPluginTrackKey( fieldKey );
if (
installationData &&
installationData.data &&
installationData.data.install_time &&
installationData.data.install_time[ fieldKey ]
) {
installed.push( key );
data[ `install_time_${ key }` ] = getTimeFrame(
installationData.data.install_time[ fieldKey ]
);
}
}
data.installed_extensions = installed;
data.activated_extensions =
installationData &&
installationData.data &&
installationData.data.activated
? installationData.data.activated
: [];
return data;
};
export const isShowWccomMigrationNotice = ( selectedOption ) =>
[ 'other-woocommerce', 'other', 'brick-mortar-other' ].includes(
selectedOption
);
export const isSellingElsewhere = ( selectedOption ) =>
[
'other',
'brick-mortar',
'brick-mortar-other',
'other-woocommerce',
].includes( selectedOption );
const getWccomMigrationUrl = ( selectedOption ) => {
return `https://woocommerce.com/migrate/?utm_source=nux&utm_medium=product&utm_campaign=migrate&utm_content=${ selectedOption }`;
};
export const isSellingOtherPlatformInPerson = ( selectedOption ) =>
[ 'other', 'brick-mortar-other' ].includes( selectedOption );
export const PERSIST_FREE_FEATURES_DATA_STORAGE_KEY =
'wc-admin-free-features-tab-values';
class BusinessDetails extends Component {
constructor( props ) {
super();
this.state = {
isPopoverVisible: false,
isValid: false,
currentTab: 'business-details',
savedValues: props.initialValues,
};
this.onContinue = this.onContinue.bind( this );
this.validate = this.validate.bind( this );
this.persistValues = this.persistValues.bind( this );
this.persistProfileItems.bind( this );
props.trackStepValueChanges(
props.step.key,
props.initialValues,
this.state.savedValues,
this.persistValues
);
// Refetch the free extensions data
props.invalidateResolutionForStoreSelector( 'getFreeExtensions' );
}
async onContinue(
extensionInstallationOptions,
installableExtensionsData
) {
const { createNotice, goToNextStep, installAndActivatePlugins } =
this.props;
const alreadyActivatedExtensions = installableExtensionsData.reduce(
( actExtensions, bundle ) => {
const activated = bundle.plugins
.filter( ( plugin ) => plugin.is_activated )
.map( ( plugin ) => plugin.key );
return [ ...actExtensions, ...activated ];
},
[]
);
const businessExtensions = filterBusinessExtensions(
extensionInstallationOptions,
alreadyActivatedExtensions
);
const installedExtensions = prepareExtensionTrackingData(
extensionInstallationOptions
);
recordEvent( 'storeprofiler_store_business_features_continue', {
all_extensions_installed: Object.values(
extensionInstallationOptions
).every( ( val ) => val ),
...installedExtensions,
} );
const promises = [
this.persistProfileItems( {
business_extensions: [
...businessExtensions,
...alreadyActivatedExtensions,
],
} ),
];
if ( businessExtensions.length ) {
const installationStartTime = window.performance.now();
promises.push(
installAndActivatePlugins( businessExtensions )
.then( ( response ) => {
const totalInstallationTime =
window.performance.now() - installationStartTime;
const installedExtensionsData =
prepareExtensionTrackingInstallationData(
extensionInstallationOptions,
response
);
recordEvent(
'storeprofiler_store_business_features_installed_and_activated',
{
success: true,
total_time: getTimeFrame(
totalInstallationTime
),
...installedExtensionsData,
}
);
createNoticesFromResponse( response );
} )
.catch( ( error ) => {
recordEvent(
'storeprofiler_store_business_features_installed_and_activated',
{
success: false,
failed_extensions: Object.keys(
error.data || {}
).map( ( key ) => getPluginTrackKey( key ) ),
}
);
createNoticesFromResponse( error );
throw new Error();
} )
);
}
Promise.all( promises )
.then( () => {
goToNextStep( { step: BUSINESS_FEATURES_TAB_NAME } );
} )
.catch( () => {
createNotice(
'error',
__(
'There was a problem updating your business details',
'woocommerce'
)
);
} );
}
async persistValues() {
await this.persistProfileItems();
try {
window.localStorage.setItem(
PERSIST_FREE_FEATURES_DATA_STORAGE_KEY,
JSON.stringify( this.state.savedValues.freeFeaturesTab )
);
} catch ( error ) {
this.props.createNotice(
'error',
__(
'There was a problem saving free features state',
'woocommerce'
)
);
}
}
async persistProfileItems( additions = {} ) {
const { updateProfileItems, createNotice } = this.props;
const { businessDetailsTab = {} } = this.state.savedValues;
const {
number_employees: numberEmployees,
other_platform: otherPlatform,
other_platform_name: otherPlatformName,
product_count: productCount,
revenue,
selling_venues: sellingVenues,
setup_client: isSetupClient,
} = businessDetailsTab;
const updates = {
number_employees: numberEmployees,
other_platform: otherPlatform,
other_platform_name:
otherPlatform === 'other' ? otherPlatformName : '',
product_count: productCount,
revenue,
selling_venues: sellingVenues,
setup_client: isSetupClient,
...additions,
};
// Remove possible empty values like `revenue` and `other_platform`.
const finalUpdates = Object.entries( updates ).reduce(
( acc, [ key, val ] ) => {
if ( val !== '' ) {
acc[ key ] = val;
}
return acc;
},
{}
);
return updateProfileItems( finalUpdates ).catch( () => {
createNotice(
'error',
__(
'There was a problem updating your business details',
'woocommerce'
)
);
} );
}
validate( values ) {
const errors = {};
if ( ! values.product_count.length ) {
errors.product_count = __(
'This field is required',
'woocommerce'
);
}
if ( ! values.selling_venues.length ) {
errors.selling_venues = __(
'This field is required',
'woocommerce'
);
}
if (
! values.other_platform.length &&
isSellingOtherPlatformInPerson( values.selling_venues )
) {
errors.other_platform = __(
'This field is required',
'woocommerce'
);
}
if (
! values.other_platform_name.trim().length &&
values.other_platform === 'other' &&
isSellingOtherPlatformInPerson( values.selling_venues )
) {
errors.other_platform_name = __(
'This field is required',
'woocommerce'
);
}
if (
! values.number_employees.length &&
isSellingElsewhere( values.selling_venues )
) {
errors.number_employees = __(
'This field is required',
'woocommerce'
);
}
if (
! values.revenue.length &&
isSellingElsewhere( values.selling_venues )
) {
errors.revenue = __( 'This field is required', 'woocommerce' );
}
if ( Object.keys( errors ).length === 0 ) {
this.setState( { isValid: true } );
}
return errors;
}
trackBusinessDetailsStep( {
number_employees: numberEmployees,
other_platform: otherPlatform,
other_platform_name: otherPlatformName,
product_count: productCount,
selling_venues: sellingVenues,
revenue,
setup_client: isSetupClient,
} ) {
const { getCurrencyConfig } = this.context;
recordEvent( 'storeprofiler_store_business_details_continue_variant', {
number_employees: numberEmployees,
already_selling: sellingVenues,
currency: getCurrencyConfig().code,
product_number: productCount,
revenue,
used_platform: otherPlatform,
used_platform_name: otherPlatformName,
setup_client: isSetupClient,
wp_version: getSetting( 'wpVersion' ),
} );
recordEvent( 'storeprofiler_step_complete', {
step: BUSINESS_DETAILS_TAB_NAME,
wc_version: getSetting( 'wcVersion' ),
} );
}
getSelectControlProps( getInputProps, name = '' ) {
const { className, ...props } = getInputProps( name );
return {
...props,
className: clsx(
`woocommerce-profile-wizard__${ name.replace( /\_/g, '-' ) }`,
className
),
};
}
renderBusinessDetailsStep() {
const {
goToNextStep,
isInstallingActivating,
hasInstallActivateError,
} = this.props;
const { formatAmount, getCurrencyConfig } = this.context;
const productCountOptions = getProductCountOptions(
getCurrencyConfig()
);
return (
<Form
initialValues={ this.state.savedValues.businessDetailsTab }
onSubmit={ ( values ) => {
if ( this.props.hasInstallableExtensions ) {
this.setState( {
savedValues: {
...this.state.savedValues,
businessDetailsTab: values,
},
currentTab: BUSINESS_FEATURES_TAB_NAME,
} );
} else {
goToNextStep( {
step: BUSINESS_FEATURES_TAB_NAME,
} );
}
this.trackBusinessDetailsStep( values );
recordEvent( 'storeprofiler_step_view', {
step: BUSINESS_FEATURES_TAB_NAME,
wc_version: getSetting( 'wcVersion' ),
} );
} }
onChange={ ( _, values, isValid ) => {
const savedValues = {
...this.state.savedValues,
businessDetailsTab: values,
};
this.setState( {
savedValues,
isValid,
} );
this.props.updateCurrentStepValues(
this.props.step.key,
savedValues
);
} }
validate={ this.validate }
>
{ ( { getInputProps, handleSubmit, values, isValidForm } ) => {
return (
<>
<div className="woocommerce-profile-wizard__step-header">
<Text
variant="title.small"
as="h2"
size="20"
lineHeight="28px"
>
{ __(
'Tell us about your business',
'woocommerce'
) }
</Text>
<Text variant="body" as="p">
{ __(
"We'd love to know if you are just getting started or you already have a business in place.",
'woocommerce'
) }
</Text>
</div>
<Card>
<CardBody>
<SelectControl
excludeSelectedOptions={ false }
label={ __(
'How many products do you plan to display?',
'woocommerce'
) }
options={ productCountOptions }
required
{ ...this.getSelectControlProps(
getInputProps,
'product_count'
) }
/>
<SelectControl
excludeSelectedOptions={ false }
label={ __(
'Currently selling elsewhere?',
'woocommerce'
) }
options={ sellingVenueOptions }
required
{ ...this.getSelectControlProps(
getInputProps,
'selling_venues'
) }
/>
{ isShowWccomMigrationNotice(
values.selling_venues
) && (
<Notice
className="woocommerce-profile-wizard__wccom-migration-notice"
status="info"
isDismissible={ false }
>
{ __(
'Need help moving your store?',
'woocommerce'
) }
&nbsp;
<Button
href={ getWccomMigrationUrl(
values.selling_venues
) }
target="_blank"
rel="noopener noreferrer"
variant="link"
>
{ __(
'Get free expert advice',
'woocommerce'
) }
</Button>
</Notice>
) }
{ isSellingElsewhere(
values.selling_venues
) && (
<SelectControl
excludeSelectedOptions={ false }
label={ __(
'How many employees do you have?',
'woocommerce'
) }
options={ employeeOptions }
required
{ ...this.getSelectControlProps(
getInputProps,
'number_employees'
) }
/>
) }
{ isSellingElsewhere(
values.selling_venues
) && (
<SelectControl
excludeSelectedOptions={ false }
label={ __(
"What's your current annual revenue?",
'woocommerce'
) }
options={ getRevenueOptions(
getCurrencyConfig(),
this.props.settings
.woocommerce_default_country,
formatAmount
) }
required
{ ...this.getSelectControlProps(
getInputProps,
'revenue'
) }
/>
) }
{ isSellingOtherPlatformInPerson(
values.selling_venues
) && (
<>
<div className="business-competitors">
<SelectControl
excludeSelectedOptions={
false
}
label={ __(
'Which platform is the store using?',
'woocommerce'
) }
options={ platformOptions }
required
{ ...this.getSelectControlProps(
getInputProps,
'other_platform'
) }
/>
{ values.other_platform ===
'other' && (
<TextControl
label={ __(
'What is the platform name?',
'woocommerce'
) }
required
{ ...this.getSelectControlProps(
getInputProps,
'other_platform_name'
) }
/>
) }
</div>
</>
) }
</CardBody>
<CardFooter isBorderless>
<FlexItem>
<div className="woocommerce-profile-wizard__client">
<CheckboxControl
label={ __(
"I'm setting up a store for a client",
'woocommerce'
) }
{ ...getInputProps(
'setup_client'
) }
/>
</div>
</FlexItem>
</CardFooter>
<CardFooter justify="center">
<Button
isPrimary
onClick={ async () => {
await handleSubmit();
this.persistProfileItems();
} }
disabled={ ! isValidForm }
aria-disabled={ ! isValidForm }
isBusy={ isInstallingActivating }
>
{ ! hasInstallActivateError
? __( 'Continue', 'woocommerce' )
: __( 'Retry', 'woocommerce' ) }
</Button>
{ hasInstallActivateError && (
<Button
onClick={ () => {
this.persistProfileItems();
goToNextStep( {
step: BUSINESS_FEATURES_TAB_NAME,
} );
} }
>
{ __(
'Continue without installing',
'woocommerce'
) }
</Button>
) }
</CardFooter>
</Card>
</>
);
} }
</Form>
);
}
renderFreeFeaturesStep() {
const { isInstallingActivating } = this.props;
const {
savedValues: { freeFeaturesTab },
} = this.state;
return (
<>
<div className="woocommerce-profile-wizard__step-header">
<Text
variant="title.small"
as="h2"
size="20"
lineHeight="28px"
>
{ __( 'Included business features', 'woocommerce' ) }
</Text>
<Text variant="body" as="p">
{ __(
'We recommend enhancing your store with these free extensions',
'woocommerce'
) }
</Text>
<Text variant="body" as="p">
{ __(
'No commitment required - you can remove them at any time.',
'woocommerce'
) }
</Text>
</div>
<SelectiveExtensionsBundle
isInstallingActivating={ isInstallingActivating }
onSubmit={ this.onContinue }
installableExtensions={ this.props.installableExtensions }
installExtensionOptions={
freeFeaturesTab.installExtensionOptions
}
setInstallExtensionOptions={ (
installExtensionOptions
) => {
const savedValues = {
...this.state.savedValues,
freeFeaturesTab: {
...freeFeaturesTab,
installExtensionOptions,
},
};
this.setState( {
savedValues,
} );
if (
// Only update current step values when current state's installExtensionOptions is not undefined.
this.state.savedValues.freeFeaturesTab
.installExtensionOptions
) {
this.props.updateCurrentStepValues(
this.props.step.key,
savedValues
);
}
} }
/>
</>
);
}
render() {
const tabs = [];
if ( ! this.props.hasFinishedGetFreeExtensionsResolution ) {
return (
<div className="woocommerce-admin__business-details__spinner">
<Spinner />
</div>
);
}
tabs.push( {
name:
this.state.currentTab === BUSINESS_DETAILS_TAB_NAME
? 'current-tab'
: BUSINESS_DETAILS_TAB_NAME,
id: BUSINESS_DETAILS_TAB_NAME,
title: __( 'Business details', 'woocommerce' ),
} );
if ( this.props.hasInstallableExtensions ) {
tabs.push( {
name:
this.state.currentTab === BUSINESS_FEATURES_TAB_NAME
? 'current-tab'
: BUSINESS_FEATURES_TAB_NAME,
id: BUSINESS_FEATURES_TAB_NAME,
title: __( 'Free features', 'woocommerce' ),
className: this.state.isValid ? '' : 'is-disabled',
} );
}
// There is a hack here to help us manage the selected tab programmatically.
// We set the tab name "current-tab". when its the one we want selected. This tricks
// the logic in the TabPanel and allows us to switch which tab has the name "current-tab"
// and force it to re-render with a different tab selected.
return (
<TabPanel
activeClass="is-active"
initialTabName="current-tab"
onSelect={ ( tabName ) => {
if (
this.state.currentTab !== tabName &&
// TabPanel calls onSelect on mount when initialTabName is provided, so we need to check if the tabName is valid.
tabName !== 'current-tab'
) {
this.setState( {
currentTab: tabName,
savedValues:
this.state.savedValues ||
this.props.initialValues,
} );
recordEvent( 'storeprofiler_step_view', {
step: tabName,
wc_version: getSetting( 'wcVersion' ),
} );
}
} }
tabs={ tabs }
>
{ ( tab ) => <>{ this.getTab( tab.id ) }</> }
</TabPanel>
);
}
getTab( tabId ) {
if ( tabId === BUSINESS_DETAILS_TAB_NAME ) {
return this.renderBusinessDetailsStep();
}
return this.renderFreeFeaturesStep();
}
}
BusinessDetails.contextType = CurrencyContext;
export const BusinessFeaturesList = compose(
withSelect( ( select ) => {
const { getSettings, getSettingsError } = select( SETTINGS_STORE_NAME );
const {
getProfileItems,
getOnboardingError,
getFreeExtensions,
hasFinishedResolution,
} = select( ONBOARDING_STORE_NAME );
const { getPluginsError, isPluginsRequesting } =
select( PLUGINS_STORE_NAME );
const { general: settings = {} } = getSettings( 'general' );
const freeExtensions = getFreeExtensions();
const profileItems = getProfileItems();
const country = settings.woocommerce_default_country
? settings.woocommerce_default_country
: null;
const installableExtensions = freeExtensions
? getInstallableExtensions( {
freeExtensionBundleByCategory: freeExtensions,
country,
productTypes: profileItems.product_types || [],
} )
: [];
const hasInstallableExtensions = installableExtensions.some(
( { plugins } ) => plugins.length > 0
);
return {
hasInstallActivateError:
getPluginsError( 'installPlugins' ) ||
getPluginsError( 'activatePlugins' ),
hasInstallableExtensions,
hasFinishedGetFreeExtensionsResolution:
hasFinishedResolution( 'getFreeExtensions' ),
installableExtensions,
isError: Boolean( getOnboardingError( 'updateProfileItems' ) ),
isSettingsError: Boolean( getSettingsError( 'general' ) ),
isInstallingActivating:
isPluginsRequesting( 'installPlugins' ) ||
isPluginsRequesting( 'activatePlugins' ) ||
isPluginsRequesting( 'getJetpackConnectUrl' ),
profileItems,
settings,
};
} ),
withDispatch( ( dispatch ) => {
const { updateProfileItems, invalidateResolutionForStoreSelector } =
dispatch( ONBOARDING_STORE_NAME );
const { installAndActivatePlugins } = dispatch( PLUGINS_STORE_NAME );
const { createNotice } = dispatch( 'core/notices' );
return {
createNotice,
installAndActivatePlugins,
updateProfileItems,
invalidateResolutionForStoreSelector,
};
} )
)( BusinessDetails );

View File

@ -1,412 +0,0 @@
/**
* External dependencies
*/
import { useEffect, useState } from '@wordpress/element';
import { Button, Card, CheckboxControl, Spinner } from '@wordpress/components';
import { Text } from '@woocommerce/experimental';
import { Link } from '@woocommerce/components';
import { __, _n, sprintf } from '@wordpress/i18n';
import { Icon, chevronDown, chevronUp } from '@wordpress/icons';
import interpolateComponents from '@automattic/interpolate-components';
import { pluginNames } from '@woocommerce/data';
import { recordEvent } from '@woocommerce/tracks';
/**
* Internal dependencies
*/
import { AppIllustration } from '../app-illustration';
import './style.scss';
import sanitizeHTML from '~/lib/sanitize-html';
import { setAllPropsToValue } from '~/lib/collections';
import { getCountryCode } from '../../../../../../dashboard/utils';
const ALLOWED_PLUGIN_CATEGORIES = [ 'obw/basics', 'obw/grow' ];
const FreeBadge = ( props ) => {
return (
<div className="woocommerce-admin__business-details__free-badge">
{ props.isFreeTrial
? __( 'Free Trial', 'woocommerce' )
: __( 'Free', 'woocommerce' ) }
</div>
);
};
const renderBusinessExtensionHelpText = ( values, isInstallingActivating ) => {
const extensions = Object.keys( values ).filter(
( key ) => values[ key ] && key !== 'install_extensions'
);
if ( extensions.length === 0 ) {
return null;
}
const extensionsList = extensions
.reduce( ( uniqueExtensionList, extension ) => {
const extensionName = pluginNames[ extension ];
return uniqueExtensionList.includes( extensionName )
? uniqueExtensionList
: [ ...uniqueExtensionList, extensionName ];
}, [] )
.join( ', ' );
if ( isInstallingActivating ) {
return (
<div className="woocommerce-profile-wizard__footnote">
<Text variant="caption" as="p" size="12" lineHeight="16px">
{ sprintf(
/* translators: %s: a comma separated list of plugins, e.g. Jetpack, WooCommerce Shipping */
_n(
'Installing the following plugin: %s',
'Installing the following plugins: %s',
extensions.length,
'woocommerce'
),
extensionsList
) }
</Text>
</div>
);
}
const accountRequiredText = __(
'User accounts are required to use these features.',
'woocommerce'
);
const extensionsWithToS = extensions.filter(
( extension ) =>
extension === 'jetpack' ||
extension.includes( 'woocommerce-services' )
);
const isInstallingJetpackAndWCServices =
extensionsWithToS.includes( 'jetpack' ) &&
( extensionsWithToS.includes( 'woocommerce-services:shipping' ) ||
extensionsWithToS.includes( 'woocommerce-services:tax' ) );
const extensionsListText = isInstallingJetpackAndWCServices
? 'Jetpack and WooCommerce Shipping & Tax'
: pluginNames[ extensionsWithToS[ 0 ] ];
const installingJetpackShippingTaxToS = sprintf(
/* translators: %s: a list of plugins, e.g. Jetpack */
_n(
'By installing %s plugin for free you agree to our {{link}}Terms of Service{{/link}}.',
'By installing %s plugins for free you agree to our {{link}}Terms of Service{{/link}}.',
extensionsWithToS.length,
'woocommerce'
),
extensionsListText
);
return (
<div className="woocommerce-profile-wizard__footnote">
<Text variant="caption" as="p" size="12" lineHeight="16px">
{ sprintf(
/* translators: %1$s: a comma separated list of plugins, e.g. Jetpack, WooCommerce Shipping, %2$s: text: 'User accounts are required to use these features.' */
_n(
'The following plugin will be installed for free: %1$s. %2$s',
'The following plugins will be installed for free: %1$s. %2$s',
extensions.length,
'woocommerce'
),
extensionsList,
accountRequiredText
) }
</Text>
{ extensionsWithToS.length > 0 && (
<Text variant="caption" as="p" size="12" lineHeight="16px">
{ interpolateComponents( {
mixedString: installingJetpackShippingTaxToS,
components: {
link: (
<Link
href="https://wordpress.com/tos/"
target="_blank"
type="external"
/>
),
},
} ) }
</Text>
) }
</div>
);
};
const BundleExtensionCheckbox = ( {
onChange,
description,
isChecked,
extensionKey,
} ) => {
const isFreeTrial = extensionKey === 'codistoconnect';
const recordProductLinkClick = ( event ) => {
const link = event.target.closest( 'a' );
if (
! link ||
! event.currentTarget.contains( link ) ||
! link.href.startsWith( 'https://woocommerce.com/products/' )
) {
return;
}
recordEvent( 'storeprofiler_store_business_features_link_click', {
extension_name: link.href.split(
'https://woocommerce.com/products/'
)[ 1 ],
} );
};
return (
<div className="woocommerce-admin__business-details__selective-extensions-bundle__extension">
<CheckboxControl
id="woocommerce-business-extensions__checkbox"
checked={ isChecked }
onChange={ onChange }
/>
{
// Disable reason: This click handler checks for interaction with anchor tags on
// dynamically inserted HTML and records clicks only on interaction with those items.
/* eslint-disable jsx-a11y/no-noninteractive-element-interactions, jsx-a11y/click-events-have-key-events */
}
<p
className="woocommerce-admin__business-details__selective-extensions-bundle__description"
dangerouslySetInnerHTML={ sanitizeHTML( description ) }
onClick={ recordProductLinkClick }
/>
{ /* eslint-disable jsx-a11y/no-noninteractive-element-interactions, jsx-a11y/click-events-have-key-events */ }
<FreeBadge
isFreeTrial={ isFreeTrial }
extensionKey={ extensionKey }
/>
</div>
);
};
export const ExtensionSection = ( {
isResolving,
title,
extensions,
installExtensionOptions,
onCheckboxChange,
} ) => {
if ( isResolving ) {
return (
<div>
<Spinner />
</div>
);
}
if ( extensions.length === 0 ) {
return null;
}
return (
<div>
<div className="woocommerce-admin__business-details__selective-extensions-bundle__category">
{ title }
</div>
{ extensions.map( ( { description, key } ) => (
<BundleExtensionCheckbox
key={ key }
extensionKey={ key }
description={ description }
isChecked={ installExtensionOptions[ key ] }
onChange={ onCheckboxChange( key ) }
/>
) ) }
</div>
);
};
export const createInstallExtensionOptions = (
installableExtensions,
prevInstallExtensionOptions
) => {
return installableExtensions.reduce( ( acc, curr ) => {
const plugins = curr.plugins.reduce( ( pluginAcc, plugin ) => {
if ( acc.hasOwnProperty( plugin.key ) ) {
return pluginAcc;
}
return {
...pluginAcc,
[ plugin.key ]: true,
};
}, {} );
return {
...acc,
...plugins,
};
}, prevInstallExtensionOptions );
};
export const getInstallableExtensions = ( {
freeExtensionBundleByCategory,
country,
productTypes,
} ) => {
return freeExtensionBundleByCategory.filter( ( extensionBundle ) => {
if (
window.wcAdminFeatures &&
window.wcAdminFeatures.subscriptions &&
getCountryCode( country ) === 'US'
) {
if ( productTypes.includes( 'subscriptions' ) ) {
extensionBundle.plugins = extensionBundle.plugins.filter(
( extension ) =>
extension.key !== 'woocommerce-payments' ||
( extension.key === 'woocommerce-payments' &&
! extension.is_activated )
);
}
}
return ALLOWED_PLUGIN_CATEGORIES.includes( extensionBundle.key );
} );
};
export const SelectiveExtensionsBundle = ( {
isInstallingActivating,
onSubmit,
setInstallExtensionOptions,
installableExtensions,
installExtensionOptions = { install_extensions: true },
} ) => {
const [ showExtensions, setShowExtensions ] = useState( false );
useEffect( () => {
if ( isInstallingActivating || installableExtensions.length === 0 ) {
return;
}
setInstallExtensionOptions(
createInstallExtensionOptions(
installableExtensions,
installExtensionOptions
)
);
// Disable reason: This effect should only called when the installableExtensions are changed.
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [ installableExtensions ] );
const getCheckboxChangeHandler = ( key ) => {
return ( checked ) => {
const newState = {
...installExtensionOptions,
[ key ]: checked,
};
const allExtensionsDisabled =
Object.entries( newState ).filter( ( [ , val ] ) => val )
.length === 1 && newState.install_extensions;
if ( allExtensionsDisabled ) {
// If all the extensions are disabled then disable the "Install Extensions" checkbox too
setInstallExtensionOptions( {
...newState,
install_extensions: false,
} );
} else {
setInstallExtensionOptions( {
...installExtensionOptions,
[ key ]: checked,
install_extensions: true,
} );
}
};
};
return (
<div className="woocommerce-profile-wizard__business-details__free-features">
<Card>
<div className="woocommerce-profile-wizard__business-details__free-features__illustration">
<AppIllustration />
</div>
<div className="woocommerce-admin__business-details__selective-extensions-bundle">
<div className="woocommerce-admin__business-details__selective-extensions-bundle__extension">
<CheckboxControl
checked={
installExtensionOptions.install_extensions
}
onChange={ ( checked ) => {
setInstallExtensionOptions(
setAllPropsToValue(
installExtensionOptions,
checked
)
);
} }
/>
<p className="woocommerce-admin__business-details__selective-extensions-bundle__description">
{ __(
'Add recommended business features to my site',
'woocommerce'
) }
</p>
<Button
className="woocommerce-admin__business-details__selective-extensions-bundle__expand"
disabled={
! installableExtensions ||
installableExtensions.length === 0
}
onClick={ () => {
setShowExtensions( ! showExtensions );
if ( ! showExtensions ) {
// only record the accordion click when the accordion is opened.
recordEvent(
'storeprofiler_store_business_features_accordion_click'
);
}
} }
>
<Icon
icon={
showExtensions ? chevronUp : chevronDown
}
/>
</Button>
</div>
{ showExtensions &&
installableExtensions.map(
( { plugins, key, title } ) => (
<ExtensionSection
key={ key }
title={ title }
extensions={ plugins }
installExtensionOptions={
installExtensionOptions
}
onCheckboxChange={
getCheckboxChangeHandler
}
/>
)
) }
</div>
<div className="woocommerce-profile-wizard__business-details__free-features__action">
<Button
onClick={ () => {
onSubmit(
installExtensionOptions,
installableExtensions
);
} }
isBusy={ isInstallingActivating }
disabled={ isInstallingActivating }
isPrimary
>
{ __( 'Continue', 'woocommerce' ) }
</Button>
</div>
</Card>
{ renderBusinessExtensionHelpText(
installExtensionOptions,
isInstallingActivating
) }
</div>
);
};

View File

@ -1,82 +0,0 @@
.woocommerce-profile-wizard__business-details__free-features {
display: flex;
flex-direction: column;
justify-content: center;
.woocommerce-admin__business-details__selective-extensions-bundle {
.woocommerce-admin__business-details__selective-extensions-bundle__category {
font-size: 11px;
text-transform: uppercase;
margin: 25px 0 0 30px;
color: $black;
font-weight: 500;
}
.woocommerce-admin__business-details__selective-extensions-bundle__extension {
display: flex;
padding: $gap-large 30px;
border-bottom: 1px solid $gray-200;
align-items: center;
}
.woocommerce-admin__business-details__selective-extensions-bundle__description {
font-size: 16px;
color: $gray-900;
margin: 0;
line-height: 18px;
}
.woocommerce-admin__business-details__free-badge {
padding: 0 $gap-smaller;
border: 1px solid $gray-700;
border-radius: 16px;
margin-left: $gap-smaller;
color: $gray-700;
font-size: 12px;
line-height: 18px;
height: 20px;
flex: none;
}
.woocommerce-admin__business-details__selective-extensions-bundle__link {
text-decoration: none;
}
}
.woocommerce-card__body {
padding: 0;
}
.components-base-control .components-base-control__field {
margin-bottom: 0;
margin-right: 21px;
.components-checkbox-control__input-container {
margin-right: 0;
vertical-align: baseline;
}
}
.woocommerce-admin__business-details__selective-extensions-bundle__expand {
margin-left: 9px;
}
.woocommerce-profile-wizard__business-details__free-features__illustration {
flex: 1;
width: 100%;
display: flex;
justify-content: center;
margin-top: 34px;
margin-bottom: 18px;
.fill-theme-color {
fill: var(--wp-admin-theme-color);
}
}
.woocommerce-profile-wizard__business-details__free-features__action {
padding-top: $gap-small;
display: flex;
justify-content: center;
margin-bottom: $gap-small;
}
}

View File

@ -1,156 +0,0 @@
/**
* External dependencies
*/
import { render } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { pluginNames } from '@woocommerce/data';
/**
* Internal dependencies
*/
import { SelectiveExtensionsBundle, ExtensionSection } from '../';
jest.mock( '../../app-illustration', () => ( {
AppIllustration: jest.fn().mockReturnValue( '[illustration]' ),
} ) );
const freeExtensions = [
{
key: 'task-list/reach',
title: 'Reach out to customers',
plugins: [
{
key: 'random',
name: 'Random',
description: 'Random description',
manage_url: 'admin.php?page=mailpoet-newsletters',
is_visible: true,
is_installed: true,
},
],
},
{
key: 'obw/basics',
title: 'Get the basics',
plugins: [
{
key: 'woocommerce-payments',
description: 'WC Pay Description',
is_visible: true,
is_installed: true,
},
{
key: 'mailpoet',
description: 'Mailpoet Description',
manage_url: 'admin.php?page=mailpoet-newsletters',
is_visible: true,
is_installed: true,
},
],
},
{
key: 'obw/grow',
title: 'Grow your store',
plugins: [
{
key: 'google-listings-and-ads',
name: 'Google Ads & Marketing by Kliken',
description: 'Google Description',
manage_url: 'admin.php?page=wc-admin&path=%2Fgoogle%2Fstart',
is_visible: true,
is_installed: false,
},
],
},
];
describe( 'Selective extensions bundle', () => {
it( 'should list installable free extensions from obw/basics and obw/grow', () => {
const mockSetInstallExtensionOptions = jest.fn();
// Render once to get installExtensionOptions
render(
<SelectiveExtensionsBundle
isInstallingActivating={ false }
installableExtensions={ freeExtensions.slice( 1 ) }
setInstallExtensionOptions={ mockSetInstallExtensionOptions }
/>
);
const { getByText, queryByText } = render(
<SelectiveExtensionsBundle
isInstallingActivating={ false }
setInstallExtensionOptions={ mockSetInstallExtensionOptions }
installableExtensions={ freeExtensions.slice( 1 ) }
installExtensionOptions={
mockSetInstallExtensionOptions.mock.calls[ 0 ][ 0 ]
}
/>
);
expect(
getByText( new RegExp( pluginNames.mailpoet ) )
).toBeInTheDocument();
expect(
getByText( new RegExp( pluginNames[ 'woocommerce-payments' ] ) )
).toBeInTheDocument();
expect(
queryByText(
new RegExp( pluginNames[ 'google-listings-and-ads' ] )
)
).toBeInTheDocument();
expect( queryByText( new RegExp( 'Random' ) ) ).not.toBeInTheDocument();
} );
it( 'should list installable extensions when dropdown is clicked', () => {
const { getAllByRole, getByText, queryByText } = render(
<SelectiveExtensionsBundle
isInstallingActivating={ false }
installableExtensions={ freeExtensions }
setInstallExtensionOptions={ jest.fn() }
/>
);
const collapseButton = getAllByRole( 'button' ).find(
( item ) => item.textContent === ''
);
userEvent.click( collapseButton );
expect( getByText( 'WC Pay Description' ) ).toBeInTheDocument();
expect( getByText( 'Mailpoet Description' ) ).toBeInTheDocument();
expect( queryByText( 'Google Description' ) ).toBeInTheDocument();
expect( queryByText( 'Random Description' ) ).not.toBeInTheDocument();
} );
describe( '<ExtensionSection />', () => {
it( 'should render title and extensions', () => {
const title = 'This is title';
const { queryByText } = render(
<ExtensionSection
isResolving={ false }
title={ title }
extensions={ freeExtensions[ 0 ].plugins }
installExtensionOptions={ {} }
onCheckboxChange={ () => {} }
/>
);
expect( queryByText( title ) ).toBeInTheDocument();
freeExtensions[ 0 ].plugins.forEach( ( { description } ) => {
expect( queryByText( description ) ).toBeInTheDocument();
} );
} );
it( 'should not render title when no plugins', () => {
const title = 'This is title';
const { queryByText } = render(
<ExtensionSection
isResolving={ false }
title={ title }
extensions={ [] }
installExtensionOptions={ {} }
onCheckboxChange={ () => {} }
/>
);
expect( queryByText( title ) ).not.toBeInTheDocument();
} );
} );
} );

View File

@ -1,32 +0,0 @@
.woocommerce-profile-wizard__container.business-details {
.components-tab-panel__tabs {
.components-tab-panel__tabs-item {
&.is-disabled {
color: $gray-600;
cursor: default;
pointer-events: none;
}
&.is-active {
pointer-events: none;
}
}
justify-content: center;
}
.components-card {
&__body {
padding: $gap $gap 0;
}
}
}
.woocommerce-profile-wizard__wccom-migration-notice {
margin: 0;
background: #f0f9fc;
height: 48px;
font-size: 14px;
a {
font-size: 14px;
}
}

View File

@ -1,73 +0,0 @@
/**
* External dependencies
*/
import { useSelect } from '@wordpress/data';
import { useMemo } from '@wordpress/element';
import { Spinner } from '@wordpress/components';
import { ONBOARDING_STORE_NAME, SETTINGS_STORE_NAME } from '@woocommerce/data';
/**
* Internal dependencies
*/
import {
BusinessFeaturesList,
PERSIST_FREE_FEATURES_DATA_STORAGE_KEY,
} from './flows/selective-bundle';
import './style.scss';
export const BusinessDetailsStep = ( props ) => {
const { profileItems, isLoading } = useSelect( ( select ) => {
return {
isLoading:
! select( ONBOARDING_STORE_NAME ).hasFinishedResolution(
'getProfileItems'
) ||
! select( SETTINGS_STORE_NAME ).hasFinishedResolution(
'getSettings',
[ 'general' ]
),
profileItems: select( ONBOARDING_STORE_NAME ).getProfileItems(),
};
} );
const freeFeaturesTabValues = useMemo( () => {
try {
const values = JSON.parse(
window.localStorage.getItem(
PERSIST_FREE_FEATURES_DATA_STORAGE_KEY
)
);
if ( values ) {
return values;
}
} catch ( _e ) {
// Skip errors
}
return { install_extensions: true };
}, [] );
if ( isLoading ) {
return (
<div className="woocommerce-admin__business-details__spinner">
<Spinner />
</div>
);
}
const initialValues = {
businessDetailsTab: {
number_employees: profileItems.number_employees || '',
other_platform: profileItems.other_platform || '',
other_platform_name: profileItems.other_platform_name || '',
product_count: profileItems.product_count || '',
selling_venues: profileItems.selling_venues || '',
revenue: profileItems.revenue || '',
setup_client: profileItems.setup_client || false,
},
freeFeaturesTab: freeFeaturesTabValues,
};
return (
<BusinessFeaturesList { ...props } initialValues={ initialValues } />
);
};

View File

@ -1,10 +0,0 @@
.business-details.woocommerce-profile-wizard__container {
.woocommerce-admin__business-details__spinner {
display: flex;
justify-content: center;
}
.woocommerce-profile-wizard__step-header {
margin-top: 28px;
}
}

View File

@ -1,204 +0,0 @@
/**
* Internal dependencies
*/
import {
filterBusinessExtensions,
isSellingElsewhere,
isSellingOtherPlatformInPerson,
prepareExtensionTrackingData,
prepareExtensionTrackingInstallationData,
} from '../flows/selective-bundle';
import { createInstallExtensionOptions } from '../flows/selective-bundle/selective-extensions-bundle';
describe( 'BusinessDetails', () => {
test( 'filtering extensions', () => {
const extensions = {
'creative-mail-by-constant-contact': true,
'facebook-for-woocommerce': true,
install_extensions: true,
jetpack: true,
'google-listings-and-ads': true,
'mailchimp-for-woocommerce': true,
'woocommerce-payments': true,
'woocommerce-services:shipping': true,
'woocommerce-services:tax': true,
};
const expectedExtensions = [
'creative-mail-by-constant-contact',
'facebook-for-woocommerce',
'jetpack',
'google-listings-and-ads',
'mailchimp-for-woocommerce',
'woocommerce-payments',
'woocommerce-services',
];
const filteredExtensions = filterBusinessExtensions( extensions );
expect( filteredExtensions ).toEqual( expectedExtensions );
} );
describe( 'prepareExtensionTrackingData', () => {
test( 'preparing extensions for tracking', () => {
const extensions = {
'creative-mail-by-constant-contact': true,
'facebook-for-woocommerce': false,
install_extensions: true,
jetpack: false,
'google-listings-and-ads': true,
'mailchimp-for-woocommerce': false,
'woocommerce-payments:us': true,
};
const expectedExtensions = {
install_creative_mail_by_constant_contact: true,
install_facebook_for_woocommerce: false,
install_jetpack: false,
install_google_listings_and_ads: true,
install_mailchimp_for_woocommerce: false,
install_wcpay: true,
};
const installedExtensions =
prepareExtensionTrackingData( extensions );
expect( installedExtensions ).toEqual( expectedExtensions );
} );
test( 'preparing shipping and tax extensions for tracking', () => {
const extensions = {
'woocommerce-services:shipping': true,
'woocommerce-services:tax': true,
};
const expectedExtensions = {
install_woocommerce_services: true,
};
expect( prepareExtensionTrackingData( extensions ) ).toEqual(
expectedExtensions
);
extensions[ 'woocommerce-services:shipping' ] = false;
extensions[ 'woocommerce-services:tax' ] = true;
expect( prepareExtensionTrackingData( extensions ) ).toEqual(
expectedExtensions
);
extensions[ 'woocommerce-services:shipping' ] = true;
extensions[ 'woocommerce-services:tax' ] = false;
expect( prepareExtensionTrackingData( extensions ) ).toEqual(
expectedExtensions
);
extensions[ 'woocommerce-services:shipping' ] = false;
extensions[ 'woocommerce-services:tax' ] = false;
expectedExtensions.install_woocommerce_services = false;
expect( prepareExtensionTrackingData( extensions ) ).toEqual(
expectedExtensions
);
} );
} );
describe( 'extension installation tracking', () => {
const extensions = {
'creative-mail-by-constant-contact': true,
'facebook-for-woocommerce': false,
install_extensions: true,
jetpack: false,
'google-listings-and-ads': true,
'mailchimp-for-woocommerce': false,
'woocommerce-payments:us': true,
};
it( 'should return a list of installed and activated extensions', () => {
const data = prepareExtensionTrackingInstallationData( extensions, {
data: {
activated: [
'woocommerce-payments',
'google-listings-and-ads',
],
install_time: {
'woocommerce-payments': 2000,
},
},
} );
expect( data.installed_extensions ).toEqual( [ 'wcpay' ] );
expect( data.activated_extensions ).toEqual( [
'woocommerce-payments',
'google-listings-and-ads',
] );
} );
it( 'should add install_time_extension_name prop if install_time is set with correct timebox', () => {
const data = prepareExtensionTrackingInstallationData( extensions, {
data: {
activated: [
'woocommerce-payments',
'google-listings-and-ads',
],
install_time: {
'woocommerce-payments': 200,
'google-listings-and-ads': 12023,
},
},
} );
expect( data.install_time_wcpay ).toEqual( '0-2s' );
expect( data.install_time_google_listings_and_ads ).toEqual(
'10-15s'
);
} );
} );
describe( 'createInstallExtensionOptions', () => {
test( 'selected by default', () => {
const installableExtensions = [
{
plugins: [
{
key: 'visible-and-not-selected',
},
{
key: 'visible-and-selected',
},
],
},
];
const values = createInstallExtensionOptions(
installableExtensions,
{ install_extensions: true }
);
expect( values ).toEqual(
expect.objectContaining( {
install_extensions: true,
'visible-and-not-selected': true,
'visible-and-selected': true,
} )
);
} );
} );
describe( 'Currently selling elsewhere', () => {
test( 'isSellingElsewhere', () => {
const sellingElsewhere = isSellingElsewhere( 'other' );
const notSellingElsewhere = isSellingElsewhere( 'no' );
expect( sellingElsewhere ).toBeTruthy();
expect( notSellingElsewhere ).toBeFalsy();
} );
test( 'isSellingOtherPlatformInPerson', () => {
const sellingAnotherPlatformAndInPerson =
isSellingOtherPlatformInPerson( 'brick-mortar-other' );
const notSellingAnotherPlatformAndInPerson =
isSellingOtherPlatformInPerson( 'no' );
expect( sellingAnotherPlatformAndInPerson ).toBeTruthy();
expect( notSellingAnotherPlatformAndInPerson ).toBeFalsy();
} );
} );
} );

View File

@ -1,350 +0,0 @@
/**
* External dependencies
*/
import { __ } from '@wordpress/i18n';
import { Component, Fragment } from '@wordpress/element';
import {
Button,
Card,
CardBody,
CardFooter,
CheckboxControl,
Spinner,
} from '@wordpress/components';
import { compose } from '@wordpress/compose';
import { filter, find, findIndex, get } from 'lodash';
import { withDispatch, withSelect } from '@wordpress/data';
import { ONBOARDING_STORE_NAME, SETTINGS_STORE_NAME } from '@woocommerce/data';
import { TextControl } from '@woocommerce/components';
import { recordEvent } from '@woocommerce/tracks';
import { Text } from '@woocommerce/experimental';
/**
* Internal dependencies
*/
import { getCurrencyRegion } from '../../dashboard/utils';
import { getAdminSetting } from '~/utils/admin-settings';
const onboarding = getAdminSetting( 'onboarding', {} );
class Industry extends Component {
constructor( props ) {
const profileItems = get( props, 'profileItems', {} );
let selected = Array.isArray( profileItems.industry )
? [ ...profileItems.industry ]
: [];
/**
* @todo Remove block on `updateProfileItems` refactor to wp.data dataStores.
*
* The following block is a side effect of wc-api not being truly async
* and is a temporary fix until a refactor to wp.data can take place.
*
* Calls to `updateProfileItems` in the previous screen happen async
* and won't be updated in wc-api's state when this component is initialized.
* As such, we need to make sure cbd is not initialized as selected when a
* user has changed location to non-US based.
*/
const { locationSettings } = props;
const region = getCurrencyRegion(
locationSettings.woocommerce_default_country
);
if ( region !== 'US' ) {
const cbdSlug = 'cbd-other-hemp-derived-products';
selected = selected.filter( ( industry ) => {
return cbdSlug !== industry && cbdSlug !== industry.slug;
} );
}
/**
* End block to be removed after refactor.
*/
super();
this.state = {
error: null,
selected,
textInputListContent: {},
};
this.onContinue = this.onContinue.bind( this );
this.onIndustryChange = this.onIndustryChange.bind( this );
this.onDetailChange = this.onDetailChange.bind( this );
const selectedSlugs = this.getSelectedSlugs();
props.trackStepValueChanges(
props.step.key,
selectedSlugs,
selectedSlugs,
this.onContinue
);
}
getSelectedSlugs() {
return this.state.selected.map( ( industry ) => industry.slug );
}
componentDidMount() {
recordEvent( 'onboarding_site_heuristics', {
page_count: onboarding.pageCount,
post_count: onboarding.postCount,
is_block_theme: onboarding.isBlockTheme,
} );
}
componentDidUpdate() {
this.props.updateCurrentStepValues(
this.props.step.key,
this.getSelectedSlugs()
);
}
async onContinue() {
await this.validateField();
if ( this.state.error ) {
return;
}
const { createNotice, isError, updateProfileItems } = this.props;
const selectedIndustriesList = this.state.selected.map(
( industry ) => industry.slug
);
// Here the selected industries are converted to a string that is a comma separated list
const industriesWithDetail = this.state.selected
.map( ( industry ) => industry.detail )
.filter( ( n ) => n )
.join( ',' );
recordEvent( 'storeprofiler_store_industry_continue', {
store_industry: selectedIndustriesList,
industries_with_detail: industriesWithDetail,
} );
await updateProfileItems( { industry: this.state.selected } );
if ( isError ) {
createNotice(
'error',
__(
'There was a problem updating your industries',
'woocommerce'
)
);
return Promise.reject();
}
return true;
}
async validateField() {
const error = this.state.selected.length
? null
: __( 'Please select at least one industry', 'woocommerce' );
this.setState( { error } );
}
onIndustryChange( slug ) {
this.setState(
( state ) => {
const newSelected = state.selected;
const selectedIndustry = find( newSelected, { slug } );
if ( selectedIndustry ) {
const newTextInputListContent = state.textInputListContent;
newTextInputListContent[ slug ] = selectedIndustry.detail;
return {
selected:
filter( state.selected, ( value ) => {
return value.slug !== slug;
} ) || [],
textInputListContent: newTextInputListContent,
};
}
newSelected.push( {
slug,
detail: state.textInputListContent[ slug ],
} );
return {
selected: newSelected,
};
},
() => this.validateField()
);
}
onDetailChange( value, slug ) {
this.setState( ( state ) => {
const newSelected = state.selected;
const newTextInputListContent = state.textInputListContent;
const industryIndex = findIndex( newSelected, { slug } );
newSelected[ industryIndex ].detail = value;
newTextInputListContent[ slug ] = value;
return {
selected: newSelected,
textInputListContent: newTextInputListContent,
};
} );
}
renderIndustryLabel( slug, industry, selectedIndustry ) {
const { textInputListContent } = this.state;
return (
<>
{ industry.label }
{ industry.use_description && selectedIndustry && (
<TextControl
key={ `text-control-${ slug }` }
label={ industry.description_label }
value={
selectedIndustry.detail ||
textInputListContent[ slug ] ||
''
}
onChange={ ( value ) =>
this.onDetailChange( value, slug )
}
className="woocommerce-profile-wizard__text"
/>
) }
</>
);
}
render() {
const { industries } = onboarding;
const { error, selected } = this.state;
const { locationSettings, isProfileItemsRequesting } = this.props;
const region = getCurrencyRegion(
locationSettings.woocommerce_default_country
);
const industryKeys = Object.keys( industries );
const filteredIndustryKeys =
region === 'US'
? industryKeys
: industryKeys.filter(
( slug ) => slug !== 'cbd-other-hemp-derived-products'
);
return (
<Fragment>
<div className="woocommerce-profile-wizard__step-header">
<Text
variant="title.small"
as="h2"
size="20"
lineHeight="28px"
>
{ __(
'In which industry does the store operate?',
'woocommerce'
) }
</Text>
<Text variant="body" as="p">
{ __( 'Choose any that apply', 'woocommerce' ) }
</Text>
</div>
<Card>
<CardBody size={ null }>
<div className="woocommerce-profile-wizard__checkbox-group">
{ filteredIndustryKeys.map( ( slug ) => {
const selectedIndustry = find( selected, {
slug,
} );
return (
<CheckboxControl
key={ `checkbox-control-${ slug }` }
label={ this.renderIndustryLabel(
slug,
industries[ slug ],
selectedIndustry
) }
onChange={ () =>
this.onIndustryChange( slug )
}
checked={ selectedIndustry || false }
className="woocommerce-profile-wizard__checkbox"
/>
);
} ) }
{ error && (
<span className="woocommerce-profile-wizard__error">
{ error }
</span>
) }
</div>
</CardBody>
<CardFooter isBorderless justify="center">
<Button
isPrimary
onClick={ () => {
this.onContinue().then(
this.props.goToNextStep
);
} }
isBusy={ isProfileItemsRequesting }
disabled={
! selected.length || isProfileItemsRequesting
}
aria-disabled={
! selected.length || isProfileItemsRequesting
}
>
{ __( 'Continue', 'woocommerce' ) }
</Button>
</CardFooter>
</Card>
</Fragment>
);
}
}
const Loader = ( props ) => {
if ( props.isLoading ) {
return (
<div
className="woocommerce-admin__industry__spinner"
style={ { textAlign: 'center' } }
>
<Spinner />
</div>
);
}
return <Industry { ...props } />;
};
export default compose(
withSelect( ( select ) => {
const {
getProfileItems,
getOnboardingError,
isOnboardingRequesting,
hasFinishedResolution: hasOnboardingFinishedResolution,
} = select( ONBOARDING_STORE_NAME );
const {
getSettings,
hasFinishedResolution: hasSettingsFinishedResolution,
} = select( SETTINGS_STORE_NAME );
const { general: locationSettings = {} } = getSettings( 'general' );
return {
isError: Boolean( getOnboardingError( 'updateProfileItems' ) ),
profileItems: getProfileItems(),
locationSettings,
isProfileItemsRequesting:
isOnboardingRequesting( 'updateProfileItems' ),
isLoading:
! hasOnboardingFinishedResolution( 'getProfileItems', [] ) ||
! hasSettingsFinishedResolution( 'getSettings', [ 'general' ] ),
};
} ),
withDispatch( ( dispatch ) => {
const { updateProfileItems } = dispatch( ONBOARDING_STORE_NAME );
const { createNotice } = dispatch( 'core/notices' );
return {
createNotice,
updateProfileItems,
};
} )
)( Loader );

View File

@ -1,394 +0,0 @@
/**
* External dependencies
*/
import { __ } from '@wordpress/i18n';
import { Component } from '@wordpress/element';
import { compose } from '@wordpress/compose';
import {
Button,
Card,
CardBody,
CardFooter,
CheckboxControl,
FormToggle,
Spinner,
} from '@wordpress/components';
import { includes, filter } from 'lodash';
import { recordEvent } from '@woocommerce/tracks';
import { withDispatch, withSelect } from '@wordpress/data';
import {
ONBOARDING_STORE_NAME,
PLUGINS_STORE_NAME,
SETTINGS_STORE_NAME,
} from '@woocommerce/data';
import { Text } from '@woocommerce/experimental';
/**
* Internal dependencies
*/
import { createNoticesFromResponse } from '~/lib/notices';
import { getCountryCode } from '../../../dashboard/utils';
import ProductTypeLabel from './label';
import './style.scss';
export class ProductTypes extends Component {
constructor() {
super();
this.state = {
error: null,
isMonthlyPricing: true,
selected: [],
isWCPayInstalled: null,
};
this.onContinue = this.onContinue.bind( this );
this.onChange = this.onChange.bind( this );
}
componentDidMount() {
const { installedPlugins, invalidateResolution } = this.props;
const { isWCPayInstalled } = this.state;
invalidateResolution( 'getProductTypes', [] );
if ( isWCPayInstalled === null && installedPlugins ) {
this.setState( {
isWCPayInstalled: installedPlugins.includes(
'woocommerce-payments'
),
} );
}
}
componentDidUpdate( prevProps, prevState ) {
const { profileItems, productTypes } = this.props;
if ( this.state.selected !== prevState.selected ) {
this.props.updateCurrentStepValues(
this.props.step.key,
this.state.selected
);
}
if ( prevProps.productTypes !== productTypes ) {
const defaultProductTypes = Object.keys( productTypes ).filter(
( key ) => !! productTypes[ key ].default
);
this.setState(
{
selected: Array.isArray( profileItems.product_types )
? [ ...profileItems.product_types ]
: defaultProductTypes,
},
() => {
this.props.trackStepValueChanges(
this.props.step.key,
[ ...this.state.selected ],
this.state.selected,
this.onContinue
);
}
);
}
}
validateField() {
const error = this.state.selected.length
? null
: __( 'Please select at least one product type', 'woocommerce' );
this.setState( { error } );
return ! error;
}
onContinue( onSuccess ) {
const { selected } = this.state;
const { installedPlugins = [] } = this.props;
if ( ! this.validateField() ) {
return;
}
const {
countryCode,
createNotice,
installAndActivatePlugins,
updateProfileItems,
productTypes,
} = this.props;
const eventProps = {
product_type: selected,
wcpay_installed: false,
};
const promises = [ updateProfileItems( { product_types: selected } ) ];
if (
window.wcAdminFeatures &&
window.wcAdminFeatures.subscriptions &&
countryCode === 'US' &&
productTypes.subscriptions &&
! productTypes.subscriptions.yearly_price &&
! installedPlugins.includes( 'woocommerce-payments' ) &&
selected.includes( 'subscriptions' )
) {
promises.push(
installAndActivatePlugins( [ 'woocommerce-payments' ] )
.then( ( response ) => {
eventProps.wcpay_installed = true;
if (
response.data &&
response.data.install_time &&
response.data.install_time[ 'woocommerce-payments' ]
) {
eventProps.install_time_wcpay =
response.data.install_time[
'woocommerce-payments'
];
}
createNoticesFromResponse( response );
} )
.catch( ( error ) => {
createNoticesFromResponse( error );
throw new Error();
} )
);
}
Promise.all( promises )
.then( () => {
recordEvent(
'storeprofiler_store_product_type_continue',
eventProps
);
if ( typeof onSuccess === 'function' ) {
onSuccess();
}
} )
.catch( () =>
createNotice(
'error',
__(
'There was a problem updating your product types',
'woocommerce'
)
)
);
}
onChange( slug ) {
this.setState(
( state ) => {
if ( includes( state.selected, slug ) ) {
return {
selected:
filter( state.selected, ( value ) => {
return value !== slug;
} ) || [],
};
}
const newSelected = state.selected;
newSelected.push( slug );
return {
selected: newSelected,
};
},
() => this.validateField()
);
}
render() {
const { productTypes = [] } = this.props;
const { error, isMonthlyPricing, isWCPayInstalled, selected } =
this.state;
const {
countryCode,
isInstallingActivating,
isProductTypesRequesting,
isProfileItemsRequesting,
} = this.props;
if ( isProductTypesRequesting ) {
return (
<div className="woocommerce-profile-wizard__product-types__spinner">
<Spinner />
</div>
);
}
return (
<div className="woocommerce-profile-wizard__product-types">
<div className="woocommerce-profile-wizard__step-header">
<Text
variant="title.small"
as="h2"
size="20"
lineHeight="28px"
>
{ __(
'What type of products will be listed?',
'woocommerce'
) }
</Text>
<Text variant="body" as="p">
{ __( 'Choose any that apply', 'woocommerce' ) }
</Text>
</div>
<Card>
<CardBody size={ null }>
{ Object.keys( productTypes ).map( ( slug ) => {
return (
<CheckboxControl
key={ slug }
label={
<ProductTypeLabel
description={
productTypes[ slug ].description
}
label={ productTypes[ slug ].label }
annualPrice={
productTypes[ slug ]
.yearly_price
}
isMonthlyPricing={
isMonthlyPricing
}
moreUrl={
productTypes[ slug ].more_url
}
slug={ slug }
/>
}
onChange={ () => this.onChange( slug ) }
checked={ selected.includes( slug ) }
className="woocommerce-profile-wizard__checkbox"
/>
);
} ) }
{ error && (
<span className="woocommerce-profile-wizard__error">
{ error }
</span>
) }
</CardBody>
<CardFooter isBorderless justify="center">
<Button
isPrimary
onClick={ () => {
this.onContinue( this.props.goToNextStep );
} }
isBusy={
isProfileItemsRequesting ||
isInstallingActivating
}
disabled={
! selected.length ||
isProfileItemsRequesting ||
isInstallingActivating
}
aria-disabled={
! selected.length ||
isProfileItemsRequesting ||
isInstallingActivating
}
>
{ __( 'Continue', 'woocommerce' ) }
</Button>
</CardFooter>
</Card>
<div className="woocommerce-profile-wizard__card-help-footnote">
<div className="woocommerce-profile-wizard__product-types-pricing-toggle woocommerce-profile-wizard__checkbox">
<label htmlFor="woocommerce-product-types__pricing-toggle">
<Text variant="body" as="p">
{ __(
'Display monthly prices',
'woocommerce'
) }
</Text>
<FormToggle
id="woocommerce-product-types__pricing-toggle"
checked={ isMonthlyPricing }
onChange={ () =>
this.setState( {
isMonthlyPricing: ! isMonthlyPricing,
} )
}
/>
</label>
</div>
<Text variant="caption" size="12" lineHeight="16px">
{ __(
'Billing is annual. All purchases are covered by our 30 day money back guarantee and include access to support and updates. Extensions will be added to a cart for you to purchase later.',
'woocommerce'
) }
</Text>
{ window.wcAdminFeatures &&
window.wcAdminFeatures.subscriptions &&
countryCode === 'US' &&
! isWCPayInstalled &&
productTypes.subscriptions &&
! productTypes.subscriptions.yearly_price &&
selected.includes( 'subscriptions' ) && (
<Text
variant="body"
size="12"
lineHeight="16px"
as="p"
>
{ __(
'The following extensions will be added to your site for free: WooPayments. An account is required to use this feature.',
'woocommerce'
) }
</Text>
) }
</div>
</div>
);
}
}
export default compose(
withSelect( ( select ) => {
const {
getProfileItems,
getProductTypes,
getOnboardingError,
hasFinishedResolution,
isOnboardingRequesting,
} = select( ONBOARDING_STORE_NAME );
const { getSettings } = select( SETTINGS_STORE_NAME );
const { getInstalledPlugins, isPluginsRequesting } =
select( PLUGINS_STORE_NAME );
const { general: settings = {} } = getSettings( 'general' );
return {
isError: Boolean( getOnboardingError( 'updateProfileItems' ) ),
profileItems: getProfileItems(),
isProfileItemsRequesting:
isOnboardingRequesting( 'updateProfileItems' ),
installedPlugins: getInstalledPlugins(),
isInstallingActivating:
isPluginsRequesting( 'installPlugins' ) ||
isPluginsRequesting( 'activatePlugins' ),
countryCode: getCountryCode( settings.woocommerce_default_country ),
productTypes: getProductTypes(),
isProductTypesRequesting:
! hasFinishedResolution( 'getProductTypes' ),
};
} ),
withDispatch( ( dispatch ) => {
const { updateProfileItems } = dispatch( ONBOARDING_STORE_NAME );
const { createNotice } = dispatch( 'core/notices' );
const { installAndActivatePlugins } = dispatch( PLUGINS_STORE_NAME );
const { invalidateResolution } = dispatch( ONBOARDING_STORE_NAME );
return {
createNotice,
installAndActivatePlugins,
invalidateResolution,
updateProfileItems,
};
} )
)( ProductTypes );

View File

@ -1,100 +0,0 @@
/**
* External dependencies
*/
import { __, sprintf } from '@wordpress/i18n';
import { Button, Popover, Tooltip } from '@wordpress/components';
import { Fragment, useState } from '@wordpress/element';
import interpolateComponents from '@automattic/interpolate-components';
import { Link, Pill } from '@woocommerce/components';
import { recordEvent } from '@woocommerce/tracks';
import { Icon, info } from '@wordpress/icons';
export default function ProductTypeLabel( {
annualPrice,
description,
isMonthlyPricing,
label,
moreUrl,
slug,
} ) {
const [ isPopoverVisible, setIsPopoverVisible ] = useState( '' );
if ( ! annualPrice ) {
return label;
}
/* eslint-disable @wordpress/i18n-no-collapsible-whitespace */
const toolTipText = __(
"This product type requires a paid extension.\nWe'll add this to a cart so that\nyou can purchase and install it later.",
'woocommerce'
);
/* eslint-enable @wordpress/i18n-no-collapsible-whitespace */
return (
<Fragment>
<span className="woocommerce-product-wizard__product-types-label">
{ label }
</span>
<Button
isTertiary
label={ __(
'Learn more about recommended free business features',
'woocommerce'
) }
onClick={ () => {
setIsPopoverVisible( true );
} }
>
<Icon icon={ info } />
</Button>
{ isPopoverVisible && (
<Popover
focusOnMount="container"
position="top center"
onClose={ () => setIsPopoverVisible( false ) }
>
{ interpolateComponents( {
mixedString:
description + ( moreUrl ? ' {{moreLink/}}' : '' ),
components: {
moreLink: moreUrl ? (
<Link
href={ moreUrl }
target="_blank"
type="external"
onClick={ () =>
recordEvent(
'storeprofiler_store_product_type_learn_more',
{
product_type: slug,
}
)
}
>
{ __( 'Learn more', 'woocommerce' ) }
</Link>
) : (
''
),
},
} ) }
</Popover>
) }
<Tooltip text={ toolTipText } position="bottom center">
<Pill>
<span className="screen-reader-text">{ toolTipText }</span>
{ isMonthlyPricing
? sprintf(
/* translators: Dollar amount (example: $4.08 ) */
__( '$%f per month', 'woocommerce' ),
( annualPrice / 12.0 ).toFixed( 2 )
)
: sprintf(
/* translators: Dollar amount (example: $49.00 ) */
__( '$%f per year', 'woocommerce' ),
annualPrice
) }
</Pill>
</Tooltip>
</Fragment>
);
}

View File

@ -1,98 +0,0 @@
/**
* External dependencies
*/
import { __, sprintf } from '@wordpress/i18n';
import { Button, Popover, Tooltip } from '@wordpress/components';
import { useState } from '@wordpress/element';
import interpolateComponents from '@automattic/interpolate-components';
import { Link, Pill } from '@woocommerce/components';
import { recordEvent } from '@woocommerce/tracks';
import { Icon, info } from '@wordpress/icons';
export default function ProductType( {
annualPrice,
description,
isMonthlyPricing,
label,
moreUrl,
slug,
} ) {
const [ isPopoverVisible, setIsPopoverVisible ] = useState( '' );
if ( ! annualPrice ) {
return label;
}
/* eslint-disable @wordpress/i18n-no-collapsible-whitespace */
const toolTipText = __(
"This product type requires a paid extension.\nWe'll add this to a cart so that\nyou can purchase and install it later.",
'woocommerce'
);
/* eslint-enable @wordpress/i18n-no-collapsible-whitespace */
return (
<div className="woocommerce-product-type">
<span className="woocommerce-product-type__label">{ label }</span>
<Button
isTertiary
label={ __(
'Learn more about recommended free business features',
'woocommerce'
) }
onClick={ () => {
setIsPopoverVisible( true );
} }
>
<Icon icon={ info } />
</Button>
{ isPopoverVisible && (
<Popover
focusOnMount="container"
position="top center"
onClose={ () => setIsPopoverVisible( false ) }
>
{ interpolateComponents( {
mixedString:
description + ( moreUrl ? ' {{moreLink/}}' : '' ),
components: {
moreLink: moreUrl ? (
<Link
href={ moreUrl }
target="_blank"
type="external"
onClick={ () =>
recordEvent(
'storeprofiler_store_product_type_learn_more',
{
product_type: slug,
}
)
}
>
{ __( 'Learn more', 'woocommerce' ) }
</Link>
) : (
''
),
},
} ) }
</Popover>
) }
<Tooltip text={ toolTipText } position="bottom center">
<Pill>
<span className="screen-reader-text">{ toolTipText }</span>
{ isMonthlyPricing
? sprintf(
/* translators: Dollar amount (example: $4.08 ) */
__( '$%f per month', 'woocommerce' ),
( annualPrice / 12.0 ).toFixed( 2 )
)
: sprintf(
/* translators: Dollar amount (example: $49.00 ) */
__( '$%f per year', 'woocommerce' ),
annualPrice
) }
</Pill>
</Tooltip>
</div>
);
}

View File

@ -1,87 +0,0 @@
.woocommerce-profile-wizard__body {
.woocommerce-profile-wizard__product-types {
.components-base-control__field {
display: flex;
}
.woocommerce-product-wizard__product-types-label {
display: inline-block;
margin-right: $gap-smallest;
}
.woocommerce-profile-wizard__checkbox-group {
.woocommerce-profile-wizard__checkbox {
display: flex;
align-items: center;
min-height: 64px;
.components-button {
padding: 0;
height: auto;
}
}
}
.components-base-control__field {
display: flex;
width: 100%;
align-items: center;
}
.components-checkbox-control__label {
display: flex;
align-items: center;
width: 100%;
.woocommerce-pill {
margin-left: auto;
}
}
.components-popover .components-popover__content {
min-width: 360px;
}
.woocommerce-profile-wizard__product-types-pricing-toggle.woocommerce-profile-wizard__checkbox {
display: flex;
align-items: center;
justify-content: flex-end;
color: $gray-600;
margin-bottom: $gap;
label {
display: inline-flex;
align-items: center;
margin: auto;
}
.components-form-toggle {
display: inline-flex;
margin-left: $gap;
}
}
&__spinner {
text-align: center;
}
}
}
@media screen and ( max-width: 438px ) {
.woocommerce-profile-wizard__body {
.woocommerce-profile-wizard__product-types {
.woocommerce-product-wizard__product-types-label {
width: min-content;
}
}
}
}
@media screen and ( max-width: 375px ) {
.woocommerce-profile-wizard__body {
.woocommerce-profile-wizard__product-types {
.woocommerce-pill {
white-space: nowrap;
}
}
}
}

View File

@ -1,393 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`ProductTypes should render product types 1`] = `
<div>
<div
class="woocommerce-profile-wizard__product-types"
>
<div
class="woocommerce-profile-wizard__step-header"
>
<h2
class="components-truncate components-text css-7a2wz9-View-Text-sx-Base em57xhy0"
data-wp-c16t="true"
data-wp-component="Text"
>
What type of products will be listed?
</h2>
<p
class="components-truncate components-text css-sgni2d-View-Text-sx-Base em57xhy0"
data-wp-c16t="true"
data-wp-component="Text"
>
Choose any that apply
</p>
</div>
<div
class="components-surface components-card css-nsno0f-View-Surface-getBorders-primary-Card-rounded em57xhy0"
data-wp-c16t="true"
data-wp-component="Card"
>
<div
class="css-mgwsf4-View-Content em57xhy0"
>
<div
class="components-card__body components-card-body css-1i4jx7i-View-Body-borderRadius em57xhy0"
data-wp-c16t="true"
data-wp-component="CardBody"
>
<div
class="components-base-control components-checkbox-control woocommerce-profile-wizard__checkbox css-wdf2ti-Wrapper ej5x27r4"
>
<div
class="components-base-control__field css-10urnh1-StyledField-deprecatedMarginField ej5x27r3"
>
<span
class="components-checkbox-control__input-container"
>
<input
class="components-checkbox-control__input"
id="inspector-checkbox-control-0"
type="checkbox"
value="1"
/>
</span>
<label
class="components-checkbox-control__label"
for="inspector-checkbox-control-0"
>
<span
class="woocommerce-product-wizard__product-types-label"
>
Paid product
</span>
<button
aria-label="Learn more about recommended free business features"
class="components-button is-tertiary"
type="button"
>
<svg
aria-hidden="true"
focusable="false"
height="24"
viewBox="0 0 24 24"
width="24"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M12 3.2c-4.8 0-8.8 3.9-8.8 8.8 0 4.8 3.9 8.8 8.8 8.8 4.8 0 8.8-3.9 8.8-8.8 0-4.8-4-8.8-8.8-8.8zm0 16c-4 0-7.2-3.3-7.2-7.2C4.8 8 8 4.8 12 4.8s7.2 3.3 7.2 7.2c0 4-3.2 7.2-7.2 7.2zM11 17h2v-6h-2v6zm0-8h2V7h-2v2z"
/>
</svg>
</button>
<span
class="components-truncate components-text woocommerce-pill css-1g4vivm-View-Text-sx-Base em57xhy0"
data-wp-c16t="true"
data-wp-component="Text"
>
<span
class="screen-reader-text"
>
This product type requires a paid extension.
We'll add this to a cart so that
you can purchase and install it later.
</span>
$10 per month
</span>
</label>
</div>
</div>
<div
class="components-base-control components-checkbox-control woocommerce-profile-wizard__checkbox css-wdf2ti-Wrapper ej5x27r4"
>
<div
class="components-base-control__field css-10urnh1-StyledField-deprecatedMarginField ej5x27r3"
>
<span
class="components-checkbox-control__input-container"
>
<input
class="components-checkbox-control__input"
id="inspector-checkbox-control-1"
type="checkbox"
value="1"
/>
</span>
<label
class="components-checkbox-control__label"
for="inspector-checkbox-control-1"
>
Free product
</label>
</div>
</div>
</div>
<div
class="components-flex components-card__footer components-card-footer css-16c019y-View-Flex-sx-Base-sx-Items-ItemsRow-Footer-borderRadius-borderColor-medium-borderless em57xhy0"
data-wp-c16t="true"
data-wp-component="CardFooter"
>
<button
aria-disabled="true"
class="components-button is-primary"
disabled=""
type="button"
>
Continue
</button>
</div>
</div>
<div
aria-hidden="true"
class="components-elevation css-91yjwm-View-Elevation-sx-Base-elevationClassName em57xhy0"
data-wp-c16t="true"
data-wp-component="Elevation"
/>
<div
aria-hidden="true"
class="components-elevation css-91yjwm-View-Elevation-sx-Base-elevationClassName em57xhy0"
data-wp-c16t="true"
data-wp-component="Elevation"
/>
</div>
<div
class="woocommerce-profile-wizard__card-help-footnote"
>
<div
class="woocommerce-profile-wizard__product-types-pricing-toggle woocommerce-profile-wizard__checkbox"
>
<label
for="woocommerce-product-types__pricing-toggle"
>
<p
class="components-truncate components-text css-sgni2d-View-Text-sx-Base em57xhy0"
data-wp-c16t="true"
data-wp-component="Text"
>
Display monthly prices
</p>
<span
class="components-form-toggle is-checked"
>
<input
checked=""
class="components-form-toggle__input"
id="woocommerce-product-types__pricing-toggle"
type="checkbox"
/>
<span
class="components-form-toggle__track"
/>
<span
class="components-form-toggle__thumb"
/>
</span>
</label>
</div>
<span
class="components-truncate components-text css-1g4vivm-View-Text-sx-Base em57xhy0"
data-wp-c16t="true"
data-wp-component="Text"
>
Billing is annual. All purchases are covered by our 30 day money back guarantee and include access to support and updates. Extensions will be added to a cart for you to purchase later.
</span>
</div>
</div>
</div>
`;
exports[`ProductTypes should show annual prices on toggle 1`] = `
<div>
<div
class="woocommerce-profile-wizard__product-types"
>
<div
class="woocommerce-profile-wizard__step-header"
>
<h2
class="components-truncate components-text css-7a2wz9-View-Text-sx-Base em57xhy0"
data-wp-c16t="true"
data-wp-component="Text"
>
What type of products will be listed?
</h2>
<p
class="components-truncate components-text css-sgni2d-View-Text-sx-Base em57xhy0"
data-wp-c16t="true"
data-wp-component="Text"
>
Choose any that apply
</p>
</div>
<div
class="components-surface components-card css-nsno0f-View-Surface-getBorders-primary-Card-rounded em57xhy0"
data-wp-c16t="true"
data-wp-component="Card"
>
<div
class="css-mgwsf4-View-Content em57xhy0"
>
<div
class="components-card__body components-card-body css-1i4jx7i-View-Body-borderRadius em57xhy0"
data-wp-c16t="true"
data-wp-component="CardBody"
>
<div
class="components-base-control components-checkbox-control woocommerce-profile-wizard__checkbox css-wdf2ti-Wrapper ej5x27r4"
>
<div
class="components-base-control__field css-10urnh1-StyledField-deprecatedMarginField ej5x27r3"
>
<span
class="components-checkbox-control__input-container"
>
<input
class="components-checkbox-control__input"
id="inspector-checkbox-control-2"
type="checkbox"
value="1"
/>
</span>
<label
class="components-checkbox-control__label"
for="inspector-checkbox-control-2"
>
<span
class="woocommerce-product-wizard__product-types-label"
>
Paid product
</span>
<button
aria-label="Learn more about recommended free business features"
class="components-button is-tertiary"
type="button"
>
<svg
aria-hidden="true"
focusable="false"
height="24"
viewBox="0 0 24 24"
width="24"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M12 3.2c-4.8 0-8.8 3.9-8.8 8.8 0 4.8 3.9 8.8 8.8 8.8 4.8 0 8.8-3.9 8.8-8.8 0-4.8-4-8.8-8.8-8.8zm0 16c-4 0-7.2-3.3-7.2-7.2C4.8 8 8 4.8 12 4.8s7.2 3.3 7.2 7.2c0 4-3.2 7.2-7.2 7.2zM11 17h2v-6h-2v6zm0-8h2V7h-2v2z"
/>
</svg>
</button>
<span
class="components-truncate components-text woocommerce-pill css-1g4vivm-View-Text-sx-Base em57xhy0"
data-wp-c16t="true"
data-wp-component="Text"
>
<span
class="screen-reader-text"
>
This product type requires a paid extension.
We'll add this to a cart so that
you can purchase and install it later.
</span>
$120 per year
</span>
</label>
</div>
</div>
<div
class="components-base-control components-checkbox-control woocommerce-profile-wizard__checkbox css-wdf2ti-Wrapper ej5x27r4"
>
<div
class="components-base-control__field css-10urnh1-StyledField-deprecatedMarginField ej5x27r3"
>
<span
class="components-checkbox-control__input-container"
>
<input
class="components-checkbox-control__input"
id="inspector-checkbox-control-3"
type="checkbox"
value="1"
/>
</span>
<label
class="components-checkbox-control__label"
for="inspector-checkbox-control-3"
>
Free product
</label>
</div>
</div>
</div>
<div
class="components-flex components-card__footer components-card-footer css-16c019y-View-Flex-sx-Base-sx-Items-ItemsRow-Footer-borderRadius-borderColor-medium-borderless em57xhy0"
data-wp-c16t="true"
data-wp-component="CardFooter"
>
<button
aria-disabled="true"
class="components-button is-primary"
disabled=""
type="button"
>
Continue
</button>
</div>
</div>
<div
aria-hidden="true"
class="components-elevation css-91yjwm-View-Elevation-sx-Base-elevationClassName em57xhy0"
data-wp-c16t="true"
data-wp-component="Elevation"
/>
<div
aria-hidden="true"
class="components-elevation css-91yjwm-View-Elevation-sx-Base-elevationClassName em57xhy0"
data-wp-c16t="true"
data-wp-component="Elevation"
/>
</div>
<div
class="woocommerce-profile-wizard__card-help-footnote"
>
<div
class="woocommerce-profile-wizard__product-types-pricing-toggle woocommerce-profile-wizard__checkbox"
>
<label
for="woocommerce-product-types__pricing-toggle"
>
<p
class="components-truncate components-text css-sgni2d-View-Text-sx-Base em57xhy0"
data-wp-c16t="true"
data-wp-component="Text"
>
Display monthly prices
</p>
<span
class="components-form-toggle"
>
<input
checked=""
class="components-form-toggle__input"
id="woocommerce-product-types__pricing-toggle"
type="checkbox"
/>
<span
class="components-form-toggle__track"
/>
<span
class="components-form-toggle__thumb"
/>
</span>
</label>
</div>
<span
class="components-truncate components-text css-1g4vivm-View-Text-sx-Base em57xhy0"
data-wp-c16t="true"
data-wp-component="Text"
>
Billing is annual. All purchases are covered by our 30 day money back guarantee and include access to support and updates. Extensions will be added to a cart for you to purchase later.
</span>
</div>
</div>
</div>
`;

View File

@ -1,95 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`ProductType should render the product type 1`] = `
<div>
<div
class="woocommerce-product-type"
>
<span
class="woocommerce-product-type__label"
>
Product type label
</span>
<button
aria-label="Learn more about recommended free business features"
class="components-button is-tertiary"
type="button"
>
<svg
aria-hidden="true"
focusable="false"
height="24"
viewBox="0 0 24 24"
width="24"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M12 3.2c-4.8 0-8.8 3.9-8.8 8.8 0 4.8 3.9 8.8 8.8 8.8 4.8 0 8.8-3.9 8.8-8.8 0-4.8-4-8.8-8.8-8.8zm0 16c-4 0-7.2-3.3-7.2-7.2C4.8 8 8 4.8 12 4.8s7.2 3.3 7.2 7.2c0 4-3.2 7.2-7.2 7.2zM11 17h2v-6h-2v6zm0-8h2V7h-2v2z"
/>
</svg>
</button>
<span
class="components-truncate components-text woocommerce-pill css-1g4vivm-View-Text-sx-Base em57xhy0"
data-wp-c16t="true"
data-wp-component="Text"
>
<span
class="screen-reader-text"
>
This product type requires a paid extension.
We'll add this to a cart so that
you can purchase and install it later.
</span>
$120 per year
</span>
</div>
</div>
`;
exports[`ProductType should render the product type with monthly prices 1`] = `
<div>
<div
class="woocommerce-product-type"
>
<span
class="woocommerce-product-type__label"
>
Product type label
</span>
<button
aria-label="Learn more about recommended free business features"
class="components-button is-tertiary"
type="button"
>
<svg
aria-hidden="true"
focusable="false"
height="24"
viewBox="0 0 24 24"
width="24"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M12 3.2c-4.8 0-8.8 3.9-8.8 8.8 0 4.8 3.9 8.8 8.8 8.8 4.8 0 8.8-3.9 8.8-8.8 0-4.8-4-8.8-8.8-8.8zm0 16c-4 0-7.2-3.3-7.2-7.2C4.8 8 8 4.8 12 4.8s7.2 3.3 7.2 7.2c0 4-3.2 7.2-7.2 7.2zM11 17h2v-6h-2v6zm0-8h2V7h-2v2z"
/>
</svg>
</button>
<span
class="components-truncate components-text woocommerce-pill css-1g4vivm-View-Text-sx-Base em57xhy0"
data-wp-c16t="true"
data-wp-component="Text"
>
<span
class="screen-reader-text"
>
This product type requires a paid extension.
We'll add this to a cart so that
you can purchase and install it later.
</span>
$10 per month
</span>
</div>
</div>
`;

View File

@ -1,140 +0,0 @@
/**
* External dependencies
*/
import { render, screen, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
/**
* Internal dependencies
*/
import { ProductTypes } from '../';
const testProps = {
countryCode: 'US',
invalidateResolution: jest.fn(),
isProductTypesRequesting: false,
productTypes: {
paidProduct: {
description: 'Paid product type',
label: 'Paid product',
more_url: 'https://woocommerce.com/paid-product',
product: 100,
slug: 'paid-product',
yearly_price: 120,
},
freeProduct: {
label: 'Free product',
},
},
};
describe( 'ProductTypes', () => {
afterEach( () => {
window.wcAdminFeatures.subscriptions = false;
} );
test( 'should render product types', () => {
const { container } = render( <ProductTypes { ...testProps } /> );
expect( container ).toMatchSnapshot();
} );
test( 'should show annual prices on toggle', () => {
const { container } = render( <ProductTypes { ...testProps } /> );
const toggle = screen.getByLabelText( 'Display monthly prices', {
selector: 'input',
} );
userEvent.click( toggle );
expect( container ).toMatchSnapshot();
} );
test( 'should validate on continue', async () => {
const mockCreateNotice = jest.fn();
const mockGoToNextStep = jest.fn();
const mockUpdateProfileItems = jest.fn().mockResolvedValue();
render(
<ProductTypes
createNotice={ mockCreateNotice }
goToNextStep={ mockGoToNextStep }
updateProfileItems={ mockUpdateProfileItems }
{ ...testProps }
/>
);
const continueButton = screen.getByText( 'Continue', {
selector: 'button',
} );
const productType = screen.getByText( 'Free product', {
selector: 'label',
} );
// Validation should fail since no product types are selected.
userEvent.click( continueButton );
await waitFor( () => {
expect( mockGoToNextStep ).not.toHaveBeenCalled();
} );
expect( mockUpdateProfileItems ).not.toHaveBeenCalled();
// Click on a product type to pass validation.
userEvent.click( productType );
userEvent.click( continueButton );
await waitFor( () => {
expect( mockUpdateProfileItems ).toHaveBeenCalled();
} );
expect( mockGoToNextStep ).toHaveBeenCalled();
} );
test( 'should show a warning message at the bottom of the step', () => {
const productTypes = {
subscriptions: {
label: 'Subscriptions',
},
};
window.wcAdminFeatures.subscriptions = true;
render(
<ProductTypes { ...testProps } productTypes={ productTypes } />
);
const subscription = screen.getByText( 'Subscriptions', {
selector: 'label',
} );
userEvent.click( subscription );
expect(
screen.queryByText(
'The following extensions will be added to your site for free: WooPayments. An account is required to use this feature.'
)
).toBeInTheDocument();
} );
test( 'should show the warning message only for US stores', () => {
const productTypes = {
subscriptions: {
label: 'Subscriptions',
},
};
const countryCode = 'FR';
window.wcAdminFeatures.subscriptions = true;
render(
<ProductTypes
{ ...testProps }
productTypes={ productTypes }
countryCode={ countryCode }
/>
);
const subscription = screen.getByText( 'Subscriptions', {
selector: 'label',
} );
userEvent.click( subscription );
expect(
screen.queryByText(
'The following extensions will be added to your site for free: WooPayments. An account is required to use this feature.'
)
).not.toBeInTheDocument();
} );
} );

View File

@ -1,58 +0,0 @@
/**
* External dependencies
*/
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { createElement } from '@wordpress/element';
/**
* Internal dependencies
*/
import ProductType from '../product-type';
const defaultProps = {
annualPrice: 120,
label: 'Product type label',
description: 'Product type description',
moreUrl: 'https://woocommerce.com/my-product-type',
slug: 'my-product-type',
};
describe( 'ProductType', () => {
test( 'should render the product type', () => {
const { container } = render(
<ProductType { ...defaultProps } isMonthlyPricing={ false } />
);
expect( container ).toMatchSnapshot();
} );
test( 'should render the product type with monthly prices', () => {
const { container } = render(
<ProductType { ...defaultProps } isMonthlyPricing={ true } />
);
expect( container ).toMatchSnapshot();
} );
test( 'should show Popover on click', () => {
const { container } = render(
<ProductType { ...defaultProps } isMonthlyPricing={ true } />
);
const infoButton = screen.getByLabelText(
'Learn more about recommended free business features',
{
selector: 'button',
}
);
userEvent.click( infoButton );
const popover = container.querySelector( '.components-popover' );
const learnMoreLink = popover.querySelector( 'a' );
expect( popover ).not.toBeNull();
expect( popover.textContent ).toBe(
defaultProps.description + ' Learn more'
);
expect( learnMoreLink.href ).toBe( defaultProps.moreUrl );
} );
} );

View File

@ -1,115 +0,0 @@
/**
* External dependencies
*/
import { __ } from '@wordpress/i18n';
import { Button, Popover } from '@wordpress/components';
import { Icon, info } from '@wordpress/icons';
import { useState } from '@wordpress/element';
import { useSelect, useDispatch } from '@wordpress/data';
import { getHistory, getNewPath } from '@woocommerce/navigation';
import { ONBOARDING_STORE_NAME, OPTIONS_STORE_NAME } from '@woocommerce/data';
/**
* Internal dependencies
*/
import UsageModal from './usage-modal';
const SkipButton: React.FC< {
onSkipped?: () => void;
} > = ( { onSkipped } ) => {
/* eslint-disable @wordpress/i18n-no-collapsible-whitespace */
const skipSetupText = __(
'Manual setup is only recommended for\n experienced WooCommerce users or developers.',
'woocommerce'
);
const { createNotice } = useDispatch( 'core/notices' );
const { invalidateResolutionForStoreSelector, updateProfileItems } =
useDispatch( ONBOARDING_STORE_NAME );
const trackingAllowed = useSelect(
( select ) =>
select( OPTIONS_STORE_NAME ).getOption(
'woocommerce_allow_tracking'
) === 'yes'
);
const [ isSkipSetupPopoverVisible, setSkipSetupPopoverVisibility ] =
useState( false );
const [ showUsageModal, setShowUsageModal ] = useState( false );
const skipProfiler = () => {
updateProfileItems( {
skipped: true,
} )
.then( () => {
if ( onSkipped ) {
onSkipped();
}
getHistory().push( getNewPath( {}, '/', {} ) );
} )
.catch( () => {
createNotice(
'error',
__(
'There was a problem skipping the setup wizard',
'woocommerce'
)
);
} );
};
return (
<>
{ showUsageModal && (
<UsageModal
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore -- ignoring it for now as UsageModal is not in ts yet.
onContinue={ () => {
skipProfiler();
} }
onClose={ () => {
invalidateResolutionForStoreSelector( 'getTaskLists' );
setShowUsageModal( false );
} }
/>
) }
<div className="woocommerce-profile-wizard__footer">
<Button
isLink
className="woocommerce-profile-wizard__footer-link"
onClick={ () => {
if ( trackingAllowed ) {
skipProfiler();
} else {
setShowUsageModal( true );
}
} }
>
{ __( 'Skip', 'woocommerce' ) }
</Button>
<Button
isTertiary
label={ skipSetupText }
onClick={ () => {
setSkipSetupPopoverVisibility( true );
} }
>
<Icon icon={ info } />
</Button>
{ isSkipSetupPopoverVisible && (
<Popover
focusOnMount="container"
position="top center"
onClose={ () => setSkipSetupPopoverVisibility( false ) }
>
{ skipSetupText }
</Popover>
) }
</div>
</>
);
};
export default SkipButton;

View File

@ -1,522 +0,0 @@
/**
* External dependencies
*/
import { __ } from '@wordpress/i18n';
import {
Button,
Card,
CardBody,
CardFooter,
CheckboxControl,
FlexItem as MaybeFlexItem,
Spinner,
Popover,
} from '@wordpress/components';
import { Component, useRef } from '@wordpress/element';
import { compose } from '@wordpress/compose';
import { withDispatch, withSelect } from '@wordpress/data';
import { Form, TextControl } from '@woocommerce/components';
import {
COUNTRIES_STORE_NAME,
ONBOARDING_STORE_NAME,
OPTIONS_STORE_NAME,
SETTINGS_STORE_NAME,
} from '@woocommerce/data';
import { recordEvent } from '@woocommerce/tracks';
import { Text } from '@woocommerce/experimental';
import { Icon, info } from '@wordpress/icons';
import { isEmail } from '@wordpress/url';
import { CurrencyContext } from '@woocommerce/currency';
/**
* Internal dependencies
*/
import { getCountryCode, getCurrencyRegion } from '../../../dashboard/utils';
import {
StoreAddress,
getStoreAddressValidator,
} from '../../../dashboard/components/settings/general/store-address';
import UsageModal from '../usage-modal';
import { getAdminSetting } from '~/utils/admin-settings';
import './style.scss';
// FlexItem is not available until WP version 5.5. This code is safe to remove
// once the minimum WP supported version becomes 5.5.
const FlextItemSubstitute = ( { children, align } ) => {
const style = {
display: 'flex',
'justify-content': align ? 'center' : 'flex-start',
};
return <div style={ style }>{ children }</div>;
};
const FlexItem = MaybeFlexItem || FlextItemSubstitute;
const LoadingPlaceholder = () => (
<div className="woocommerce-admin__store-details__spinner">
<Spinner />
</div>
);
export class StoreDetails extends Component {
constructor( props ) {
super( props );
this.state = {
showUsageModal: false,
skipping: false,
isSkipSetupPopoverVisible: false,
};
this.onContinue = this.onContinue.bind( this );
this.onSubmit = this.onSubmit.bind( this );
this.validateStoreDetails = this.validateStoreDetails.bind( this );
this.onFormValueChange = this.onFormValueChange.bind( this );
this.changedFormValues = {};
}
componentDidUpdate() {
if (
this.props.isLoading === false &&
Object.keys( this.changedFormValues ).length === 0
) {
// Make a copy of the initialValues.
// The values in this object gets updated on onFormValueChange.
this.changedFormValues = { ...this.props.initialValues };
this.props.trackStepValueChanges(
this.props.step.key,
this.props.initialValues,
this.changedFormValues,
() => {
this.onContinue( this.changedFormValues );
}
);
}
}
deriveCurrencySettings( countryState ) {
if ( ! countryState ) {
return null;
}
const Currency = this.context;
const country = getCountryCode( countryState );
const { currencySymbols = {}, localeInfo = {} } = getAdminSetting(
'onboarding',
{}
);
return Currency.getDataForCountry(
country,
localeInfo,
currencySymbols
);
}
onSubmit() {
this.setState( {
showUsageModal: true,
skipping: false,
} );
}
onFormValueChange( changedFormValue ) {
this.changedFormValues[ changedFormValue.name ] =
changedFormValue.value;
}
async onContinue( values ) {
const {
createNotice,
updateProfileItems,
updateAndPersistSettingsForGroup,
profileItems,
settings,
errorsRef,
} = this.props;
const currencySettings = this.deriveCurrencySettings(
values.countryState
);
const Currency = this.context;
Currency.setCurrency( currencySettings );
recordEvent( 'storeprofiler_store_details_continue', {
store_country: getCountryCode( values.countryState ),
derived_currency: currencySettings.code,
email_signup: values.isAgreeMarketing,
} );
await updateAndPersistSettingsForGroup( 'general', {
general: {
...settings,
woocommerce_store_address: values.addressLine1,
woocommerce_store_address_2: values.addressLine2,
woocommerce_default_country: values.countryState,
woocommerce_store_city: values.city,
woocommerce_store_postcode: values.postCode,
woocommerce_currency: currencySettings.code,
woocommerce_currency_pos: currencySettings.symbolPosition,
woocommerce_price_thousand_sep:
currencySettings.thousandSeparator,
woocommerce_price_decimal_sep:
currencySettings.decimalSeparator,
woocommerce_price_num_decimals: currencySettings.precision,
},
} );
const profileItemsToUpdate = {
is_agree_marketing: values.isAgreeMarketing,
store_email: values.storeEmail,
is_store_country_set:
typeof values.countryState === 'string' &&
values.countryState !== '',
};
const region = getCurrencyRegion( values.countryState );
/**
* If a user has already selected cdb industry and returns to change to a
* non US store, remove cbd industry.
*
* NOTE: the following call to `updateProfileItems` does not respect the
* `await` and performs an update aysnchronously. This means the following
* screen may not be initialized with correct profile settings.
*
* This comment may be removed when a refactor to wp.data datastores is complete.
*/
if (
region !== 'US' &&
profileItems.industry &&
profileItems.industry.length
) {
const cbdSlug = 'cbd-other-hemp-derived-products';
const trimmedIndustries = profileItems.industry.filter(
( industry ) => {
return cbdSlug !== industry && cbdSlug !== industry.slug;
}
);
profileItemsToUpdate.industry = trimmedIndustries;
}
let errorMessages = [];
try {
await updateProfileItems( profileItemsToUpdate );
} catch ( error ) {
// Array of error messages obtained from API response.
if ( error?.data?.params ) {
errorMessages = Object.values( error.data.params );
}
}
if (
! Boolean( errorsRef.current.settings ) &&
! errorMessages.length
) {
return true;
}
createNotice(
'error',
__( 'There was a problem saving your store details', 'woocommerce' )
);
errorMessages.forEach( ( message ) =>
createNotice( 'error', message )
);
}
validateStoreDetails( values ) {
const { getLocale } = this.props;
const locale = getLocale( values.countryState );
const validateAddress = getStoreAddressValidator( locale );
const errors = validateAddress( values );
if ( values.storeEmail && ! isEmail( values.storeEmail ) ) {
errors.storeEmail = __( 'Invalid email address', 'woocommerce' );
}
if (
values.isAgreeMarketing &&
( ! values.storeEmail || ! values.storeEmail.trim().length )
) {
errors.storeEmail = __(
'Please enter your email address to subscribe',
'woocommerce'
);
}
return errors;
}
render() {
const { showUsageModal, skipping, isSkipSetupPopoverVisible } =
this.state;
const {
skipProfiler,
isLoading,
isBusy,
initialValues,
invalidateResolutionForStoreSelector,
} = this.props;
/* eslint-disable @wordpress/i18n-no-collapsible-whitespace */
const skipSetupText = __(
'Manual setup is only recommended for\n experienced WooCommerce users or developers.',
'woocommerce'
);
/* eslint-enable @wordpress/i18n-no-collapsible-whitespace */
if ( isLoading ) {
return (
<div className="woocommerce-profile-wizard__store-details">
<LoadingPlaceholder />
</div>
);
}
return (
<div className="woocommerce-profile-wizard__store-details">
<div className="woocommerce-profile-wizard__step-header">
<Text
variant="title.small"
as="h2"
size="20"
lineHeight="28px"
>
{ __( 'Welcome to WooCommerce', 'woocommerce' ) }
</Text>
<Text variant="body" as="p">
{ __(
'Tell us where you run your business to help us configure currency, shipping, taxes, and more in a fully automated way.',
'woocommerce'
) }
</Text>
</div>
<Form
initialValues={ initialValues }
onSubmit={ this.onSubmit }
validate={ this.validateStoreDetails }
onChange={ this.onFormValueChange }
>
{ ( {
getInputProps,
handleSubmit,
values,
isValidForm,
setValue,
} ) => (
<Card>
{ showUsageModal && (
<UsageModal
onContinue={ () => {
if ( skipping ) {
skipProfiler();
} else {
this.onContinue( values ).then(
() => this.props.goToNextStep()
);
}
} }
onClose={ () =>
this.setState( {
showUsageModal: false,
skipping: false,
} )
}
/>
) }
<CardBody>
<StoreAddress
getInputProps={ getInputProps }
setValue={ setValue }
/>
<TextControl
label={
values.isAgreeMarketing
? __(
'Email address',
'woocommerce'
)
: __(
'Email address',
'woocommerce'
)
}
required={ values.isAgreeMarketing }
autoComplete="email"
{ ...getInputProps( 'storeEmail' ) }
/>
<FlexItem>
<div className="woocommerce-profile-wizard__newsletter-signup">
<CheckboxControl
label={
<>
{ __(
'Get tips, product updates and inspiration straight to your mailbox.',
'woocommerce'
) }{ ' ' }
<span className="woocommerce-profile-wizard__powered-by-mailchimp">
{ __(
'Powered by Mailchimp',
'woocommerce'
) }
</span>
</>
}
{ ...getInputProps(
'isAgreeMarketing'
) }
/>
</div>
</FlexItem>
</CardBody>
<CardFooter justify="center">
<Button
isPrimary
onClick={ handleSubmit }
isBusy={ isBusy }
disabled={ ! isValidForm || isBusy }
aria-disabled={ ! isValidForm || isBusy }
>
{ __( 'Continue', 'woocommerce' ) }
</Button>
</CardFooter>
</Card>
) }
</Form>
<div className="woocommerce-profile-wizard__footer">
<Button
isLink
className="woocommerce-profile-wizard__footer-link"
onClick={ () => {
invalidateResolutionForStoreSelector(
'getTaskLists'
);
this.setState( {
showUsageModal: true,
skipping: true,
} );
return false;
} }
>
{ __( 'Skip setup store details', 'woocommerce' ) }
</Button>
<Button
isTertiary
label={ skipSetupText }
onClick={ () =>
this.setState( { isSkipSetupPopoverVisible: true } )
}
>
<Icon icon={ info } />
</Button>
{ isSkipSetupPopoverVisible && (
<Popover
focusOnMount="container"
position="top center"
onClose={ () =>
this.setState( {
isSkipSetupPopoverVisible: false,
} )
}
>
{ skipSetupText }
</Popover>
) }
</div>
</div>
);
}
}
StoreDetails.contextType = CurrencyContext;
export default compose(
withSelect( ( select ) => {
const { getSettings, getSettingsError, isUpdateSettingsRequesting } =
select( SETTINGS_STORE_NAME );
const {
getProfileItems,
isOnboardingRequesting,
getEmailPrefill,
hasFinishedResolution: hasFinishedResolutionOnboarding,
} = select( ONBOARDING_STORE_NAME );
const {
getLocale,
getLocales,
getCountries,
hasFinishedResolution: hasFinishedResolutionCountries,
} = select( COUNTRIES_STORE_NAME );
const { isResolving } = select( OPTIONS_STORE_NAME );
const profileItems = getProfileItems();
const emailPrefill = getEmailPrefill();
const { general: settings = {} } = getSettings( 'general' );
const isBusy =
isOnboardingRequesting( 'updateProfileItems' ) ||
isUpdateSettingsRequesting( 'general' ) ||
isResolving( 'getOption', [ 'woocommerce_allow_tracking' ] );
const isLoading =
! hasFinishedResolutionOnboarding( 'getProfileItems' ) ||
! hasFinishedResolutionOnboarding( 'getEmailPrefill' ) ||
! hasFinishedResolutionCountries( 'getLocales' ) ||
! hasFinishedResolutionCountries( 'getCountries' );
const errorsRef = useRef( {
settings: null,
} );
errorsRef.current = {
settings: getSettingsError( 'general' ),
};
// Check if a store country is set so that we don't default
// to WooCommerce's default country of the US:CA.
const countryState = profileItems.is_store_country_set
? settings.woocommerce_default_country
: '';
getCountries();
getLocales();
const initialValues = {
addressLine1: settings.woocommerce_store_address || '',
addressLine2: settings.woocommerce_store_address_2 || '',
city: settings.woocommerce_store_city || '',
countryState,
postCode: settings.woocommerce_store_postcode || '',
// By default, the marketing checkbox should be unticked by default to comply with WordPress.org plugin review guidelines.
isAgreeMarketing:
typeof profileItems.is_agree_marketing === 'boolean'
? profileItems.is_agree_marketing
: false,
storeEmail:
typeof profileItems.store_email === 'string'
? profileItems.store_email
: emailPrefill,
};
return {
getLocale,
initialValues,
isLoading,
profileItems,
isBusy,
settings,
errorsRef,
};
} ),
withDispatch( ( dispatch ) => {
const { createNotice } = dispatch( 'core/notices' );
const { invalidateResolutionForStoreSelector, updateProfileItems } =
dispatch( ONBOARDING_STORE_NAME );
const { updateAndPersistSettingsForGroup } =
dispatch( SETTINGS_STORE_NAME );
return {
createNotice,
invalidateResolutionForStoreSelector,
updateProfileItems,
updateAndPersistSettingsForGroup,
};
} )
)( StoreDetails );

View File

@ -1,29 +0,0 @@
.woocommerce-profile-wizard__store-details {
.woocommerce-admin__store-details__spinner {
display: flex;
justify-content: center;
}
.components-popover .components-popover__content {
min-width: 360px;
}
}
.woocommerce-profile-wizard__newsletter-signup {
.components-base-control__field {
display: flex;
align-items: center;
}
.woocommerce-profile-wizard__powered-by-mailchimp {
color: $studio-gray-20;
}
}
.woocommerce-profile-wizard__store-details-error {
margin-top: -$gap-small;
font-size: 12px;
font-style: normal;
color: #d63638;
padding-left: $gap-small;
}

View File

@ -1,712 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`StoreDetails Snapshot test should match saved snapshot 1`] = `
Object {
"asFragment": [Function],
"baseElement": <body>
<p
class="a11y-speak-intro-text"
hidden="hidden"
id="a11y-speak-intro-text"
style="position: absolute;margin: -1px;padding: 0;height: 1px;width: 1px;overflow: hidden;clip: rect(1px, 1px, 1px, 1px);-webkit-clip-path: inset(50%);clip-path: inset(50%);border: 0;word-wrap: normal !important;"
>
Notifications
</p>
<div
aria-atomic="true"
aria-live="assertive"
aria-relevant="additions text"
class="a11y-speak-region"
id="a11y-speak-assertive"
style="position: absolute;margin: -1px;padding: 0;height: 1px;width: 1px;overflow: hidden;clip: rect(1px, 1px, 1px, 1px);-webkit-clip-path: inset(50%);clip-path: inset(50%);border: 0;word-wrap: normal !important;"
/>
<div
aria-atomic="true"
aria-live="polite"
aria-relevant="additions text"
class="a11y-speak-region"
id="a11y-speak-polite"
style="position: absolute;margin: -1px;padding: 0;height: 1px;width: 1px;overflow: hidden;clip: rect(1px, 1px, 1px, 1px);-webkit-clip-path: inset(50%);clip-path: inset(50%);border: 0;word-wrap: normal !important;"
/>
<div>
<div
class="woocommerce-profile-wizard__store-details"
>
<div
class="woocommerce-profile-wizard__step-header"
>
<h2
class="components-truncate components-text css-7a2wz9-View-Text-sx-Base em57xhy0"
data-wp-c16t="true"
data-wp-component="Text"
>
Welcome to WooCommerce
</h2>
<p
class="components-truncate components-text css-sgni2d-View-Text-sx-Base em57xhy0"
data-wp-c16t="true"
data-wp-component="Text"
>
Tell us where you run your business to help us configure currency, shipping, taxes, and more in a fully automated way.
</p>
</div>
<div
class="components-surface components-card css-nsno0f-View-Surface-getBorders-primary-Card-rounded em57xhy0"
data-wp-c16t="true"
data-wp-component="Card"
>
<div
class="css-mgwsf4-View-Content em57xhy0"
>
<div
class="components-card__body components-card-body css-1nwhnu3-View-Body-borderRadius-medium em57xhy0"
data-wp-c16t="true"
data-wp-component="CardBody"
>
<div
class="woocommerce-store-address-fields"
>
<div>
<div
class="woocommerce-select-control is-searchable"
>
<input
autocomplete="country"
class="woocommerce-select-control__autofill-input"
name="country"
tabindex="-1"
type="text"
value=""
/>
<input
autocomplete="address-level1"
class="woocommerce-select-control__autofill-input"
name="state"
tabindex="-1"
type="text"
value=""
/>
<div
class="components-base-control woocommerce-select-control__control empty"
>
<svg
aria-hidden="true"
class="woocommerce-select-control__control-icon"
focusable="false"
height="24"
viewBox="0 0 24 24"
width="24"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M13.5 6C10.5 6 8 8.5 8 11.5c0 1.1.3 2.1.9 3l-3.4 3 1 1.1 3.4-2.9c1 .9 2.2 1.4 3.6 1.4 3 0 5.5-2.5 5.5-5.5C19 8.5 16.5 6 13.5 6zm0 9.5c-2.2 0-4-1.8-4-4s1.8-4 4-4 4 1.8 4 4-1.8 4-4 4z"
/>
</svg>
<div
class="components-base-control__field"
>
<label
class="components-base-control__label"
for="woocommerce-select-control-0__control-input"
>
Country / Region *
</label>
<input
aria-autocomplete="list"
aria-expanded="false"
aria-haspopup="true"
aria-label="Country / Region *"
autocomplete="new-password"
class="woocommerce-select-control__control-input"
id="woocommerce-select-control-0__control-input"
placeholder=""
role="combobox"
type="search"
value=""
/>
</div>
</div>
</div>
</div>
<div>
<div
class="components-base-control muriel-component muriel-input-text empty css-wdf2ti-Wrapper ej5x27r4"
>
<div
class="components-base-control__field css-10urnh1-StyledField-deprecatedMarginField ej5x27r3"
>
<label
class="components-base-control__label css-eweeby-StyledLabel-labelStyles ej5x27r2"
for="inspector-text-control-0"
>
Address
</label>
<input
autocomplete="address-line1"
class="components-text-control__input"
id="woocommerce-store-address-form-address_1"
placeholder="Address"
type="text"
value=""
/>
</div>
</div>
</div>
<div>
<div
class="components-base-control muriel-component muriel-input-text empty css-wdf2ti-Wrapper ej5x27r4"
>
<div
class="components-base-control__field css-10urnh1-StyledField-deprecatedMarginField ej5x27r3"
>
<label
class="components-base-control__label css-eweeby-StyledLabel-labelStyles ej5x27r2"
for="inspector-text-control-1"
>
Post code
</label>
<input
autocomplete="postal-code"
class="components-text-control__input"
id="woocommerce-store-address-form-postcode"
placeholder="Post code"
type="text"
value=""
/>
</div>
</div>
</div>
<div>
<div
class="components-base-control muriel-component muriel-input-text empty css-wdf2ti-Wrapper ej5x27r4"
>
<div
class="components-base-control__field css-10urnh1-StyledField-deprecatedMarginField ej5x27r3"
>
<label
class="components-base-control__label css-eweeby-StyledLabel-labelStyles ej5x27r2"
for="inspector-text-control-2"
>
City
</label>
<input
autocomplete="address-level2"
class="components-text-control__input"
id="woocommerce-store-address-form-city"
placeholder="City"
type="text"
value=""
/>
</div>
</div>
</div>
</div>
<div>
<div
class="components-base-control muriel-component muriel-input-text with-value css-wdf2ti-Wrapper ej5x27r4"
>
<div
class="components-base-control__field css-10urnh1-StyledField-deprecatedMarginField ej5x27r3"
>
<label
class="components-base-control__label css-eweeby-StyledLabel-labelStyles ej5x27r2"
for="inspector-text-control-3"
>
Email address
</label>
<input
autocomplete="email"
checked=""
class="components-text-control__input"
id="inspector-text-control-3"
placeholder="Email address"
required=""
type="text"
value="wordpress@example.com"
/>
</div>
</div>
</div>
<div
class="components-flex-item css-mw3lhz-View-Item-sx-Base em57xhy0"
data-wp-c16t="true"
data-wp-component="FlexItem"
>
<div
class="woocommerce-profile-wizard__newsletter-signup"
>
<div
class="components-base-control components-checkbox-control css-wdf2ti-Wrapper ej5x27r4"
>
<div
class="components-base-control__field css-10urnh1-StyledField-deprecatedMarginField ej5x27r3"
>
<span
class="components-checkbox-control__input-container"
>
<input
checked=""
class="components-checkbox-control__input"
id="inspector-checkbox-control-0"
type="checkbox"
value="true"
/>
<svg
aria-hidden="true"
class="components-checkbox-control__checked"
focusable="false"
height="24"
role="presentation"
viewBox="0 0 24 24"
width="24"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M16.7 7.1l-6.3 8.5-3.3-2.5-.9 1.2 4.5 3.4L17.9 8z"
/>
</svg>
</span>
<label
class="components-checkbox-control__label"
for="inspector-checkbox-control-0"
>
Get tips, product updates and inspiration straight to your mailbox.
<span
class="woocommerce-profile-wizard__powered-by-mailchimp"
>
Powered by Mailchimp
</span>
</label>
</div>
</div>
</div>
</div>
</div>
<div
class="components-flex components-card__footer components-card-footer css-142znvl-View-Flex-sx-Base-sx-Items-ItemsRow-Footer-borderRadius-borderColor-medium em57xhy0"
data-wp-c16t="true"
data-wp-component="CardFooter"
>
<button
aria-disabled="true"
class="components-button is-primary"
disabled=""
type="button"
>
Continue
</button>
</div>
</div>
<div
aria-hidden="true"
class="components-elevation css-91yjwm-View-Elevation-sx-Base-elevationClassName em57xhy0"
data-wp-c16t="true"
data-wp-component="Elevation"
/>
<div
aria-hidden="true"
class="components-elevation css-91yjwm-View-Elevation-sx-Base-elevationClassName em57xhy0"
data-wp-c16t="true"
data-wp-component="Elevation"
/>
</div>
<div
class="woocommerce-profile-wizard__footer"
>
<button
class="components-button woocommerce-profile-wizard__footer-link is-link"
type="button"
>
Skip setup store details
</button>
<button
aria-label="Manual setup is only recommended for
experienced WooCommerce users or developers."
class="components-button is-tertiary"
type="button"
>
<svg
aria-hidden="true"
focusable="false"
height="24"
viewBox="0 0 24 24"
width="24"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M12 3.2c-4.8 0-8.8 3.9-8.8 8.8 0 4.8 3.9 8.8 8.8 8.8 4.8 0 8.8-3.9 8.8-8.8 0-4.8-4-8.8-8.8-8.8zm0 16c-4 0-7.2-3.3-7.2-7.2C4.8 8 8 4.8 12 4.8s7.2 3.3 7.2 7.2c0 4-3.2 7.2-7.2 7.2zM11 17h2v-6h-2v6zm0-8h2V7h-2v2z"
/>
</svg>
</button>
</div>
</div>
</div>
</body>,
"container": <div>
<div
class="woocommerce-profile-wizard__store-details"
>
<div
class="woocommerce-profile-wizard__step-header"
>
<h2
class="components-truncate components-text css-7a2wz9-View-Text-sx-Base em57xhy0"
data-wp-c16t="true"
data-wp-component="Text"
>
Welcome to WooCommerce
</h2>
<p
class="components-truncate components-text css-sgni2d-View-Text-sx-Base em57xhy0"
data-wp-c16t="true"
data-wp-component="Text"
>
Tell us where you run your business to help us configure currency, shipping, taxes, and more in a fully automated way.
</p>
</div>
<div
class="components-surface components-card css-nsno0f-View-Surface-getBorders-primary-Card-rounded em57xhy0"
data-wp-c16t="true"
data-wp-component="Card"
>
<div
class="css-mgwsf4-View-Content em57xhy0"
>
<div
class="components-card__body components-card-body css-1nwhnu3-View-Body-borderRadius-medium em57xhy0"
data-wp-c16t="true"
data-wp-component="CardBody"
>
<div
class="woocommerce-store-address-fields"
>
<div>
<div
class="woocommerce-select-control is-searchable"
>
<input
autocomplete="country"
class="woocommerce-select-control__autofill-input"
name="country"
tabindex="-1"
type="text"
value=""
/>
<input
autocomplete="address-level1"
class="woocommerce-select-control__autofill-input"
name="state"
tabindex="-1"
type="text"
value=""
/>
<div
class="components-base-control woocommerce-select-control__control empty"
>
<svg
aria-hidden="true"
class="woocommerce-select-control__control-icon"
focusable="false"
height="24"
viewBox="0 0 24 24"
width="24"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M13.5 6C10.5 6 8 8.5 8 11.5c0 1.1.3 2.1.9 3l-3.4 3 1 1.1 3.4-2.9c1 .9 2.2 1.4 3.6 1.4 3 0 5.5-2.5 5.5-5.5C19 8.5 16.5 6 13.5 6zm0 9.5c-2.2 0-4-1.8-4-4s1.8-4 4-4 4 1.8 4 4-1.8 4-4 4z"
/>
</svg>
<div
class="components-base-control__field"
>
<label
class="components-base-control__label"
for="woocommerce-select-control-0__control-input"
>
Country / Region *
</label>
<input
aria-autocomplete="list"
aria-expanded="false"
aria-haspopup="true"
aria-label="Country / Region *"
autocomplete="new-password"
class="woocommerce-select-control__control-input"
id="woocommerce-select-control-0__control-input"
placeholder=""
role="combobox"
type="search"
value=""
/>
</div>
</div>
</div>
</div>
<div>
<div
class="components-base-control muriel-component muriel-input-text empty css-wdf2ti-Wrapper ej5x27r4"
>
<div
class="components-base-control__field css-10urnh1-StyledField-deprecatedMarginField ej5x27r3"
>
<label
class="components-base-control__label css-eweeby-StyledLabel-labelStyles ej5x27r2"
for="inspector-text-control-0"
>
Address
</label>
<input
autocomplete="address-line1"
class="components-text-control__input"
id="woocommerce-store-address-form-address_1"
placeholder="Address"
type="text"
value=""
/>
</div>
</div>
</div>
<div>
<div
class="components-base-control muriel-component muriel-input-text empty css-wdf2ti-Wrapper ej5x27r4"
>
<div
class="components-base-control__field css-10urnh1-StyledField-deprecatedMarginField ej5x27r3"
>
<label
class="components-base-control__label css-eweeby-StyledLabel-labelStyles ej5x27r2"
for="inspector-text-control-1"
>
Post code
</label>
<input
autocomplete="postal-code"
class="components-text-control__input"
id="woocommerce-store-address-form-postcode"
placeholder="Post code"
type="text"
value=""
/>
</div>
</div>
</div>
<div>
<div
class="components-base-control muriel-component muriel-input-text empty css-wdf2ti-Wrapper ej5x27r4"
>
<div
class="components-base-control__field css-10urnh1-StyledField-deprecatedMarginField ej5x27r3"
>
<label
class="components-base-control__label css-eweeby-StyledLabel-labelStyles ej5x27r2"
for="inspector-text-control-2"
>
City
</label>
<input
autocomplete="address-level2"
class="components-text-control__input"
id="woocommerce-store-address-form-city"
placeholder="City"
type="text"
value=""
/>
</div>
</div>
</div>
</div>
<div>
<div
class="components-base-control muriel-component muriel-input-text with-value css-wdf2ti-Wrapper ej5x27r4"
>
<div
class="components-base-control__field css-10urnh1-StyledField-deprecatedMarginField ej5x27r3"
>
<label
class="components-base-control__label css-eweeby-StyledLabel-labelStyles ej5x27r2"
for="inspector-text-control-3"
>
Email address
</label>
<input
autocomplete="email"
checked=""
class="components-text-control__input"
id="inspector-text-control-3"
placeholder="Email address"
required=""
type="text"
value="wordpress@example.com"
/>
</div>
</div>
</div>
<div
class="components-flex-item css-mw3lhz-View-Item-sx-Base em57xhy0"
data-wp-c16t="true"
data-wp-component="FlexItem"
>
<div
class="woocommerce-profile-wizard__newsletter-signup"
>
<div
class="components-base-control components-checkbox-control css-wdf2ti-Wrapper ej5x27r4"
>
<div
class="components-base-control__field css-10urnh1-StyledField-deprecatedMarginField ej5x27r3"
>
<span
class="components-checkbox-control__input-container"
>
<input
checked=""
class="components-checkbox-control__input"
id="inspector-checkbox-control-0"
type="checkbox"
value="true"
/>
<svg
aria-hidden="true"
class="components-checkbox-control__checked"
focusable="false"
height="24"
role="presentation"
viewBox="0 0 24 24"
width="24"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M16.7 7.1l-6.3 8.5-3.3-2.5-.9 1.2 4.5 3.4L17.9 8z"
/>
</svg>
</span>
<label
class="components-checkbox-control__label"
for="inspector-checkbox-control-0"
>
Get tips, product updates and inspiration straight to your mailbox.
<span
class="woocommerce-profile-wizard__powered-by-mailchimp"
>
Powered by Mailchimp
</span>
</label>
</div>
</div>
</div>
</div>
</div>
<div
class="components-flex components-card__footer components-card-footer css-142znvl-View-Flex-sx-Base-sx-Items-ItemsRow-Footer-borderRadius-borderColor-medium em57xhy0"
data-wp-c16t="true"
data-wp-component="CardFooter"
>
<button
aria-disabled="true"
class="components-button is-primary"
disabled=""
type="button"
>
Continue
</button>
</div>
</div>
<div
aria-hidden="true"
class="components-elevation css-91yjwm-View-Elevation-sx-Base-elevationClassName em57xhy0"
data-wp-c16t="true"
data-wp-component="Elevation"
/>
<div
aria-hidden="true"
class="components-elevation css-91yjwm-View-Elevation-sx-Base-elevationClassName em57xhy0"
data-wp-c16t="true"
data-wp-component="Elevation"
/>
</div>
<div
class="woocommerce-profile-wizard__footer"
>
<button
class="components-button woocommerce-profile-wizard__footer-link is-link"
type="button"
>
Skip setup store details
</button>
<button
aria-label="Manual setup is only recommended for
experienced WooCommerce users or developers."
class="components-button is-tertiary"
type="button"
>
<svg
aria-hidden="true"
focusable="false"
height="24"
viewBox="0 0 24 24"
width="24"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M12 3.2c-4.8 0-8.8 3.9-8.8 8.8 0 4.8 3.9 8.8 8.8 8.8 4.8 0 8.8-3.9 8.8-8.8 0-4.8-4-8.8-8.8-8.8zm0 16c-4 0-7.2-3.3-7.2-7.2C4.8 8 8 4.8 12 4.8s7.2 3.3 7.2 7.2c0 4-3.2 7.2-7.2 7.2zM11 17h2v-6h-2v6zm0-8h2V7h-2v2z"
/>
</svg>
</button>
</div>
</div>
</div>,
"debug": [Function],
"findAllByAltText": [Function],
"findAllByDisplayValue": [Function],
"findAllByLabelText": [Function],
"findAllByPlaceholderText": [Function],
"findAllByRole": [Function],
"findAllByTestId": [Function],
"findAllByText": [Function],
"findAllByTitle": [Function],
"findByAltText": [Function],
"findByDisplayValue": [Function],
"findByLabelText": [Function],
"findByPlaceholderText": [Function],
"findByRole": [Function],
"findByTestId": [Function],
"findByText": [Function],
"findByTitle": [Function],
"getAllByAltText": [Function],
"getAllByDisplayValue": [Function],
"getAllByLabelText": [Function],
"getAllByPlaceholderText": [Function],
"getAllByRole": [Function],
"getAllByTestId": [Function],
"getAllByText": [Function],
"getAllByTitle": [Function],
"getByAltText": [Function],
"getByDisplayValue": [Function],
"getByLabelText": [Function],
"getByPlaceholderText": [Function],
"getByRole": [Function],
"getByTestId": [Function],
"getByText": [Function],
"getByTitle": [Function],
"queryAllByAltText": [Function],
"queryAllByDisplayValue": [Function],
"queryAllByLabelText": [Function],
"queryAllByPlaceholderText": [Function],
"queryAllByRole": [Function],
"queryAllByTestId": [Function],
"queryAllByText": [Function],
"queryAllByTitle": [Function],
"queryByAltText": [Function],
"queryByDisplayValue": [Function],
"queryByLabelText": [Function],
"queryByPlaceholderText": [Function],
"queryByRole": [Function],
"queryByTestId": [Function],
"queryByText": [Function],
"queryByTitle": [Function],
"rerender": [Function],
"unmount": [Function],
}
`;

View File

@ -1,176 +0,0 @@
/**
* External dependencies
*/
import { render } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
/**
* Internal dependencies
*/
import { StoreDetails } from '../';
const testProps = {
query: {
page: 'wc-admin',
path: '/setup-wizard',
},
step: {
key: 'store-details',
label: 'Store Details',
isComplete: false,
},
initialValues: {
addressLine1: '',
addressLine2: '',
city: '',
countryState: '',
postCode: '',
isAgreeMarketing: true,
storeEmail: 'wordpress@example.com',
},
getLocale: jest.fn(),
isLoading: false,
createNotice: jest.fn(),
profileItems: {},
updateAndPersistSettingsForGroup: jest.fn(),
goToNextStep: jest.fn(),
errorsRef: {
current: {},
},
};
jest.mock( '@wordpress/data', () => {
const originalModule = jest.requireActual( '@wordpress/data' );
return {
__esModule: true,
...originalModule,
useSelect: jest.fn().mockReturnValue( {
locale: 'en_US',
countries: [
{
code: 'US',
name: 'United States',
states: [],
},
],
loadingCountries: false,
hasFinishedResolution: true,
} ),
};
} );
describe( 'StoreDetails', () => {
describe( 'Snapshot test', () => {
test( 'should match saved snapshot', () => {
const container = render( <StoreDetails { ...testProps } /> );
expect( container ).toMatchSnapshot();
} );
} );
it( 'should disable the "Continue" button when the mandatory field (Country / Region) is empty', () => {
const { getByRole } = render( <StoreDetails { ...testProps } /> );
expect( getByRole( 'button', { name: 'Continue' } ) ).toBeDisabled();
} );
it( 'should enable the "Continue" button when the mandatory field (Country / Region) is filled', () => {
const { getByRole } = render(
<StoreDetails
{ ...{
...testProps,
initialValues: {
...testProps.initialValues,
countryState: 'US',
},
} }
/>
);
expect(
getByRole( 'button', { name: 'Continue' } )
).not.toBeDisabled();
} );
describe( 'Email validation test cases', () => {
test( 'should fail email validation and disable continue button when isAgreeMarketing is true and email is empty', async () => {
const container = render(
<StoreDetails
{ ...testProps }
initialValues={ {
addressLine1: 'address1',
addressLine2: 'address2',
city: 'city',
countryState: 'state',
postCode: '123',
isAgreeMarketing: true,
storeEmail: 'wordpress@example.com',
} }
/>
);
const emailInput = container.getByLabelText( 'Email address' );
await userEvent.clear( emailInput );
userEvent.tab();
expect(
container.queryByText(
'Please enter your email address to subscribe'
)
).toBeInTheDocument();
expect(
container.queryByRole( 'button', {
name: 'Continue',
} ).disabled
).toBe( true );
} );
// test cases taken from wordpress php is_email test cases
// https://github.com/WordPress/wordpress-develop/blob/2648a5f984b8abf06872151898e3a61d3458a628/tests/phpunit/tests/formatting/isEmail.php
test.each( [
'khaaaaaaaaaaaaaaan!',
'http://bob.example.com/',
"sif i'd give u it, spamer!1",
'com.exampleNOSPAMbob',
'bob@your mom',
'a@b.c',
] )( 'should fail email validation when given %s', async ( email ) => {
const container = render( <StoreDetails { ...testProps } /> );
const emailInput = container.getByLabelText( 'Email address' );
await userEvent.clear( emailInput );
await userEvent.type( emailInput, email );
// validation is triggered onChange but error message only renders on blur
// react testing lib doesn't have a "blur" event that can be triggered so this does the job of triggering the error message rendering
userEvent.tab();
expect(
container.queryByText( 'Invalid email address' )
).toBeInTheDocument();
} );
test.each( [
'bob@example.com',
'phil@example.info',
// 'ace@204.32.222.14', this testcase passes for the backend validation but fails here, following up in a PR to fix this in @wordpress/url
'kevin@many.subdomains.make.a.happy.man.edu',
'a@b.co',
'bill+ted@example.com',
] )( 'should pass email validation when given %s', async ( email ) => {
const container = render( <StoreDetails { ...testProps } /> );
const emailInput = container.getByLabelText( 'Email address' );
await userEvent.clear( emailInput );
await userEvent.type( emailInput, email );
userEvent.tab();
expect(
container.queryByText( 'Invalid email address' )
).toBeNull();
} );
test( 'should pass email validation when field is empty', async () => {
const container = render( <StoreDetails { ...testProps } /> );
const emailInput = container.getByLabelText( 'Email address' );
await userEvent.clear( emailInput );
userEvent.tab();
expect(
container.queryByText( 'Invalid email address' )
).toBeNull();
} );
} );
} );

View File

@ -1,204 +0,0 @@
/**
* External dependencies
*/
import { __ } from '@wordpress/i18n';
import { Component } from '@wordpress/element';
import { compose } from '@wordpress/compose';
import { withDispatch, withSelect } from '@wordpress/data';
import interpolateComponents from '@automattic/interpolate-components';
import { Button, Modal } from '@wordpress/components';
import { Link } from '@woocommerce/components';
import { OPTIONS_STORE_NAME } from '@woocommerce/data';
import { initializeExPlat } from '@woocommerce/explat';
class UsageModal extends Component {
constructor( props ) {
super( props );
this.state = {
isLoadingScripts: false,
isRequestStarted: false,
selectedAction: null,
};
}
async componentDidUpdate( prevProps, prevState ) {
const { hasErrors, isRequesting, onClose, onContinue, createNotice } =
this.props;
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 =
! isRequesting &&
! isLoadingScripts &&
( prevProps.isRequesting || prevState.isLoadingScripts ) &&
! hasErrors;
const isRequestError =
! isRequesting && prevProps.isRequesting && hasErrors;
if ( isRequestSuccessful ) {
onClose();
onContinue();
}
if ( isRequestError ) {
createNotice(
'error',
__(
'There was a problem updating your preferences',
'woocommerce'
)
);
onClose();
}
}
updateTracking( { allowTracking } ) {
const { updateOptions } = this.props;
if ( allowTracking && typeof window.wcTracks.enable === 'function' ) {
this.setState( { isLoadingScripts: true } );
window.wcTracks.enable( () => {
// Don't update state if component is unmounted already
if ( ! this._isMounted ) {
return;
}
initializeExPlat();
this.setState( { isLoadingScripts: false } );
} );
} else if ( ! allowTracking ) {
window.wcTracks.isEnabled = false;
}
const trackingValue = allowTracking ? 'yes' : 'no';
this.setState( { isRequestStarted: true } );
updateOptions( {
woocommerce_allow_tracking: trackingValue,
} );
}
componentDidMount() {
this._isMounted = true;
}
componentWillUnmount() {
this._isMounted = false;
}
render() {
const { allowTracking, isResolving, onClose, onContinue } = this.props;
if ( isResolving ) {
return null;
}
// Bail if site has already opted in to tracking
if ( allowTracking ) {
onClose();
onContinue();
return null;
}
const {
isRequesting,
title = __( 'Build a better WooCommerce', 'woocommerce' ),
message = interpolateComponents( {
mixedString: __(
'Get improved features and faster fixes by sharing non-sensitive data via {{link}}usage tracking{{/link}} ' +
'that shows us how WooCommerce is used. No personal data is tracked or stored.',
'woocommerce'
),
components: {
link: (
<Link
href="https://woocommerce.com/usage-tracking?utm_medium=product"
target="_blank"
type="external"
/>
),
},
} ),
dismissActionText = __( 'No thanks', 'woocommerce' ),
acceptActionText = __( 'Yes, count me in!', 'woocommerce' ),
} = this.props;
const { isRequestStarted, selectedAction } = this.state;
const isBusy = isRequestStarted && isRequesting;
return (
<Modal
title={ title }
isDismissible={ this.props.isDismissible }
onRequestClose={ () => this.props.onClose() }
className="woocommerce-usage-modal"
>
<div className="woocommerce-usage-modal__wrapper">
<div className="woocommerce-usage-modal__message">
{ message }
</div>
<div className="woocommerce-usage-modal__actions">
<Button
isSecondary
isBusy={ isBusy && selectedAction === 'dismiss' }
disabled={ isBusy && selectedAction === 'accept' }
onClick={ () => {
this.setState( { selectedAction: 'dismiss' } );
this.updateTracking( { allowTracking: false } );
} }
>
{ dismissActionText }
</Button>
<Button
isPrimary
isBusy={ isBusy && selectedAction === 'accept' }
disabled={ isBusy && selectedAction === 'dismiss' }
onClick={ () => {
this.setState( { selectedAction: 'accept' } );
this.updateTracking( { allowTracking: true } );
} }
>
{ acceptActionText }
</Button>
</div>
</div>
</Modal>
);
}
}
export default compose(
withSelect( ( select ) => {
const {
getOption,
getOptionsUpdatingError,
isOptionsUpdating,
hasFinishedResolution,
} = select( OPTIONS_STORE_NAME );
return {
allowTracking: getOption( 'woocommerce_allow_tracking' ) === 'yes',
isRequesting: Boolean( isOptionsUpdating() ),
isResolving:
! hasFinishedResolution( 'getOption', [
'woocommerce_allow_tracking',
] ) ||
typeof getOption( 'woocommerce_allow_tracking' ) ===
'undefined',
hasErrors: Boolean( getOptionsUpdatingError() ),
};
} ),
withDispatch( ( dispatch ) => {
const { createNotice } = dispatch( 'core/notices' );
const { updateOptions } = dispatch( OPTIONS_STORE_NAME );
return {
createNotice,
updateOptions,
};
} )
)( UsageModal );

View File

@ -1,435 +0,0 @@
.woocommerce-profile-wizard__body {
.woocommerce-profile-wizard__step-header {
text-align: center;
margin: $gap 0 $gap-large;
h2 {
margin-bottom: $gap-smallest;
color: $gray-900;
}
p {
color: $gray-700;
display: flex;
align-items: center;
justify-content: center;
line-height: 1.5em;
.woocommerce-profile-wizard__tooltip-icon {
height: 16px;
}
}
}
.woocommerce-profile-wizard__header {
height: $header-height;
border-bottom: 1px solid $studio-gray-5;
display: flex;
align-items: center;
justify-content: center;
background: $studio-white;
}
.woocommerce-profile-wizard__header-subtitle {
font-weight: 400;
text-align: center;
}
.woocommerce-profile-wizard__header-subtitle {
color: $gray-700;
font-size: 16px;
line-height: 24px;
margin-top: $gap-smaller;
margin-bottom: $gap * 2;
margin-right: $gap-smaller;
display: flex;
justify-content: center;
}
.woocommerce-profile-wizard__intro-paragraph {
margin-top: 5px;
margin-bottom: 18px;
}
.woocommerce-profile-wizard__container {
margin-top: $gap-larger;
margin-bottom: $gap;
margin-left: auto;
margin-right: auto;
text-align: left;
> * {
max-width: 504px;
margin-left: auto;
margin-right: auto;
}
@include breakpoint( "<782px" ) {
padding-left: $gap;
padding-right: $gap;
margin-bottom: 56 + $gap; /* 56px is height of footer */
margin-top: 0;
}
.components-popover__content {
padding: $gap $gap-large;
}
.woocommerce-profile-wizard__footer {
margin: 34px auto;
display: flex;
justify-content: center;
}
.woocommerce-profile-wizard__footer-link {
display: flex;
text-decoration: none;
}
.woocommerce-profile-wizard__footnote {
max-width: 424px;
margin: 32px auto 25px auto;
p {
color: $gray-700;
text-align: center;
margin-bottom: 12px;
}
}
/* Muriel style overrides */
.muriel-component {
margin-top: $gap;
margin-bottom: $gap;
}
.components-base-control.has-error {
margin-bottom: $gap * 2 !important;
border-color: $studio-red-50;
@include breakpoint( "<782px" ) {
margin-bottom: $gap-small + $gap * 2 !important;
}
.components-base-control__help {
top: 100%;
left: $gap-small;
position: absolute;
margin-top: $gap-smallest;
font-size: 12px;
font-style: normal;
color: $studio-red-50;
}
}
.components-form-toggle {
display: inline-block;
label {
font-size: 14px;
}
.components-base-control {
display: inline-block;
}
.components-base-control__field {
margin-bottom: 0;
}
}
}
#woocommerce-layout__primary {
text-align: center;
margin: 0;
width: 100%;
}
.woocommerce-layout .woocommerce-layout__main {
padding-right: 0;
}
.woocommerce-profile-wizard__error {
display: block;
padding: $gap $gap-large;
font-size: 12px;
color: $studio-red-50;
}
.woocommerce-profile-wizard__benefit {
display: flex;
svg:first-child {
width: 24px;
min-width: 24px;
margin-right: $gap-large;
}
.woocommerce-profile-wizard__benefit-title {
font-size: 16px;
font-weight: 400;
margin-top: 0;
margin-bottom: $gap-smaller;
}
.woocommerce-profile-wizard__benefit-content {
margin-left: $gap;
p {
padding-bottom: $gap;
margin-top: 0;
border-bottom: 1px solid $studio-gray-5;
font-size: 14px;
}
}
.woocommerce-profile-wizard__benefit-toggle {
padding-top: $gap-larger;
margin-left: $gap;
}
&:last-child p {
border-bottom: 0;
margin-bottom: 0;
}
}
.woocommerce-profile-wizard__business-extension {
background: #f6f6f6;
padding-right: 4px;
padding-left: 4px;
height: 100%;
padding-top: 12px;
padding-bottom: 12px;
img {
box-sizing: unset;
max-width: 100px;
max-height: 50%;
vertical-align: middle;
}
}
.woocommerce-profile-wizard__tracking {
.woocommerce-profile-wizard__tracking-checkbox {
margin-top: $gap;
}
.components-form-toggle {
display: none;
}
@include breakpoint( "<782px" ) {
.components-form-toggle {
display: inline-block;
}
.components-checkbox-control__input {
display: none;
}
display: flex;
flex-direction: row-reverse;
align-items: center;
justify-content: flex-end;
}
}
.woocommerce-profile-wizard__checkbox {
margin-top: 0;
margin-bottom: 0;
position: relative;
padding: $gap-small $gap-large;
min-height: 62px;
border-bottom: 1px solid $gray-100;
display: flex;
align-items: center;
.components-base-control {
position: relative;
}
.components-base-control__field {
width: 100%;
margin: 0;
}
label.components-checkbox-control__label {
font-size: $gap;
margin-left: 0;
}
.components-base-control__help {
margin-left: $gap-largest + $gap-smaller;
font-style: normal;
}
.components-base-control__help {
color: $gray-600;
font-size: 14px;
line-height: 20px;
margin-top: 3px;
margin-bottom: 0;
}
svg.dashicon.components-checkbox-control__checked {
left: 1px;
top: -1px;
}
}
@include breakpoint( "<600px" ) {
svg.dashicon.components-checkbox-control__checked {
left: -2px;
top: -1px;
width: 21px;
height: 21px;
}
}
.woocommerce-select-control__control {
margin: $gap 0;
padding-right: $gap + 24px;
box-shadow: $shadow-popover;
&.is-active {
border-color: var(--wp-admin-theme-color);
}
.components-base-control__label {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
max-width: calc(100% - #{$gap * 2} - 24px);
}
&::after {
display: block;
pointer-events: none;
cursor: pointer;
position: absolute;
float: right;
line-height: 56px;
font-family: dashicons, sans-serif;
font-size: 20px;
content: "\f140";
z-index: 1;
height: 24px;
width: 24px;
margin-top: 0;
top: 0;
right: $gap;
bottom: $gap;
color: $studio-black;
}
}
#wpadminbar {
display: none;
}
#wpbody {
padding-top: 0;
}
}
.woocommerce-profile-wizard__plugins-card {
.woocommerce-profile-wizard__plugins-actions {
text-align: left;
margin-left: $gap-largest + $gap-smallest;
button.is-button {
margin: $gap 0 0;
height: 40px;
min-width: auto;
display: initial;
margin-right: $gap-small;
}
}
}
.woocommerce-profile-wizard__header {
svg > g {
transform: initial;
}
@include breakpoint( "<782px" ) {
position: fixed;
z-index: 999;
width: 100%;
bottom: 0;
border-top: 1px solid $studio-gray-5;
border-bottom: none;
}
.woocommerce-stepper {
margin: 0 $gap 0 $gap;
width: 100%;
}
.woocommerce-stepper__steps {
margin: 0;
}
}
.woocommerce-profile-wizard__tooltip-icon {
color: $studio-gray-60;
display: flex;
align-items: center;
margin-left: $gap-smallest;
cursor: help;
}
.woocommerce-business-extensions {
display: flex;
align-items: center;
width: 100%;
label {
display: flex;
align-items: center;
}
.components-checkbox-control__input-container {
margin-right: $gap;
}
.woocommerce-business-extensions__label-subtext {
display: block;
color: $gray-600;
}
.woocommerce-business-extensions__popover-wrapper {
margin-left: auto;
}
}
.woocommerce-obw-unsaved-changes {
width: 565px;
max-width: 100%;
.components-modal__header {
border-bottom: 1px solid #ddd;
}
.woocommerce-usage-modal__message {
box-sizing: border-box;
border-bottom: 1px solid #ddd;
padding: 0 32px;
background: #fff;
align-items: center;
height: 60px;
z-index: 10;
position: sticky;
top: 0;
margin: 0 -32px;
display: flex;
}
.woocommerce-usage-modal__actions {
display: flex;
justify-content: flex-end;
margin-top: $gap;
button {
margin-left: $gap;
}
}
}

View File

@ -1,40 +0,0 @@
/**
* External dependencies
*/
import { Button, Modal } from '@wordpress/components';
import { __ } from '@wordpress/i18n';
const UnsavedChangesModal = ( { onClose, onSave } ) => {
const title = __( 'Save changes?', 'woocommerce' );
const message = __(
"You're about to go to a different step. Do you want to save the changes you've made here so far?",
'woocommerce'
);
const discardText = __( 'Discard', 'woocommerce' );
const saveText = __( 'Save', 'woocommerce' );
return (
<>
<Modal
title={ title }
className="woocommerce-obw-unsaved-changes"
onRequestClose={ onClose }
>
<div className="woocommerce-obw-unsaved-changes-modal__wrapper">
<div className="woocommerce-usage-modal__message">
{ message }
</div>
<div className="woocommerce-usage-modal__actions">
<Button onClick={ () => onClose() }>
{ discardText }
</Button>
<Button isPrimary onClick={ onSave }>
{ saveText }
</Button>
</div>
</div>
</Modal>
</>
);
};
export default UnsavedChangesModal;

View File

@ -10,7 +10,7 @@ import { Link } from '@woocommerce/components';
/** /**
* Internal dependencies * Internal dependencies
*/ */
import Modal from '~/profile-wizard/steps/usage-modal'; import Modal from '~/task-lists/components/usage-modal';
export const UsageModal = () => { export const UsageModal = () => {
const query = getQuery(); const query = getQuery();

View File

@ -1,3 +1,2 @@
export * from './utils'; export * from './utils';
export { Suggestion as WCPaySuggestion } from './Suggestion'; export { Suggestion as WCPaySuggestion } from './Suggestion';
export { UsageModal as WCPayUsageModal } from './UsageModal';

View File

@ -0,0 +1,4 @@
Significance: minor
Type: update
Clean up 'Profile Wizard' code