Migrate `@woocommerce/data` onboarding store to TS (#33401)

* Migrate onboarding data store to TS

* Update deprecated-tasks.tsx type annotations

* Fix onboarding resolvers
This commit is contained in:
Chi-Hsuan Huang 2022-06-14 10:23:40 +08:00 committed by GitHub
parent 4ebdd2b226
commit 94367ca559
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 386 additions and 236 deletions

View File

@ -0,0 +1,4 @@
Significance: minor
Type: dev
Migrate onboarding data store to TS

View File

@ -36,6 +36,6 @@ const TYPES = {
VISITED_TASK: 'VISITED_TASK',
KEEP_COMPLETED_TASKS_REQUEST: 'KEEP_COMPLETED_TASKS_REQUEST',
KEEP_COMPLETED_TASKS_SUCCESS: 'KEEP_COMPLETED_TASKS_SUCCESS',
};
} as const;
export default TYPES;

View File

@ -11,22 +11,30 @@ import TYPES from './action-types';
import { WC_ADMIN_NAMESPACE } from '../constants';
import { DeprecatedTasks } from './deprecated-tasks';
import { STORE_NAME as OPTIONS_STORE_NAME } from '../options/constants';
import {
ExtensionList,
ProfileItems,
TaskListType,
TaskType,
OnboardingProductType,
} from './types';
import { Plugin } from '../plugins/types';
export function getFreeExtensionsError( error ) {
export function getFreeExtensionsError( error: unknown ) {
return {
type: TYPES.GET_FREE_EXTENSIONS_ERROR,
error,
};
}
export function getFreeExtensionsSuccess( freeExtensions ) {
export function getFreeExtensionsSuccess( freeExtensions: ExtensionList[] ) {
return {
type: TYPES.GET_FREE_EXTENSIONS_SUCCESS,
freeExtensions,
};
}
export function setError( selector, error ) {
export function setError( selector: string, error: unknown ) {
return {
type: TYPES.SET_ERROR,
selector,
@ -34,7 +42,7 @@ export function setError( selector, error ) {
};
}
export function setIsRequesting( selector, isRequesting ) {
export function setIsRequesting( selector: string, isRequesting: boolean ) {
return {
type: TYPES.SET_IS_REQUESTING,
selector,
@ -42,7 +50,7 @@ export function setIsRequesting( selector, isRequesting ) {
};
}
export function setProfileItems( profileItems, replace = false ) {
export function setProfileItems( profileItems: ProfileItems, replace = false ) {
return {
type: TYPES.SET_PROFILE_ITEMS,
profileItems,
@ -50,21 +58,21 @@ export function setProfileItems( profileItems, replace = false ) {
};
}
export function getTaskListsError( error ) {
export function getTaskListsError( error: unknown ) {
return {
type: TYPES.GET_TASK_LISTS_ERROR,
error,
};
}
export function getTaskListsSuccess( taskLists ) {
export function getTaskListsSuccess( taskLists: TaskListType[] ) {
return {
type: TYPES.GET_TASK_LISTS_SUCCESS,
taskLists,
};
}
export function snoozeTaskError( taskId, error ) {
export function snoozeTaskError( taskId: string, error: unknown ) {
return {
type: TYPES.SNOOZE_TASK_ERROR,
taskId,
@ -72,21 +80,21 @@ export function snoozeTaskError( taskId, error ) {
};
}
export function snoozeTaskRequest( taskId ) {
export function snoozeTaskRequest( taskId: string ) {
return {
type: TYPES.SNOOZE_TASK_REQUEST,
taskId,
};
}
export function snoozeTaskSuccess( task ) {
export function snoozeTaskSuccess( task: Partial< TaskType > ) {
return {
type: TYPES.SNOOZE_TASK_SUCCESS,
task,
};
}
export function undoSnoozeTaskError( taskId, error ) {
export function undoSnoozeTaskError( taskId: string, error: unknown ) {
return {
type: TYPES.UNDO_SNOOZE_TASK_ERROR,
taskId,
@ -94,21 +102,21 @@ export function undoSnoozeTaskError( taskId, error ) {
};
}
export function undoSnoozeTaskRequest( taskId ) {
export function undoSnoozeTaskRequest( taskId: string ) {
return {
type: TYPES.UNDO_SNOOZE_TASK_REQUEST,
taskId,
};
}
export function undoSnoozeTaskSuccess( task ) {
export function undoSnoozeTaskSuccess( task: Partial< TaskType > ) {
return {
type: TYPES.UNDO_SNOOZE_TASK_SUCCESS,
task,
};
}
export function dismissTaskError( taskId, error ) {
export function dismissTaskError( taskId: string, error: unknown ) {
return {
type: TYPES.DISMISS_TASK_ERROR,
taskId,
@ -116,21 +124,21 @@ export function dismissTaskError( taskId, error ) {
};
}
export function dismissTaskRequest( taskId ) {
export function dismissTaskRequest( taskId: string ) {
return {
type: TYPES.DISMISS_TASK_REQUEST,
taskId,
};
}
export function dismissTaskSuccess( task ) {
export function dismissTaskSuccess( task: Partial< TaskType > ) {
return {
type: TYPES.DISMISS_TASK_SUCCESS,
task,
};
}
export function undoDismissTaskError( taskId, error ) {
export function undoDismissTaskError( taskId: string, error: unknown ) {
return {
type: TYPES.UNDO_DISMISS_TASK_ERROR,
taskId,
@ -138,21 +146,21 @@ export function undoDismissTaskError( taskId, error ) {
};
}
export function undoDismissTaskRequest( taskId ) {
export function undoDismissTaskRequest( taskId: string ) {
return {
type: TYPES.UNDO_DISMISS_TASK_REQUEST,
taskId,
};
}
export function undoDismissTaskSuccess( task ) {
export function undoDismissTaskSuccess( task: Partial< TaskType > ) {
return {
type: TYPES.UNDO_DISMISS_TASK_SUCCESS,
task,
};
}
export function hideTaskListError( taskListId, error ) {
export function hideTaskListError( taskListId: string, error: unknown ) {
return {
type: TYPES.HIDE_TASK_LIST_ERROR,
taskListId,
@ -160,14 +168,14 @@ export function hideTaskListError( taskListId, error ) {
};
}
export function hideTaskListRequest( taskListId ) {
export function hideTaskListRequest( taskListId: string ) {
return {
type: TYPES.HIDE_TASK_LIST_REQUEST,
taskListId,
};
}
export function hideTaskListSuccess( taskList ) {
export function hideTaskListSuccess( taskList: TaskListType ) {
return {
type: TYPES.HIDE_TASK_LIST_SUCCESS,
taskList,
@ -175,7 +183,7 @@ export function hideTaskListSuccess( taskList ) {
};
}
export function unhideTaskListError( taskListId, error ) {
export function unhideTaskListError( taskListId: string, error: unknown ) {
return {
type: TYPES.UNHIDE_TASK_LIST_ERROR,
taskListId,
@ -183,14 +191,14 @@ export function unhideTaskListError( taskListId, error ) {
};
}
export function unhideTaskListRequest( taskListId ) {
export function unhideTaskListRequest( taskListId: string ) {
return {
type: TYPES.UNHIDE_TASK_LIST_REQUEST,
taskListId,
};
}
export function unhideTaskListSuccess( taskList ) {
export function unhideTaskListSuccess( taskList: TaskListType ) {
return {
type: TYPES.UNHIDE_TASK_LIST_SUCCESS,
taskList,
@ -198,14 +206,17 @@ export function unhideTaskListSuccess( taskList ) {
};
}
export function optimisticallyCompleteTaskRequest( taskId ) {
export function optimisticallyCompleteTaskRequest( taskId: string ) {
return {
type: TYPES.OPTIMISTICALLY_COMPLETE_TASK_REQUEST,
taskId,
};
}
export function keepCompletedTaskListSuccess( taskListId, keepCompletedList ) {
export function keepCompletedTaskListSuccess(
taskListId: string,
keepCompletedList: 'yes' | 'no'
) {
return {
type: TYPES.KEEP_COMPLETED_TASKS_SUCCESS,
taskListId,
@ -213,28 +224,28 @@ export function keepCompletedTaskListSuccess( taskListId, keepCompletedList ) {
};
}
export function visitedTask( taskId ) {
export function visitedTask( taskId: string ) {
return {
type: TYPES.VISITED_TASK,
taskId,
};
}
export function setPaymentMethods( paymentMethods ) {
export function setPaymentMethods( paymentMethods: Plugin[] ) {
return {
type: TYPES.GET_PAYMENT_METHODS_SUCCESS,
paymentMethods,
};
}
export function setEmailPrefill( email ) {
export function setEmailPrefill( email: string ) {
return {
type: TYPES.SET_EMAIL_PREFILL,
emailPrefill: email,
};
}
export function actionTaskError( taskId, error ) {
export function actionTaskError( taskId: string, error: unknown ) {
return {
type: TYPES.ACTION_TASK_ERROR,
taskId,
@ -242,39 +253,43 @@ export function actionTaskError( taskId, error ) {
};
}
export function actionTaskRequest( taskId ) {
export function actionTaskRequest( taskId: string ) {
return {
type: TYPES.ACTION_TASK_REQUEST,
taskId,
};
}
export function actionTaskSuccess( task ) {
export function actionTaskSuccess( task: Partial< TaskType > ) {
return {
type: TYPES.ACTION_TASK_SUCCESS,
task,
};
}
export function getProductTypesSuccess( productTypes ) {
export function getProductTypesSuccess(
productTypes: OnboardingProductType[]
) {
return {
type: TYPES.GET_PRODUCT_TYPES_SUCCESS,
productTypes,
};
}
export function getProductTypesError( error ) {
export function getProductTypesError( error: unknown ) {
return {
type: TYPES.GET_PRODUCT_TYPES_ERROR,
error,
};
}
export function* keepCompletedTaskList( taskListId ) {
export function* keepCompletedTaskList( taskListId: string ) {
const updateOptionsParams = {
woocommerce_task_list_keep_completed: 'yes',
};
const response = yield controls.dispatch(
const response: {
success: 'yes' | 'no';
} = yield controls.dispatch(
OPTIONS_STORE_NAME,
'updateOptions',
updateOptionsParams
@ -284,12 +299,15 @@ export function* keepCompletedTaskList( taskListId ) {
}
}
export function* updateProfileItems( items ) {
export function* updateProfileItems( items: ProfileItems ) {
yield setIsRequesting( 'updateProfileItems', true );
yield setError( 'updateProfileItems', null );
try {
const results = yield apiFetch( {
const results: {
items: ProfileItems;
status: string;
} = yield apiFetch( {
path: `${ WC_ADMIN_NAMESPACE }/onboarding/profile`,
method: 'POST',
data: items,
@ -309,11 +327,11 @@ export function* updateProfileItems( items ) {
}
}
export function* snoozeTask( id ) {
export function* snoozeTask( id: string ) {
yield snoozeTaskRequest( id );
try {
const task = yield apiFetch( {
const task: TaskType = yield apiFetch( {
path: `${ WC_ADMIN_NAMESPACE }/onboarding/tasks/${ id }/snooze`,
method: 'POST',
} );
@ -331,11 +349,11 @@ export function* snoozeTask( id ) {
}
}
export function* undoSnoozeTask( id ) {
export function* undoSnoozeTask( id: string ) {
yield undoSnoozeTaskRequest( id );
try {
const task = yield apiFetch( {
const task: TaskType = yield apiFetch( {
path: `${ WC_ADMIN_NAMESPACE }/onboarding/tasks/${ id }/undo_snooze`,
method: 'POST',
} );
@ -353,11 +371,11 @@ export function* undoSnoozeTask( id ) {
}
}
export function* dismissTask( id ) {
export function* dismissTask( id: string ) {
yield dismissTaskRequest( id );
try {
const task = yield apiFetch( {
const task: TaskType = yield apiFetch( {
path: `${ WC_ADMIN_NAMESPACE }/onboarding/tasks/${ id }/dismiss`,
method: 'POST',
} );
@ -374,11 +392,11 @@ export function* dismissTask( id ) {
}
}
export function* undoDismissTask( id ) {
export function* undoDismissTask( id: string ) {
yield undoDismissTaskRequest( id );
try {
const task = yield apiFetch( {
const task: TaskType = yield apiFetch( {
path: `${ WC_ADMIN_NAMESPACE }/onboarding/tasks/${ id }/undo_dismiss`,
method: 'POST',
} );
@ -395,11 +413,11 @@ export function* undoDismissTask( id ) {
}
}
export function* hideTaskList( id ) {
export function* hideTaskList( id: string ) {
yield hideTaskListRequest( id );
try {
const taskList = yield apiFetch( {
const taskList: TaskListType = yield apiFetch( {
path: `${ WC_ADMIN_NAMESPACE }/onboarding/tasks/${ id }/hide`,
method: 'POST',
} );
@ -411,11 +429,11 @@ export function* hideTaskList( id ) {
}
}
export function* unhideTaskList( id ) {
export function* unhideTaskList( id: string ) {
yield unhideTaskListRequest( id );
try {
const taskList = yield apiFetch( {
const taskList: TaskListType = yield apiFetch( {
path: `${ WC_ADMIN_NAMESPACE }/onboarding/tasks/${ id }/unhide`,
method: 'POST',
} );
@ -427,15 +445,15 @@ export function* unhideTaskList( id ) {
}
}
export function* optimisticallyCompleteTask( id ) {
export function* optimisticallyCompleteTask( id: string ) {
yield optimisticallyCompleteTaskRequest( id );
}
export function* actionTask( id ) {
export function* actionTask( id: string ) {
yield actionTaskRequest( id );
try {
const task = yield apiFetch( {
const task: TaskType = yield apiFetch( {
path: `${ WC_ADMIN_NAMESPACE }/onboarding/tasks/${ id }/action`,
method: 'POST',
} );
@ -448,3 +466,41 @@ export function* actionTask( id ) {
throw new Error();
}
}
export type Action = ReturnType<
| typeof getFreeExtensionsError
| typeof getFreeExtensionsSuccess
| typeof setError
| typeof setIsRequesting
| typeof setProfileItems
| typeof snoozeTaskRequest
| typeof snoozeTaskSuccess
| typeof snoozeTaskError
| typeof getTaskListsError
| typeof getTaskListsSuccess
| typeof undoSnoozeTaskError
| typeof undoSnoozeTaskSuccess
| typeof dismissTaskError
| typeof dismissTaskSuccess
| typeof dismissTaskRequest
| typeof undoDismissTaskError
| typeof undoDismissTaskSuccess
| typeof undoDismissTaskRequest
| typeof undoSnoozeTaskRequest
| typeof hideTaskListError
| typeof hideTaskListSuccess
| typeof hideTaskListRequest
| typeof unhideTaskListError
| typeof unhideTaskListSuccess
| typeof unhideTaskListRequest
| typeof optimisticallyCompleteTaskRequest
| typeof keepCompletedTaskListSuccess
| typeof visitedTask
| typeof setPaymentMethods
| typeof setEmailPrefill
| typeof actionTaskError
| typeof actionTaskSuccess
| typeof actionTaskRequest
| typeof getProductTypesError
| typeof getProductTypesSuccess
>;

View File

@ -5,6 +5,11 @@ import { applyFilters } from '@wordpress/hooks';
import { parse } from 'qs';
import deprecated from '@wordpress/deprecated';
/**
* Internal dependencies
*/
import { TaskListType, TaskType, DeprecatedTaskType } from './types';
function getQuery() {
const searchString = window.location && window.location.search;
if ( ! searchString ) {
@ -19,6 +24,11 @@ function getQuery() {
* A simple class to handle deprecated tasks using the woocommerce_admin_onboarding_task_list filter.
*/
export class DeprecatedTasks {
filteredTasks: DeprecatedTaskType[];
tasks: {
[ key: string ]: DeprecatedTaskType[];
};
constructor() {
/**
* **Deprecated** Filter Onboarding tasks.
@ -32,7 +42,7 @@ export class DeprecatedTasks {
'woocommerce_admin_onboarding_task_list',
[],
getQuery()
);
) as DeprecatedTaskType[];
if ( this.filteredTasks && this.filteredTasks.length > 0 ) {
deprecated( 'woocommerce_admin_onboarding_task_list', {
version: '2.10.0',
@ -72,7 +82,7 @@ export class DeprecatedTasks {
: null;
}
mergeDeprecatedCallbackFunctions( taskLists ) {
mergeDeprecatedCallbackFunctions( taskLists: TaskListType[] ) {
if ( this.filteredTasks.length > 0 ) {
for ( const taskList of taskLists ) {
// Merge any extended task list items, to keep the callback functions around.
@ -99,10 +109,10 @@ export class DeprecatedTasks {
* @param {Array} keys to keep in the task object.
* @return {Object} task with the keys specified.
*/
static possiblyPruneTaskData( task, keys ) {
static possiblyPruneTaskData( task: TaskType, keys: ( keyof TaskType )[] ) {
if ( ! task.time && ! task.title ) {
// client side task
return keys.reduce(
return keys.reduce< Partial< TaskType > >(
( simplifiedTask, key ) => {
return {
...simplifiedTask,

View File

@ -1,12 +1,10 @@
/**
* External dependencies
*/
import { registerStore } from '@wordpress/data';
import { controls } from '@wordpress/data-controls';
import { SelectFromMap, DispatchFromMap } from '@automattic/data-stores';
import { Reducer, AnyAction } from 'redux';
/**
* Internal dependencies
*/
@ -14,11 +12,13 @@ import { STORE_NAME } from './constants';
import * as selectors from './selectors';
import * as actions from './actions';
import * as resolvers from './resolvers';
import reducer from './reducer';
import { WPDataSelectors } from '../types';
import reducer, { State } from './reducer';
import { WPDataActions, WPDataSelectors } from '../types';
export * from './types';
export type { State };
registerStore( STORE_NAME, {
reducer: reducer as Reducer< ReturnType< Reducer >, AnyAction >,
registerStore< State >( STORE_NAME, {
reducer: reducer as Reducer< State, AnyAction >,
actions,
controls,
selectors,
@ -27,12 +27,12 @@ registerStore( STORE_NAME, {
export const ONBOARDING_STORE_NAME = STORE_NAME;
export type OnboardingSelector = SelectFromMap< typeof selectors >;
export type OnboardingSelector = SelectFromMap< typeof selectors > &
WPDataSelectors;
declare module '@wordpress/data' {
// TODO: convert action.js to TS
function dispatch( key: typeof STORE_NAME ): DispatchFromMap< AnyAction >;
function select(
function dispatch(
key: typeof STORE_NAME
): SelectFromMap< typeof selectors > & WPDataSelectors;
): DispatchFromMap< typeof actions & WPDataActions >;
function select( key: typeof STORE_NAME ): OnboardingSelector;
}

View File

@ -1,9 +1,17 @@
/**
* External dependencies
*/
import type { Reducer } from 'redux';
/**
* Internal dependencies
*/
import TYPES from './action-types';
import { Action } from './actions';
import { OnboardingState, TaskListType, TaskType } from './types';
export const defaultState = {
export const defaultState: OnboardingState = {
errors: {},
freeExtensions: [],
profileItems: {
@ -31,7 +39,10 @@ export const defaultState = {
taskLists: {},
};
const getUpdatedTaskLists = ( taskLists, args ) => {
const getUpdatedTaskLists = (
taskLists: Record< string, TaskListType >,
args: Partial< TaskType >
) => {
return Object.keys( taskLists ).reduce(
( lists, taskListId ) => {
return {
@ -54,46 +65,29 @@ const getUpdatedTaskLists = ( taskLists, args ) => {
);
};
const onboarding = (
const reducer: Reducer< OnboardingState, Action > = (
state = defaultState,
{
freeExtensions,
type,
profileItems,
emailPrefill,
paymentMethods,
productTypes,
replace,
error,
isRequesting,
selector,
task,
taskId,
taskListId,
taskList,
taskLists,
keepCompletedTaskList,
}
action
) => {
switch ( type ) {
switch ( action.type ) {
case TYPES.SET_PROFILE_ITEMS:
return {
...state,
profileItems: replace
? profileItems
: { ...state.profileItems, ...profileItems },
profileItems: action.replace
? action.profileItems
: { ...state.profileItems, ...action.profileItems },
};
case TYPES.SET_EMAIL_PREFILL:
return {
...state,
emailPrefill,
emailPrefill: action.emailPrefill,
};
case TYPES.SET_ERROR:
return {
...state,
errors: {
...state.errors,
[ selector ]: error,
[ action.selector ]: action.error,
},
};
case TYPES.SET_IS_REQUESTING:
@ -101,25 +95,25 @@ const onboarding = (
...state,
requesting: {
...state.requesting,
[ selector ]: isRequesting,
[ action.selector ]: action.isRequesting,
},
};
case TYPES.GET_PAYMENT_METHODS_SUCCESS:
return {
...state,
paymentMethods,
paymentMethods: action.paymentMethods,
};
case TYPES.GET_PRODUCT_TYPES_SUCCESS:
return {
...state,
productTypes,
productTypes: action.productTypes,
};
case TYPES.GET_PRODUCT_TYPES_ERROR:
return {
...state,
errors: {
...state.errors,
productTypes: error,
productTypes: action.error,
},
};
case TYPES.GET_FREE_EXTENSIONS_ERROR:
@ -127,26 +121,26 @@ const onboarding = (
...state,
errors: {
...state.errors,
getFreeExtensions: error,
getFreeExtensions: action.error,
},
};
case TYPES.GET_FREE_EXTENSIONS_SUCCESS:
return {
...state,
freeExtensions,
freeExtensions: action.freeExtensions,
};
case TYPES.GET_TASK_LISTS_ERROR:
return {
...state,
errors: {
...state.errors,
getTaskLists: error,
getTaskLists: action.error,
},
};
case TYPES.GET_TASK_LISTS_SUCCESS:
return {
...state,
taskLists: taskLists.reduce( ( lists, list ) => {
taskLists: action.taskLists.reduce( ( lists, list ) => {
return {
...lists,
[ list.id ]: list,
@ -158,10 +152,10 @@ const onboarding = (
...state,
errors: {
...state.errors,
dismissTask: error,
dismissTask: action.error,
},
taskLists: getUpdatedTaskLists( state.taskLists, {
id: taskId,
id: action.taskId,
isDismissed: false,
} ),
};
@ -173,7 +167,7 @@ const onboarding = (
dismissTask: true,
},
taskLists: getUpdatedTaskLists( state.taskLists, {
id: taskId,
id: action.taskId,
isDismissed: true,
} ),
};
@ -184,17 +178,17 @@ const onboarding = (
...state.requesting,
dismissTask: false,
},
taskLists: getUpdatedTaskLists( state.taskLists, task ),
taskLists: getUpdatedTaskLists( state.taskLists, action.task ),
};
case TYPES.UNDO_DISMISS_TASK_ERROR:
return {
...state,
errors: {
...state.errors,
undoDismissTask: error,
undoDismissTask: action.error,
},
taskLists: getUpdatedTaskLists( state.taskLists, {
id: taskId,
id: action.taskId,
isDismissed: true,
} ),
};
@ -206,7 +200,7 @@ const onboarding = (
undoDismissTask: true,
},
taskLists: getUpdatedTaskLists( state.taskLists, {
id: taskId,
id: action.taskId,
isDismissed: false,
} ),
};
@ -217,17 +211,17 @@ const onboarding = (
...state.requesting,
undoDismissTask: false,
},
taskLists: getUpdatedTaskLists( state.taskLists, task ),
taskLists: getUpdatedTaskLists( state.taskLists, action.task ),
};
case TYPES.SNOOZE_TASK_ERROR:
return {
...state,
errors: {
...state.errors,
snoozeTask: error,
snoozeTask: action.error,
},
taskLists: getUpdatedTaskLists( state.taskLists, {
id: taskId,
id: action.taskId,
isSnoozed: false,
} ),
};
@ -239,7 +233,7 @@ const onboarding = (
snoozeTask: true,
},
taskLists: getUpdatedTaskLists( state.taskLists, {
id: taskId,
id: action.taskId,
isSnoozed: true,
} ),
};
@ -250,17 +244,17 @@ const onboarding = (
...state.requesting,
snoozeTask: false,
},
taskLists: getUpdatedTaskLists( state.taskLists, task ),
taskLists: getUpdatedTaskLists( state.taskLists, action.task ),
};
case TYPES.UNDO_SNOOZE_TASK_ERROR:
return {
...state,
errors: {
...state.errors,
undoSnoozeTask: error,
undoSnoozeTask: action.error,
},
taskLists: getUpdatedTaskLists( state.taskLists, {
id: taskId,
id: action.taskId,
isSnoozed: true,
} ),
};
@ -272,7 +266,7 @@ const onboarding = (
undoSnoozeTask: true,
},
taskLists: getUpdatedTaskLists( state.taskLists, {
id: taskId,
id: action.taskId,
isSnoozed: false,
} ),
};
@ -283,19 +277,19 @@ const onboarding = (
...state.requesting,
undoSnoozeTask: false,
},
taskLists: getUpdatedTaskLists( state.taskLists, task ),
taskLists: getUpdatedTaskLists( state.taskLists, action.task ),
};
case TYPES.HIDE_TASK_LIST_ERROR:
return {
...state,
errors: {
...state.errors,
hideTaskList: error,
hideTaskList: action.error,
},
taskLists: {
...state.taskLists,
[ taskListId ]: {
...state.taskLists[ taskListId ],
[ action.taskListId ]: {
...state.taskLists[ action.taskListId ],
isHidden: false,
isVisible: true,
},
@ -310,8 +304,8 @@ const onboarding = (
},
taskLists: {
...state.taskLists,
[ taskListId ]: {
...state.taskLists[ taskListId ],
[ action.taskListId ]: {
...state.taskLists[ action.taskListId ],
isHidden: true,
isVisible: false,
},
@ -326,7 +320,7 @@ const onboarding = (
},
taskLists: {
...state.taskLists,
[ taskListId ]: taskList,
[ action.taskListId ]: action.taskList,
},
};
case TYPES.UNHIDE_TASK_LIST_ERROR:
@ -334,12 +328,12 @@ const onboarding = (
...state,
errors: {
...state.errors,
unhideTaskList: error,
unhideTaskList: action.error,
},
taskLists: {
...state.taskLists,
[ taskListId ]: {
...state.taskLists[ taskListId ],
[ action.taskListId ]: {
...state.taskLists[ action.taskListId ],
isHidden: true,
isVisible: false,
},
@ -354,8 +348,8 @@ const onboarding = (
},
taskLists: {
...state.taskLists,
[ taskListId ]: {
...state.taskLists[ taskListId ],
[ action.taskListId ]: {
...state.taskLists[ action.taskListId ],
isHidden: false,
isVisible: true,
},
@ -370,7 +364,7 @@ const onboarding = (
},
taskLists: {
...state.taskLists,
[ taskListId ]: taskList,
[ action.taskListId ]: action.taskList,
},
};
case TYPES.KEEP_COMPLETED_TASKS_SUCCESS:
@ -378,9 +372,9 @@ const onboarding = (
...state,
taskLists: {
...state.taskLists,
[ taskListId ]: {
...state.taskLists[ taskListId ],
keepCompletedTaskList,
[ action.taskListId ]: {
...state.taskLists[ action.taskListId ],
keepCompletedTaskList: action.keepCompletedTaskList,
},
},
};
@ -388,7 +382,7 @@ const onboarding = (
return {
...state,
taskLists: getUpdatedTaskLists( state.taskLists, {
id: taskId,
id: action.taskId,
isComplete: true,
} ),
};
@ -396,7 +390,7 @@ const onboarding = (
return {
...state,
taskLists: getUpdatedTaskLists( state.taskLists, {
id: taskId,
id: action.taskId,
isVisited: true,
} ),
};
@ -405,10 +399,10 @@ const onboarding = (
...state,
errors: {
...state.errors,
actionTask: error,
actionTask: action.error,
},
taskLists: getUpdatedTaskLists( state.taskLists, {
id: taskId,
id: action.taskId,
isActioned: false,
} ),
};
@ -420,7 +414,7 @@ const onboarding = (
actionTask: true,
},
taskLists: getUpdatedTaskLists( state.taskLists, {
id: taskId,
id: action.taskId,
isActioned: true,
} ),
};
@ -431,11 +425,12 @@ const onboarding = (
...state.requesting,
actionTask: false,
},
taskLists: getUpdatedTaskLists( state.taskLists, task ),
taskLists: getUpdatedTaskLists( state.taskLists, action.task ),
};
default:
return state;
}
};
export default onboarding;
export type State = ReturnType< typeof reducer >;
export default reducer;

View File

@ -22,13 +22,20 @@ import {
getProductTypesError,
} from './actions';
import { DeprecatedTasks } from './deprecated-tasks';
import {
ExtensionList,
OnboardingProductType,
ProfileItems,
TaskListType,
} from './types';
import { Plugin } from '../plugins/types';
const resolveSelect =
controls && controls.resolveSelect ? controls.resolveSelect : select;
export function* getProfileItems() {
try {
const results = yield apiFetch( {
const results: ProfileItems = yield apiFetch( {
path: WC_ADMIN_NAMESPACE + '/onboarding/profile',
method: 'GET',
} );
@ -41,7 +48,9 @@ export function* getProfileItems() {
export function* getEmailPrefill() {
try {
const results = yield apiFetch( {
const results: {
email: string;
} = yield apiFetch( {
path:
WC_ADMIN_NAMESPACE +
'/onboarding/profile/experimental_get_email_prefill',
@ -57,7 +66,7 @@ export function* getEmailPrefill() {
export function* getTaskLists() {
const deprecatedTasks = new DeprecatedTasks();
try {
const results = yield apiFetch( {
const results: TaskListType[] = yield apiFetch( {
path: WC_ADMIN_NAMESPACE + '/onboarding/tasks',
method: deprecatedTasks.hasDeprecatedTasks() ? 'POST' : 'GET',
data: deprecatedTasks.getPostData(),
@ -85,7 +94,7 @@ export function* getTask() {
export function* getPaymentGatewaySuggestions() {
try {
const results = yield apiFetch( {
const results: Plugin[] = yield apiFetch( {
path: WC_ADMIN_NAMESPACE + '/payment-gateway-suggestions',
method: 'GET',
} );
@ -98,7 +107,7 @@ export function* getPaymentGatewaySuggestions() {
export function* getFreeExtensions() {
try {
const results = yield apiFetch( {
const results: ExtensionList[] = yield apiFetch( {
path: WC_ADMIN_NAMESPACE + '/onboarding/free-extensions',
method: 'GET',
} );
@ -111,7 +120,7 @@ export function* getFreeExtensions() {
export function* getProductTypes() {
try {
const results = yield apiFetch( {
const results: OnboardingProductType[] = yield apiFetch( {
path: WC_ADMIN_NAMESPACE + '/onboarding/product-types',
method: 'GET',
} );

View File

@ -11,7 +11,7 @@ import {
TaskListType,
OnboardingState,
ExtensionList,
ProfileItemsState,
ProfileItems,
OnboardingProductType,
} from './types';
import { WPDataSelectors } from '../types';
@ -25,7 +25,7 @@ export const getFreeExtensions = (
export const getProfileItems = (
state: OnboardingState
): ProfileItemsState | Record< string, never > => {
): ProfileItems | Record< string, never > => {
return state.profileItems || {};
};

View File

@ -8,8 +8,40 @@
import reducer, { defaultState } from '../reducer';
import TYPES from '../action-types';
const profileItems = {
business_extensions: [],
completed: false,
industry: null,
number_employees: null,
other_platform: null,
other_platform_name: '',
product_count: null,
product_types: null,
revenue: null,
selling_venues: null,
setup_client: false,
skipped: true,
theme: null,
wccom_connected: null,
is_agree_marketing: null,
store_email: null,
};
const paymentMethods = [
{
id: '',
content: '',
plugins: [],
title: '',
category_additional: [],
category_other: [],
image: '',
},
];
describe( 'plugins reducer', () => {
it( 'should return a default state', () => {
// @ts-expect-error - we're testing the default state
const state = reducer( undefined, {} );
expect( state ).toEqual( defaultState );
} );
@ -17,52 +49,53 @@ describe( 'plugins reducer', () => {
it( 'should handle SET_PROFILE_ITEMS', () => {
const state = reducer(
{
profileItems: { previousItem: 'value' },
// @ts-expect-error - we're only testing profileItems
profileItems,
},
{
type: TYPES.SET_PROFILE_ITEMS,
profileItems: { propertyName: 'value' },
profileItems: { is_agree_marketing: true },
}
);
expect( state.profileItems ).toHaveProperty( 'previousItem' );
expect( state.profileItems ).toHaveProperty( 'propertyName' );
expect( state.profileItems.propertyName ).toBe( 'value' );
expect( state.profileItems.is_agree_marketing ).toBe( true );
} );
it( 'should handle SET_PROFILE_ITEMS with replace', () => {
const state = reducer(
{
profileItems: { previousItem: 'value' },
// @ts-expect-error - we're only testing profileItems
profileItems,
},
{
type: TYPES.SET_PROFILE_ITEMS,
profileItems: { propertyName: 'value' },
profileItems: { is_agree_marketing: true },
replace: true,
}
);
expect( state.profileItems ).not.toHaveProperty( 'previousItem' );
expect( state.profileItems ).toHaveProperty( 'propertyName' );
expect( state.profileItems.propertyName ).toBe( 'value' );
expect( state.profileItems ).not.toHaveProperty( 'store_email' );
expect( state.profileItems ).toHaveProperty( 'is_agree_marketing' );
expect( state.profileItems.is_agree_marketing ).toBe( true );
} );
it( 'should handle GET_PAYMENT_METHODS_SUCCESS', () => {
const state = reducer(
// @ts-expect-error - we're only testing paymentMethods
{
paymentMethods: [ { previousItem: 'value' } ],
paymentMethods,
},
{
type: TYPES.GET_PAYMENT_METHODS_SUCCESS,
paymentMethods: [ { newItem: 'changed' } ],
paymentMethods: [ { image_72x72: 'changed' } ],
}
);
expect( state.paymentMethods[ 0 ] ).not.toHaveProperty(
'previousItem'
);
expect( state.paymentMethods[ 0 ] ).toHaveProperty( 'newItem' );
expect( state.paymentMethods[ 0 ].newItem ).toBe( 'changed' );
expect( state.paymentMethods[ 0 ] ).toHaveProperty( 'image_72x72' );
expect( state.paymentMethods[ 0 ].image_72x72 ).toBe( 'changed' );
} );
it( 'should handle SET_ERROR', () => {
@ -73,6 +106,7 @@ describe( 'plugins reducer', () => {
} );
/* eslint-disable dot-notation */
// @ts-expect-error we're asserting error properties
expect( state.errors[ 'getProfileItems' ].code ).toBe( 'error' );
/* eslint-enable dot-notation */
} );

View File

@ -30,6 +30,27 @@ export type TaskType = {
taxJarActivated?: boolean;
avalaraActivated?: boolean;
};
// Possibly added in DeprecatedTasks.mergeDeprecatedCallbackFunctions
isDeprecated?: boolean;
};
// reference: https://github.com/woocommerce/woocommerce-admin/blob/75cf5292f66bf69202f67356d143743a8796a7f6/docs/examples/extensions/add-task/js/index.js#L77-L101
export type DeprecatedTaskType = {
key: string;
title: string;
content: string;
container: React.ReactNode;
completed: boolean;
visible: boolean;
additionalInfo: string;
time: string;
isDismissable: boolean;
onDelete: () => void;
onDismiss: () => void;
allowRemindMeLater: string;
remindMeLater: () => () => void;
level?: string;
type?: string;
};
export type TaskListSection = {
@ -60,7 +81,7 @@ export type TaskListType = {
export type OnboardingState = {
freeExtensions: ExtensionList[];
profileItems: ProfileItemsState;
profileItems: ProfileItems;
taskLists: Record< string, TaskListType >;
paymentMethods: Plugin[];
productTypes: OnboardingProductType[];
@ -104,7 +125,7 @@ export type RevenueTypeSlug =
| '50000-250000'
| 'more-than-250000';
export type ProfileItemsState = {
export type ProfileItems = {
business_extensions: [ ] | null;
completed: boolean | null;
industry: Industry[] | null;

View File

@ -1,54 +0,0 @@
/**
* External dependencies
*/
import { createHigherOrderComponent } from '@wordpress/compose';
import { useSelect } from '@wordpress/data';
import { createElement, useRef } from '@wordpress/element';
/**
* Internal dependencies
*/
import { STORE_NAME } from './constants';
export const withOnboardingHydration = ( data ) => {
let hydratedProfileItems = false;
return createHigherOrderComponent(
( OriginalComponent ) => ( props ) => {
const onboardingRef = useRef( data );
useSelect( ( select, registry ) => {
if ( ! onboardingRef.current ) {
return;
}
const { isResolving, hasFinishedResolution } = select(
STORE_NAME
);
const {
startResolution,
finishResolution,
setProfileItems,
} = registry.dispatch( STORE_NAME );
const { profileItems } = onboardingRef.current;
if (
profileItems &&
! hydratedProfileItems &&
! isResolving( 'getProfileItems', [] ) &&
! hasFinishedResolution( 'getProfileItems', [] )
) {
startResolution( 'getProfileItems', [] );
setProfileItems( profileItems, true );
finishResolution( 'getProfileItems', [] );
hydratedProfileItems = true;
}
}, [] );
return <OriginalComponent { ...props } />;
},
'withOnboardingHydration'
);
};

View File

@ -0,0 +1,62 @@
/**
* External dependencies
*/
import { createHigherOrderComponent } from '@wordpress/compose';
import { useSelect } from '@wordpress/data';
import { createElement, useRef } from '@wordpress/element';
/**
* Internal dependencies
*/
import { STORE_NAME } from './constants';
import { ProfileItems } from './types';
import { OnboardingSelector } from './';
export const withOnboardingHydration = ( data: {
profileItems: ProfileItems;
} ) => {
let hydratedProfileItems = false;
return createHigherOrderComponent< Record< string, unknown > >(
( OriginalComponent ) => ( props ) => {
const onboardingRef = useRef( data );
useSelect(
// @ts-expect-error // @ts-expect-error registry is not defined in the wp.data typings
( select: ( s: string ) => OnboardingSelector, registry ) => {
if ( ! onboardingRef.current ) {
return;
}
const { isResolving, hasFinishedResolution } = select(
STORE_NAME
);
const {
startResolution,
finishResolution,
setProfileItems,
} = registry.dispatch( STORE_NAME );
const { profileItems } = onboardingRef.current;
if (
profileItems &&
! hydratedProfileItems &&
! isResolving( 'getProfileItems', [] ) &&
! hasFinishedResolution( 'getProfileItems', [] )
) {
startResolution( 'getProfileItems', [] );
setProfileItems( profileItems, true );
finishResolution( 'getProfileItems', [] );
hydratedProfileItems = true;
}
},
[]
);
return <OriginalComponent { ...props } />;
},
'withOnboardingHydration'
);
};

View File

@ -1,15 +1,24 @@
// Type for the basic selectors built into @wordpress/data, note these
// types define the interface for the public selectors, so state is not an
// argument.
// [wp.data.getSelectors](https://github.com/WordPress/gutenberg/blob/319deee5f4d4838d6bc280e9e2be89c7f43f2509/packages/data/src/store/index.js#L16-L20)
// [selector.js](https://github.com/WordPress/gutenberg/blob/trunk/packages/data/src/redux-store/metadata/selectors.js#L48-L52)
export type WPDataSelectors = {
getIsResolving: ( selector: string, args?: string[] ) => boolean;
hasStartedResolution: ( selector: string, args?: string[] ) => boolean;
hasFinishedResolution: ( selector: string, args?: string[] ) => boolean;
isResolving: ( selector: string, args?: string[] ) => boolean;
getCachedResolvers: () => unknown;
};
// [wp.data.getActions](https://github.com/WordPress/gutenberg/blob/319deee5f4d4838d6bc280e9e2be89c7f43f2509/packages/data/src/store/index.js#L31-L35)
// [actions.js](https://github.com/WordPress/gutenberg/blob/aa2bed9010aa50467cb43063e370b70a91591e9b/packages/data/src/redux-store/metadata/actions.js)
export type WPDataActions = {
startResolution: ( selector: string, args?: string[] ) => void;
finishResolution: ( selector: string, args?: string[] ) => void;
invalidateResolution: ( selector: string ) => void;
invalidateResolutionForStore: ( selector: string ) => void;
invalidateResolutionForStoreSelector: ( selector: string ) => void;
};
// Omitting state from selector parameter

View File

@ -4,18 +4,19 @@
import { registerPlugin } from '@wordpress/plugins';
import { WooOnboardingTask } from '@woocommerce/onboarding';
import { useSelect } from '@wordpress/data';
import { ONBOARDING_STORE_NAME, TaskType } from '@woocommerce/data';
import {
ONBOARDING_STORE_NAME,
TaskType,
DeprecatedTaskType,
} from '@woocommerce/data';
import { useEffect, useState } from '@wordpress/element';
type DeprecatedTask = TaskType & {
container?: React.ReactNode;
isDeprecated?: boolean;
};
type MergedTask = TaskType & DeprecatedTaskType;
const DeprecatedWooOnboardingTaskFills = () => {
const [ deprecatedTasks, setDeprecatedTasks ] = useState<
DeprecatedTask[]
>( [] );
const [ deprecatedTasks, setDeprecatedTasks ] = useState< MergedTask[] >(
[]
);
const { isResolving, taskLists } = useSelect( ( select ) => {
return {
isResolving: select( ONBOARDING_STORE_NAME ).isResolving(
@ -27,20 +28,18 @@ const DeprecatedWooOnboardingTaskFills = () => {
useEffect( () => {
if ( taskLists && taskLists.length > 0 ) {
const deprecatedTasksWithContainer = [];
const deprecatedTasksWithContainer: MergedTask[] = [];
for ( const tasklist of taskLists ) {
for ( const task of tasklist.tasks ) {
if (
( task as DeprecatedTask ).isDeprecated &&
( task as DeprecatedTask ).container
( task as MergedTask ).isDeprecated &&
( task as MergedTask ).container
) {
deprecatedTasksWithContainer.push( task );
deprecatedTasksWithContainer.push( task as MergedTask );
}
}
}
setDeprecatedTasks(
deprecatedTasksWithContainer as DeprecatedTask[]
);
setDeprecatedTasks( deprecatedTasksWithContainer );
}
}, [ taskLists ] );

View File

@ -0,0 +1,5 @@
Significance: patch
Type: dev
Comment: Simply update deprecated-tasks.tsx type annotations