This commit is contained in:
Paul Sealock 2020-06-11 11:49:27 +12:00 committed by GitHub
parent 9be6bdd841
commit 41eeb4f9f9
36 changed files with 688 additions and 661 deletions

View File

@ -4,7 +4,7 @@
import { __, sprintf } from '@wordpress/i18n';
import { Fragment, Suspense, lazy, useState } from '@wordpress/element';
import { compose } from '@wordpress/compose';
import { partial, get } from 'lodash';
import { partial } from 'lodash';
import { Dropdown, Button, Icon } from '@wordpress/components';
import { applyFilters } from '@wordpress/hooks';
import { Icon as WPIcon, plusCircleFilled } from '@wordpress/icons';
@ -15,6 +15,7 @@ import { Icon as WPIcon, plusCircleFilled } from '@wordpress/icons';
import { H, Spinner } from '@woocommerce/components';
import {
SETTINGS_STORE_NAME,
OPTIONS_STORE_NAME,
useUserPreferences,
} from '@woocommerce/data';
@ -329,8 +330,7 @@ const CustomizableDashboard = ( {
export default compose(
withSelect( ( select ) => {
const { getOptions } = select( 'wc-api' );
const { getOption } = select( OPTIONS_STORE_NAME );
const { woocommerce_default_date_range: defaultDateRange } = select(
SETTINGS_STORE_NAME
).getSetting( 'wc_admin', 'wcAdminSettings' );
@ -340,16 +340,13 @@ export default compose(
};
if ( isOnboardingEnabled() ) {
const options = getOptions( [
'woocommerce_task_list_complete',
'woocommerce_task_list_hidden',
] );
withSelectData.homepageEnabled =
window.wcAdminFeatures.homepage &&
getOption( 'woocommerce_homescreen_enabled' ) === 'yes';
withSelectData.taskListHidden =
get( options, [ 'woocommerce_task_list_hidden' ], 'no' ) ===
'yes';
getOption( 'woocommerce_task_list_hidden' ) === 'yes';
withSelectData.taskListComplete =
get( options, [ 'woocommerce_task_list_complete' ], 'no' ) ===
'yes';
getOption( 'woocommerce_task_list_complete' ) === 'yes';
}
return withSelectData;

View File

@ -51,6 +51,7 @@ export function getProductIdsForCart(
installedPlugins
) {
const onboarding = getSetting( 'onboarding', {} );
const productIds = [];
// The population of onboarding.productTypes only happens if the task list should be shown
// so bail early if it isn't present.
@ -58,7 +59,6 @@ export function getProductIdsForCart(
return productIds;
}
const productIds = [];
const productTypes = profileItems.product_types || [];
productTypes.forEach( ( productType ) => {

View File

@ -11,9 +11,13 @@ import {
} from '@wordpress/element';
import { compose } from '@wordpress/compose';
import classnames from 'classnames';
import { get } from 'lodash';
import PropTypes from 'prop-types';
/**
* WooCommerce dependencies
*/
import { OPTIONS_STORE_NAME } from '@woocommerce/data';
/**
* Internal dependencies
*/
@ -21,9 +25,9 @@ import QuickLinks from '../quick-links';
import StatsOverview from './stats-overview';
import './style.scss';
import { isOnboardingEnabled } from 'dashboard/utils';
import withSelect from 'wc-api/with-select';
import TaskListPlaceholder from '../task-list/placeholder';
import InboxPanel from '../header/activity-panel/panels/inbox';
import withWCApiSelect from 'wc-api/with-select';
const TaskList = lazy( () =>
import( /* webpackChunkName: "task-list" */ '../task-list' )
@ -138,32 +142,27 @@ Layout.propTypes = {
};
export default compose(
withSelect( ( select ) => {
withWCApiSelect( ( select ) => {
const {
getOptions,
getUndoDismissRequesting,
isGetOptionsRequesting,
} = select( 'wc-api' );
const { isUndoRequesting } = getUndoDismissRequesting();
const { getOption, isResolving } = select( OPTIONS_STORE_NAME );
if ( isOnboardingEnabled() ) {
const options = getOptions( [
'woocommerce_task_list_complete',
'woocommerce_task_list_hidden',
] );
const { isUndoRequesting } = getUndoDismissRequesting();
return {
isUndoRequesting,
requestingTaskList: isGetOptionsRequesting( [
'woocommerce_task_list_complete',
'woocommerce_task_list_hidden',
] ),
taskListComplete:
get( options, [ 'woocommerce_task_list_complete' ] ) ===
'yes',
getOption( 'woocommerce_task_list_complete' ) === 'yes',
taskListHidden:
get( options, [ 'woocommerce_task_list_hidden' ] ) ===
'yes',
getOption( 'woocommerce_task_list_hidden' ) === 'yes',
requestingTaskList:
isResolving( 'getOption', [
'woocommerce_task_list_complete',
] ) ||
isResolving( 'getOption', [
'woocommerce_task_list_hidden',
] ),
};
}

View File

@ -6,7 +6,7 @@ import { withSelect } from '@wordpress/data';
import { Component, lazy, Suspense } from '@wordpress/element';
import { Router, Route, Switch } from 'react-router-dom';
import PropTypes from 'prop-types';
import { get, isFunction } from 'lodash';
import { get, isFunction, identity } from 'lodash';
/**
* WooCommerce dependencies
@ -14,7 +14,12 @@ import { get, isFunction } from 'lodash';
import { useFilters, Spinner } from '@woocommerce/components';
import { getHistory } from '@woocommerce/navigation';
import { getSetting } from '@woocommerce/wc-admin-settings';
import { PLUGINS_STORE_NAME, withPluginsHydration } from '@woocommerce/data';
import {
OPTIONS_STORE_NAME,
PLUGINS_STORE_NAME,
withPluginsHydration,
withOptionsHydration,
} from '@woocommerce/data';
/**
* Internal dependencies
@ -25,7 +30,6 @@ import Header from 'header';
import Notices from './notices';
import { recordPageView } from 'lib/tracks';
import TransientNotices from './transient-notices';
import withWCApiSelect from 'wc-api/with-select';
const StoreAlerts = lazy( () =>
import( /* webpackChunkName: "store-alerts" */ './store-alerts' )
);
@ -211,13 +215,16 @@ class _PageLayout extends Component {
export const PageLayout = compose(
// Use the useFilters HoC so PageLayout is re-rendered when filters are used to add new pages or reports
useFilters( [ PAGES_FILTER, REPORTS_FILTER ] ),
withWCApiSelect( ( select ) => {
const { getOptions } = select( 'wc-api' );
const options = getOptions( [ 'woocommerce_homescreen_enabled' ] );
window.wcSettings.preloadOptions
? withOptionsHydration( {
...window.wcSettings.preloadOptions,
} )
: identity,
withSelect( ( select ) => {
const { getOption } = select( OPTIONS_STORE_NAME );
const homepageEnabled =
window.wcAdminFeatures.homepage &&
get( options, [ 'woocommerce_homescreen_enabled' ], false ) ===
'yes';
getOption( 'woocommerce_homescreen_enabled' ) === 'yes';
return { homepageEnabled };
} )
)( _PageLayout );

View File

@ -3,6 +3,11 @@
*/
import { getSetting } from '@woocommerce/wc-admin-settings';
/**
* WooCommerce dependencies
*/
import { withOptionsHydration } from '@woocommerce/data';
/**
* Internal dependencies
*/
@ -26,4 +31,6 @@ const MarketingOverview = () => {
);
};
export default MarketingOverview;
export default withOptionsHydration( {
...( window.wcSettings.preloadOptions || {} ),
} )( MarketingOverview );

View File

@ -2,24 +2,23 @@
* External dependencies
*/
import { __ } from '@wordpress/i18n';
import { get } from 'lodash';
import { Button } from '@wordpress/components';
import Gridicon from 'gridicons';
import { compose } from '@wordpress/compose';
import { withDispatch } from '@wordpress/data';
import { withDispatch, withSelect } from '@wordpress/data';
import PropTypes from 'prop-types';
/**
* WooCommerce dependencies
*/
import { Card } from '@woocommerce/components';
import { OPTIONS_STORE_NAME } from '@woocommerce/data';
/**
* Internal dependencies
*/
import './style.scss';
import { recordEvent } from 'lib/tracks';
import withSelect from 'wc-api/with-select';
import WelcomeImage from './images/welcome.svg';
const WelcomeCard = ( {
@ -72,17 +71,17 @@ export { WelcomeCard }
// default export
export default compose(
withSelect( ( select ) => {
const { getOptions, isUpdateOptionsRequesting } = select( 'wc-api' );
const hideOptionName = 'woocommerce_marketing_overview_welcome_hidden';
const options = getOptions( [ hideOptionName ] );
const isHidden = get( options, [ hideOptionName ], 'no' ) === 'yes';
const isUpdateRequesting = Boolean( isUpdateOptionsRequesting( [ hideOptionName ] ) );
const { getOption, isOptionsUpdating } = select( OPTIONS_STORE_NAME );
const isUpdateRequesting = isOptionsUpdating();
return {
isHidden: isHidden || isUpdateRequesting,
isHidden:
getOption( 'woocommerce_marketing_overview_welcome_hidden' ) ===
'yes' || isUpdateRequesting,
};
} ),
withDispatch( ( dispatch ) => {
const { updateOptions } = dispatch( 'wc-api' );
const { updateOptions } = dispatch( OPTIONS_STORE_NAME );
return {
updateOptions,
};

View File

@ -247,7 +247,9 @@ const hydrateSettings =
export default compose(
withSelect( ( select ) => {
const { getNotes } = select( 'wc-api' );
const { getProfileItems, getOnboardingError } = select( ONBOARDING_STORE_NAME );
const { getProfileItems, getOnboardingError } = select(
ONBOARDING_STORE_NAME
);
const { getActivePlugins } = select( PLUGINS_STORE_NAME );
const notesQuery = {

View File

@ -5,7 +5,7 @@ import { __, _n, sprintf } from '@wordpress/i18n';
import { Button } from '@wordpress/components';
import { Component } from '@wordpress/element';
import { compose } from '@wordpress/compose';
import { withDispatch } from '@wordpress/data';
import { withDispatch, withSelect } from '@wordpress/data';
import { filter } from 'lodash';
/**
@ -17,6 +17,7 @@ import {
pluginNames,
ONBOARDING_STORE_NAME,
PLUGINS_STORE_NAME,
OPTIONS_STORE_NAME
} from '@woocommerce/data';
/**
@ -28,7 +29,6 @@ import ManagementIcon from './images/management';
import SalesTaxIcon from './images/sales_tax';
import ShippingLabels from './images/shipping_labels';
import SpeedIcon from './images/speed';
import withSelect from 'wc-api/with-select';
import { recordEvent } from 'lib/tracks';
class Benefits extends Component {
@ -108,12 +108,12 @@ class Benefits extends Component {
goToNextStep();
}
async startPluginInstall() {
startPluginInstall() {
const { updateProfileItems, updateOptions } = this.props;
this.setState( { isInstalling: true } );
await updateOptions( {
updateOptions( {
woocommerce_setup_jetpack_opted_in: true,
} );
@ -328,7 +328,7 @@ export default compose(
} ),
withDispatch( ( dispatch ) => {
const { updateProfileItems } = dispatch( ONBOARDING_STORE_NAME );
const { updateOptions } = dispatch( 'wc-api' );
const { updateOptions } = dispatch( OPTIONS_STORE_NAME );
const { createNotice } = dispatch( 'core/notices' );
return {

View File

@ -4,8 +4,7 @@
import { __ } from '@wordpress/i18n';
import { Component } from '@wordpress/element';
import { compose } from '@wordpress/compose';
import { withDispatch } from '@wordpress/data';
import { get } from 'lodash';
import { withDispatch, withSelect } from '@wordpress/data';
import interpolateComponents from 'interpolate-components';
import {
Button,
@ -14,11 +13,15 @@ import {
Modal,
} from '@wordpress/components';
/**
* WooCommerce dependencies
*/
import { OPTIONS_STORE_NAME } from '@woocommerce/data';
/**
* Internal dependencies
*/
import { Link } from '@woocommerce/components';
import withSelect from 'wc-api/with-select';
class UsageModal extends Component {
constructor( props ) {
@ -159,20 +162,15 @@ class UsageModal extends Component {
export default compose(
withSelect( ( select ) => {
const {
getOptions,
getOptionsError,
isUpdateOptionsRequesting,
} = select( 'wc-api' );
getOption,
getOptionsUpdatingError,
isOptionsUpdating,
} = select( OPTIONS_STORE_NAME );
const options = getOptions( [ 'woocommerce_allow_tracking' ] );
const allowTracking =
get( options, [ 'woocommerce_allow_tracking' ], false ) === 'yes';
const isRequesting = Boolean(
isUpdateOptionsRequesting( [ 'woocommerce_allow_tracking' ] )
);
const hasErrors = Boolean(
getOptionsError( [ 'woocommerce_allow_tracking' ] )
);
getOption( 'woocommerce_allow_tracking' ) === 'yes';
const isRequesting = Boolean( isOptionsUpdating() );
const hasErrors = Boolean( getOptionsUpdatingError() );
return {
allowTracking,
@ -182,7 +180,7 @@ export default compose(
} ),
withDispatch( ( dispatch ) => {
const { createNotice } = dispatch( 'core/notices' );
const { updateOptions } = dispatch( 'wc-api' );
const { updateOptions } = dispatch( OPTIONS_STORE_NAME );
return {
createNotice,

View File

@ -3,7 +3,7 @@
*/
import { __ } from '@wordpress/i18n';
import { Component, cloneElement, Fragment } from '@wordpress/element';
import { get, isEqual } from 'lodash';
import { isEqual } from 'lodash';
import { compose } from '@wordpress/compose';
import classNames from 'classnames';
import {
@ -21,7 +21,11 @@ import { Icon, check, chevronRight } from '@wordpress/icons';
*/
import { H, List, EllipsisMenu } from '@woocommerce/components';
import { updateQueryString } from '@woocommerce/navigation';
import { ONBOARDING_STORE_NAME, PLUGINS_STORE_NAME } from '@woocommerce/data';
import {
PLUGINS_STORE_NAME,
OPTIONS_STORE_NAME,
ONBOARDING_STORE_NAME,
} from '@woocommerce/data';
/**
* Internal dependencies
@ -118,7 +122,7 @@ class TaskDashboard extends Component {
return getAllTasks( {
profileItems,
options: taskListPayments,
taskListPayments,
query,
toggleCartModal: this.toggleCartModal.bind( this ),
installedPlugins,
@ -383,7 +387,7 @@ class TaskDashboard extends Component {
export default compose(
withSelect( ( select, props ) => {
const { getProfileItems } = select( ONBOARDING_STORE_NAME );
const { getOptions } = select( 'wc-api' );
const { getOption } = select( OPTIONS_STORE_NAME );
const {
getActivePlugins,
getInstalledPlugins,
@ -391,28 +395,18 @@ export default compose(
} = select( PLUGINS_STORE_NAME );
const profileItems = getProfileItems();
const options = getOptions( [
'woocommerce_task_list_welcome_modal_dismissed',
'woocommerce_task_list_hidden',
'woocommerce_task_list_tracked_completed_tasks',
] );
const modalDismissed = get(
options,
[ 'woocommerce_task_list_welcome_modal_dismissed' ],
false
);
const taskListPayments = getOptions( [
'woocommerce_task_list_payments',
] );
const trackedCompletedTasks = get(
options,
[ 'woocommerce_task_list_tracked_completed_tasks' ],
[]
);
const modalDismissed =
getOption( 'woocommerce_task_list_welcome_modal_dismissed' ) ||
false;
const taskListPayments = getOption( 'woocommerce_task_list_payments' );
const trackedCompletedTasks =
getOption( 'woocommerce_task_list_tracked_completed_tasks' ) || [];
const payments = getOption( 'woocommerce_task_list_payments' );
const installedPlugins = getInstalledPlugins();
const tasks = getAllTasks( {
profileItems,
options: getOptions( [ 'woocommerce_task_list_payments' ] ),
options: payments,
query: props.query,
installedPlugins,
} );
@ -437,7 +431,7 @@ export default compose(
};
} ),
withDispatch( ( dispatch ) => {
const { updateOptions } = dispatch( 'wc-api' );
const { updateOptions } = dispatch( OPTIONS_STORE_NAME );
return {
updateOptions,
};

View File

@ -4,7 +4,6 @@
import { __ } from '@wordpress/i18n';
import { applyFilters } from '@wordpress/hooks';
import { get } from 'lodash';
/**
* WooCommerce dependencies
@ -25,7 +24,7 @@ import Payments from './tasks/payments';
export function getAllTasks( {
profileItems,
options,
taskListPayments,
query,
toggleCartModal,
installedPlugins,
@ -55,15 +54,11 @@ export function getAllTasks( {
installedPlugins
);
const paymentsCompleted = get(
options,
[ 'woocommerce_task_list_payments', 'completed' ],
false
const paymentsCompleted = Boolean(
taskListPayments && taskListPayments.completed
);
const paymentsSkipped = get(
options,
[ 'woocommerce_task_list_payments', 'skipped' ],
false
const paymentsSkipped = Boolean(
taskListPayments && taskListPayments.skipped
);
const tasks = [

View File

@ -6,8 +6,8 @@ import apiFetch from '@wordpress/api-fetch';
import { Button } from '@wordpress/components';
import { Component, Fragment } from '@wordpress/element';
import { compose } from '@wordpress/compose';
import { difference, filter } from 'lodash';
import { withDispatch } from '@wordpress/data';
import { filter } from 'lodash';
import { withDispatch, withSelect } from '@wordpress/data';
/**
* WooCommerce dependencies
@ -20,13 +20,13 @@ import {
} from '@woocommerce/components';
import { getHistory, getNewPath } from '@woocommerce/navigation';
import { getSetting, setSetting } from '@woocommerce/wc-admin-settings';
import { OPTIONS_STORE_NAME } from '@woocommerce/data';
/**
* Internal dependencies
*/
import { queueRecordEvent, recordEvent } from 'lib/tracks';
import { WC_ADMIN_NAMESPACE } from 'wc-api/constants';
import withSelect from 'wc-api/with-select';
class Appearance extends Component {
constructor( props ) {
@ -43,7 +43,9 @@ class Appearance extends Component {
isPending: false,
logo: null,
stepIndex: 0,
storeNoticeText: props.options.woocommerce_demo_store_notice || '',
isUpdatingLogo: false,
isUpdatingNotice: false,
storeNoticeText: props.demoStoreNotice || '',
};
this.completeStep = this.completeStep.bind( this );
@ -63,18 +65,9 @@ class Appearance extends Component {
}
}
async componentDidUpdate( prevProps ) {
const { isPending, logo, stepIndex } = this.state;
const {
createNotice,
errors,
hasErrors,
isRequesting,
options,
} = this.props;
const step = this.getSteps()[ stepIndex ].key;
const isRequestSuccessful =
! isRequesting && prevProps.isRequesting && ! hasErrors;
componentDidUpdate( prevProps ) {
const { isPending, logo } = this.state;
const { demoStoreNotice } = this.props;
if ( logo && ! logo.url && ! isPending ) {
/* eslint-disable react/no-did-update-set-state */
@ -93,38 +86,15 @@ class Appearance extends Component {
}
if (
options.woocommerce_demo_store_notice &&
prevProps.options.woocommerce_demo_store_notice !==
options.woocommerce_demo_store_notice
demoStoreNotice &&
prevProps.demoStoreNotice !== demoStoreNotice
) {
/* eslint-disable react/no-did-update-set-state */
this.setState( {
storeNoticeText: options.woocommerce_demo_store_notice,
storeNoticeText: demoStoreNotice,
} );
/* eslint-enable react/no-did-update-set-state */
}
if ( step === 'logo' && isRequestSuccessful ) {
createNotice(
'success',
__( 'Store logo updated sucessfully.', 'woocommerce-admin' )
);
this.completeStep();
}
if ( step === 'notice' && isRequestSuccessful ) {
createNotice(
'success',
__(
"🎨 Your store is looking great! Don't forget to continue personalizing it.",
'woocommerce-admin'
)
);
this.completeStep();
}
const newErrors = difference( errors, prevProps.errors );
newErrors.map( ( error ) => createNotice( 'error', error ) );
}
completeStep() {
@ -222,8 +192,8 @@ class Appearance extends Component {
} );
}
updateLogo() {
const { updateOptions } = this.props;
async updateLogo() {
const { updateOptions, createNotice } = this.props;
const { logo } = this.state;
const { stylesheet, themeMods } = getSetting( 'onboarding', {} );
const updatedThemeMods = {
@ -238,13 +208,25 @@ class Appearance extends Component {
themeMods: updatedThemeMods,
} );
updateOptions( {
this.setState( { isUpdatingLogo: true } );
const update = await updateOptions( {
[ `theme_mods_${ stylesheet }` ]: updatedThemeMods,
} );
if ( update.success ) {
this.setState( { isUpdatingLogo: false } );
createNotice(
'success',
__( 'Store logo updated sucessfully.', 'woocommerce-admin' )
);
this.completeStep();
} else {
createNotice( 'error', update.message );
}
}
updateNotice() {
const { updateOptions } = this.props;
async updateNotice() {
const { updateOptions, createNotice } = this.props;
const { storeNoticeText } = this.state;
recordEvent( 'tasklist_appearance_set_store_notice', {
@ -256,16 +238,36 @@ class Appearance extends Component {
isAppearanceComplete: true,
} );
updateOptions( {
this.setState( { isUpdatingNotice: true } );
const update = await updateOptions( {
woocommerce_task_list_appearance_complete: true,
woocommerce_demo_store: storeNoticeText.length ? 'yes' : 'no',
woocommerce_demo_store_notice: storeNoticeText,
} );
if ( update.success ) {
this.setState( { isUpdatingNotice: false } );
createNotice(
'success',
__(
"🎨 Your store is looking great! Don't forget to continue personalizing it.",
'woocommerce-admin'
)
);
this.completeStep();
} else {
createNotice( 'error', update.message );
}
}
getSteps() {
const { isDirty, isPending, logo, storeNoticeText } = this.state;
const { isRequesting } = this.props;
const {
isDirty,
isPending,
logo,
storeNoticeText,
isUpdatingLogo,
} = this.state;
const steps = [
{
@ -340,7 +342,7 @@ class Appearance extends Component {
<Button
disabled={ ! logo && ! isDirty }
onClick={ this.updateLogo }
isBusy={ isRequesting }
isBusy={ isUpdatingLogo }
isPrimary
>
{ __( 'Proceed', 'woocommerce-admin' ) }
@ -388,8 +390,12 @@ class Appearance extends Component {
}
render() {
const { isPending, stepIndex } = this.state;
const { isRequesting, hasErrors } = this.props;
const {
isPending,
stepIndex,
isUpdatingLogo,
isUpdatingNotice,
} = this.state;
const currentStep = this.getSteps()[ stepIndex ].key;
return (
@ -397,7 +403,7 @@ class Appearance extends Component {
<Card className="is-narrow">
<Stepper
isPending={
( isRequesting && ! hasErrors ) || isPending
isUpdatingNotice || isUpdatingLogo || isPending
}
isVertical
currentStep={ currentStep }
@ -411,49 +417,15 @@ class Appearance extends Component {
export default compose(
withSelect( ( select ) => {
const {
getOptions,
getOptionsError,
isUpdateOptionsRequesting,
} = select( 'wc-api' );
const { stylesheet } = getSetting( 'onboarding', {} );
const { getOption } = select( OPTIONS_STORE_NAME );
const options = getOptions( [
'woocommerce_demo_store',
'woocommerce_demo_store_notice',
] );
const errors = [];
const uploadLogoError = getOptionsError( [
`theme_mods_${ stylesheet }`,
] );
const storeNoticeError = getOptionsError( [
'woocommerce_demo_store',
'woocommerce_demo_store_notice',
] );
if ( uploadLogoError ) {
errors.push( uploadLogoError.message );
}
if ( storeNoticeError ) {
errors.push( storeNoticeError.message );
}
const hasErrors = Boolean( errors.length );
const isRequesting =
Boolean(
isUpdateOptionsRequesting( [ `theme_mods_${ stylesheet }` ] )
) ||
Boolean(
isUpdateOptionsRequesting( [
'woocommerce_task_list_appearance_complete',
'woocommerce_demo_store',
'woocommerce_demo_store_notice',
] )
);
return { errors, getOptionsError, hasErrors, isRequesting, options };
return {
demoStoreNotice: getOption( 'woocommerce_demo_store_notice' ),
};
} ),
withDispatch( ( dispatch ) => {
const { createNotice } = dispatch( 'core/notices' );
const { updateOptions } = dispatch( 'wc-api' );
const { updateOptions } = dispatch( OPTIONS_STORE_NAME );
return {
createNotice,

View File

@ -5,17 +5,13 @@ import { __ } from '@wordpress/i18n';
import { Component, Fragment } from '@wordpress/element';
import { Button } from '@wordpress/components';
import { compose } from '@wordpress/compose';
import { withDispatch } from '@wordpress/data';
import { withDispatch, withSelect } from '@wordpress/data';
/**
* WooCommerce dependencies
*/
import { Form, H, TextControl } from '@woocommerce/components';
/**
* Internal dependencies
*/
import withSelect from 'wc-api/with-select';
import { OPTIONS_STORE_NAME } from '@woocommerce/data';
class PayFast extends Component {
getInitialConfigValues = () => {
@ -42,45 +38,34 @@ class PayFast extends Component {
return errors;
};
componentDidUpdate( prevProps ) {
const {
createNotice,
isOptionsRequesting,
hasOptionsError,
markConfigured,
} = this.props;
updateSettings = async ( values ) => {
const { updateOptions, createNotice, markConfigured } = this.props;
if ( prevProps.isOptionsRequesting && ! isOptionsRequesting ) {
if ( ! hasOptionsError ) {
markConfigured( 'bacs' );
createNotice(
'success',
__(
'Direct bank transfer details added successfully',
'woocommerce-admin'
)
);
} else {
createNotice(
'error',
__(
'There was a problem saving your payment setings',
'woocommerce-admin'
)
);
}
}
}
updateSettings = ( values ) => {
const { updateOptions } = this.props;
updateOptions( {
const update = await updateOptions( {
woocommerce_bacs_settings: {
enabled: 'yes',
},
woocommerce_bacs_accounts: [ values ],
} );
if ( update.success ) {
markConfigured( 'bacs' );
createNotice(
'success',
__(
'Direct bank transfer details added successfully',
'woocommerce-admin'
)
);
} else {
createNotice(
'error',
__(
'There was a problem saving your payment setings',
'woocommerce-admin'
)
);
}
};
render() {
@ -171,28 +156,16 @@ class PayFast extends Component {
export default compose(
withSelect( ( select ) => {
const { getOptionsError, isUpdateOptionsRequesting } = select(
'wc-api'
);
const isOptionsRequesting = Boolean(
isUpdateOptionsRequesting( [
'woocommerce_bacs_settings',
'woocommerce_bacs_accounts',
] )
);
const hasOptionsError = getOptionsError( [
'woocommerce_bacs_settings',
'woocommerce_bacs_accounts',
] );
const { isOptionsUpdating } = select( OPTIONS_STORE_NAME );
const isOptionsRequesting = isOptionsUpdating();
return {
hasOptionsError,
isOptionsRequesting,
};
} ),
withDispatch( ( dispatch ) => {
const { createNotice } = dispatch( 'core/notices' );
const { updateOptions } = dispatch( 'wc-api' );
const { updateOptions } = dispatch( OPTIONS_STORE_NAME );
return {
createNotice,

View File

@ -19,8 +19,9 @@ import {
} from '@woocommerce/navigation';
import {
ONBOARDING_STORE_NAME,
pluginNames,
OPTIONS_STORE_NAME,
PLUGINS_STORE_NAME,
pluginNames,
} from '@woocommerce/data';
/**
@ -53,32 +54,15 @@ class Payments extends Component {
if ( prevProps === this.props ) {
return;
}
const { createNotice, errors, methods, requesting } = this.props;
const { methods } = this.props;
let recommendedMethod = 'stripe';
methods.forEach( ( method ) => {
const { key, title, visible } = method;
const { key, visible } = method;
if ( key === 'wcpay' && visible ) {
recommendedMethod = 'wcpay';
}
if (
prevProps.requesting[ key ] &&
! requesting[ key ] &&
errors[ key ]
) {
createNotice(
'error',
sprintf(
__(
'There was a problem updating settings for %s',
'woocommerce-admin'
),
title
)
);
}
} );
if ( this.state.recommendedMethod !== recommendedMethod ) {
@ -88,10 +72,10 @@ class Payments extends Component {
}
}
completeTask() {
async completeTask() {
const { createNotice, methods, updateOptions } = this.props;
updateOptions( {
const update = await updateOptions( {
woocommerce_task_list_payments: {
completed: 1,
timestamp: Math.floor( Date.now() / 1000 ),
@ -104,15 +88,25 @@ class Payments extends Component {
.map( ( method ) => method.key ),
} );
createNotice(
'success',
__(
'💰 Ka-ching! Your store can now accept payments 💳',
'woocommerce-admin'
)
);
if ( update.success ) {
createNotice(
'success',
__(
'💰 Ka-ching! Your store can now accept payments 💳',
'woocommerce-admin'
)
);
getHistory().push( getNewPath( {}, '/', {} ) );
getHistory().push( getNewPath( {}, '/', {} ) );
} else {
createNotice(
'error',
__(
'There was a problem updating settings',
'woocommerce-admin'
)
);
}
}
skipTask() {
@ -218,8 +212,8 @@ class Payments extends Component {
render() {
const currentMethod = this.getCurrentMethod();
const { methods, query } = this.props;
const { enabledMethods, recommendedMethod } = this.state;
const { methods, query, requesting } = this.props;
const configuredMethods = methods.filter(
( method ) => method.isConfigured
).length;
@ -344,7 +338,11 @@ class Payments extends Component {
) }
</Button>
) : (
<Button isPrimary onClick={ this.completeTask }>
<Button
isPrimary
isBusy={ requesting }
onClick={ this.completeTask }
>
{ __( 'Done', 'woocommerce-admin' ) }
</Button>
) }
@ -357,18 +355,15 @@ class Payments extends Component {
export default compose(
withSelect( ( select ) => {
const { getProfileItems } = select( ONBOARDING_STORE_NAME );
const {
getOptions,
getUpdateOptionsError,
isUpdateOptionsRequesting,
} = select( 'wc-api' );
const { getOption, isOptionsUpdating } = select( OPTIONS_STORE_NAME );
const { getActivePlugins, isJetpackConnected } = select(
PLUGINS_STORE_NAME
);
const activePlugins = getActivePlugins();
const profileItems = getProfileItems();
const options = getOptions( [
const optionNames = [
'woocommerce_default_country',
'woocommerce_woocommerce_payments_settings',
'woocommerce_stripe_settings',
@ -381,7 +376,12 @@ export default compose(
'woocommerce_cod_settings',
'woocommerce_bacs_settings',
'woocommerce_bacs_accounts',
] );
];
const options = optionNames.reduce( ( result, name ) => {
result[ name ] = getOption( name );
return result;
}, {} );
const countryCode = getCountryCode(
options.woocommerce_default_country
);
@ -394,20 +394,10 @@ export default compose(
profileItems,
} );
const errors = {};
const requesting = {};
methods.forEach( ( method ) => {
errors[ method.key ] = Boolean(
getUpdateOptionsError( [ method.optionName ] )
);
requesting[ method.key ] = Boolean(
isUpdateOptionsRequesting( [ method.optionName ] )
);
} );
const requesting = isOptionsUpdating();
return {
countryCode,
errors,
profileItems,
activePlugins,
options,
@ -417,7 +407,7 @@ export default compose(
} ),
withDispatch( ( dispatch ) => {
const { createNotice } = dispatch( 'core/notices' );
const { updateOptions } = dispatch( 'wc-api' );
const { updateOptions } = dispatch( OPTIONS_STORE_NAME );
return {
createNotice,
updateOptions,

View File

@ -6,17 +6,13 @@ import { Component, Fragment } from '@wordpress/element';
import { Button } from '@wordpress/components';
import interpolateComponents from 'interpolate-components';
import { compose } from '@wordpress/compose';
import { withDispatch } from '@wordpress/data';
import { withDispatch, withSelect } from '@wordpress/data';
/**
* WooCommerce dependencies
*/
import { Form, Link, Stepper, TextControl } from '@woocommerce/components';
/**
* Internal dependencies
*/
import withSelect from 'wc-api/with-select';
import { OPTIONS_STORE_NAME } from '@woocommerce/data';
class PayFast extends Component {
getInitialConfigValues = () => {
@ -54,39 +50,12 @@ class PayFast extends Component {
return errors;
};
componentDidUpdate( prevProps ) {
const {
createNotice,
isOptionsRequesting,
hasOptionsError,
markConfigured,
} = this.props;
if ( prevProps.isOptionsRequesting && ! isOptionsRequesting ) {
if ( ! hasOptionsError ) {
markConfigured( 'payfast' );
createNotice(
'success',
__( 'PayFast connected successfully', 'woocommerce-admin' )
);
} else {
createNotice(
'error',
__(
'There was a problem saving your payment setings',
'woocommerce-admin'
)
);
}
}
}
updateSettings = ( values ) => {
const { updateOptions } = this.props;
updateSettings = async ( values ) => {
const { updateOptions, createNotice, markConfigured } = this.props;
// Because the PayFast extension only works with the South African Rand
// currency, force the store to use it while setting the PayFast settings
updateOptions( {
const update = await updateOptions( {
woocommerce_currency: 'ZAR',
woocommerce_payfast_settings: {
merchant_id: values.merchant_id,
@ -95,6 +64,22 @@ class PayFast extends Component {
enabled: 'yes',
},
} );
if ( update.success ) {
markConfigured( 'payfast' );
createNotice(
'success',
__( 'PayFast connected successfully', 'woocommerce-admin' )
);
} else {
createNotice(
'error',
__(
'There was a problem saving your payment setings',
'woocommerce-admin'
)
);
}
};
renderConnectStep() {
@ -190,28 +175,16 @@ class PayFast extends Component {
export default compose(
withSelect( ( select ) => {
const { getOptionsError, isUpdateOptionsRequesting } = select(
'wc-api'
);
const isOptionsRequesting = Boolean(
isUpdateOptionsRequesting( [
'woocommerce_currency',
'woocommerce_payfast_settings',
] )
);
const hasOptionsError = getOptionsError( [
'woocommerce_currency',
'woocommerce_payfast_settings',
] );
const { isOptionsUpdating } = select( OPTIONS_STORE_NAME );
const isOptionsRequesting = isOptionsUpdating();
return {
hasOptionsError,
isOptionsRequesting,
};
} ),
withDispatch( ( dispatch ) => {
const { createNotice } = dispatch( 'core/notices' );
const { updateOptions } = dispatch( 'wc-api' );
const { updateOptions } = dispatch( OPTIONS_STORE_NAME );
return {
createNotice,

View File

@ -7,7 +7,7 @@ import { Button } from '@wordpress/components';
import { Component, Fragment } from '@wordpress/element';
import { compose } from '@wordpress/compose';
import interpolateComponents from 'interpolate-components';
import { withDispatch } from '@wordpress/data';
import { withDispatch, withSelect } from '@wordpress/data';
/**
* WooCommerce dependencies
@ -15,8 +15,7 @@ import { withDispatch } from '@wordpress/data';
import { Form, Link, Stepper, TextControl } from '@woocommerce/components';
import { getQuery } from '@woocommerce/navigation';
import { WC_ADMIN_NAMESPACE } from 'wc-api/constants';
import withSelect from 'wc-api/with-select';
import { PLUGINS_STORE_NAME } from '@woocommerce/data';
import { PLUGINS_STORE_NAME, OPTIONS_STORE_NAME } from '@woocommerce/data';
class PayPal extends Component {
constructor( props ) {
@ -119,13 +118,12 @@ class PayPal extends Component {
async updateSettings( values ) {
const {
createNotice,
isSettingsError,
options,
updateOptions,
markConfigured,
} = this.props;
await updateOptions( {
const update = await updateOptions( {
woocommerce_ppec_paypal_settings: {
...options.woocommerce_ppec_paypal_settings,
api_username: values.api_username,
@ -134,7 +132,7 @@ class PayPal extends Component {
},
} );
if ( ! isSettingsError ) {
if ( update.success ) {
createNotice(
'success',
__( 'PayPal connected successfully.', 'woocommerce-admin' )
@ -178,7 +176,7 @@ class PayPal extends Component {
}
renderManualConfig() {
const { isOptionsRequesting } = this.props;
const { isOptionsUpdating } = this.props;
const link = (
<Link
href="https://docs.woocommerce.com/document/paypal-express-checkout/#section-8"
@ -225,7 +223,7 @@ class PayPal extends Component {
<Button
onClick={ handleSubmit }
isPrimary
disabled={ isOptionsRequesting }
isBusy={ isOptionsUpdating }
>
{ __( 'Proceed', 'woocommerce-admin' ) }
</Button>
@ -291,23 +289,20 @@ PayPal.defaultProps = {
export default compose(
withSelect( ( select ) => {
const { getOptions, isGetOptionsRequesting } = select( 'wc-api' );
const { getOption, isOptionsUpdating } = select( OPTIONS_STORE_NAME );
const { getActivePlugins } = select( PLUGINS_STORE_NAME );
const options = getOptions( [ 'woocommerce_ppec_paypal_settings' ] );
const isOptionsRequesting = Boolean(
isGetOptionsRequesting( [ 'woocommerce_ppec_paypal_settings' ] )
);
const options = getOption( 'woocommerce_ppec_paypal_settings' );
const activePlugins = getActivePlugins();
return {
activePlugins,
options,
isOptionsRequesting,
isOptionsUpdating: isOptionsUpdating(),
};
} ),
withDispatch( ( dispatch ) => {
const { createNotice } = dispatch( 'core/notices' );
const { updateOptions } = dispatch( 'wc-api' );
const { updateOptions } = dispatch( OPTIONS_STORE_NAME );
return {
createNotice,
updateOptions,

View File

@ -5,7 +5,7 @@ import { __ } from '@wordpress/i18n';
import { Component, Fragment } from '@wordpress/element';
import apiFetch from '@wordpress/api-fetch';
import { Button } from '@wordpress/components';
import { withDispatch } from '@wordpress/data';
import { withDispatch, withSelect } from '@wordpress/data';
import { compose } from '@wordpress/compose';
/**
@ -13,9 +13,9 @@ import { compose } from '@wordpress/compose';
*/
import { getQuery } from '@woocommerce/navigation';
import { WC_ADMIN_NAMESPACE } from 'wc-api/constants';
import withSelect from 'wc-api/with-select';
import { Stepper } from '@woocommerce/components';
import { getAdminLink } from '@woocommerce/wc-admin-settings';
import { OPTIONS_STORE_NAME } from '@woocommerce/data';
class Square extends Component {
constructor( props ) {
@ -145,15 +145,11 @@ class Square extends Component {
export default compose(
withSelect( ( select ) => {
const { getOptions, isGetOptionsRequesting } = select( 'wc-api' );
const options = getOptions( [
const { getOption, isResolving } = select( OPTIONS_STORE_NAME );
const options = getOption( 'woocommerce_square_credit_card_settings' );
const optionsIsRequesting = isResolving( 'getOption', [
'woocommerce_square_credit_card_settings',
] );
const optionsIsRequesting = Boolean(
isGetOptionsRequesting( [
'woocommerce_square_credit_card_settings',
] )
);
return {
options,
@ -162,7 +158,7 @@ export default compose(
} ),
withDispatch( ( dispatch ) => {
const { createNotice } = dispatch( 'core/notices' );
const { updateOptions } = dispatch( 'wc-api' );
const { updateOptions } = dispatch( OPTIONS_STORE_NAME );
return {
createNotice,
updateOptions,

View File

@ -5,10 +5,9 @@ import { __ } from '@wordpress/i18n';
import { Component, Fragment } from '@wordpress/element';
import { compose } from '@wordpress/compose';
import apiFetch from '@wordpress/api-fetch';
import { withDispatch } from '@wordpress/data';
import { withDispatch, withSelect } from '@wordpress/data';
import interpolateComponents from 'interpolate-components';
import { Button } from '@wordpress/components';
import { get } from 'lodash';
/**
* WooCommerce dependencies
@ -17,8 +16,7 @@ import { Form, Link, Stepper, TextControl } from '@woocommerce/components';
import { getAdminLink } from '@woocommerce/wc-admin-settings';
import { getQuery } from '@woocommerce/navigation';
import { WCS_NAMESPACE } from 'wc-api/constants';
import withSelect from 'wc-api/with-select';
import { PLUGINS_STORE_NAME } from '@woocommerce/data';
import { PLUGINS_STORE_NAME, OPTIONS_STORE_NAME } from '@woocommerce/data';
class Stripe extends Component {
constructor( props ) {
@ -54,26 +52,7 @@ class Stripe extends Component {
}
componentDidUpdate( prevProps ) {
const {
activePlugins,
createNotice,
isOptionsRequesting,
hasOptionsError,
} = this.props;
if ( prevProps.isOptionsRequesting && ! isOptionsRequesting ) {
if ( ! hasOptionsError ) {
this.completeMethod();
} else {
createNotice(
'error',
__(
'There was a problem saving your payment setings',
'woocommerce-admin'
)
);
}
}
const { activePlugins } = this.props;
if (
! prevProps.activePlugins.includes(
@ -154,10 +133,10 @@ class Stripe extends Component {
);
}
updateSettings( values ) {
const { updateOptions, stripeSettings } = this.props;
async updateSettings( values ) {
const { updateOptions, stripeSettings, createNotice } = this.props;
updateOptions( {
const update = await updateOptions( {
woocommerce_stripe_settings: {
...stripeSettings,
publishable_key: values.publishable_key,
@ -165,6 +144,18 @@ class Stripe extends Component {
enabled: 'yes',
},
} );
if ( update.success ) {
this.completeMethod();
} else {
createNotice(
'error',
__(
'There was a problem saving your payment setings',
'woocommerce-admin'
)
);
}
}
getInitialConfigValues() {
@ -194,7 +185,7 @@ class Stripe extends Component {
}
renderManualConfig() {
const { isOptionsRequesting } = this.props;
const { isOptionsUpdating } = this.props;
const stripeHelp = interpolateComponents( {
mixedString: __(
'Your API details can be obtained from your {{docsLink}}Stripe account{{/docsLink}}. Dont have a Stripe account? {{registerLink}}Create one.{{/registerLink}}',
@ -246,7 +237,7 @@ class Stripe extends Component {
<Button
isPrimary
isBusy={ isOptionsRequesting }
isBusy={ isOptionsUpdating }
onClick={ handleSubmit }
>
{ __( 'Proceed', 'woocommerce-admin' ) }
@ -294,14 +285,14 @@ class Stripe extends Component {
}
render() {
const { installStep, isOptionsRequesting } = this.props;
const { installStep, isOptionsUpdating } = this.props;
const { isPending } = this.state;
return (
<Stepper
isVertical
isPending={
! installStep.isComplete || isOptionsRequesting || isPending
! installStep.isComplete || isOptionsUpdating || isPending
}
currentStep={ installStep.isComplete ? 'connect' : 'install' }
steps={ [ installStep, this.getConnectStep() ] }
@ -312,38 +303,21 @@ class Stripe extends Component {
export default compose(
withSelect( ( select ) => {
const {
getOptions,
getOptionsError,
isUpdateOptionsRequesting,
} = select( 'wc-api' );
const { getOption, isOptionsUpdating } = select( OPTIONS_STORE_NAME );
const { getActivePlugins, isJetpackConnected } = select(
PLUGINS_STORE_NAME
);
const options = getOptions( [ 'woocommerce_stripe_settings' ] );
const stripeSettings = get(
options,
[ 'woocommerce_stripe_settings' ],
[]
);
const isOptionsRequesting = Boolean(
isUpdateOptionsRequesting( [ 'woocommerce_stripe_settings' ] )
);
const hasOptionsError = getOptionsError( [
'woocommerce_stripe_settings',
] );
return {
activePlugins: getActivePlugins(),
hasOptionsError,
isJetpackConnected: isJetpackConnected(),
isOptionsRequesting,
stripeSettings,
isOptionsUpdating: isOptionsUpdating(),
stripeSettings: getOption( 'woocommerce_stripe_settings' ) || [],
};
} ),
withDispatch( ( dispatch ) => {
const { createNotice } = dispatch( 'core/notices' );
const { updateOptions } = dispatch( 'wc-api' );
const { updateOptions } = dispatch( OPTIONS_STORE_NAME );
return {
createNotice,
updateOptions,

View File

@ -5,7 +5,7 @@ import { __ } from '@wordpress/i18n';
import { Button } from '@wordpress/components';
import { Component, Fragment } from '@wordpress/element';
import { compose } from '@wordpress/compose';
import { difference, filter, get } from 'lodash';
import { difference, filter } from 'lodash';
import interpolateComponents from 'interpolate-components';
import { withDispatch, withSelect } from '@wordpress/data';
@ -19,7 +19,11 @@ import {
getSetting,
setSetting,
} from '@woocommerce/wc-admin-settings';
import { SETTINGS_STORE_NAME, PLUGINS_STORE_NAME } from '@woocommerce/data';
import {
SETTINGS_STORE_NAME,
PLUGINS_STORE_NAME,
OPTIONS_STORE_NAME,
} from '@woocommerce/data';
/**
* Internal dependencies
@ -27,7 +31,6 @@ import { SETTINGS_STORE_NAME, PLUGINS_STORE_NAME } from '@woocommerce/data';
import Connect from 'dashboard/components/connect';
import { getCountryCode } from 'dashboard/utils';
import StoreLocation from './steps/location';
import withWCApiSelect from 'wc-api/with-select';
import { recordEvent, queueRecordEvent } from 'lib/tracks';
class Tax extends Component {
@ -491,38 +494,16 @@ class Tax extends Component {
}
export default compose(
withWCApiSelect( ( select ) => {
const { getOptions } = select( 'wc-api' );
const { getActivePlugins, isJetpackConnected } = select(
PLUGINS_STORE_NAME
);
const activePlugins = getActivePlugins();
const pluginsToActivate = difference(
[ 'jetpack', 'woocommerce-services' ],
activePlugins
);
const options = getOptions( [
'wc_connect_options',
'woocommerce_setup_jetpack_opted_in',
] );
const connectOptions = get( options, 'wc_connect_options', {} );
const tosAccepted =
connectOptions.tos_accepted ||
options.woocommerce_setup_jetpack_opted_in;
return {
isJetpackConnected: isJetpackConnected(),
pluginsToActivate,
tosAccepted,
};
} ),
withSelect( ( select ) => {
const {
getSettings,
getSettingsError,
isGetSettingsRequesting,
} = select( SETTINGS_STORE_NAME );
const { getOption } = select( OPTIONS_STORE_NAME );
const { getActivePlugins, isJetpackConnected } = select(
PLUGINS_STORE_NAME
);
const { general: generalSettings = {} } = getSettings( 'general' );
const isGeneralSettingsError = Boolean( getSettingsError( 'general' ) );
@ -537,6 +518,16 @@ export default compose(
const isTaxSettingsError = Boolean( getSettingsError( 'tax' ) );
const isTaxSettingsRequesting = isGetSettingsRequesting( 'tax' );
const activePlugins = getActivePlugins();
const pluginsToActivate = difference(
[ 'jetpack', 'woocommerce-services' ],
activePlugins
);
const connectOptions = getOption( 'wc_connect_options' ) || {};
const tosAccepted =
connectOptions.tos_accepted ||
getOption( 'woocommerce_setup_jetpack_opted_in' );
return {
isGeneralSettingsError,
isGeneralSettingsRequesting,
@ -545,6 +536,9 @@ export default compose(
taxSettings,
isTaxSettingsError,
isTaxSettingsRequesting,
isJetpackConnected: isJetpackConnected(),
pluginsToActivate,
tosAccepted,
};
} ),
withDispatch( ( dispatch ) => {

View File

@ -1,12 +0,0 @@
/**
* Internal dependencies
*/
import operations from './operations';
import selectors from './selectors';
import mutations from './mutations';
export default {
operations,
selectors,
mutations,
};

View File

@ -1,16 +0,0 @@
/**
* Internal dependencies
*/
import { getResourceName } from '../utils';
const updateOptions = ( operations ) => ( options ) => {
const resourceName = getResourceName(
'options-update',
Object.keys( options )
);
operations.update( [ resourceName ], { [ resourceName ]: options } );
};
export default {
updateOptions,
};

View File

@ -1,84 +0,0 @@
/**
* External dependencies
*/
import apiFetch from '@wordpress/api-fetch';
/**
* Internal dependencies
*/
import { getResourceIdentifier, getResourceName } from '../utils';
import { WC_ADMIN_NAMESPACE } from '../constants';
function read( resourceNames, fetch = apiFetch ) {
return [ ...readOptions( resourceNames, fetch ) ];
}
function update( resourceNames, data, fetch = apiFetch ) {
return [ ...updateOptions( resourceNames, data, fetch ) ];
}
function readOptions( resourceNames, fetch ) {
const filteredNames = resourceNames.filter( ( name ) => {
return name.startsWith( 'options' );
} );
return filteredNames.map( async ( resourceName ) => {
const optionNames = getResourceIdentifier( resourceName );
const url =
WC_ADMIN_NAMESPACE + '/options?options=' + optionNames.join( ',' );
return fetch( { path: url } )
.then( optionsToResource )
.catch( ( error ) => {
return { [ resourceName ]: { error: String( error.message ) } };
} );
} );
}
function updateOptions( resourceNames, data, fetch ) {
const url = WC_ADMIN_NAMESPACE + '/options';
const filteredNames = resourceNames.filter( ( name ) => {
return name.startsWith( 'options-update' );
} );
return filteredNames.map( async ( resourceName ) => {
return fetch( {
path: url,
method: 'POST',
data: data[ resourceName ],
} )
.then( () => optionsToResource( data[ resourceName ], true ) )
.catch( ( error ) => {
return { [ resourceName ]: { data: {}, error } };
} );
} );
}
function optionsToResource( options, updateResource = false ) {
const optionNames = Object.keys( options );
const resourceName = getResourceName(
updateResource ? 'options-update' : 'options',
optionNames
);
const resources = {};
optionNames.forEach(
( optionName ) =>
( resources[ getResourceName( 'options', optionName ) ] = {
data: options[ optionName ],
} )
);
return {
[ resourceName ]: {
data: optionNames,
},
...resources,
};
}
export default {
read,
update,
};

View File

@ -1,81 +0,0 @@
/**
* External dependencies
*/
import { isNil } from 'lodash';
/**
* WooCommerce dependencies
*/
import { getSetting } from '@woocommerce/wc-admin-settings';
/**
* Internal dependencies
*/
import { DEFAULT_REQUIREMENT } from '../constants';
import { getResourceName } from '../utils';
const getOptions = ( getResource, requireResource ) => (
optionNames,
requirement = DEFAULT_REQUIREMENT
) => {
const resourceName = getResourceName( 'options', optionNames );
const options = {};
const names =
requireResource( requirement, resourceName ).data || optionNames;
names.forEach( ( name ) => {
const data = getSetting(
'preloadOptions',
{},
( po ) =>
getResource( getResourceName( 'options', name ) ).data ||
po[ name ]
);
if ( data ) {
options[ name ] = data;
}
} );
return options;
};
const getOptionsError = ( getResource ) => ( optionNames ) => {
return getResource( getResourceName( 'options', optionNames ) ).error;
};
const getUpdateOptionsError = ( getResource ) => ( optionNames ) => {
return getResource( getResourceName( 'options-update', optionNames ) )
.error;
};
const isGetOptionsRequesting = ( getResource ) => ( optionNames ) => {
const { lastReceived, lastRequested } = getResource(
getResourceName( 'options', optionNames )
);
if ( isNil( lastRequested ) || isNil( lastReceived ) ) {
return true;
}
return lastRequested > lastReceived;
};
const isUpdateOptionsRequesting = ( getResource ) => ( optionNames ) => {
const { lastReceived, lastRequested } = getResource(
getResourceName( 'options-update', optionNames )
);
if ( ! isNil( lastRequested ) && isNil( lastReceived ) ) {
return true;
}
return lastRequested > lastReceived;
};
export default {
getOptions,
getOptionsError,
getUpdateOptionsError,
isGetOptionsRequesting,
isUpdateOptionsRequesting,
};

View File

@ -5,7 +5,6 @@ import reportExport from './export';
import items from './items';
import imports from './imports';
import notes from './notes';
import options from './options';
import reportItems from './reports/items';
import reportStats from './reports/stats';
import reviews from './reviews';
@ -17,13 +16,11 @@ function createWcApiSpec() {
...reportExport.mutations,
...items.mutations,
...notes.mutations,
...options.mutations,
},
selectors: {
...imports.selectors,
...items.selectors,
...notes.selectors,
...options.selectors,
...reportItems.selectors,
...reportStats.selectors,
...reviews.selectors,
@ -39,7 +36,6 @@ function createWcApiSpec() {
...imports.operations.read( resourceNames ),
...items.operations.read( resourceNames ),
...notes.operations.read( resourceNames ),
...options.operations.read( resourceNames ),
...reportItems.operations.read( resourceNames ),
...reportStats.operations.read( resourceNames ),
...reviews.operations.read( resourceNames ),
@ -50,7 +46,6 @@ function createWcApiSpec() {
...reportExport.operations.update( resourceNames, data ),
...items.operations.update( resourceNames, data ),
...notes.operations.update( resourceNames, data ),
...options.operations.update( resourceNames, data ),
];
},
remove( resourceNames, data ) {

View File

@ -7,6 +7,11 @@ import { Button, Modal } from '@wordpress/components';
import { withDispatch } from '@wordpress/data';
import { compose } from '@wordpress/compose';
/**
* WooCommerce dependencies
*/
import { OPTIONS_STORE_NAME } from '@woocommerce/data';
/**
* Internal dependencies
*/
@ -75,7 +80,7 @@ export class DismissModal extends Component {
export default compose(
withDispatch( ( dispatch ) => {
const { updateOptions } = dispatch( 'wc-api' );
const { updateOptions } = dispatch( OPTIONS_STORE_NAME );
return { updateOptions };
} )
)( DismissModal );

View File

@ -12,3 +12,6 @@ export { withOnboardingHydration } from './onboarding/with-onboarding-hydration'
export { USER_STORE_NAME } from './user-preferences';
export { withCurrentUserHydration } from './user-preferences/with-current-user-hydration';
export { useUserPreferences } from './user-preferences/use-user-preferences';
export { OPTIONS_STORE_NAME } from './options';
export { withOptionsHydration } from './options/with-options-hydration';

View File

@ -0,0 +1,9 @@
const TYPES = {
RECEIVE_OPTIONS: 'RECEIVE_OPTIONS',
SET_IS_REQUESTING: 'SET_IS_REQUESTING',
SET_IS_UPDATING: 'SET_IS_UPDATING',
SET_REQUESTING_ERROR: 'SET_REQUESTING_ERROR',
SET_UPDATING_ERROR: 'SET_UPDATING_ERROR',
};
export default TYPES;

View File

@ -0,0 +1,59 @@
/**
* External Dependencies
*/
import { apiFetch } from '@wordpress/data-controls';
/**
* Internal Dependencies
*/
import TYPES from './action-types';
import { WC_ADMIN_NAMESPACE } from '../constants';
export function receiveOptions( options ) {
return {
type: TYPES.RECEIVE_OPTIONS,
options,
};
}
export function setRequestingError( error, name ) {
return {
type: TYPES.SET_REQUESTING_ERROR,
error,
name,
};
}
export function setUpdatingError( error ) {
return {
type: TYPES.SET_UPDATING_ERROR,
error,
};
}
export function setIsUpdating( isUpdating ) {
return {
type: TYPES.SET_IS_UPDATING,
isUpdating,
};
}
export function* updateOptions( data ) {
yield setIsUpdating( true );
yield receiveOptions( data );
try {
const results = yield apiFetch( {
path: WC_ADMIN_NAMESPACE + '/options',
method: 'POST',
data,
} );
yield setIsUpdating( false );
return { success: true, ...results };
} catch ( error ) {
yield setUpdatingError( error );
return { success: false, ...error };
}
}

View File

@ -0,0 +1,3 @@
export const STORE_NAME = 'wc/admin/options';

View File

@ -0,0 +1,50 @@
/**
* External dependencies
*/
import { controls as dataControls } from '@wordpress/data-controls';
import apiFetch from '@wordpress/api-fetch';
/**
* Internal dependencies
*/
import { WC_ADMIN_NAMESPACE } from '../constants';
let optionNames = [];
const fetches = {};
export const batchFetch = ( optionName ) => {
return {
type: 'BATCH_FETCH',
optionName,
};
};
export const controls = {
...dataControls,
BATCH_FETCH( { optionName } ) {
optionNames.push( optionName );
return new Promise( resolve => {
setTimeout( function() {
const names = optionNames.join(',');
if ( fetches[ names ] ) {
return fetches[ names ].then( ( result ) => {
resolve( result[ optionName ] );
} );
}
const url = WC_ADMIN_NAMESPACE + '/options?options=' + names;
fetches[ names ] = apiFetch( { path: url } );
fetches[names].then( ( result ) => resolve( result ) )
// Clear option names after all resolved;
setTimeout( () => {
optionNames = [];
// Delete the fetch after to allow wp data to handle cache invalidation.
delete fetches[ names ];
}, 1 )
}, 1 );
} );
},
};

View File

@ -0,0 +1,25 @@
/**
* External dependencies
*/
import { registerStore } from '@wordpress/data';
/**
* Internal dependencies
*/
import { STORE_NAME } from './constants';
import * as selectors from './selectors';
import * as actions from './actions';
import * as resolvers from './resolvers';
import { controls } from './controls';
import reducer from './reducer';
registerStore( STORE_NAME, {
reducer,
actions,
controls,
selectors,
resolvers,
} );
export const OPTIONS_STORE_NAME = STORE_NAME;

View File

@ -0,0 +1,43 @@
/**
* Internal dependencies
*/
import TYPES from './action-types';
const optionsReducer = (
state = { isUpdating: false, requestingErrors: {} },
{ type, options, error, isUpdating, name }
) => {
switch ( type ) {
case TYPES.RECEIVE_OPTIONS:
state = {
...state,
...options,
};
break;
case TYPES.SET_IS_UPDATING:
state = {
...state,
isUpdating,
};
break;
case TYPES.SET_REQUESTING_ERROR:
state = {
...state,
requestingErrors: {
[ name ]: error,
},
};
break;
case TYPES.SET_UPDATING_ERROR:
state = {
...state,
error,
updatingError: error,
isUpdating: false,
};
break;
}
return state;
};
export default optionsReducer;

View File

@ -0,0 +1,23 @@
/**
* External Dependencies
*/
import { batchFetch } from './controls';
/**
* Internal dependencies
*/
import { receiveOptions, setRequestingError } from './actions';
/**
* Request an option value.
*
* @param {string} name - Option name
*/
export function* getOption( name ) {
try {
const result = yield batchFetch( name );
yield receiveOptions( result );
} catch ( error ) {
yield setRequestingError( error, name );
}
}

View File

@ -0,0 +1,37 @@
/**
* Get option from state tree.
*
* @param {Object} state - Reducer state
* @param {Array} name - Option name
*/
export const getOption = ( state, name ) => {
return state[ name ];
};
/**
* Determine if an options request resulted in an error.
*
* @param {Object} state - Reducer state
* @param {string} name - Option name
*/
export const getOptionsRequestingError = ( state, name ) => {
return state.requestingErrors[ name ] || false;
};
/**
* Determine if options are being updated.
*
* @param {Object} state - Reducer state
*/
export const isOptionsUpdating = ( state ) => {
return state.isUpdating || false;
};
/**
* Determine if an options update resulted in an error.
*
* @param {Object} state - Reducer state
*/
export const getOptionsUpdatingError = ( state ) => {
return state.updatingError || false;
};

View File

@ -0,0 +1,60 @@
/**
* Internal dependencies
*/
import reducer from '../reducer';
import TYPES from '../action-types';
const defaultState = { isUpdating: false, requestingErrors: {} };
describe( 'options reducer', () => {
it( 'should return a default state', () => {
const state = reducer( undefined, {} );
expect( state ).toEqual( defaultState );
expect( state ).not.toBe( defaultState );
} );
it( 'should handle RECEIVE_OPTIONS', () => {
const state = reducer( defaultState, {
type: TYPES.RECEIVE_OPTIONS,
options: { test_option: 'abc' },
} );
/* eslint-disable dot-notation */
expect( state.requestingErrors[ 'test_option' ] ).toBeUndefined();
expect( state[ 'test_option' ] ).toBe( 'abc' );
/* eslint-enable dot-notation */
} );
it( 'should handle SET_REQUESTING_ERROR', () => {
const state = reducer( defaultState, {
type: TYPES.SET_REQUESTING_ERROR,
error: 'My bad',
name: 'test_option'
} );
/* eslint-disable dot-notation */
expect( state.requestingErrors[ 'test_option' ] ).toBe( 'My bad' );
expect( state[ 'test_option' ] ).toBeUndefined();
/* eslint-enable dot-notation */
} );
it( 'should handle SET_UPDATING_ERROR', () => {
const state = reducer( defaultState, {
type: TYPES.SET_UPDATING_ERROR,
error: 'My bad',
} );
expect( state.updatingError ).toBe( 'My bad' );
expect( state.isUpdating ).toBe( false );
} );
it( 'should handle SET_IS_UPDATING', () => {
const state = reducer( defaultState, {
type: TYPES.SET_IS_UPDATING,
isUpdating: true,
} );
expect( state.isUpdating ).toBe( true );
} );
} );

View File

@ -0,0 +1,43 @@
/**
* External dependencies
*/
import { useRef } from '@wordpress/element';
import { useSelect } from '@wordpress/data';
/**
* Internal dependencies
*/
import { STORE_NAME } from './constants';
export const withOptionsHydration = ( data ) => ( OriginalComponent ) => {
return ( props ) => {
const dataRef = useRef( data );
useSelect( ( select, registry ) => {
if ( ! dataRef.current ) {
return;
}
const { isResolving, hasFinishedResolution } = select( STORE_NAME );
const {
startResolution,
finishResolution,
receiveOptions,
} = registry.dispatch( STORE_NAME );
const names = Object.keys( dataRef.current );
names.forEach( ( name ) => {
if (
! isResolving( 'getOption', [ name ] ) &&
! hasFinishedResolution( 'getOption', [ name ] )
) {
startResolution( 'getOption', [ name ] );
receiveOptions( { [ name ]: dataRef.current[ name ] } );
finishResolution( 'getOption', [ name ] );
}
} );
}, [] );
return <OriginalComponent { ...props } />;
};
};