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:
Fernando 2021-10-13 13:15:47 -03:00 committed by GitHub
parent a8a3238c38
commit f20113fa01
19 changed files with 396 additions and 135 deletions

View File

@ -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

View File

@ -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 );

View File

@ -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
);

View File

@ -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(

View File

@ -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,
};
} )

View File

@ -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 {

View File

@ -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();
} );
} );

View File

@ -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,
};

View File

@ -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 (

View File

@ -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' )
) {

View File

@ -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
);

View File

@ -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',

View File

@ -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 );

View File

@ -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,

View File

@ -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 );
}
}

View File

@ -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;

View File

@ -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',

View File

@ -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();
}
}

View File

@ -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();