Migrate @woocommerce/data settings store to TS (#34184)

* Migrate wc.data.settings to TS

* Correct data.settings type

* Fix wc admin client type errors

* Add changelog

* Add changelog

* Update types

* Update RawSetting type
This commit is contained in:
Chi-Hsuan Huang 2022-08-08 13:25:46 +08:00 committed by GitHub
parent 3bbee139ed
commit af97aaf410
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
25 changed files with 310 additions and 201 deletions

View File

@ -0,0 +1,4 @@
Significance: patch
Type: dev
Migrate setting store to TS

View File

@ -4,6 +4,6 @@ const TYPES = {
CLEAR_SETTINGS: 'CLEAR_SETTINGS', CLEAR_SETTINGS: 'CLEAR_SETTINGS',
SET_IS_REQUESTING: 'SET_IS_REQUESTING', SET_IS_REQUESTING: 'SET_IS_REQUESTING',
CLEAR_IS_DIRTY: 'CLEAR_IS_DIRTY', CLEAR_IS_DIRTY: 'CLEAR_IS_DIRTY',
}; } as const;
export default TYPES; export default TYPES;

View File

@ -6,6 +6,7 @@ import { __ } from '@wordpress/i18n';
import { apiFetch, select } from '@wordpress/data-controls'; import { apiFetch, select } from '@wordpress/data-controls';
import { controls } from '@wordpress/data'; import { controls } from '@wordpress/data';
import { concat } from 'lodash'; import { concat } from 'lodash';
import { DispatchFromMap } from '@automattic/data-stores';
/** /**
* Internal dependencies * Internal dependencies
@ -13,12 +14,17 @@ import { concat } from 'lodash';
import { NAMESPACE } from '../constants'; import { NAMESPACE } from '../constants';
import { STORE_NAME } from './constants'; import { STORE_NAME } from './constants';
import TYPES from './action-types'; import TYPES from './action-types';
import { Settings } from './types';
// Can be removed in WP 5.9, wp.data is supported in >5.7. // Can be removed in WP 5.9, wp.data is supported in >5.7.
const resolveSelect = const resolveSelect =
controls && controls.resolveSelect ? controls.resolveSelect : select; controls && controls.resolveSelect ? controls.resolveSelect : select;
export function updateSettingsForGroup( group, data, time = new Date() ) { export function updateSettingsForGroup(
group: string,
data: Settings,
time = new Date()
) {
return { return {
type: TYPES.UPDATE_SETTINGS_FOR_GROUP, type: TYPES.UPDATE_SETTINGS_FOR_GROUP,
group, group,
@ -27,7 +33,12 @@ export function updateSettingsForGroup( group, data, time = new Date() ) {
}; };
} }
export function updateErrorForGroup( group, data, error, time = new Date() ) { export function updateErrorForGroup(
group: string,
data: Settings | null,
error: unknown,
time = new Date()
) {
return { return {
type: TYPES.UPDATE_ERROR_FOR_GROUP, type: TYPES.UPDATE_ERROR_FOR_GROUP,
group, group,
@ -37,7 +48,7 @@ export function updateErrorForGroup( group, data, error, time = new Date() ) {
}; };
} }
export function setIsRequesting( group, isRequesting ) { export function setIsRequesting( group: string, isRequesting: boolean ) {
return { return {
type: TYPES.SET_IS_REQUESTING, type: TYPES.SET_IS_REQUESTING,
group, group,
@ -45,25 +56,23 @@ export function setIsRequesting( group, isRequesting ) {
}; };
} }
export function clearIsDirty( group ) { export function clearIsDirty( group: string ) {
return { return {
type: TYPES.CLEAR_IS_DIRTY, type: TYPES.CLEAR_IS_DIRTY,
group, group,
}; };
} }
// allows updating and persisting immediately in one action.
export function* updateAndPersistSettingsForGroup( group, data ) {
yield updateSettingsForGroup( group, data );
yield* persistSettingsForGroup( group );
}
// this would replace setSettingsForGroup // this would replace setSettingsForGroup
export function* persistSettingsForGroup( group ) { export function* persistSettingsForGroup( group: string ) {
// first dispatch the is persisting action // first dispatch the is persisting action
yield setIsRequesting( group, true ); yield setIsRequesting( group, true );
// get all dirty keys with select control // get all dirty keys with select control
const dirtyKeys = yield resolveSelect( STORE_NAME, 'getDirtyKeys', group ); const dirtyKeys: string[] = yield resolveSelect(
STORE_NAME,
'getDirtyKeys',
group
);
// if there is nothing dirty, bail // if there is nothing dirty, bail
if ( dirtyKeys.length === 0 ) { if ( dirtyKeys.length === 0 ) {
yield setIsRequesting( group, false ); yield setIsRequesting( group, false );
@ -71,22 +80,27 @@ export function* persistSettingsForGroup( group ) {
} }
// get data slice for keys // get data slice for keys
const dirtyData = yield resolveSelect( const dirtyData: {
[ key: string ]: Record< string, unknown >;
} = yield resolveSelect(
STORE_NAME, STORE_NAME,
'getSettingsForGroup', 'getSettingsForGroup',
group, group,
dirtyKeys dirtyKeys
); );
const url = `${ NAMESPACE }/settings/${ group }/batch`; const url = `${ NAMESPACE }/settings/${ group }/batch`;
const update = dirtyKeys.reduce< Array< { id: string; value: unknown } > >(
const update = dirtyKeys.reduce( ( updates, key ) => { ( updates, key ) => {
const u = Object.keys( dirtyData[ key ] ).map( ( k ) => { const u = Object.keys( dirtyData[ key ] ).map( ( k ) => {
return { id: k, value: dirtyData[ key ][ k ] }; return { id: k, value: dirtyData[ key ][ k ] };
} ); } );
return concat( updates, u ); return concat( updates, u );
}, [] ); },
[]
);
try { try {
const results = yield apiFetch( { const results: unknown = yield apiFetch( {
path: url, path: url,
method: 'POST', method: 'POST',
data: { update }, data: { update },
@ -112,8 +126,30 @@ export function* persistSettingsForGroup( group ) {
} }
} }
// allows updating and persisting immediately in one action.
export function* updateAndPersistSettingsForGroup(
group: string,
data: Settings
) {
yield updateSettingsForGroup( group, data );
yield* persistSettingsForGroup( group );
}
export function clearSettings() { export function clearSettings() {
return { return {
type: TYPES.CLEAR_SETTINGS, type: TYPES.CLEAR_SETTINGS,
}; };
} }
export type Actions = ReturnType<
| typeof updateSettingsForGroup
| typeof updateErrorForGroup
| typeof setIsRequesting
| typeof clearIsDirty
| typeof clearSettings
>;
export type ActionDispatchers = DispatchFromMap< {
createProduct: typeof persistSettingsForGroup;
updateProduct: typeof updateAndPersistSettingsForGroup;
} >;

View File

@ -1 +1 @@
export const STORE_NAME = 'wc/admin/settings'; export const STORE_NAME = 'wc/admin/settings' as const;

View File

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

View File

@ -0,0 +1,40 @@
/**
* External dependencies
*/
import { registerStore } from '@wordpress/data';
import { controls } from '@wordpress/data-controls';
import { Reducer } from 'redux';
import { SelectFromMap, DispatchFromMap } from '@automattic/data-stores';
/**
* Internal dependencies
*/
import { WPDataSelectors } from '../types';
import { STORE_NAME } from './constants';
import * as selectors from './selectors';
import * as actions from './actions';
import * as resolvers from './resolvers';
import reducer, { State } from './reducer';
import { SettingsState } from './types';
export * from './types';
export type { State };
registerStore< State >( STORE_NAME, {
reducer: reducer as Reducer< SettingsState >,
actions,
controls,
selectors,
resolvers,
} );
export const SETTINGS_STORE_NAME = STORE_NAME;
declare module '@wordpress/data' {
function dispatch(
key: typeof STORE_NAME
): DispatchFromMap< typeof actions >;
function select(
key: typeof STORE_NAME
): SelectFromMap< typeof selectors > & WPDataSelectors;
}

View File

@ -2,16 +2,31 @@
* External dependencies * External dependencies
*/ */
import { union } from 'lodash'; import { union } from 'lodash';
import { Reducer } from 'redux';
/** /**
* Internal dependencies * Internal dependencies
*/ */
import TYPES from './action-types'; import TYPES from './action-types';
import { getResourceName } from '../utils'; import { getResourceName } from '../utils';
import { Actions } from './actions';
import { Settings, SettingsState } from './types';
const updateGroupDataInNewState = ( const updateGroupDataInNewState = (
newState, newState: SettingsState,
{ group, groupIds, data, time, error } {
group,
groupIds,
data,
time,
error,
}: {
group: string;
groupIds: string[];
data: Settings;
time: Date;
error: unknown;
}
) => { ) => {
groupIds.forEach( ( id ) => { groupIds.forEach( ( id ) => {
newState[ getResourceName( group, id ) ] = { newState[ getResourceName( group, id ) ] = {
@ -23,33 +38,35 @@ const updateGroupDataInNewState = (
return newState; return newState;
}; };
const receiveSettings = ( const reducer: Reducer< SettingsState, Actions > = ( state = {}, action ) => {
state = {},
{ type, group, data, error, time, isRequesting }
) => {
const newState = {}; const newState = {};
switch ( type ) { switch ( action.type ) {
case TYPES.SET_IS_REQUESTING: case TYPES.SET_IS_REQUESTING:
state = { state = {
...state, ...state,
[ group ]: { [ action.group ]: {
...state[ group ], ...state[ action.group ],
isRequesting, isRequesting: action.isRequesting,
}, },
}; };
break; break;
case TYPES.CLEAR_IS_DIRTY: case TYPES.CLEAR_IS_DIRTY:
state = { state = {
...state, ...state,
[ group ]: { [ action.group ]: {
...state[ group ], ...state[ action.group ],
dirty: [], dirty: [],
}, },
}; };
break; break;
case TYPES.UPDATE_SETTINGS_FOR_GROUP: case TYPES.UPDATE_SETTINGS_FOR_GROUP:
case TYPES.UPDATE_ERROR_FOR_GROUP: case TYPES.UPDATE_ERROR_FOR_GROUP:
const { data, group, time } = action;
const groupIds = data ? Object.keys( data ) : []; const groupIds = data ? Object.keys( data ) : [];
const error =
action.type === TYPES.UPDATE_ERROR_FOR_GROUP
? action.error
: null;
if ( data === null ) { if ( data === null ) {
state = { state = {
...state, ...state,
@ -60,12 +77,15 @@ const receiveSettings = (
}, },
}; };
} else { } else {
const stateGroup = state[ group ];
state = { state = {
...state, ...state,
[ group ]: { [ group ]: {
data: data:
state[ group ] && state[ group ].data stateGroup &&
? [ ...state[ group ].data, ...groupIds ] stateGroup.data &&
Array.isArray( stateGroup.data )
? [ ...stateGroup.data, ...groupIds ]
: groupIds, : groupIds,
error, error,
lastReceived: time, lastReceived: time,
@ -91,4 +111,5 @@ const receiveSettings = (
return state; return state;
}; };
export default receiveSettings; export type State = ReturnType< typeof reducer >;
export default reducer;

View File

@ -1,48 +0,0 @@
/**
* External dependencies
*/
import {
apiFetch,
dispatch as depreciatedDispatch,
} from '@wordpress/data-controls';
import { controls } from '@wordpress/data';
/**
* Internal dependencies
*/
import { NAMESPACE } from '../constants';
import { STORE_NAME } from './constants';
import { updateSettingsForGroup, updateErrorForGroup } from './actions';
// Can be removed in WP 5.9.
const dispatch =
controls && controls.dispatch ? controls.dispatch : depreciatedDispatch;
function settingsToSettingsResource( settings ) {
return settings.reduce( ( resource, setting ) => {
resource[ setting.id ] = setting.value;
return resource;
}, {} );
}
export function* getSettings( group ) {
yield dispatch( STORE_NAME, 'setIsRequesting', group, true );
try {
const url = NAMESPACE + '/settings/' + group;
const results = yield apiFetch( {
path: url,
method: 'GET',
} );
const resource = settingsToSettingsResource( results );
return updateSettingsForGroup( group, { [ group ]: resource } );
} catch ( error ) {
return updateErrorForGroup( group, null, error.message );
}
}
export function* getSettingsForGroup( group ) {
return getSettings( group );
}

View File

@ -0,0 +1,78 @@
/**
* External dependencies
*/
import {
apiFetch,
dispatch as depreciatedDispatch,
} from '@wordpress/data-controls';
import { controls } from '@wordpress/data';
/**
* Internal dependencies
*/
import { NAMESPACE } from '../constants';
import { STORE_NAME } from './constants';
import { updateSettingsForGroup, updateErrorForGroup } from './actions';
import { isRestApiError } from '../types';
// Can be removed in WP 5.9.
const dispatch =
controls && controls.dispatch ? controls.dispatch : depreciatedDispatch;
// [class-wc-rest-setting-options-controller.php](https://github.com/woocommerce/woocommerce/blob/28926968bdcd2b504e16761a483388f85ee0c151/plugins/woocommerce/includes/rest-api/Controllers/Version3/class-wc-rest-setting-options-controller.php#L158-L248)
type RawSetting = {
id: string;
group_id: string;
label: string;
description: string;
tip?: string;
type:
| 'text'
| 'email'
| 'number'
| 'color'
| 'password'
| 'textarea'
| 'select'
| 'multiselect'
| 'radio'
| 'image_width'
| 'checkbox';
default: unknown;
value: unknown;
options: {
[ key: string ]: string;
};
placeholder?: string;
};
function settingsToSettingsResource( settings: RawSetting[] ) {
return settings.reduce< {
[ id: string ]: unknown;
} >( ( resource, setting ) => {
resource[ setting.id ] = setting.value;
return resource;
}, {} );
}
export function* getSettings( group: string ) {
yield dispatch( STORE_NAME, 'setIsRequesting', group, true );
try {
const url = NAMESPACE + '/settings/' + group;
const results: RawSetting[] = yield apiFetch( {
path: url,
method: 'GET',
} );
const resource = settingsToSettingsResource( results );
return updateSettingsForGroup( group, { [ group ]: resource } );
} catch ( error ) {
if ( error instanceof Error || isRestApiError( error ) ) {
return updateErrorForGroup( group, null, error.message );
}
throw `Unexpected error ${ error }`;
}
}
export function* getSettingsForGroup( group: string ) {
return getSettings( group );
}

View File

@ -2,8 +2,9 @@
* Internal dependencies * Internal dependencies
*/ */
import { getResourceName, getResourcePrefix } from '../utils'; import { getResourceName, getResourcePrefix } from '../utils';
import { SettingsState, Settings } from './types';
export const getSettingsGroupNames = ( state ) => { export const getSettingsGroupNames = ( state: SettingsState ) => {
const groupNames = new Set( const groupNames = new Set(
Object.keys( state ).map( ( resourceName ) => { Object.keys( state ).map( ( resourceName ) => {
return getResourcePrefix( resourceName ); return getResourcePrefix( resourceName );
@ -12,10 +13,10 @@ export const getSettingsGroupNames = ( state ) => {
return [ ...groupNames ]; return [ ...groupNames ];
}; };
export const getSettings = ( state, group ) => { export const getSettings = ( state: SettingsState, group: string ) => {
const settings = {}; const settings: Settings = {};
const settingIds = ( state[ group ] && state[ group ].data ) || []; const settingIds = ( state[ group ] && state[ group ].data ) || [];
if ( settingIds.length === 0 ) { if ( ! Array.isArray( settingIds ) || settingIds.length === 0 ) {
return settings; return settings;
} }
settingIds.forEach( ( id ) => { settingIds.forEach( ( id ) => {
@ -24,11 +25,15 @@ export const getSettings = ( state, group ) => {
return settings; return settings;
}; };
export const getDirtyKeys = ( state, group ) => { export const getDirtyKeys = ( state: SettingsState, group: string ) => {
return state[ group ].dirty || []; return state[ group ].dirty || [];
}; };
export const getIsDirty = ( state, group, keys = [] ) => { export const getIsDirty = (
state: SettingsState,
group: string,
keys: string[] = []
) => {
const dirtyMap = getDirtyKeys( state, group ); const dirtyMap = getDirtyKeys( state, group );
// if empty array bail // if empty array bail
if ( dirtyMap.length === 0 ) { if ( dirtyMap.length === 0 ) {
@ -39,15 +44,22 @@ export const getIsDirty = ( state, group, keys = [] ) => {
return keys.some( ( key ) => dirtyMap.includes( key ) ); return keys.some( ( key ) => dirtyMap.includes( key ) );
}; };
export const getSettingsForGroup = ( state, group, keys ) => { export const getSettingsForGroup = (
state: SettingsState,
group: string,
keys: string[]
) => {
const allSettings = getSettings( state, group ); const allSettings = getSettings( state, group );
return keys.reduce( ( accumulator, key ) => { return keys.reduce< Settings >( ( accumulator, key ) => {
accumulator[ key ] = allSettings[ key ] || {}; accumulator[ key ] = allSettings[ key ] || {};
return accumulator; return accumulator;
}, {} ); }, {} );
}; };
export const isUpdateSettingsRequesting = ( state, group ) => { export const isUpdateSettingsRequesting = (
state: SettingsState,
group: string
) => {
return state[ group ] && Boolean( state[ group ].isRequesting ); return state[ group ] && Boolean( state[ group ].isRequesting );
}; };
@ -70,11 +82,12 @@ export const isUpdateSettingsRequesting = ( state, group ) => {
* name. * name.
*/ */
export function getSetting( export function getSetting(
state, state: SettingsState,
group, group: string,
name, name: string,
fallback = false, fallback = false,
filter = ( val ) => val // eslint-disable-next-line @typescript-eslint/no-unused-vars -- _fallback in default filter is unused.
filter = ( val: unknown, _fallback: unknown | boolean ) => val
) { ) {
const resourceName = getResourceName( group, name ); const resourceName = getResourceName( group, name );
const value = const value =
@ -82,15 +95,22 @@ export function getSetting(
return filter( value, fallback ); return filter( value, fallback );
} }
export const getLastSettingsErrorForGroup = ( state, group ) => { export const getLastSettingsErrorForGroup = (
state: SettingsState,
group: string
) => {
const settingsIds = state[ group ].data; const settingsIds = state[ group ].data;
if ( settingsIds.length === 0 ) { if ( ! Array.isArray( settingsIds ) || settingsIds.length === 0 ) {
return state[ group ].error; return state[ group ].error;
} }
return [ ...settingsIds ].pop().error; return [ ...settingsIds ].pop().error;
}; };
export const getSettingsError = ( state, group, id ) => { export const getSettingsError = (
state: SettingsState,
group: string,
id: string
) => {
if ( ! id ) { if ( ! id ) {
return ( state[ group ] && state[ group ].error ) || false; return ( state[ group ] && state[ group ].error ) || false;
} }

View File

@ -0,0 +1,20 @@
export type Settings = {
[ key: string ]: unknown;
} & {
general?: {
[ key: string ]: string;
};
tax?: {
[ key: string ]: string;
};
};
export type SettingsState = {
[ key: string ]: {
data?: unknown;
lastReceived?: Date;
error?: unknown;
isRequesting?: boolean;
dirty?: string[];
};
};

View File

@ -9,7 +9,7 @@ import { useCallback } from '@wordpress/element';
*/ */
import { STORE_NAME } from './constants'; import { STORE_NAME } from './constants';
export const useSettings = ( group, settingsKeys = [] ) => { export const useSettings = ( group: string, settingsKeys: string[] = [] ) => {
const { requestedSettings, settingsError, isRequesting, isDirty } = const { requestedSettings, settingsError, isRequesting, isDirty } =
useSelect( useSelect(
( select ) => { ( select ) => {

View File

@ -2,20 +2,22 @@
* External dependencies * External dependencies
*/ */
import { createHigherOrderComponent } from '@wordpress/compose'; import { createHigherOrderComponent } from '@wordpress/compose';
import { useSelect } from '@wordpress/data'; import { useSelect, select as wpSelect } from '@wordpress/data';
import { createElement, useRef } from '@wordpress/element'; import { createElement, useRef } from '@wordpress/element';
/** /**
* Internal dependencies * Internal dependencies
*/ */
import { STORE_NAME } from './constants'; import { STORE_NAME } from './constants';
import { Settings } from './types';
export const withSettingsHydration = ( group, settings ) => export const withSettingsHydration = ( group: string, settings: Settings ) =>
createHigherOrderComponent( createHigherOrderComponent< Record< string, unknown > >(
( OriginalComponent ) => ( props ) => { ( OriginalComponent ) => ( props ) => {
const settingsRef = useRef( settings ); const settingsRef = useRef( settings );
useSelect( ( select, registry ) => { // @ts-expect-error registry is not defined in the wp.data typings
useSelect( ( select: typeof wpSelect, registry ) => {
if ( ! settingsRef.current ) { if ( ! settingsRef.current ) {
return; return;
} }

View File

@ -12,7 +12,7 @@ import { fetchWithHeaders } from './controls';
export function getResourceName( export function getResourceName(
prefix: string, prefix: string,
identifier: Record< string, unknown > identifier: Record< string, unknown > | string
) { ) {
const identifierString = JSON.stringify( const identifierString = JSON.stringify(
identifier, identifier,

View File

@ -25,11 +25,7 @@ const ShippingRecommendations: React.FC = () => {
isJetpackConnected, isJetpackConnected,
isSellingDigitalProductsOnly, isSellingDigitalProductsOnly,
} = useSelect( ( select ) => { } = useSelect( ( select ) => {
const settings = select( SETTINGS_STORE_NAME ).getSettings< { const settings = select( SETTINGS_STORE_NAME ).getSettings( 'general' );
general?: {
woocommerce_default_country: string;
};
} >( 'general' );
const { const {
getActivePlugins, getActivePlugins,

View File

@ -64,9 +64,7 @@ export const Products = () => {
const { isStoreInUS } = useSelect( ( select ) => { const { isStoreInUS } = useSelect( ( select ) => {
const { getSettings } = select( SETTINGS_STORE_NAME ); const { getSettings } = select( SETTINGS_STORE_NAME );
const { general: settings = {} } = getSettings< { const { general: settings = {} } = getSettings( 'general' );
general?: { [ key: string ]: unknown };
} >( 'general' );
const country = const country =
typeof settings.woocommerce_default_country === 'string' typeof settings.woocommerce_default_country === 'string'

View File

@ -10,7 +10,7 @@ import { useSelect, useDispatch } from '@wordpress/data';
* Internal dependencies * Internal dependencies
*/ */
import { getCountryCode } from '~/dashboard/utils'; import { getCountryCode } from '~/dashboard/utils';
import { hasCompleteAddress, SettingsSelector } from '../../tax/utils'; import { hasCompleteAddress } from '../../tax/utils';
import { default as StoreLocationForm } from '~/tasks/fills/steps/location'; import { default as StoreLocationForm } from '~/tasks/fills/steps/location';
export const StoreLocation: React.FC< { export const StoreLocation: React.FC< {
@ -21,9 +21,8 @@ export const StoreLocation: React.FC< {
const { updateAndPersistSettingsForGroup } = const { updateAndPersistSettingsForGroup } =
useDispatch( SETTINGS_STORE_NAME ); useDispatch( SETTINGS_STORE_NAME );
const { generalSettings, isResolving } = useSelect( ( select ) => { const { generalSettings, isResolving } = useSelect( ( select ) => {
const { getSettings, hasFinishedResolution } = select( const { getSettings, hasFinishedResolution } =
SETTINGS_STORE_NAME select( SETTINGS_STORE_NAME );
) as SettingsSelector;
return { return {
generalSettings: getSettings( 'general' )?.general, generalSettings: getSettings( 'general' )?.general,
@ -34,7 +33,7 @@ export const StoreLocation: React.FC< {
} ); } );
useEffect( () => { useEffect( () => {
if ( isResolving || ! hasCompleteAddress( generalSettings ) ) { if ( isResolving || ! hasCompleteAddress( generalSettings || {} ) ) {
return; return;
} }
onLocationComplete(); onLocationComplete();

View File

@ -14,15 +14,12 @@ import { compose } from '@wordpress/compose';
/** /**
* Internal dependencies * Internal dependencies
*/ */
import { SettingsSelector } from '../tax/utils';
import { ShippingRecommendation } from './shipping-recommendation'; import { ShippingRecommendation } from './shipping-recommendation';
import { TaskProps } from './types'; import { TaskProps } from './types';
const ShippingRecommendationWrapper = compose( const ShippingRecommendationWrapper = compose(
withSelect( ( select ) => { withSelect( ( select ) => {
const { getSettings } = select( const { getSettings } = select( SETTINGS_STORE_NAME );
SETTINGS_STORE_NAME
) as SettingsSelector;
const { hasFinishedResolution } = select( OPTIONS_STORE_NAME ); const { hasFinishedResolution } = select( OPTIONS_STORE_NAME );
const { getActivePlugins } = select( PLUGINS_STORE_NAME ); const { getActivePlugins } = select( PLUGINS_STORE_NAME );

View File

@ -11,7 +11,7 @@ import { useSelect, useDispatch } from '@wordpress/data';
* Internal dependencies * Internal dependencies
*/ */
import { getCountryCode } from '~/dashboard/utils'; import { getCountryCode } from '~/dashboard/utils';
import { hasCompleteAddress, SettingsSelector } from '../utils'; import { hasCompleteAddress } from '../utils';
import { default as StoreLocationForm } from '~/tasks/fills/steps/location'; import { default as StoreLocationForm } from '~/tasks/fills/steps/location';
export const StoreLocation: React.FC< { export const StoreLocation: React.FC< {
@ -21,9 +21,8 @@ export const StoreLocation: React.FC< {
const { updateAndPersistSettingsForGroup } = const { updateAndPersistSettingsForGroup } =
useDispatch( SETTINGS_STORE_NAME ); useDispatch( SETTINGS_STORE_NAME );
const { generalSettings, isResolving } = useSelect( ( select ) => { const { generalSettings, isResolving } = useSelect( ( select ) => {
const { getSettings, hasFinishedResolution } = select( const { getSettings, hasFinishedResolution } =
SETTINGS_STORE_NAME select( SETTINGS_STORE_NAME );
) as SettingsSelector;
return { return {
generalSettings: getSettings( 'general' )?.general, generalSettings: getSettings( 'general' )?.general,
@ -34,7 +33,7 @@ export const StoreLocation: React.FC< {
} ); } );
useEffect( () => { useEffect( () => {
if ( isResolving || ! hasCompleteAddress( generalSettings ) ) { if ( isResolving || ! hasCompleteAddress( generalSettings || {} ) ) {
return; return;
} }
nextStep(); nextStep();

View File

@ -24,11 +24,7 @@ import { WooOnboardingTask } from '@woocommerce/onboarding';
/** /**
* Internal dependencies * Internal dependencies
*/ */
import { import { redirectToTaxSettings, supportsAvalara } from './utils';
redirectToTaxSettings,
SettingsSelector,
supportsAvalara,
} from './utils';
import { Card as AvalaraCard } from './avalara/card'; import { Card as AvalaraCard } from './avalara/card';
import { Card as WooCommerceTaxCard } from './woocommerce-tax/card'; import { Card as WooCommerceTaxCard } from './woocommerce-tax/card';
import { createNoticesFromResponse } from '../../../lib/notices'; import { createNoticesFromResponse } from '../../../lib/notices';
@ -59,10 +55,8 @@ const Tax: React.FC< TaxProps > = ( { onComplete, query, task } ) => {
useDispatch( SETTINGS_STORE_NAME ); useDispatch( SETTINGS_STORE_NAME );
const { generalSettings, isResolving, taxSettings } = useSelect( const { generalSettings, isResolving, taxSettings } = useSelect(
( select ) => { ( select ) => {
const { getSettings, hasFinishedResolution } = select( const { getSettings, hasFinishedResolution } =
SETTINGS_STORE_NAME select( SETTINGS_STORE_NAME );
) as SettingsSelector;
return { return {
generalSettings: getSettings( 'general' ).general, generalSettings: getSettings( 'general' ).general,
isResolving: ! hasFinishedResolution( 'getSettings', [ isResolving: ! hasFinishedResolution( 'getSettings', [
@ -75,7 +69,7 @@ const Tax: React.FC< TaxProps > = ( { onComplete, query, task } ) => {
const onManual = useCallback( async () => { const onManual = useCallback( async () => {
setIsPending( true ); setIsPending( true );
if ( generalSettings.woocommerce_calc_taxes !== 'yes' ) { if ( generalSettings?.woocommerce_calc_taxes !== 'yes' ) {
updateAndPersistSettingsForGroup( 'tax', { updateAndPersistSettingsForGroup( 'tax', {
tax: { tax: {
...taxSettings, ...taxSettings,
@ -88,7 +82,6 @@ const Tax: React.FC< TaxProps > = ( { onComplete, query, task } ) => {
woocommerce_calc_taxes: 'yes', woocommerce_calc_taxes: 'yes',
}, },
} ) } )
// @ts-expect-error updateAndPersistSettingsForGroup returns a Promise, but it is not typed in source.
.then( () => redirectToTaxSettings() ) .then( () => redirectToTaxSettings() )
.catch( ( error: unknown ) => { .catch( ( error: unknown ) => {
setIsPending( false ); setIsPending( false );

View File

@ -12,15 +12,13 @@ import { useSelect } from '@wordpress/data';
/** /**
* Internal dependencies * Internal dependencies
*/ */
import { SettingsSelector, TaxChildProps } from '../utils'; import { TaxChildProps } from '../utils';
export const Configure: React.FC< export const Configure: React.FC<
Pick< TaxChildProps, 'isPending' | 'onManual' > Pick< TaxChildProps, 'isPending' | 'onManual' >
> = ( { isPending, onManual } ) => { > = ( { isPending, onManual } ) => {
const { generalSettings } = useSelect( ( select ) => { const { generalSettings } = useSelect( ( select ) => {
const { getSettings } = select( const { getSettings } = select( SETTINGS_STORE_NAME );
SETTINGS_STORE_NAME
) as SettingsSelector;
return { return {
generalSettings: getSettings( 'general' )?.general, generalSettings: getSettings( 'general' )?.general,
@ -41,7 +39,7 @@ export const Configure: React.FC<
{ __( 'Configure', 'woocommerce' ) } { __( 'Configure', 'woocommerce' ) }
</Button> </Button>
<p> <p>
{ generalSettings.woocommerce_calc_taxes !== 'yes' && { generalSettings?.woocommerce_calc_taxes !== 'yes' &&
interpolateComponents( { interpolateComponents( {
mixedString: __( mixedString: __(
/*eslint-disable max-len*/ /*eslint-disable max-len*/

View File

@ -2,7 +2,7 @@
* External dependencies * External dependencies
*/ */
import { getAdminLink } from '@woocommerce/settings'; import { getAdminLink } from '@woocommerce/settings';
import { WPDataSelectors, TaskType } from '@woocommerce/data'; import { TaskType } from '@woocommerce/data';
/** /**
* Plugins required to automate taxes. * Plugins required to automate taxes.
@ -17,11 +17,9 @@ export const AUTOMATION_PLUGINS = [ 'jetpack', 'woocommerce-services' ];
* @param {Object} generalSettings.woocommerce_default_country Store default country. * @param {Object} generalSettings.woocommerce_default_country Store default country.
* @param {Object} generalSettings.woocommerce_store_postcode Store postal code. * @param {Object} generalSettings.woocommerce_store_postcode Store postal code.
*/ */
export const hasCompleteAddress = ( generalSettings: { export const hasCompleteAddress = (
woocommerce_store_address?: string; generalSettings: Record< string, string >
woocommerce_default_country?: string; ): boolean => {
woocommerce_store_postcode?: string;
} ): boolean => {
const { const {
woocommerce_store_address: storeAddress, woocommerce_store_address: storeAddress,
woocommerce_default_country: defaultCountry, woocommerce_default_country: defaultCountry,
@ -39,22 +37,6 @@ export const redirectToTaxSettings = (): void => {
); );
}; };
/**
* Types for settings selectors.
*/
export type SettingsSelector = WPDataSelectors & {
getSettings: ( type: string ) => {
general: {
woocommerce_default_country?: string;
woocommerce_calc_taxes?: string;
};
tax: { [ key: string ]: string };
};
getOption: ( type: string ) => {
tos_accepted?: boolean;
};
};
/** /**
* Types for child tax components. * Types for child tax components.
*/ */

View File

@ -13,7 +13,6 @@ import { PLUGINS_STORE_NAME, SETTINGS_STORE_NAME } from '@woocommerce/data';
import { import {
AUTOMATION_PLUGINS, AUTOMATION_PLUGINS,
hasCompleteAddress, hasCompleteAddress,
SettingsSelector,
TaxChildProps, TaxChildProps,
} from '../utils'; } from '../utils';
import { AutomatedTaxes } from './automated-taxes'; import { AutomatedTaxes } from './automated-taxes';
@ -31,9 +30,7 @@ export const WooCommerceTax: React.FC< TaxChildProps > = ( {
isResolving, isResolving,
pluginsToActivate, pluginsToActivate,
} = useSelect( ( select ) => { } = useSelect( ( select ) => {
const { getSettings } = select( const { getSettings } = select( SETTINGS_STORE_NAME );
SETTINGS_STORE_NAME
) as SettingsSelector;
const { getActivePlugins, hasFinishedResolution } = const { getActivePlugins, hasFinishedResolution } =
select( PLUGINS_STORE_NAME ); select( PLUGINS_STORE_NAME );
const activePlugins = getActivePlugins(); const activePlugins = getActivePlugins();
@ -55,7 +52,7 @@ export const WooCommerceTax: React.FC< TaxChildProps > = ( {
const canAutomateTaxes = () => { const canAutomateTaxes = () => {
return ( return (
hasCompleteAddress( generalSettings ) && hasCompleteAddress( generalSettings || {} ) &&
! pluginsToActivate.length && ! pluginsToActivate.length &&
isJetpackConnected isJetpackConnected
); );

View File

@ -15,7 +15,7 @@ import { useSelect } from '@wordpress/data';
/** /**
* Internal dependencies * Internal dependencies
*/ */
import { AUTOMATION_PLUGINS, SettingsSelector } from '../utils'; import { AUTOMATION_PLUGINS } from '../utils';
import { Connect } from './connect'; import { Connect } from './connect';
import { Plugins } from './plugins'; import { Plugins } from './plugins';
import { StoreLocation } from '../components/store-location'; import { StoreLocation } from '../components/store-location';
@ -48,9 +48,7 @@ export const Setup: React.FC< SetupProps > = ( {
[] []
); );
const { activePlugins, isResolving } = useSelect( ( select ) => { const { activePlugins, isResolving } = useSelect( ( select ) => {
const { getSettings } = select( const { getSettings } = select( SETTINGS_STORE_NAME );
SETTINGS_STORE_NAME
) as SettingsSelector;
const { hasFinishedResolution } = select( OPTIONS_STORE_NAME ); const { hasFinishedResolution } = select( OPTIONS_STORE_NAME );
const { getActivePlugins } = select( PLUGINS_STORE_NAME ); const { getActivePlugins } = select( PLUGINS_STORE_NAME );

View File

@ -0,0 +1,4 @@
Significance: patch
Type: dev
Fix typescript errors for setting selector