diff --git a/plugins/woocommerce-admin/client/launch-your-store/hub/sidebar/components/launch-store-hub.tsx b/plugins/woocommerce-admin/client/launch-your-store/hub/sidebar/components/launch-store-hub.tsx index d748b988fb0..9376e521dd7 100644 --- a/plugins/woocommerce-admin/client/launch-your-store/hub/sidebar/components/launch-store-hub.tsx +++ b/plugins/woocommerce-admin/client/launch-your-store/hub/sidebar/components/launch-store-hub.tsx @@ -25,8 +25,9 @@ import { */ import type { SidebarComponentProps } from '../xstate'; import { SidebarContainer } from './sidebar-container'; -import { taskCompleteIcon, taskIcons } from './icons'; +import { taskCompleteIcon } from './icons'; import { SiteHub } from '~/customize-store/assembler-hub/site-hub'; +import { CompletedTaskItem, IncompleteTaskItem } from '../tasklist'; export const LaunchYourStoreHubSidebar: React.FC< SidebarComponentProps > = ( props ) => { @@ -109,28 +110,25 @@ export const LaunchYourStoreHubSidebar: React.FC< SidebarComponentProps > = ( { tasklist && hasIncompleteTasks && - tasklist.tasks.map( ( task ) => ( - { - props.sendEventToSidebar( { - type: 'TASK_CLICKED', - task, - } ); - } } - > - { task.title } - - ) ) } + tasklist.tasks.map( ( task ) => + task.isComplete ? ( + + ) : ( + { + props.sendEventToSidebar( { + type: 'TASK_CLICKED', + task, + } ); + } } + /> + ) + ) } { tasklist && ! hasIncompleteTasks && ( ( + LYS_RECENTLY_ACTIONED_TASKS_KEY, + SEVEN_DAYS_IN_SECONDS +); + +export const getLysTasklist = async () => { + const LYS_TASKS_WHITELIST = [ + 'products', + 'customize-store', + 'woocommerce-payments', + 'payments', + 'shipping', + 'tax', + ]; + + /** + * This filter allows customizing the list of tasks to show in WooCommerce Launch Your Store feature. + * + * @filter woocommerce_launch_your_store_tasklist_whitelist + * @param {string[]} LYS_TASKS_WHITELIST Default list of task IDs to show in LYS. + * + */ + const filteredTasks = applyFilters( + 'woocommerce_launch_your_store_tasklist_whitelist', + [ ...LYS_TASKS_WHITELIST ] + ) as string[]; + + const tasklist = await resolveSelect( + ONBOARDING_STORE_NAME + ).getTaskListsByIds( [ 'setup' ] ); + + const recentlyActionedTasks = getRecentlyActionedTasks() ?? []; + + /** + * Show tasks that fulfill all the following conditions: + * 1. part of the whitelist of tasks to show in LYS + * 2. either not completed or recently actioned + */ + const visibleTasks = tasklist[ 0 ].tasks.filter( + ( task ) => + filteredTasks.includes( task.id ) && + ( ! task.isComplete || recentlyActionedTasks.includes( task.id ) ) + ); + + return { + ...tasklist[ 0 ], + tasks: visibleTasks, + }; +}; + +export function taskClickedAction( event: { + type: 'TASK_CLICKED'; + task: TaskType; +} ) { + const recentlyActionedTasks = getRecentlyActionedTasks() ?? []; + saveRecentlyActionedTask( [ ...recentlyActionedTasks, event.task.id ] ); + if ( event.task.actionUrl ) { + navigateTo( { url: event.task.actionUrl } ); + } else { + navigateTo( { + url: getNewPath( { task: event.task.id }, '/', {} ), + } ); + } +} + +export const CompletedTaskItem: React.FC< { + task: TaskType; + classNames?: string; +} > = ( { task, classNames } ) => ( + + { task.title } + +); + +export const IncompleteTaskItem: React.FC< { + task: TaskType; + classNames?: string; + onClick: () => void; +} > = ( { task, classNames, onClick } ) => ( + + { task.title } + +); diff --git a/plugins/woocommerce-admin/client/launch-your-store/hub/sidebar/xstate.tsx b/plugins/woocommerce-admin/client/launch-your-store/hub/sidebar/xstate.tsx index 605395e4e79..5d211969816 100644 --- a/plugins/woocommerce-admin/client/launch-your-store/hub/sidebar/xstate.tsx +++ b/plugins/woocommerce-admin/client/launch-your-store/hub/sidebar/xstate.tsx @@ -11,14 +11,8 @@ import { } from 'xstate5'; import React from 'react'; import classnames from 'classnames'; -import { getNewPath, getQuery, navigateTo } from '@woocommerce/navigation'; -import { resolveSelect } from '@wordpress/data'; -import { - ONBOARDING_STORE_NAME, - TaskListType, - TaskType, -} from '@woocommerce/data'; -import { applyFilters } from '@wordpress/hooks'; +import { getQuery, navigateTo } from '@woocommerce/navigation'; +import { TaskListType, TaskType } from '@woocommerce/data'; /** * Internal dependencies @@ -27,6 +21,7 @@ import { LaunchYourStoreHubSidebar } from './components/launch-store-hub'; import type { LaunchYourStoreComponentProps } from '..'; import type { mainContentMachine } from '../main-content/xstate'; import { updateQueryParams, createQueryParamsListener } from '../common'; +import { taskClickedAction, getLysTasklist } from './tasklist'; export type SidebarMachineContext = { externalUrl: string | null; @@ -52,31 +47,6 @@ const sidebarQueryParamListener = fromCallback( ( { sendBack } ) => { return createQueryParamsListener( 'sidebar', sendBack ); } ); -const getLysTasklist = async () => { - const LYS_TASKS = [ - 'products', - 'customize-store', - 'woocommerce-payments', - 'payments', - 'shipping', - 'tax', - ]; - - const tasklist = await resolveSelect( - ONBOARDING_STORE_NAME - ).getTaskListsByIds( [ 'setup' ] ); - const visibleTasks: string[] = applyFilters( - 'woocommerce_launch_your_store_tasklist_visible', - [ ...LYS_TASKS ] - ) as string[]; - return { - ...tasklist[ 0 ], - tasks: tasklist[ 0 ].tasks.filter( ( task ) => - visibleTasks.includes( task.id ) - ), - }; -}; - export const sidebarMachine = setup( { types: {} as { context: SidebarMachineContext; @@ -107,13 +77,7 @@ export const sidebarMachine = setup( { }, taskClicked: ( { event } ) => { if ( event.type === 'TASK_CLICKED' ) { - if ( event.task.actionUrl ) { - navigateTo( { url: event.task.actionUrl } ); - } else { - navigateTo( { - url: getNewPath( { task: event.task.id }, '/', {} ), - } ); - } + taskClickedAction( event ); } }, openWcAdminUrl: ( { event } ) => { diff --git a/plugins/woocommerce-admin/client/launch-your-store/hub/styles.scss b/plugins/woocommerce-admin/client/launch-your-store/hub/styles.scss index aaa587faab9..ed99fe1f3d5 100644 --- a/plugins/woocommerce-admin/client/launch-your-store/hub/styles.scss +++ b/plugins/woocommerce-admin/client/launch-your-store/hub/styles.scss @@ -242,6 +242,15 @@ .edit-site-sidebar-navigation-item.is-complete { text-decoration: line-through; color: $gray-700; + + &:hover { + background: transparent; + color: $gray-700; + } + + .components-flex-block { + color: $gray-700; + } } } } diff --git a/plugins/woocommerce-admin/client/utils/localStorage.ts b/plugins/woocommerce-admin/client/utils/localStorage.ts new file mode 100644 index 00000000000..93fa32a2e55 --- /dev/null +++ b/plugins/woocommerce-admin/client/utils/localStorage.ts @@ -0,0 +1,41 @@ +type StoredItem< T > = { + data: T; + expiry: string; +}; + +/** + * This constructor function exists for type safety purposes, so when you create a pair of set/get functions they have the + * same type for the value they store/retrieve. + */ +export function createStorageUtils< T >( + key: string, + durationInSeconds: number +) { + return { + setWithExpiry: ( value: T ): void => { + const now = new Date(); + const expiry = new Date( now.getTime() + durationInSeconds * 1000 ); + + const item: StoredItem< T > = { + data: value, + expiry: expiry.toISOString(), + }; + + localStorage.setItem( key, JSON.stringify( item ) ); + }, + getWithExpiry: (): T | null => { + const itemStr = localStorage.getItem( key ); + if ( ! itemStr ) { + return null; + } + + const item: StoredItem< T > = JSON.parse( itemStr ); + const now = new Date(); + if ( now > new Date( item.expiry ) ) { + localStorage.removeItem( key ); + return null; + } + return item.data; + }, + }; +} diff --git a/plugins/woocommerce/changelog/fix-lys-incomplete-tasks b/plugins/woocommerce/changelog/fix-lys-incomplete-tasks new file mode 100644 index 00000000000..96ec29fe2a3 --- /dev/null +++ b/plugins/woocommerce/changelog/fix-lys-incomplete-tasks @@ -0,0 +1,4 @@ +Significance: patch +Type: fix + +Fix the LYS Hub tasklist so that it only shows incomplete tasks and tasks that were recently actioned \ No newline at end of file