Merge pull request #32302 from woocommerce/feature/32164_new_task_list_version_2
Add sectioned task list component
This commit is contained in:
commit
212198b3c4
|
@ -34,6 +34,7 @@ export { withPluginsHydration } from './plugins/with-plugins-hydration';
|
|||
|
||||
export { ONBOARDING_STORE_NAME } from './onboarding';
|
||||
export { withOnboardingHydration } from './onboarding/with-onboarding-hydration';
|
||||
export { getVisibleTasks } from './onboarding/utils';
|
||||
export type { TaskType, TaskListType } from './onboarding/types';
|
||||
|
||||
export { USER_STORE_NAME } from './user';
|
||||
|
|
|
@ -9,12 +9,22 @@ export type TaskType = {
|
|||
isSnoozed: boolean;
|
||||
isVisible: boolean;
|
||||
isSnoozable: boolean;
|
||||
isDisabled: boolean;
|
||||
snoozedUntil: number;
|
||||
time: string;
|
||||
title: string;
|
||||
isVisited: boolean;
|
||||
};
|
||||
|
||||
export type TaskListSection = {
|
||||
id: string;
|
||||
title: string;
|
||||
description: string;
|
||||
image: string;
|
||||
tasks: string[];
|
||||
isComplete: boolean;
|
||||
};
|
||||
|
||||
export type TaskListType = {
|
||||
id: string;
|
||||
isCollapsible?: boolean;
|
||||
|
@ -25,4 +35,5 @@ export type TaskListType = {
|
|||
title: string;
|
||||
eventPrefix: string;
|
||||
displayProgressHeader: boolean;
|
||||
sections?: TaskListSection[];
|
||||
};
|
||||
|
|
|
@ -0,0 +1,16 @@
|
|||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { TaskType } from './types';
|
||||
|
||||
/**
|
||||
* Filters tasks to only visible tasks, taking in account snoozed tasks.
|
||||
*/
|
||||
export function getVisibleTasks( tasks: TaskType[] ) {
|
||||
const nowTimestamp = Date.now();
|
||||
return tasks.filter(
|
||||
( task ) =>
|
||||
! task.isDismissed &&
|
||||
( ! task.isSnoozed || task.snoozedUntil < nowTimestamp )
|
||||
);
|
||||
}
|
|
@ -1,5 +1,7 @@
|
|||
# Unreleased
|
||||
|
||||
- Update TaskList types.
|
||||
|
||||
# 3.0.1
|
||||
|
||||
- Add missing dependency.
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
Significance: minor
|
||||
Type: Add
|
||||
|
||||
Add support for sections in our TaskList class and create a new sectional task list component. #32302
|
|
@ -1,3 +1,4 @@
|
|||
@import 'node_modules/@wordpress/base-styles/colors.native';
|
||||
// By using CSS variables, we can switch the spacing rhythm using a single media query.
|
||||
:root {
|
||||
--large-gap: 40px;
|
||||
|
@ -37,6 +38,14 @@
|
|||
max-width: 100%;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.components-panel__body > .components-panel__body-title,
|
||||
.woocommerce-experimental-list__item,
|
||||
.woocommerce-inbox-message {
|
||||
&:hover {
|
||||
background: $gray-0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
body.woocommerce-page {
|
||||
|
|
|
@ -4,7 +4,11 @@
|
|||
import { __, sprintf } from '@wordpress/i18n';
|
||||
import { useMemo } from '@wordpress/element';
|
||||
import { useSelect } from '@wordpress/data';
|
||||
import { ONBOARDING_STORE_NAME, TaskListType } from '@woocommerce/data';
|
||||
import {
|
||||
getVisibleTasks,
|
||||
ONBOARDING_STORE_NAME,
|
||||
TaskListType,
|
||||
} from '@woocommerce/data';
|
||||
import { getSetting } from '@woocommerce/settings';
|
||||
|
||||
/**
|
||||
|
@ -20,37 +24,38 @@ type ProgressHeaderProps = {
|
|||
export const ProgressHeader: React.FC< ProgressHeaderProps > = ( {
|
||||
taskListId,
|
||||
} ) => {
|
||||
const { loading, tasksCount, completedCount, hasVisitedTasks } = useSelect(
|
||||
( select ) => {
|
||||
const taskList: TaskListType = select(
|
||||
ONBOARDING_STORE_NAME
|
||||
).getTaskList( taskListId );
|
||||
const finishedResolution = select(
|
||||
ONBOARDING_STORE_NAME
|
||||
).hasFinishedResolution( 'getTaskList', [ taskListId ] );
|
||||
const nowTimestamp = Date.now();
|
||||
const visibleTasks = taskList?.tasks.filter(
|
||||
( task ) =>
|
||||
! task.isDismissed &&
|
||||
( ! task.isSnoozed || task.snoozedUntil < nowTimestamp )
|
||||
);
|
||||
const {
|
||||
loading,
|
||||
tasksCount,
|
||||
completedCount,
|
||||
hasVisitedTasks,
|
||||
disabledCompletedCount,
|
||||
} = useSelect( ( select ) => {
|
||||
const taskList: TaskListType = select(
|
||||
ONBOARDING_STORE_NAME
|
||||
).getTaskList( taskListId );
|
||||
const finishedResolution = select(
|
||||
ONBOARDING_STORE_NAME
|
||||
).hasFinishedResolution( 'getTaskList', [ taskListId ] );
|
||||
const visibleTasks = getVisibleTasks( taskList?.tasks );
|
||||
|
||||
return {
|
||||
loading: ! finishedResolution,
|
||||
tasksCount: visibleTasks?.length,
|
||||
completedCount: visibleTasks?.filter(
|
||||
( task ) => task.isComplete
|
||||
).length,
|
||||
hasVisitedTasks:
|
||||
visibleTasks?.filter( ( task ) => task.isVisited ).length >
|
||||
0,
|
||||
};
|
||||
}
|
||||
);
|
||||
return {
|
||||
loading: ! finishedResolution,
|
||||
tasksCount: visibleTasks?.length,
|
||||
completedCount: visibleTasks?.filter( ( task ) => task.isComplete )
|
||||
.length,
|
||||
disabledCompletedCount: visibleTasks?.filter(
|
||||
( task ) => task.isComplete && task.isDisabled
|
||||
).length,
|
||||
hasVisitedTasks:
|
||||
visibleTasks?.filter( ( task ) => task.isVisited ).length > 0,
|
||||
};
|
||||
} );
|
||||
|
||||
const progressTitle = useMemo( () => {
|
||||
if (
|
||||
( ! hasVisitedTasks && completedCount < 2 ) ||
|
||||
( ! hasVisitedTasks &&
|
||||
completedCount < 2 + disabledCompletedCount ) ||
|
||||
completedCount === tasksCount
|
||||
) {
|
||||
const siteTitle = getSetting( 'siteTitle' );
|
||||
|
|
|
@ -6,7 +6,11 @@ import { useEffect, useRef, useState } from '@wordpress/element';
|
|||
import { Card, CardHeader } from '@wordpress/components';
|
||||
import { useSelect } from '@wordpress/data';
|
||||
import { Badge } from '@woocommerce/components';
|
||||
import { ONBOARDING_STORE_NAME, TaskListType } from '@woocommerce/data';
|
||||
import {
|
||||
getVisibleTasks,
|
||||
ONBOARDING_STORE_NAME,
|
||||
TaskListType,
|
||||
} from '@woocommerce/data';
|
||||
import { recordEvent } from '@woocommerce/tracks';
|
||||
import { Text, List, CollapsibleList } from '@woocommerce/experimental';
|
||||
|
||||
|
@ -45,12 +49,7 @@ export const TaskList: React.FC< TaskListProps > = ( {
|
|||
};
|
||||
} );
|
||||
const prevQueryRef = useRef( query );
|
||||
const nowTimestamp = Date.now();
|
||||
const visibleTasks = tasks.filter(
|
||||
( task ) =>
|
||||
! task.isDismissed &&
|
||||
( ! task.isSnoozed || task.snoozedUntil < nowTimestamp )
|
||||
);
|
||||
const visibleTasks = getVisibleTasks( tasks );
|
||||
|
||||
const incompleteTasks = tasks.filter(
|
||||
( task ) => ! task.isComplete && ! task.isDismissed
|
||||
|
|
|
@ -19,6 +19,7 @@ import { TasksPlaceholder, TasksPlaceholderProps } from './placeholder';
|
|||
import './tasks.scss';
|
||||
import { TaskListProps, TaskList } from './task-list';
|
||||
import { TaskList as TwoColumnTaskList } from '../two-column-tasks/task-list';
|
||||
import { SectionedTaskList } from '../two-column-tasks/sectioned-task-list';
|
||||
import TwoColumnTaskListPlaceholder from '../two-column-tasks/placeholder';
|
||||
import '../two-column-tasks/style.scss';
|
||||
import { getAdminSetting } from '~/utils/admin-settings';
|
||||
|
@ -31,6 +32,8 @@ function getTaskListComponent( taskListId: string ): React.FC< TaskListProps > {
|
|||
switch ( taskListId ) {
|
||||
case 'setup_experiment_1':
|
||||
return TwoColumnTaskList;
|
||||
case 'setup_experiment_2':
|
||||
return SectionedTaskList;
|
||||
default:
|
||||
return TaskList;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
.woocommerce-task-section-header__container {
|
||||
display: flex;
|
||||
|
||||
.woocommerce-task-header__illustration {
|
||||
max-width: 150px;
|
||||
width: 34%;
|
||||
margin-left: auto;
|
||||
margin-right: 7%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.illustration-background {
|
||||
max-width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.woocommerce-task-header__contents p {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.woocommerce-task-header__contents h1 {
|
||||
font-size: 20px;
|
||||
line-height: 28px;
|
||||
padding: 0;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import './section-header.scss';
|
||||
|
||||
type Props = {
|
||||
title: string;
|
||||
description: string;
|
||||
image: string;
|
||||
};
|
||||
|
||||
const SectionHeader: React.FC< Props > = ( { title, description, image } ) => {
|
||||
return (
|
||||
<div className="woocommerce-task-header__contents-container woocommerce-task-section-header__container">
|
||||
<div className="woocommerce-task-header__contents">
|
||||
<h1>{ title }</h1>
|
||||
<p>{ description }</p>
|
||||
</div>
|
||||
<div className="woocommerce-task-header__illustration">
|
||||
<img
|
||||
src={ image }
|
||||
alt={ title }
|
||||
className="illustration-background"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default SectionHeader;
|
|
@ -0,0 +1,53 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { Badge } from '@woocommerce/components';
|
||||
import { TaskListSection, TaskType } from '@woocommerce/data';
|
||||
import { Icon, check } from '@wordpress/icons';
|
||||
import { Text } from '@woocommerce/experimental';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import SectionHeader from './headers/section-header';
|
||||
|
||||
type SectionPanelTitleProps = {
|
||||
section: TaskListSection;
|
||||
active: boolean;
|
||||
tasks: TaskType[];
|
||||
};
|
||||
|
||||
export const SectionPanelTitle: React.FC< SectionPanelTitleProps > = ( {
|
||||
section,
|
||||
active,
|
||||
tasks,
|
||||
} ) => {
|
||||
if ( active ) {
|
||||
return (
|
||||
<div className="wooocommerce-task-card__header-container">
|
||||
<div className="wooocommerce-task-card__header">
|
||||
<SectionHeader { ...section } />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const uncompletedTasksCount = tasks.filter(
|
||||
( task ) => ! task.isComplete && section.tasks.includes( task.id )
|
||||
).length;
|
||||
const isComplete = section.isComplete || uncompletedTasksCount === 0;
|
||||
|
||||
return (
|
||||
<>
|
||||
<Text variant="title.small" size="20" lineHeight="28px">
|
||||
{ section.title }
|
||||
</Text>
|
||||
{ ! isComplete && <Badge count={ uncompletedTasksCount } /> }
|
||||
{ isComplete && (
|
||||
<div className="woocommerce-task__icon">
|
||||
<Icon icon={ check } />
|
||||
</div>
|
||||
) }
|
||||
</>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,92 @@
|
|||
.woocommerce-sectioned-task-list {
|
||||
.components-panel {
|
||||
width: 100%;
|
||||
background: transparent;
|
||||
border: 0;
|
||||
}
|
||||
|
||||
.components-panel__body {
|
||||
padding-bottom: 0;
|
||||
margin-bottom: $gap-smaller;
|
||||
background: #fff;
|
||||
border: 1px solid $gray-200;
|
||||
|
||||
&.is-opened {
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
.components-panel__body-title {
|
||||
margin-bottom: 0;
|
||||
border-bottom: 1px solid #e0e0e0;
|
||||
|
||||
&:hover {
|
||||
border-bottom: 1px solid #e0e0e0;
|
||||
}
|
||||
|
||||
> .components-button {
|
||||
font-size: 20px;
|
||||
font-weight: 400;
|
||||
padding-top: 20px;
|
||||
padding-bottom: 20px;
|
||||
}
|
||||
|
||||
.components-panel__arrow {
|
||||
right: $gap-large;
|
||||
}
|
||||
}
|
||||
.wooocommerce-task-card__header-container {
|
||||
width: 100%;
|
||||
border-bottom: none;
|
||||
}
|
||||
.components-panel__body-toggle {
|
||||
box-shadow: none;
|
||||
padding-left: $gap-large;
|
||||
}
|
||||
&.is-opened .components-panel__body-toggle {
|
||||
width: 100%;
|
||||
padding: 0;
|
||||
.components-panel__arrow {
|
||||
top: 32px;
|
||||
}
|
||||
}
|
||||
|
||||
.woocommerce-experimental-list {
|
||||
width: calc(100% + 32px);
|
||||
margin: 0 -16px;
|
||||
}
|
||||
}
|
||||
ul li.woocommerce-task-list__item {
|
||||
padding-top: $gap;
|
||||
padding-bottom: $gap;
|
||||
|
||||
&.is-disabled {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
&:not(.is-complete) .woocommerce-task-list__item-before .woocommerce-task__icon {
|
||||
border-color: $gray-300;
|
||||
}
|
||||
}
|
||||
|
||||
.woocommerce-task-list__item.is-complete .woocommerce-task__icon {
|
||||
background-color: $alert-green;
|
||||
}
|
||||
|
||||
.components-panel__body-title {
|
||||
.woocommerce-badge {
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
}
|
||||
.woocommerce-task__icon {
|
||||
margin-left: $gap;
|
||||
background-color: $alert-green;
|
||||
border-radius: 50%;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
svg {
|
||||
fill: #fff;
|
||||
position: relative;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,252 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { useEffect, useRef, useState } from '@wordpress/element';
|
||||
import { Panel, PanelBody, PanelRow } from '@wordpress/components';
|
||||
import { useSelect, useDispatch } from '@wordpress/data';
|
||||
import { updateQueryString } from '@woocommerce/navigation';
|
||||
import {
|
||||
OPTIONS_STORE_NAME,
|
||||
ONBOARDING_STORE_NAME,
|
||||
TaskType,
|
||||
getVisibleTasks,
|
||||
} from '@woocommerce/data';
|
||||
import { recordEvent } from '@woocommerce/tracks';
|
||||
import { List, TaskItem } from '@woocommerce/experimental';
|
||||
import classnames from 'classnames';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import '../tasks/task-list.scss';
|
||||
import './sectioned-task-list.scss';
|
||||
import TaskListCompleted from './completed';
|
||||
import { TaskListProps } from '~/tasks/task-list';
|
||||
import { ProgressHeader } from '~/task-lists/progress-header';
|
||||
import { SectionPanelTitle } from './section-panel-title';
|
||||
|
||||
type PanelBodyProps = Omit< PanelBody.Props, 'title' | 'onToggle' > & {
|
||||
title: string | React.ReactNode | undefined;
|
||||
onToggle?: ( isOpen: boolean ) => void;
|
||||
};
|
||||
const PanelBodyWithUpdatedType = PanelBody as React.ComponentType< PanelBodyProps >;
|
||||
|
||||
export const SectionedTaskList: React.FC< TaskListProps > = ( {
|
||||
query,
|
||||
id,
|
||||
eventName,
|
||||
tasks,
|
||||
keepCompletedTaskList,
|
||||
isComplete,
|
||||
sections,
|
||||
displayProgressHeader,
|
||||
} ) => {
|
||||
const { createNotice } = useDispatch( 'core/notices' );
|
||||
const { updateOptions, dismissTask, undoDismissTask } = useDispatch(
|
||||
OPTIONS_STORE_NAME
|
||||
);
|
||||
const { profileItems } = useSelect( ( select ) => {
|
||||
const { getProfileItems } = select( ONBOARDING_STORE_NAME );
|
||||
return {
|
||||
profileItems: getProfileItems(),
|
||||
};
|
||||
} );
|
||||
const { hideTaskList } = useDispatch( ONBOARDING_STORE_NAME );
|
||||
const [ openPanel, setOpenPanel ] = useState< string | null >(
|
||||
sections?.find( ( section ) => ! section.isComplete )?.id || null
|
||||
);
|
||||
|
||||
const prevQueryRef = useRef( query );
|
||||
|
||||
const visibleTasks = getVisibleTasks( tasks );
|
||||
|
||||
const recordTaskListView = () => {
|
||||
if ( query.task ) {
|
||||
return;
|
||||
}
|
||||
|
||||
recordEvent( `${ eventName }_view`, {
|
||||
number_tasks: visibleTasks.length,
|
||||
store_connected: profileItems.wccom_connected,
|
||||
} );
|
||||
};
|
||||
|
||||
useEffect( () => {
|
||||
recordTaskListView();
|
||||
}, [] );
|
||||
|
||||
useEffect( () => {
|
||||
const { task: prevTask } = prevQueryRef.current;
|
||||
const { task } = query;
|
||||
|
||||
if ( prevTask !== task ) {
|
||||
window.document.documentElement.scrollTop = 0;
|
||||
prevQueryRef.current = query;
|
||||
}
|
||||
}, [ query ] );
|
||||
|
||||
const onDismissTask = ( taskId: string ) => {
|
||||
dismissTask( taskId );
|
||||
createNotice( 'success', __( 'Task dismissed' ), {
|
||||
actions: [
|
||||
{
|
||||
label: __( 'Undo', 'woocommerce-admin' ),
|
||||
onClick: () => undoDismissTask( taskId ),
|
||||
},
|
||||
],
|
||||
} );
|
||||
};
|
||||
|
||||
const hideTasks = () => {
|
||||
hideTaskList( id );
|
||||
};
|
||||
|
||||
const keepTasks = () => {
|
||||
const updateOptionsParams = {
|
||||
woocommerce_task_list_keep_completed: 'yes',
|
||||
};
|
||||
|
||||
updateOptions( {
|
||||
...updateOptionsParams,
|
||||
} );
|
||||
};
|
||||
|
||||
let selectedHeaderCard = visibleTasks.find(
|
||||
( listTask ) => listTask.isComplete === false
|
||||
);
|
||||
|
||||
// If nothing is selected, default to the last task since everything is completed.
|
||||
if ( ! selectedHeaderCard ) {
|
||||
selectedHeaderCard = visibleTasks[ visibleTasks.length - 1 ];
|
||||
}
|
||||
|
||||
const trackClick = ( task: TaskType ) => {
|
||||
recordEvent( `${ eventName }_click`, {
|
||||
task_name: task.id,
|
||||
} );
|
||||
};
|
||||
|
||||
const goToTask = ( task: TaskType ) => {
|
||||
trackClick( task );
|
||||
updateQueryString( { task: task.id } );
|
||||
};
|
||||
|
||||
const onTaskSelected = ( task: TaskType ) => {
|
||||
goToTask( task );
|
||||
};
|
||||
|
||||
const getSectionTasks = ( sectionTaskIds: string[] ) => {
|
||||
return visibleTasks.filter( ( task ) =>
|
||||
sectionTaskIds.includes( task.id )
|
||||
);
|
||||
};
|
||||
|
||||
if ( ! visibleTasks.length ) {
|
||||
return <div className="woocommerce-task-dashboard__container"></div>;
|
||||
}
|
||||
|
||||
if ( isComplete && ! keepCompletedTaskList ) {
|
||||
return (
|
||||
<>
|
||||
<TaskListCompleted
|
||||
hideTasks={ hideTasks }
|
||||
keepTasks={ keepTasks }
|
||||
twoColumns={ false }
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{ displayProgressHeader ? (
|
||||
<ProgressHeader taskListId={ id } />
|
||||
) : null }
|
||||
<div
|
||||
className={ classnames(
|
||||
`woocommerce-task-dashboard__container woocommerce-sectioned-task-list two-column-experiment woocommerce-task-list__${ id }`
|
||||
) }
|
||||
>
|
||||
<Panel>
|
||||
{ ( sections || [] ).map( ( section ) => (
|
||||
<PanelBodyWithUpdatedType
|
||||
key={ section.id }
|
||||
title={
|
||||
<SectionPanelTitle
|
||||
section={ section }
|
||||
tasks={ tasks }
|
||||
active={ openPanel === section.id }
|
||||
/>
|
||||
}
|
||||
opened={ openPanel === section.id }
|
||||
onToggle={ ( isOpen: boolean ) => {
|
||||
if ( ! isOpen && openPanel === section.id ) {
|
||||
setOpenPanel( null );
|
||||
} else {
|
||||
setOpenPanel( section.id );
|
||||
}
|
||||
} }
|
||||
initialOpen={ false }
|
||||
>
|
||||
<PanelRow>
|
||||
<List animation="custom">
|
||||
{ getSectionTasks( section.tasks ).map(
|
||||
( task ) => {
|
||||
const className = classnames(
|
||||
'woocommerce-task-list__item',
|
||||
{
|
||||
'is-complete':
|
||||
task.isComplete,
|
||||
'is-disabled':
|
||||
task.isDisabled,
|
||||
}
|
||||
);
|
||||
return (
|
||||
<TaskItem
|
||||
key={ task.id }
|
||||
className={ className }
|
||||
title={ task.title }
|
||||
completed={
|
||||
task.isComplete
|
||||
}
|
||||
expanded={
|
||||
! task.isComplete
|
||||
}
|
||||
content={ task.content }
|
||||
onClick={ () => {
|
||||
if (
|
||||
! task.isDisabled
|
||||
) {
|
||||
onTaskSelected(
|
||||
task
|
||||
);
|
||||
}
|
||||
} }
|
||||
onDismiss={
|
||||
task.isDismissable
|
||||
? () =>
|
||||
onDismissTask(
|
||||
task.id
|
||||
)
|
||||
: undefined
|
||||
}
|
||||
action={ () => {} }
|
||||
actionLabel={
|
||||
task.actionLabel
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
) }
|
||||
</List>
|
||||
</PanelRow>
|
||||
</PanelBodyWithUpdatedType>
|
||||
) ) }
|
||||
</Panel>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default SectionedTaskList;
|
|
@ -103,7 +103,7 @@
|
|||
margin: 0 auto;
|
||||
justify-content: space-between;
|
||||
|
||||
ul li.complete .woocommerce-task-list__item-title {
|
||||
ul li.is-complete .woocommerce-task-list__item-title {
|
||||
font-weight: 600;
|
||||
color: $gray-600;
|
||||
}
|
||||
|
@ -178,10 +178,13 @@
|
|||
height: 100%;
|
||||
}
|
||||
}
|
||||
.woocommerce-task-list__item:not(.complete) .woocommerce-task__icon {
|
||||
.woocommerce-task-list__item:not(.is-complete) .woocommerce-task__icon {
|
||||
border: 1px solid var(--wp-admin-theme-color);
|
||||
background: transparent;
|
||||
}
|
||||
.woocommerce-task-list__item.is-complete:not(.complete) .woocommerce-task__icon {
|
||||
border: none;
|
||||
}
|
||||
|
||||
.woocommerce-task-list__item-before {
|
||||
display: block;
|
||||
|
@ -203,7 +206,7 @@
|
|||
}
|
||||
|
||||
@for $i from 1 through 10 {
|
||||
.woocommerce-task-list__item:not(.complete).index-#{$i} .woocommerce-task__icon::after {
|
||||
.woocommerce-task-list__item:not(.is-complete).index-#{$i} .woocommerce-task__icon::after {
|
||||
content: '#{$i}';
|
||||
@extend .numbered-circle;
|
||||
color: var(--wp-admin-theme-color);
|
||||
|
|
|
@ -16,6 +16,7 @@ import {
|
|||
ONBOARDING_STORE_NAME,
|
||||
TaskType,
|
||||
useUserPreferences,
|
||||
getVisibleTasks,
|
||||
} from '@woocommerce/data';
|
||||
import { recordEvent } from '@woocommerce/tracks';
|
||||
import { List, TaskItem } from '@woocommerce/experimental';
|
||||
|
@ -63,12 +64,7 @@ export const TaskList: React.FC< TaskListProps > = ( {
|
|||
|
||||
const prevQueryRef = useRef( query );
|
||||
|
||||
const nowTimestamp = Date.now();
|
||||
const visibleTasks = tasks.filter(
|
||||
( task ) =>
|
||||
! task.isDismissed &&
|
||||
( ! task.isSnoozed || task.snoozedUntil < nowTimestamp )
|
||||
);
|
||||
const visibleTasks = getVisibleTasks( tasks );
|
||||
|
||||
const recordTaskListView = () => {
|
||||
if ( query.task ) {
|
||||
|
@ -295,7 +291,7 @@ export const TaskList: React.FC< TaskListProps > = ( {
|
|||
const className = classnames(
|
||||
'woocommerce-task-list__item index-' + index,
|
||||
{
|
||||
complete: task.isComplete,
|
||||
'is-complete': task.isComplete,
|
||||
'is-active': task.id === activeTaskId,
|
||||
}
|
||||
);
|
||||
|
|
Binary file not shown.
After Width: | Height: | Size: 16 KiB |
Binary file not shown.
After Width: | Height: | Size: 16 KiB |
Binary file not shown.
After Width: | Height: | Size: 29 KiB |
|
@ -0,0 +1,4 @@
|
|||
Significance: minor
|
||||
Type: enhancement
|
||||
|
||||
Add new sectioned task list component. #32302
|
|
@ -21,6 +21,7 @@
|
|||
"transient-notices": true,
|
||||
"wc-pay-promotion": true,
|
||||
"wc-pay-welcome-page": true,
|
||||
"tasklist-setup-experiment-1": false
|
||||
"tasklist-setup-experiment-1": false,
|
||||
"tasklist-setup-experiment-2": false
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,6 +21,7 @@
|
|||
"transient-notices": true,
|
||||
"wc-pay-promotion": true,
|
||||
"wc-pay-welcome-page": true,
|
||||
"tasklist-setup-experiment-1": false
|
||||
"tasklist-setup-experiment-1": false,
|
||||
"tasklist-setup-experiment-2": false
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,6 +21,7 @@
|
|||
"transient-notices": true,
|
||||
"wc-pay-promotion": true,
|
||||
"wc-pay-welcome-page": true,
|
||||
"tasklist-setup-experiment-1": false
|
||||
"tasklist-setup-experiment-1": false,
|
||||
"tasklist-setup-experiment-2": false
|
||||
}
|
||||
}
|
||||
|
|
|
@ -403,6 +403,15 @@ abstract class Task {
|
|||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if task is disabled.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function is_disabled() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the task is complete.
|
||||
*
|
||||
|
@ -451,6 +460,7 @@ abstract class Task {
|
|||
'isSnoozed' => $this->is_snoozed(),
|
||||
'isSnoozeable' => $this->is_snoozeable(),
|
||||
'isVisited' => $this->is_visited(),
|
||||
'isDisabled' => $this->is_disabled(),
|
||||
'snoozedUntil' => $this->get_snoozed_until(),
|
||||
'additionalData' => self::convert_object_to_camelcase( $this->get_additional_data() ),
|
||||
'eventPrefix' => $this->prefix_event( '' ),
|
||||
|
|
|
@ -96,6 +96,20 @@ class TaskList {
|
|||
*/
|
||||
public $options = array();
|
||||
|
||||
/**
|
||||
* Array of TaskListSection.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $sections = array();
|
||||
|
||||
/**
|
||||
* Key value map of task class and id used for sections.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public $task_class_id_map = array();
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
|
@ -112,6 +126,7 @@ class TaskList {
|
|||
'options' => array(),
|
||||
'visible' => true,
|
||||
'display_progress_header' => false,
|
||||
'sections' => array(),
|
||||
);
|
||||
|
||||
$data = wp_parse_args( $data, $defaults );
|
||||
|
@ -132,6 +147,12 @@ class TaskList {
|
|||
}
|
||||
|
||||
$this->possibly_remove_reminder_bar();
|
||||
$this->sections = array_map(
|
||||
function( $section ) {
|
||||
return new TaskListSection( $section, $this );
|
||||
},
|
||||
$data['sections']
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -243,7 +264,9 @@ class TaskList {
|
|||
return;
|
||||
}
|
||||
|
||||
$this->tasks[] = $task;
|
||||
$task_class_name = substr( get_class( $task ), strrpos( get_class( $task ), '\\' ) + 1 );
|
||||
$this->task_class_id_map[ $task_class_name ] = $task->get_id();
|
||||
$this->tasks[] = $task;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -279,6 +302,15 @@ class TaskList {
|
|||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get task list sections.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_sections() {
|
||||
return $this->sections;
|
||||
}
|
||||
|
||||
/**
|
||||
* Track list completion of viewable tasks.
|
||||
*/
|
||||
|
@ -377,6 +409,12 @@ class TaskList {
|
|||
'eventPrefix' => $this->prefix_event( '' ),
|
||||
'displayProgressHeader' => $this->display_progress_header,
|
||||
'keepCompletedTaskList' => $this->get_keep_completed_task_list(),
|
||||
'sections' => array_map(
|
||||
function( $section ) {
|
||||
return $section->get_json();
|
||||
},
|
||||
$this->sections
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,122 @@
|
|||
<?php
|
||||
/**
|
||||
* Handles storage and retrieval of a task list section
|
||||
*/
|
||||
|
||||
namespace Automattic\WooCommerce\Admin\Features\OnboardingTasks;
|
||||
|
||||
/**
|
||||
* Task List section class.
|
||||
*/
|
||||
class TaskListSection {
|
||||
|
||||
/**
|
||||
* Title.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $id = '';
|
||||
|
||||
/**
|
||||
* Title.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $title = '';
|
||||
|
||||
/**
|
||||
* Description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $description = '';
|
||||
|
||||
/**
|
||||
* Image.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $image = '';
|
||||
|
||||
/**
|
||||
* Tasks.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public $task_names = array();
|
||||
|
||||
/**
|
||||
* Parent task list.
|
||||
*
|
||||
* @var TaskList
|
||||
*/
|
||||
protected $task_list;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param array $data Task list data.
|
||||
* @param TaskList|null $task_list Parent task list.
|
||||
*/
|
||||
public function __construct( $data = array(), $task_list = null ) {
|
||||
$defaults = array(
|
||||
'id' => '',
|
||||
'title' => '',
|
||||
'description' => '',
|
||||
'image' => '',
|
||||
'tasks' => array(),
|
||||
);
|
||||
|
||||
$data = wp_parse_args( $data, $defaults );
|
||||
|
||||
$this->task_list = $task_list;
|
||||
$this->id = $data['id'];
|
||||
$this->title = $data['title'];
|
||||
$this->description = $data['description'];
|
||||
$this->image = $data['image'];
|
||||
$this->task_names = $data['task_names'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns if section is complete.
|
||||
*
|
||||
* @return boolean;
|
||||
*/
|
||||
private function is_complete() {
|
||||
$complete = true;
|
||||
foreach ( $this->task_names as $task_name ) {
|
||||
if ( null !== $this->task_list && isset( $this->task_list->task_class_id_map[ $task_name ] ) ) {
|
||||
$task = $this->task_list->get_task( $this->task_list->task_class_id_map[ $task_name ] );
|
||||
if ( $task->can_view() && ! $task->is_complete() ) {
|
||||
$complete = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return $complete;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the list for use in JSON.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_json() {
|
||||
return array(
|
||||
'id' => $this->id,
|
||||
'title' => $this->title,
|
||||
'description' => $this->description,
|
||||
'image' => $this->image,
|
||||
'tasks' => array_map(
|
||||
function( $task_name ) {
|
||||
if ( null !== $this->task_list && isset( $this->task_list->task_class_id_map[ $task_name ] ) ) {
|
||||
return $this->task_list->task_class_id_map[ $task_name ];
|
||||
}
|
||||
return '';
|
||||
},
|
||||
$this->task_names
|
||||
),
|
||||
'isComplete' => $this->is_complete(),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -93,7 +93,7 @@ class TaskLists {
|
|||
'Appearance',
|
||||
),
|
||||
'event_prefix' => 'tasklist_',
|
||||
'visible' => ! Features::is_enabled( 'tasklist-setup-experiment-1' ),
|
||||
'visible' => ! Features::is_enabled( 'tasklist-setup-experiment-1' ) && ! Features::is_enabled( 'tasklist-setup-experiment-2' ),
|
||||
)
|
||||
);
|
||||
|
||||
|
@ -121,6 +121,63 @@ class TaskLists {
|
|||
)
|
||||
);
|
||||
|
||||
self::add_list(
|
||||
array(
|
||||
'id' => 'setup_experiment_2',
|
||||
'hidden_id' => 'setup',
|
||||
'title' => __( 'Get ready to start selling', 'woocommerce' ),
|
||||
'tasks' => array(
|
||||
'StoreCreation',
|
||||
'StoreDetails',
|
||||
'Products',
|
||||
'WooCommercePayments',
|
||||
'Payments',
|
||||
'Tax',
|
||||
'Shipping',
|
||||
'Marketing',
|
||||
'Appearance',
|
||||
),
|
||||
'event_prefix' => 'tasklist_',
|
||||
'visible' => Features::is_enabled( 'tasklist-setup-experiment-2' ),
|
||||
'options' => array(
|
||||
'use_completed_title' => true,
|
||||
),
|
||||
'display_progress_header' => true,
|
||||
'sections' => array(
|
||||
array(
|
||||
'id' => 'basics',
|
||||
'title' => __( 'Cover the basics', 'woocommerce' ),
|
||||
'description' => __( 'Make sure you’ve got everything you need to start selling—from business details to products.', 'woocommerce' ),
|
||||
'image' => plugins_url(
|
||||
'/assets/images/task_list/basics-section-illustration.png',
|
||||
WC_ADMIN_PLUGIN_FILE
|
||||
),
|
||||
'task_names' => array( 'StoreCreation', 'StoreDetails', 'Products', 'Payments', 'WooCommercePayments' ),
|
||||
),
|
||||
array(
|
||||
'id' => 'sales',
|
||||
'title' => __( 'Get ready to sell', 'woocommerce' ),
|
||||
'description' => __( 'Easily set up the backbone of your store’s operations and get ready to accept first orders.', 'woocommerce' ),
|
||||
'image' => plugins_url(
|
||||
'/assets/images/task_list/sales-section-illustration.png',
|
||||
WC_ADMIN_PLUGIN_FILE
|
||||
),
|
||||
'task_names' => array( 'Shipping', 'Tax' ),
|
||||
),
|
||||
array(
|
||||
'id' => 'expand',
|
||||
'title' => __( 'Customize & expand', 'woocommerce' ),
|
||||
'description' => __( 'Personalize your store’s design and grow your business by enabling new sales channels.', 'woocommerce' ),
|
||||
'image' => plugins_url(
|
||||
'/assets/images/task_list/expand-section-illustration.png',
|
||||
WC_ADMIN_PLUGIN_FILE
|
||||
),
|
||||
'task_names' => array( 'Appearance', 'Marketing' ),
|
||||
),
|
||||
),
|
||||
)
|
||||
);
|
||||
|
||||
self::add_list(
|
||||
array(
|
||||
'id' => 'extended',
|
||||
|
|
|
@ -39,6 +39,9 @@ class Appearance extends Task {
|
|||
* @return string
|
||||
*/
|
||||
public function get_title() {
|
||||
if ( count( $this->task_list->get_sections() ) > 0 && ! $this->is_complete() ) {
|
||||
return __( 'Make your store stand out with unique design', 'woocommerce' );
|
||||
}
|
||||
if ( true === $this->get_parent_option( 'use_completed_title' ) ) {
|
||||
if ( $this->is_complete() ) {
|
||||
return __( 'You personalized your store', 'woocommerce' );
|
||||
|
@ -54,6 +57,9 @@ class Appearance extends Task {
|
|||
* @return string
|
||||
*/
|
||||
public function get_content() {
|
||||
if ( count( $this->task_list->get_sections() ) > 0 ) {
|
||||
return __( 'Upload your logo to adapt the store to your brand’s personality.', 'woocommerce' );
|
||||
}
|
||||
return __(
|
||||
'Add your logo, create a homepage, and start designing your store.',
|
||||
'woocommerce'
|
||||
|
|
|
@ -25,6 +25,9 @@ class Marketing extends Task {
|
|||
* @return string
|
||||
*/
|
||||
public function get_title() {
|
||||
if ( count( $this->task_list->get_sections() ) > 0 && ! $this->is_complete() ) {
|
||||
return __( 'Grow your business with marketing tools', 'woocommerce' );
|
||||
}
|
||||
if ( true === $this->get_parent_option( 'use_completed_title' ) ) {
|
||||
if ( $this->is_complete() ) {
|
||||
return __( 'You added sales channels', 'woocommerce' );
|
||||
|
@ -40,6 +43,9 @@ class Marketing extends Task {
|
|||
* @return string
|
||||
*/
|
||||
public function get_content() {
|
||||
if ( count( $this->task_list->get_sections() ) > 0 ) {
|
||||
return __( 'Promote your store in other sales channels, like email, Google, and Facebook.', 'woocommerce' );
|
||||
}
|
||||
return __(
|
||||
'Add recommended marketing tools to reach new customers and grow your business',
|
||||
'woocommerce'
|
||||
|
|
|
@ -25,6 +25,9 @@ class Payments extends Task {
|
|||
* @return string
|
||||
*/
|
||||
public function get_title() {
|
||||
if ( count( $this->task_list->get_sections() ) > 0 && ! $this->is_complete() ) {
|
||||
return __( 'Add a way to get paid', 'woocommerce' );
|
||||
}
|
||||
if ( true === $this->get_parent_option( 'use_completed_title' ) ) {
|
||||
if ( $this->is_complete() ) {
|
||||
return __( 'You set up payments', 'woocommerce' );
|
||||
|
@ -40,6 +43,9 @@ class Payments extends Task {
|
|||
* @return string
|
||||
*/
|
||||
public function get_content() {
|
||||
if ( count( $this->task_list->get_sections() ) > 0 ) {
|
||||
return __( 'Let your customers pay the way they like.', 'woocommerce' );
|
||||
}
|
||||
return __(
|
||||
'Choose payment providers and enable payment methods at checkout.',
|
||||
'woocommerce'
|
||||
|
|
|
@ -36,6 +36,9 @@ class Products extends Task {
|
|||
* @return string
|
||||
*/
|
||||
public function get_title() {
|
||||
if ( count( $this->task_list->get_sections() ) > 0 && ! $this->is_complete() ) {
|
||||
return __( 'Create or upload your first products', 'woocommerce' );
|
||||
}
|
||||
if ( true === $this->get_parent_option( 'use_completed_title' ) ) {
|
||||
if ( $this->is_complete() ) {
|
||||
return __( 'You added products', 'woocommerce' );
|
||||
|
@ -51,6 +54,9 @@ class Products extends Task {
|
|||
* @return string
|
||||
*/
|
||||
public function get_content() {
|
||||
if ( count( $this->task_list->get_sections() ) > 0 ) {
|
||||
return __( 'Add products to sell and build your catalog.', 'woocommerce' );
|
||||
}
|
||||
return __(
|
||||
'Start by adding the first product to your store. You can add your products manually, via CSV, or import them from another service.',
|
||||
'woocommerce'
|
||||
|
|
|
@ -24,6 +24,9 @@ class Shipping extends Task {
|
|||
* @return string
|
||||
*/
|
||||
public function get_title() {
|
||||
if ( count( $this->task_list->get_sections() ) > 0 && ! $this->is_complete() ) {
|
||||
return __( 'Select how to ship your products', 'woocommerce' );
|
||||
}
|
||||
if ( true === $this->get_parent_option( 'use_completed_title' ) ) {
|
||||
if ( $this->is_complete() ) {
|
||||
return __( 'You added shipping costs', 'woocommerce' );
|
||||
|
@ -39,6 +42,9 @@ class Shipping extends Task {
|
|||
* @return string
|
||||
*/
|
||||
public function get_content() {
|
||||
if ( count( $this->task_list->get_sections() ) > 0 ) {
|
||||
return __( 'Set delivery costs and enable extra features, like shipping label printing.', 'woocommerce' );
|
||||
}
|
||||
return __(
|
||||
"Set your store location and where you'll ship to.",
|
||||
'woocommerce'
|
||||
|
|
|
@ -0,0 +1,77 @@
|
|||
<?php
|
||||
|
||||
namespace Automattic\WooCommerce\Admin\Features\OnboardingTasks\Tasks;
|
||||
|
||||
use Automattic\WooCommerce\Admin\Features\Onboarding;
|
||||
use Automattic\WooCommerce\Admin\Features\OnboardingTasks\Task;
|
||||
|
||||
/**
|
||||
* Store Details Task
|
||||
*/
|
||||
class StoreCreation extends Task {
|
||||
|
||||
/**
|
||||
* ID.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_id() {
|
||||
return 'store_creation';
|
||||
}
|
||||
|
||||
/**
|
||||
* Title.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_title() {
|
||||
/* translators: Store name */
|
||||
return sprintf( __( 'You created %s', 'woocommerce' ), get_bloginfo( 'name' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Content.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_content() {
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Time.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_time() {
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Time.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_action_url() {
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Task completion.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function is_complete() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if task is disabled.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function is_disabled() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
@ -65,6 +65,9 @@ class Tax extends Task {
|
|||
* @return string
|
||||
*/
|
||||
public function get_title() {
|
||||
if ( count( $this->task_list->get_sections() ) > 0 && ! $this->is_complete() ) {
|
||||
return __( 'Get taxes out of your mind', 'woocommerce' );
|
||||
}
|
||||
if ( true === $this->get_parent_option( 'use_completed_title' ) ) {
|
||||
if ( $this->is_complete() ) {
|
||||
return __( 'You added tax rates', 'woocommerce' );
|
||||
|
@ -80,6 +83,9 @@ class Tax extends Task {
|
|||
* @return string
|
||||
*/
|
||||
public function get_content() {
|
||||
if ( count( $this->task_list->get_sections() ) > 0 ) {
|
||||
return __( 'Have sales tax calculated automatically, or add the rates manually.', 'woocommerce' );
|
||||
}
|
||||
return self::can_use_automated_taxes()
|
||||
? __(
|
||||
'Good news! WooCommerce Services and Jetpack can automate your sales tax calculations for you.',
|
||||
|
|
Loading…
Reference in New Issue