Add country validation to subscription inclusion (https://github.com/woocommerce/woocommerce-admin/pull/7777)
* Add country validation * Add OnboardingProductTypes * Add OnboardingProductTypes * Add country validation to product task * Add `productTypes` data handling * Add country validation and new productTypes handling * Fix to get `productTypes` from a SSOT * Add `invalidateResolution` for `getTaskLists` * Fixed testing instructions * Fix `isTaskListHidden` issue * Fixed product type * Added constant `EMPTY_ARRAY` to `selectors.ts` * Fixed constant `EMPTY_ARRAY` * Moved `invalidateResolutionForStoreSelector` into OBW * Updated testing instructions * Updated testing instructions * Fixed testing instructions Co-authored-by: Fernando Marichal <contacto@fernandomarichal.com>
This commit is contained in:
parent
a8a3238c38
commit
f20113fa01
|
@ -1,27 +1,26 @@
|
|||
# Testing instructions
|
||||
|
||||
## Unreleased
|
||||
|
||||
## 2.8.0
|
||||
|
||||
### Store Profiler and Product task - include Subscriptions #7734
|
||||
|
||||
##### Feature: turned off
|
||||
##### Non US stores
|
||||
|
||||
1. Deactivate and delete `WooCommerce Payments` if you have it installed.
|
||||
2. Go to the 3rd step of the store profiler (`Product Types`).
|
||||
3. Verify `Subscriptions` is shown as a paid extension (with a price chip).
|
||||
4. Check `Subscriptions` and continue with the OBW.
|
||||
5. Go back to the `Home` screen. Check that the task item `Add Subscriptions to my store` is visible in the setup task list.
|
||||
6. Press `Add my products` in the setup task list.
|
||||
7. Select `Start with a template`. Verify that the option `Subscription product` is not visible in the popup.
|
||||
2. Go to step one of the store profiler and select `France` (or any country other than the US) as the store `Country / Region`.
|
||||
3. Go to step three of the store profiler (`Product Types`).
|
||||
4. Verify `Subscriptions` is shown as a paid extension (with a price chip).
|
||||
5. Check `Subscriptions` and continue with the OBW.
|
||||
6. Go back to the `Home` screen by pressing `Skip setup store details` in step one of the store profiler. Check that the task item `Add Subscriptions to my store` is visible in the setup task list.
|
||||
7. Press `Add my products` in the setup task list.
|
||||
8. Select `Start with a template`. Verify that the option `Subscription product` is not visible in the popup.
|
||||
|
||||
##### Feature: turned on
|
||||
##### US stores
|
||||
|
||||
1. Install and activate this plugin to turn on the subscriptions inclusion -> https://gist.github.com/octaedro/455eb4c85887608c253249bad533ccb3
|
||||
2. Deactivate and delete `WooCommerce Payments` if you have it installed.
|
||||
3. Go to the 3rd step of the store profiler (`Product Types`).
|
||||
4. Verify `Subscriptions` is shown as free (without a price chip). Also, verify that the text
|
||||
9. Deactivate and delete `WooCommerce Payments`.
|
||||
10. Go to step one of the store profiler and select `US` as the store `Country / Region`.
|
||||
11. Go to step three of the store profiler (`Product Types`).
|
||||
12. Verify `Subscriptions` is shown as free (without a price chip). Also, verify that the text
|
||||
|
||||
```
|
||||
The following extensions will be added to your site for free: WooCommerce Payments. An account is required to use this feature
|
||||
|
@ -31,20 +30,23 @@ is visible at the bottom when `WooCommerce Payments` is not installed.
|
|||
|
||||
![screenshot-one wordpress test-2021 09 30-14_12_58](https://user-images.githubusercontent.com/1314156/135506696-b7812f7e-437f-4d89-956a-b73248f70f6b.png)
|
||||
|
||||
5. Press `Continue` and verify that the `WooCommerce Payments` plugin is installed and activated and it's not shown in the `Free features` list
|
||||
|
||||
13. Check `Subscriptions` and press `Continue` and verify that the `WooCommerce Payments` plugin is installed and activated and it's not shown in the `Free features` list
|
||||
|
||||
![screenshot-one wordpress test-2021 09 30-14_32_20](https://user-images.githubusercontent.com/1314156/135506727-d8888f2b-3424-4cf5-a4bf-b67a14a198b6.png)
|
||||
|
||||
6. Go back to the `Home` screen. Check that the task item `Add Subscriptions to my store` is not visible in the setup task list.
|
||||
14. Verify that the `WooCommerce Payments` plugin is being shown in the `Free features` list when the store country is other than the `US`.
|
||||
|
||||
15. Go back to the `Home` screen by pressing `Skip setup store details` in step one of the store profiler. Check that the task item `Add Subscriptions to my store` is not visible in the setup task list. It should be visible if the store is from any country other than the `US`.
|
||||
|
||||
![screenshot-one wordpress test-2021 09 30-14_39_28](https://user-images.githubusercontent.com/1314156/135506770-91571f8f-2e2e-43a7-b092-b9e5fdf56df8.png)
|
||||
|
||||
7. Press `Add my products` in the setup task list.
|
||||
8. Select `Start with a template`. Verify that the option `Subscription product` is visible in the popup
|
||||
16. Press `Add my products` in the setup task list.
|
||||
17. Select `Start with a template`. Verify that the option `Subscription product` is visible in the popup
|
||||
|
||||
![screenshot-one wordpress test-2021 09 30-14_35_22](https://user-images.githubusercontent.com/1314156/135506748-0b7bdce5-b006-47f9-9289-03ed26e4950c.png)
|
||||
|
||||
9. Select `Subscription product` and press `Ok`. You should have been redirected to `post-new.php?post_type=product&subscription_pointers=true`
|
||||
18. Select `Subscription product` and press `Ok`. You should have been redirected to `post-new.php?post_type=product&subscription_pointers=true`.
|
||||
|
||||
## 2.7.1
|
||||
|
||||
|
|
|
@ -80,11 +80,8 @@ class CartModal extends Component {
|
|||
}
|
||||
|
||||
renderProducts() {
|
||||
const { productIds } = this.props;
|
||||
const { productTypes = {}, themes = [] } = getSetting(
|
||||
'onboarding',
|
||||
{}
|
||||
);
|
||||
const { productIds, productTypes } = this.props;
|
||||
const { themes = [] } = getSetting( 'onboarding', {} );
|
||||
const listItems = [];
|
||||
|
||||
productIds.forEach( ( productId ) => {
|
||||
|
@ -169,15 +166,19 @@ class CartModal extends Component {
|
|||
export default compose(
|
||||
withSelect( ( select ) => {
|
||||
const { getInstalledPlugins } = select( PLUGINS_STORE_NAME );
|
||||
const { getProfileItems } = select( ONBOARDING_STORE_NAME );
|
||||
const { getProductTypes, getProfileItems } = select(
|
||||
ONBOARDING_STORE_NAME
|
||||
);
|
||||
const profileItems = getProfileItems();
|
||||
const installedPlugins = getInstalledPlugins();
|
||||
const productTypes = getProductTypes();
|
||||
const productIds = getProductIdsForCart(
|
||||
productTypes,
|
||||
profileItems,
|
||||
false,
|
||||
installedPlugins
|
||||
);
|
||||
|
||||
return { profileItems, productIds };
|
||||
return { profileItems, productIds, productTypes };
|
||||
} )
|
||||
)( CartModal );
|
||||
|
|
|
@ -36,12 +36,14 @@ export function getCurrencyRegion( countryState ) {
|
|||
/**
|
||||
* Gets the product IDs for items based on the product types and theme selected in the onboarding profiler.
|
||||
*
|
||||
* @param {Object} productTypes Product Types.
|
||||
* @param {Object} profileItems Onboarding profile.
|
||||
* @param {boolean} includeInstalledItems Include installed items in returned product IDs.
|
||||
* @param {Array} installedPlugins Installed plugins.
|
||||
* @return {Array} Product Ids.
|
||||
*/
|
||||
export function getProductIdsForCart(
|
||||
productTypes,
|
||||
profileItems,
|
||||
includeInstalledItems = false,
|
||||
installedPlugins
|
||||
|
@ -49,7 +51,8 @@ export function getProductIdsForCart(
|
|||
const productList = getProductList(
|
||||
profileItems,
|
||||
includeInstalledItems,
|
||||
installedPlugins
|
||||
installedPlugins,
|
||||
productTypes
|
||||
);
|
||||
const productIds = productList.map(
|
||||
( product ) => product.id || product.product
|
||||
|
@ -60,11 +63,13 @@ export function getProductIdsForCart(
|
|||
/**
|
||||
* Gets the labeled/categorized product names and types for items based on the product types and theme selected in the onboarding profiler.
|
||||
*
|
||||
* @param {Object} productTypes Product Types.
|
||||
* @param {Object} profileItems Onboarding profile.
|
||||
* @param {Array} installedPlugins Installed plugins.
|
||||
* @return {Array} Objects with labeled/categorized product names and types.
|
||||
*/
|
||||
export function getCategorizedOnboardingProducts(
|
||||
productTypes,
|
||||
profileItems,
|
||||
installedPlugins
|
||||
) {
|
||||
|
@ -72,12 +77,14 @@ export function getCategorizedOnboardingProducts(
|
|||
productList.products = getProductList(
|
||||
profileItems,
|
||||
true,
|
||||
installedPlugins
|
||||
installedPlugins,
|
||||
productTypes
|
||||
);
|
||||
productList.remainingProducts = getProductList(
|
||||
profileItems,
|
||||
false,
|
||||
installedPlugins
|
||||
installedPlugins,
|
||||
productTypes
|
||||
);
|
||||
|
||||
const uniqueItemsList = [
|
||||
|
@ -106,37 +113,37 @@ export function getCategorizedOnboardingProducts(
|
|||
* @param {Object} profileItems Onboarding profile.
|
||||
* @param {boolean} includeInstalledItems Include installed items in returned product list.
|
||||
* @param {Array} installedPlugins Installed plugins.
|
||||
* @param {Object} productTypes Product Types.
|
||||
* @return {Array} Products.
|
||||
*/
|
||||
export function getProductList(
|
||||
profileItems,
|
||||
includeInstalledItems = false,
|
||||
installedPlugins
|
||||
installedPlugins,
|
||||
productTypes
|
||||
) {
|
||||
const onboarding = getSetting( 'onboarding', {} );
|
||||
const productList = [];
|
||||
|
||||
// The population of onboarding.productTypes only happens if the task list should be shown
|
||||
// so bail early if it isn't present.
|
||||
if ( ! onboarding.productTypes ) {
|
||||
if ( ! productTypes ) {
|
||||
return productList;
|
||||
}
|
||||
|
||||
const productTypes = profileItems.product_types || [];
|
||||
const profileItemsProductTypes = profileItems.product_types || [];
|
||||
|
||||
productTypes.forEach( ( productType ) => {
|
||||
profileItemsProductTypes.forEach( ( productType ) => {
|
||||
if (
|
||||
onboarding.productTypes[ productType ] &&
|
||||
onboarding.productTypes[ productType ].product &&
|
||||
productTypes[ productType ] &&
|
||||
productTypes[ productType ].product &&
|
||||
( includeInstalledItems ||
|
||||
! installedPlugins.includes(
|
||||
onboarding.productTypes[ productType ].slug
|
||||
productTypes[ productType ].slug
|
||||
) )
|
||||
) {
|
||||
productList.push( onboarding.productTypes[ productType ] );
|
||||
productList.push( productTypes[ productType ] );
|
||||
}
|
||||
} );
|
||||
|
||||
const onboarding = getSetting( 'onboarding', {} );
|
||||
const theme = onboarding.themes.find(
|
||||
( themeData ) => themeData.slug === profileItems.theme
|
||||
);
|
||||
|
|
|
@ -8,7 +8,11 @@ import { Link } from '@woocommerce/components';
|
|||
import { __, _n, sprintf } from '@wordpress/i18n';
|
||||
import { Icon, chevronDown, chevronUp } from '@wordpress/icons';
|
||||
import interpolateComponents from 'interpolate-components';
|
||||
import { pluginNames, ONBOARDING_STORE_NAME } from '@woocommerce/data';
|
||||
import {
|
||||
pluginNames,
|
||||
ONBOARDING_STORE_NAME,
|
||||
SETTINGS_STORE_NAME,
|
||||
} from '@woocommerce/data';
|
||||
import { recordEvent } from '@woocommerce/tracks';
|
||||
import { useSelect } from '@wordpress/data';
|
||||
|
||||
|
@ -19,6 +23,7 @@ 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_LISTS = [ 'basics' ];
|
||||
|
||||
|
@ -178,28 +183,35 @@ export const SelectiveExtensionsBundle = ( {
|
|||
const [ showExtensions, setShowExtensions ] = useState( false );
|
||||
const [ values, setValues ] = useState( baseValues );
|
||||
|
||||
const { freeExtensions, isResolving, profileItems } = useSelect(
|
||||
( select ) => {
|
||||
const {
|
||||
getFreeExtensions,
|
||||
getProfileItems,
|
||||
hasFinishedResolution,
|
||||
} = select( ONBOARDING_STORE_NAME );
|
||||
const {
|
||||
countryCode,
|
||||
freeExtensions,
|
||||
isResolving,
|
||||
profileItems,
|
||||
} = useSelect( ( select ) => {
|
||||
const {
|
||||
getFreeExtensions,
|
||||
getProfileItems,
|
||||
hasFinishedResolution,
|
||||
} = select( ONBOARDING_STORE_NAME );
|
||||
const { getSettings } = select( SETTINGS_STORE_NAME );
|
||||
const { general: settings = {} } = getSettings( 'general' );
|
||||
|
||||
return {
|
||||
freeExtensions: getFreeExtensions(),
|
||||
isResolving: ! hasFinishedResolution( 'getFreeExtensions' ),
|
||||
profileItems: getProfileItems(),
|
||||
};
|
||||
}
|
||||
);
|
||||
return {
|
||||
countryCode: getCountryCode( settings.woocommerce_default_country ),
|
||||
freeExtensions: getFreeExtensions(),
|
||||
isResolving: ! hasFinishedResolution( 'getFreeExtensions' ),
|
||||
profileItems: getProfileItems(),
|
||||
};
|
||||
} );
|
||||
|
||||
const installableExtensions = useMemo( () => {
|
||||
const { product_types: productTypes } = profileItems;
|
||||
return freeExtensions.filter( ( list ) => {
|
||||
if (
|
||||
window.wcAdminFeatures &&
|
||||
window.wcAdminFeatures.subscriptions
|
||||
window.wcAdminFeatures.subscriptions &&
|
||||
countryCode === 'US'
|
||||
) {
|
||||
if ( productTypes.includes( 'subscriptions' ) ) {
|
||||
list.plugins = list.plugins.filter(
|
||||
|
|
|
@ -11,35 +11,34 @@ import {
|
|||
CardFooter,
|
||||
CheckboxControl,
|
||||
FormToggle,
|
||||
Spinner,
|
||||
} from '@wordpress/components';
|
||||
import { includes, filter, get } from 'lodash';
|
||||
import { getSetting } from '@woocommerce/wc-admin-settings';
|
||||
import { includes, filter } from 'lodash';
|
||||
import { recordEvent } from '@woocommerce/tracks';
|
||||
import { withDispatch, withSelect } from '@wordpress/data';
|
||||
import { ONBOARDING_STORE_NAME, PLUGINS_STORE_NAME } from '@woocommerce/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( props ) {
|
||||
constructor() {
|
||||
super();
|
||||
const profileItems = get( props, 'profileItems', {} );
|
||||
|
||||
const { productTypes = {} } = getSetting( 'onboarding', {} );
|
||||
const defaultProductTypes = Object.keys( productTypes ).filter(
|
||||
( key ) => !! productTypes[ key ].default
|
||||
);
|
||||
|
||||
this.state = {
|
||||
error: null,
|
||||
isMonthlyPricing: true,
|
||||
selected: profileItems.product_types || defaultProductTypes,
|
||||
selected: [],
|
||||
isWCPayInstalled: null,
|
||||
};
|
||||
|
||||
|
@ -48,9 +47,11 @@ export class ProductTypes extends Component {
|
|||
}
|
||||
|
||||
componentDidMount() {
|
||||
const { installedPlugins } = this.props;
|
||||
const { installedPlugins, invalidateResolution } = this.props;
|
||||
const { isWCPayInstalled } = this.state;
|
||||
|
||||
invalidateResolution( 'getProductTypes', [] );
|
||||
|
||||
if ( isWCPayInstalled === null && installedPlugins ) {
|
||||
this.setState( {
|
||||
isWCPayInstalled: installedPlugins.includes(
|
||||
|
@ -60,6 +61,19 @@ export class ProductTypes extends Component {
|
|||
}
|
||||
}
|
||||
|
||||
componentDidUpdate( prevProps ) {
|
||||
const { profileItems, productTypes } = this.props;
|
||||
|
||||
if ( prevProps.productTypes !== productTypes ) {
|
||||
const defaultProductTypes = Object.keys( productTypes ).filter(
|
||||
( key ) => !! productTypes[ key ].default
|
||||
);
|
||||
this.setState( {
|
||||
selected: profileItems.product_types || defaultProductTypes,
|
||||
} );
|
||||
}
|
||||
}
|
||||
|
||||
validateField() {
|
||||
const error = this.state.selected.length
|
||||
? null
|
||||
|
@ -80,6 +94,7 @@ export class ProductTypes extends Component {
|
|||
}
|
||||
|
||||
const {
|
||||
countryCode,
|
||||
createNotice,
|
||||
goToNextStep,
|
||||
installAndActivatePlugins,
|
||||
|
@ -95,6 +110,7 @@ export class ProductTypes extends Component {
|
|||
if (
|
||||
window.wcAdminFeatures &&
|
||||
window.wcAdminFeatures.subscriptions &&
|
||||
countryCode === 'US' &&
|
||||
! installedPlugins.includes( 'woocommerce-payments' ) &&
|
||||
selected.includes( 'subscriptions' )
|
||||
) {
|
||||
|
@ -145,14 +161,27 @@ export class ProductTypes extends Component {
|
|||
}
|
||||
|
||||
render() {
|
||||
const { productTypes = {} } = getSetting( 'onboarding', {} );
|
||||
const { productTypes = [] } = this.props;
|
||||
const {
|
||||
error,
|
||||
isMonthlyPricing,
|
||||
isWCPayInstalled,
|
||||
selected,
|
||||
} = this.state;
|
||||
const { isInstallingActivating, isProfileItemsRequesting } = this.props;
|
||||
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">
|
||||
|
@ -256,6 +285,7 @@ export class ProductTypes extends Component {
|
|||
</Text>
|
||||
{ window.wcAdminFeatures &&
|
||||
window.wcAdminFeatures.subscriptions &&
|
||||
countryCode === 'US' &&
|
||||
! isWCPayInstalled &&
|
||||
selected.includes( 'subscriptions' ) && (
|
||||
<Text
|
||||
|
@ -280,12 +310,16 @@ 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' ) ),
|
||||
|
@ -297,16 +331,23 @@ export default compose(
|
|||
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,
|
||||
};
|
||||
} )
|
||||
|
|
|
@ -60,10 +60,13 @@
|
|||
margin-left: $gap;
|
||||
}
|
||||
}
|
||||
&__spinner {
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 438px) {
|
||||
@media screen and ( max-width: 438px ) {
|
||||
.woocommerce-profile-wizard__body {
|
||||
.woocommerce-profile-wizard__product-types {
|
||||
.woocommerce-product-wizard__product-types-label {
|
||||
|
@ -73,7 +76,7 @@
|
|||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 375px) {
|
||||
@media screen and ( max-width: 375px ) {
|
||||
.woocommerce-profile-wizard__body {
|
||||
.woocommerce-profile-wizard__product-types {
|
||||
.woocommerce-pill {
|
||||
|
|
|
@ -2,46 +2,44 @@
|
|||
* External dependencies
|
||||
*/
|
||||
import { render, screen, waitFor } from '@testing-library/react';
|
||||
import { setSetting } from '@woocommerce/wc-admin-settings';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { createElement } from '@wordpress/element';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { ProductTypes } from '../';
|
||||
|
||||
describe( 'ProductTypes', () => {
|
||||
beforeEach( () => {
|
||||
setSetting( 'onboarding', {
|
||||
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',
|
||||
},
|
||||
},
|
||||
} );
|
||||
} );
|
||||
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( () => {
|
||||
setSetting( 'onboarding', {} );
|
||||
window.wcAdminFeatures.subscriptions = false;
|
||||
} );
|
||||
|
||||
test( 'should render product types', () => {
|
||||
const { container } = render( <ProductTypes /> );
|
||||
const { container } = render( <ProductTypes { ...testProps } /> );
|
||||
expect( container ).toMatchSnapshot();
|
||||
} );
|
||||
|
||||
test( 'should show annual prices on toggle', () => {
|
||||
const { container } = render( <ProductTypes /> );
|
||||
const { container } = render( <ProductTypes { ...testProps } /> );
|
||||
|
||||
const toggle = screen.getByLabelText( 'Display monthly prices', {
|
||||
selector: 'input',
|
||||
|
@ -62,6 +60,7 @@ describe( 'ProductTypes', () => {
|
|||
createNotice={ mockCreateNotice }
|
||||
goToNextStep={ mockGoToNextStep }
|
||||
updateProfileItems={ mockUpdateProfileItems }
|
||||
{ ...testProps }
|
||||
/>
|
||||
);
|
||||
|
||||
|
@ -88,16 +87,16 @@ describe( 'ProductTypes', () => {
|
|||
} );
|
||||
} );
|
||||
test( 'should show a warning message at the bottom of the step', () => {
|
||||
setSetting( 'onboarding', {
|
||||
productTypes: {
|
||||
subscriptions: {
|
||||
label: 'Subscriptions',
|
||||
},
|
||||
const productTypes = {
|
||||
subscriptions: {
|
||||
label: 'Subscriptions',
|
||||
},
|
||||
} );
|
||||
};
|
||||
window.wcAdminFeatures.subscriptions = true;
|
||||
|
||||
render( <ProductTypes /> );
|
||||
render(
|
||||
<ProductTypes { ...testProps } productTypes={ productTypes } />
|
||||
);
|
||||
|
||||
const subscription = screen.getByText( 'Subscriptions', {
|
||||
selector: 'label',
|
||||
|
@ -110,4 +109,32 @@ describe( 'ProductTypes', () => {
|
|||
)
|
||||
).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: WooCommerce Payments. An account is required to use this feature.'
|
||||
)
|
||||
).not.toBeInTheDocument();
|
||||
} );
|
||||
} );
|
||||
|
|
|
@ -230,7 +230,13 @@ class StoreDetails extends Component {
|
|||
isStoreDetailsPopoverVisible,
|
||||
isSkipSetupPopoverVisible,
|
||||
} = this.state;
|
||||
const { skipProfiler, isLoading, isBusy, initialValues } = this.props;
|
||||
const {
|
||||
skipProfiler,
|
||||
isLoading,
|
||||
isBusy,
|
||||
initialValues,
|
||||
invalidateResolutionForStoreSelector,
|
||||
} = this.props;
|
||||
|
||||
/* eslint-disable @wordpress/i18n-no-collapsible-whitespace */
|
||||
const skipSetupText = __(
|
||||
|
@ -390,6 +396,9 @@ class StoreDetails extends Component {
|
|||
isLink
|
||||
className="woocommerce-profile-wizard__footer-link"
|
||||
onClick={ () => {
|
||||
invalidateResolutionForStoreSelector(
|
||||
'getTaskLists'
|
||||
);
|
||||
this.setState( {
|
||||
showUsageModal: true,
|
||||
skipping: true,
|
||||
|
@ -498,13 +507,17 @@ export default compose(
|
|||
} ),
|
||||
withDispatch( ( dispatch ) => {
|
||||
const { createNotice } = dispatch( 'core/notices' );
|
||||
const { updateProfileItems } = dispatch( ONBOARDING_STORE_NAME );
|
||||
const {
|
||||
invalidateResolutionForStoreSelector,
|
||||
updateProfileItems,
|
||||
} = dispatch( ONBOARDING_STORE_NAME );
|
||||
const { updateAndPersistSettingsForGroup } = dispatch(
|
||||
SETTINGS_STORE_NAME
|
||||
);
|
||||
|
||||
return {
|
||||
createNotice,
|
||||
invalidateResolutionForStoreSelector,
|
||||
updateProfileItems,
|
||||
updateAndPersistSettingsForGroup,
|
||||
};
|
||||
|
|
|
@ -5,11 +5,12 @@ import { __ } from '@wordpress/i18n';
|
|||
import { Button, Modal, RadioControl } from '@wordpress/components';
|
||||
import { useState } from '@wordpress/element';
|
||||
import { useDispatch, useSelect } from '@wordpress/data';
|
||||
import { addFilter, applyFilters } from '@wordpress/hooks';
|
||||
import { applyFilters } from '@wordpress/hooks';
|
||||
import {
|
||||
ITEMS_STORE_NAME,
|
||||
ONBOARDING_STORE_NAME,
|
||||
PLUGINS_STORE_NAME,
|
||||
SETTINGS_STORE_NAME,
|
||||
} from '@woocommerce/data';
|
||||
import { getAdminLink } from '@woocommerce/wc-admin-settings';
|
||||
import { recordEvent } from '@woocommerce/tracks';
|
||||
|
@ -19,11 +20,12 @@ import { recordEvent } from '@woocommerce/tracks';
|
|||
*/
|
||||
import './product-template-modal.scss';
|
||||
import { createNoticesFromResponse } from '../../../lib/notices';
|
||||
import { getCountryCode } from '../../../dashboard/utils';
|
||||
|
||||
export const ONBOARDING_PRODUCT_TEMPLATES_FILTER =
|
||||
'woocommerce_admin_onboarding_product_templates';
|
||||
|
||||
const PRODUCT_TEMPLATES = [
|
||||
const getProductTemplates = () => [
|
||||
{
|
||||
key: 'physical',
|
||||
title: __( 'Physical product', 'woocommerce-admin' ),
|
||||
|
@ -62,10 +64,13 @@ export default function ProductTemplateModal( { onClose } ) {
|
|||
const [ selectedTemplate, setSelectedTemplate ] = useState( null );
|
||||
const [ isRedirecting, setIsRedirecting ] = useState( false );
|
||||
const { createProductFromTemplate } = useDispatch( ITEMS_STORE_NAME );
|
||||
const { profileItems } = useSelect( ( select ) => {
|
||||
const { countryCode, profileItems } = useSelect( ( select ) => {
|
||||
const { getProfileItems } = select( ONBOARDING_STORE_NAME );
|
||||
const { getSettings } = select( SETTINGS_STORE_NAME );
|
||||
const { general: settings = {} } = getSettings( 'general' );
|
||||
|
||||
return {
|
||||
countryCode: getCountryCode( settings.woocommerce_default_country ),
|
||||
profileItems: getProfileItems(),
|
||||
};
|
||||
} );
|
||||
|
@ -116,25 +121,21 @@ export default function ProductTemplateModal( { onClose } ) {
|
|||
}
|
||||
};
|
||||
|
||||
if (
|
||||
const removeSubscriptions =
|
||||
( window.wcAdminFeatures && ! window.wcAdminFeatures.subscriptions ) ||
|
||||
countryCode !== 'US' ||
|
||||
! profileItems.product_types.includes( 'subscriptions' ) ||
|
||||
! installedPlugins.includes( 'woocommerce-payments' )
|
||||
) {
|
||||
addFilter(
|
||||
ONBOARDING_PRODUCT_TEMPLATES_FILTER,
|
||||
'woocommerce-admin',
|
||||
( productTemplates ) => {
|
||||
return productTemplates.filter(
|
||||
( template ) => template.key !== 'subscription'
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
! installedPlugins.includes( 'woocommerce-payments' );
|
||||
|
||||
const productTemplates = removeSubscriptions
|
||||
? getProductTemplates().filter(
|
||||
( template ) => template.key !== 'subscription'
|
||||
)
|
||||
: getProductTemplates();
|
||||
|
||||
const templates = applyFilters(
|
||||
ONBOARDING_PRODUCT_TEMPLATES_FILTER,
|
||||
PRODUCT_TEMPLATES
|
||||
productTemplates
|
||||
);
|
||||
|
||||
return (
|
||||
|
|
|
@ -12,7 +12,11 @@ import {
|
|||
archive,
|
||||
download,
|
||||
} from '@wordpress/icons';
|
||||
import { ONBOARDING_STORE_NAME, PLUGINS_STORE_NAME } from '@woocommerce/data';
|
||||
import {
|
||||
ONBOARDING_STORE_NAME,
|
||||
PLUGINS_STORE_NAME,
|
||||
SETTINGS_STORE_NAME,
|
||||
} from '@woocommerce/data';
|
||||
import { useSelect } from '@wordpress/data';
|
||||
import { List, Pill } from '@woocommerce/components';
|
||||
import { getAdminLink } from '@woocommerce/wc-admin-settings';
|
||||
|
@ -24,8 +28,9 @@ import { WooOnboardingTask } from '@woocommerce/onboarding';
|
|||
* Internal dependencies
|
||||
*/
|
||||
import ProductTemplateModal from './product-template-modal';
|
||||
import { getCountryCode } from '../../../dashboard/utils';
|
||||
|
||||
const subTasks = [
|
||||
const getSubTasks = () => [
|
||||
{
|
||||
key: 'addProductTemplate',
|
||||
title: (
|
||||
|
@ -94,10 +99,13 @@ const subTasks = [
|
|||
|
||||
const Products = () => {
|
||||
const [ selectTemplate, setSelectTemplate ] = useState( null );
|
||||
const { profileItems } = useSelect( ( select ) => {
|
||||
const { countryCode, profileItems } = useSelect( ( select ) => {
|
||||
const { getProfileItems } = select( ONBOARDING_STORE_NAME );
|
||||
const { getSettings } = select( SETTINGS_STORE_NAME );
|
||||
const { general: settings = {} } = getSettings( 'general' );
|
||||
|
||||
return {
|
||||
countryCode: getCountryCode( settings.woocommerce_default_country ),
|
||||
profileItems: getProfileItems(),
|
||||
};
|
||||
} );
|
||||
|
@ -109,9 +117,12 @@ const Products = () => {
|
|||
};
|
||||
} );
|
||||
|
||||
const subTasks = getSubTasks();
|
||||
|
||||
if (
|
||||
window.wcAdminFeatures &&
|
||||
window.wcAdminFeatures.subscriptions &&
|
||||
countryCode === 'US' &&
|
||||
profileItems.product_types.includes( 'subscriptions' ) &&
|
||||
installedPlugins.includes( 'woocommerce-payments' )
|
||||
) {
|
||||
|
|
|
@ -18,15 +18,20 @@ import { getCategorizedOnboardingProducts } from '../../dashboard/utils';
|
|||
const PurchaseTaskItem = () => {
|
||||
const [ cartModalOpen, setCartModalOpen ] = useState( false );
|
||||
|
||||
const { installedPlugins, profileItems } = useSelect( ( select ) => {
|
||||
const { getProfileItems } = select( ONBOARDING_STORE_NAME );
|
||||
const { getInstalledPlugins } = select( PLUGINS_STORE_NAME );
|
||||
const { installedPlugins, productTypes, profileItems } = useSelect(
|
||||
( select ) => {
|
||||
const { getProductTypes, getProfileItems } = select(
|
||||
ONBOARDING_STORE_NAME
|
||||
);
|
||||
const { getInstalledPlugins } = select( PLUGINS_STORE_NAME );
|
||||
|
||||
return {
|
||||
installedPlugins: getInstalledPlugins(),
|
||||
profileItems: getProfileItems(),
|
||||
};
|
||||
} );
|
||||
return {
|
||||
installedPlugins: getInstalledPlugins(),
|
||||
productTypes: getProductTypes(),
|
||||
profileItems: getProfileItems(),
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
const toggleCartModal = useCallback( () => {
|
||||
if ( ! cartModalOpen ) {
|
||||
|
@ -37,6 +42,7 @@ const PurchaseTaskItem = () => {
|
|||
}, [ cartModalOpen ] );
|
||||
|
||||
const groupedProducts = getCategorizedOnboardingProducts(
|
||||
productTypes,
|
||||
profileItems,
|
||||
installedPlugins
|
||||
);
|
||||
|
|
|
@ -5,6 +5,8 @@ const TYPES = {
|
|||
SET_EMAIL_PREFILL: 'SET_EMAIL_PREFILL',
|
||||
SET_TASKS_STATUS: 'SET_TASKS_STATUS',
|
||||
GET_PAYMENT_METHODS_SUCCESS: 'GET_PAYMENT_METHODS_SUCCESS',
|
||||
GET_PRODUCT_TYPES_SUCCESS: 'GET_PRODUCT_TYPES_SUCCESS',
|
||||
GET_PRODUCT_TYPES_ERROR: 'GET_PRODUCT_TYPES_ERROR',
|
||||
GET_FREE_EXTENSIONS_ERROR: 'GET_FREE_EXTENSIONS_ERROR',
|
||||
GET_FREE_EXTENSIONS_SUCCESS: 'GET_FREE_EXTENSIONS_SUCCESS',
|
||||
GET_TASK_LISTS_ERROR: 'GET_TASK_LISTS_ERROR',
|
||||
|
|
|
@ -222,6 +222,20 @@ export function actionTaskSuccess( task ) {
|
|||
};
|
||||
}
|
||||
|
||||
export function getProductTypesSuccess( productTypes ) {
|
||||
return {
|
||||
type: TYPES.GET_PRODUCT_TYPES_SUCCESS,
|
||||
productTypes,
|
||||
};
|
||||
}
|
||||
|
||||
export function getProductTypesError( error ) {
|
||||
return {
|
||||
type: TYPES.GET_PRODUCT_TYPES_ERROR,
|
||||
error,
|
||||
};
|
||||
}
|
||||
|
||||
export function* updateProfileItems( items ) {
|
||||
yield setIsRequesting( 'updateProfileItems', true );
|
||||
yield setError( 'updateProfileItems', null );
|
||||
|
|
|
@ -25,6 +25,7 @@ export const defaultState = {
|
|||
},
|
||||
emailPrefill: '',
|
||||
paymentMethods: [],
|
||||
productTypes: [],
|
||||
requesting: {},
|
||||
taskLists: [],
|
||||
tasksStatus: {},
|
||||
|
@ -55,6 +56,7 @@ const onboarding = (
|
|||
profileItems,
|
||||
emailPrefill,
|
||||
paymentMethods,
|
||||
productTypes,
|
||||
replace,
|
||||
error,
|
||||
isRequesting,
|
||||
|
@ -106,6 +108,19 @@ const onboarding = (
|
|||
...state,
|
||||
paymentMethods,
|
||||
};
|
||||
case TYPES.GET_PRODUCT_TYPES_SUCCESS:
|
||||
return {
|
||||
...state,
|
||||
productTypes,
|
||||
};
|
||||
case TYPES.GET_PRODUCT_TYPES_ERROR:
|
||||
return {
|
||||
...state,
|
||||
errors: {
|
||||
...state.errors,
|
||||
productTypes: error,
|
||||
},
|
||||
};
|
||||
case TYPES.GET_FREE_EXTENSIONS_ERROR:
|
||||
return {
|
||||
...state,
|
||||
|
|
|
@ -17,6 +17,8 @@ import {
|
|||
setTasksStatus,
|
||||
setPaymentMethods,
|
||||
setEmailPrefill,
|
||||
getProductTypesSuccess,
|
||||
getProductTypesError,
|
||||
} from './actions';
|
||||
import { DeprecatedTasks } from './deprecated-tasks';
|
||||
|
||||
|
@ -103,3 +105,16 @@ export function* getFreeExtensions() {
|
|||
yield getFreeExtensionsError( error );
|
||||
}
|
||||
}
|
||||
|
||||
export function* getProductTypes() {
|
||||
try {
|
||||
const results = yield apiFetch( {
|
||||
path: WC_ADMIN_NAMESPACE + '/onboarding/product-types',
|
||||
method: 'GET',
|
||||
} );
|
||||
|
||||
yield getProductTypesSuccess( results );
|
||||
} catch ( error ) {
|
||||
yield getProductTypesError( error );
|
||||
}
|
||||
}
|
||||
|
|
|
@ -24,6 +24,7 @@ export const getTasksStatus = (
|
|||
|
||||
const initialTaskLists: TaskListType[] = [];
|
||||
|
||||
const EMPTY_ARRAY: Product[] = [];
|
||||
export const getTaskLists = ( state: OnboardingState ): TaskListType[] => {
|
||||
return state.taskLists || initialTaskLists;
|
||||
};
|
||||
|
@ -52,6 +53,10 @@ export const getEmailPrefill = ( state: OnboardingState ): string => {
|
|||
return state.emailPrefill || '';
|
||||
};
|
||||
|
||||
export const getProductTypes = ( state: OnboardingState ): Product[] => {
|
||||
return state.productTypes || EMPTY_ARRAY;
|
||||
};
|
||||
|
||||
// Types
|
||||
export type OnboardingSelectors = {
|
||||
getProfileItems: () => ReturnType< typeof getProfileItems >;
|
||||
|
@ -69,6 +74,7 @@ export type OnboardingState = {
|
|||
taskLists: TaskListType[];
|
||||
tasksStatus: TasksStatusState;
|
||||
paymentMethods: PaymentMethodsState[];
|
||||
productTypes: Product[];
|
||||
emailPrefill: string;
|
||||
// TODO clarify what the error record's type is
|
||||
errors: Record< string, unknown >;
|
||||
|
@ -151,6 +157,12 @@ export type MethodFields = {
|
|||
value?: string;
|
||||
};
|
||||
|
||||
export type Product = {
|
||||
default?: boolean;
|
||||
label: string;
|
||||
product?: number;
|
||||
};
|
||||
|
||||
export type PaymentMethodsState = {
|
||||
locale: string;
|
||||
title: string;
|
||||
|
|
|
@ -76,6 +76,7 @@ class Init {
|
|||
'Automattic\WooCommerce\Admin\API\Plugins',
|
||||
'Automattic\WooCommerce\Admin\API\OnboardingFreeExtensions',
|
||||
'Automattic\WooCommerce\Admin\API\OnboardingPayments',
|
||||
'Automattic\WooCommerce\Admin\API\OnboardingProductTypes',
|
||||
'Automattic\WooCommerce\Admin\API\OnboardingProfile',
|
||||
'Automattic\WooCommerce\Admin\API\OnboardingTasks',
|
||||
'Automattic\WooCommerce\Admin\API\OnboardingThemes',
|
||||
|
|
|
@ -0,0 +1,78 @@
|
|||
<?php
|
||||
/**
|
||||
* REST API Onboarding Product Types Controller
|
||||
*
|
||||
* Handles requests to /onboarding/product-types
|
||||
*/
|
||||
|
||||
namespace Automattic\WooCommerce\Admin\API;
|
||||
|
||||
use Automattic\WooCommerce\Admin\Features\Onboarding;
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
/**
|
||||
* Onboarding Product Types Controller.
|
||||
*
|
||||
* @extends WC_REST_Data_Controller
|
||||
*/
|
||||
class OnboardingProductTypes extends \WC_REST_Data_Controller {
|
||||
|
||||
/**
|
||||
* Endpoint namespace.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $namespace = 'wc-admin';
|
||||
|
||||
/**
|
||||
* Route base.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $rest_base = 'onboarding/product-types';
|
||||
|
||||
/**
|
||||
* Register routes.
|
||||
*/
|
||||
public function register_routes() {
|
||||
register_rest_route(
|
||||
$this->namespace,
|
||||
'/' . $this->rest_base,
|
||||
array(
|
||||
array(
|
||||
'methods' => \WP_REST_Server::READABLE,
|
||||
'callback' => array( $this, 'get_product_types' ),
|
||||
'permission_callback' => array( $this, 'get_items_permissions_check' ),
|
||||
),
|
||||
'schema' => array( $this, 'get_public_item_schema' ),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether a given request has permission to read onboarding profile data.
|
||||
*
|
||||
* @param WP_REST_Request $request Full details about the request.
|
||||
* @return WP_Error|boolean
|
||||
*/
|
||||
public function get_items_permissions_check( $request ) {
|
||||
if ( ! wc_rest_check_manager_permissions( 'settings', 'read' ) ) {
|
||||
return new \WP_Error( 'woocommerce_rest_cannot_view', __( 'Sorry, you cannot list resources.', 'woocommerce-admin' ), array( 'status' => rest_authorization_required_code() ) );
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return available product types.
|
||||
*
|
||||
* @param \WP_REST_Request $request Request data.
|
||||
*
|
||||
* @return \WP_Error|\WP_REST_Response
|
||||
*/
|
||||
public function get_product_types( $request ) {
|
||||
return Onboarding::get_allowed_product_types();
|
||||
}
|
||||
|
||||
}
|
|
@ -433,7 +433,7 @@ class Onboarding {
|
|||
* @return array
|
||||
*/
|
||||
public static function get_allowed_product_types() {
|
||||
$products = array(
|
||||
$products = array(
|
||||
'physical' => array(
|
||||
'label' => __( 'Physical products', 'woocommerce-admin' ),
|
||||
'default' => true,
|
||||
|
@ -461,7 +461,8 @@ class Onboarding {
|
|||
'product' => 18618,
|
||||
),
|
||||
);
|
||||
if ( ! Features::is_enabled( 'subscriptions' ) ) {
|
||||
$base_location = wc_get_base_location();
|
||||
if ( ! Features::is_enabled( 'subscriptions' ) || 'US' !== $base_location['country'] ) {
|
||||
$products['subscriptions']['product'] = 27147;
|
||||
}
|
||||
$product_types = self::append_product_data( $products );
|
||||
|
@ -690,7 +691,6 @@ class Onboarding {
|
|||
$settings['onboarding']['euCountries'] = WC()->countries->get_european_union_countries();
|
||||
$settings['onboarding']['industries'] = self::get_allowed_industries();
|
||||
$settings['onboarding']['localeInfo'] = include WC()->plugin_path() . '/i18n/locale-info.php';
|
||||
$settings['onboarding']['productTypes'] = self::get_allowed_product_types();
|
||||
$settings['onboarding']['profile'] = $profile;
|
||||
$settings['onboarding']['themes'] = self::get_themes();
|
||||
|
||||
|
|
Loading…
Reference in New Issue