Add progress header bar to task list experiment

This commit is contained in:
Lourens Schep 2022-03-21 10:52:04 -03:00
parent 9056be919d
commit c281ddb820
15 changed files with 255 additions and 33 deletions

View File

@ -33,6 +33,7 @@ const TYPES = {
ACTION_TASK_ERROR: 'ACTION_TASK_ERROR',
ACTION_TASK_REQUEST: 'ACTION_TASK_REQUEST',
ACTION_TASK_SUCCESS: 'ACTION_TASK_SUCCESS',
VISITED_TASK: 'VISITED_TASK',
};
export default TYPES;

View File

@ -201,6 +201,13 @@ export function optimisticallyCompleteTaskRequest( taskId ) {
};
}
export function visitedTask( taskId ) {
return {
type: TYPES.VISITED_TASK,
taskId,
};
}
export function setPaymentMethods( paymentMethods ) {
return {
type: TYPES.GET_PAYMENT_METHODS_SUCCESS,

View File

@ -380,6 +380,14 @@ const onboarding = (
isComplete: true,
} ),
};
case TYPES.VISITED_TASK:
return {
...state,
taskLists: getUpdatedTaskLists( state.taskLists, {
id: taskId,
isVisited: true,
} ),
};
case TYPES.ACTION_TASK_ERROR:
return {
...state,

View File

@ -6,8 +6,9 @@ export type TaskType = {
isComplete: boolean;
isDismissable: boolean;
isDismissed: boolean;
isVisible: boolean;
isSnoozed: boolean;
isVisible: boolean;
isVisited: boolean;
isSnoozable: boolean;
snoozedUntil: number;
time: string;
@ -24,4 +25,5 @@ export type TaskListType = {
tasks: TaskType[];
title: string;
eventPrefix: string;
displayProgressHeader: boolean;
};

View File

@ -0,0 +1 @@
export * from './progress-header';

View File

@ -0,0 +1,53 @@
$progress-complete-color: #007cba;
.woocommerce-task-progress-header {
position: relative;
h1 {
font-size: 24px;
}
p {
color: $gray-700;
font-size: 16px;
line-height: 24px;
margin-top: $gap-smaller;
}
.woocommerce-task-progress-header__progress-bar {
-webkit-appearance: none;
-moz-appearance: none;
appearance: none;
border: 1px solid #ddd;
border-radius: 16px;
height: 10px;
width: 100%;
margin-bottom: 20px;
// Firefox
& {
background-color: #fff;
}
&::-moz-progress-bar {
background-color: $progress-complete-color;
border-radius: 16px;
}
// Chrome
&::-webkit-progress-bar {
background-color: #fff;
border-radius: 16px;
}
&::-webkit-progress-value {
background-color: $progress-complete-color;
border-bottom-left-radius: 16px;
border-top-left-radius: 16px;
}
}
.woocommerce-card__menu {
position: absolute;
right: 0;
}
}

View File

@ -0,0 +1,105 @@
/**
* External dependencies
*/
import { __, sprintf } from '@wordpress/i18n';
import { useMemo } from '@wordpress/element';
import { useSelect } from '@wordpress/data';
import { ONBOARDING_STORE_NAME, TaskListType } from '@woocommerce/data';
import { getSetting } from '@woocommerce/settings';
/**
* Internal dependencies
*/
import './progress-header.scss';
import { TaskListMenu } from '~/tasks/task-list-menu';
type ProgressHeaderProps = {
taskListId: string;
};
export const ProgressHeader: React.FC< ProgressHeaderProps > = ( {
taskListId,
} ) => {
const { loading, tasksCount, completedCount, hasVisitedTasks } = useSelect(
( select ) => {
const isResolving = select( ONBOARDING_STORE_NAME ).isResolving(
'getTaskList'
);
const taskList: TaskListType = select(
ONBOARDING_STORE_NAME
).getTaskList( taskListId );
const nowTimestamp = Date.now();
const visibleTasks = taskList?.tasks.filter(
( task ) =>
! task.isDismissed &&
( ! task.isSnoozed || task.snoozedUntil < nowTimestamp )
);
return {
loading: isResolving,
tasksCount: visibleTasks?.length,
completedCount: visibleTasks?.filter(
( task ) => task.isComplete
).length,
hasVisitedTasks:
visibleTasks?.filter( ( task ) => task.isVisited ).length >
0,
};
}
);
const progressTitle = useMemo( () => {
if ( ! hasVisitedTasks || completedCount === tasksCount ) {
const siteTitle = getSetting( 'siteTitle' );
return siteTitle
? sprintf(
/* translators: %s = site title */
__( 'Welcome to %s', 'woocommerce-admin' ),
siteTitle
)
: __( 'Welcome', 'woocommerce-admin' );
} else if ( completedCount > 0 && completedCount < 3 ) {
return __( "Let's get you set up", 'woocommerce-admin' ) + ' 🚀';
} else if ( completedCount > 2 && completedCount < 5 ) {
return __( 'You are on the right track', 'woocommerce-admin' );
}
return __( 'Just a few tasks left', 'woocommerce-admin' );
}, [ completedCount, hasVisitedTasks ] );
if ( loading || completedCount === tasksCount ) {
return null;
}
return (
<div className="woocommerce-task-progress-header">
<TaskListMenu
id={ taskListId }
hideTaskListText={ __(
'Hide setup list',
'woocommerce-admin'
) }
/>
<div className="woocommerce-task-progress-header__contents">
<h1 className="woocommerce-task-progress-header__title">
{ progressTitle }
</h1>
<p>
{ sprintf(
/* translators: 1: completed tasks, 2: total tasks */
__(
'Follow these steps to start selling quickly. %1$d out of %2$d complete.',
'woocommerce-admin'
),
completedCount,
tasksCount
) }
</p>
<progress
className="woocommerce-task-progress-header__progress-bar"
max={ tasksCount }
value={ completedCount || 0 }
/>
</div>
</div>
);
};

View File

@ -43,6 +43,7 @@ export const TaskListItem: React.FC< TaskListItemProps > = ( {
snoozeTask,
undoDismissTask,
undoSnoozeTask,
visitedTask,
} = useDispatch( ONBOARDING_STORE_NAME );
const userPreferences = useUserPreferences();
@ -106,6 +107,7 @@ export const TaskListItem: React.FC< TaskListItemProps > = ( {
const trackedStartedTasks =
userPreferences.task_list_tracked_started_tasks || {};
visitedTask( id );
userPreferences.updateUserPreferences( {
task_list_tracked_started_tasks: {
...( trackedStartedTasks || {} ),

View File

@ -9,9 +9,13 @@ import { useDispatch } from '@wordpress/data';
export type TaskListMenuProps = {
id: string;
hideTaskListText?: string;
};
export const TaskListMenu: React.FC< TaskListMenuProps > = ( { id } ) => {
export const TaskListMenu: React.FC< TaskListMenuProps > = ( {
id,
hideTaskListText,
} ) => {
const { hideTaskList } = useDispatch( ONBOARDING_STORE_NAME );
return (
@ -21,7 +25,8 @@ export const TaskListMenu: React.FC< TaskListMenuProps > = ( { id } ) => {
renderContent={ () => (
<div className="woocommerce-task-card__section-controls">
<Button onClick={ () => hideTaskList( id ) }>
{ __( 'Hide this', 'woocommerce-admin' ) }
{ hideTaskListText ||
__( 'Hide this', 'woocommerce-admin' ) }
</Button>
</div>
) }

View File

@ -16,6 +16,7 @@ import { Text, List, CollapsibleList } from '@woocommerce/experimental';
import { TaskListItem } from './task-list-item';
import { TaskListMenu } from './task-list-menu';
import './task-list.scss';
import { ProgressHeader } from '~/task-lists/progress-header';
export type TaskListProps = TaskListType & {
query: {
@ -33,6 +34,7 @@ export const TaskList: React.FC< TaskListProps > = ( {
title: listTitle,
isCollapsible = false,
isExpandable = false,
displayProgressHeader = false,
query,
} ) => {
const { profileItems } = useSelect( ( select ) => {
@ -114,6 +116,9 @@ export const TaskList: React.FC< TaskListProps > = ( {
id
}
>
{ displayProgressHeader ? (
<ProgressHeader taskListId={ id } />
) : null }
<Card
size="large"
className="woocommerce-task-card woocommerce-homescreen-card"

View File

@ -140,6 +140,7 @@ export const Tasks: React.FC< TasksProps > = ( { query } ) => {
isToggleable,
title,
tasks,
displayProgressHeader,
} = taskList;
if ( ! isVisible ) {
@ -163,6 +164,7 @@ export const Tasks: React.FC< TasksProps > = ( { query } ) => {
tasks={ tasks }
title={ title }
twoColumns={ false }
displayProgressHeader={ displayProgressHeader }
/>
</Suspense>
{ isToggleable && (

View File

@ -24,6 +24,7 @@ import taskHeaders from './task-headers';
import DismissModal from './dismiss-modal';
import TaskListCompleted from './completed';
import { TaskListProps } from '~/tasks/task-list';
import { ProgressHeader } from '~/task-lists/progress-header';
export const TaskList: React.FC< TaskListProps > = ( {
query,
@ -33,6 +34,7 @@ export const TaskList: React.FC< TaskListProps > = ( {
twoColumns,
keepCompletedTaskList,
isComplete,
displayProgressHeader,
} ) => {
const { createNotice } = useDispatch( 'core/notices' );
const { updateOptions, dismissTask, undoDismissTask } = useDispatch(
@ -223,6 +225,9 @@ export const TaskList: React.FC< TaskListProps > = ( {
hideTasks={ hideTasks }
/>
) }
{ displayProgressHeader ? (
<ProgressHeader taskListId={ id } />
) : null }
<div
className={ classnames(
`woocommerce-task-dashboard__container two-column-experiment woocommerce-task-list__${ id }`,
@ -241,7 +246,7 @@ export const TaskList: React.FC< TaskListProps > = ( {
headerData
) }
</div>
{ renderMenu() }
{ ! displayProgressHeader && renderMenu() }
</div>
<List animation="custom">
{ visibleTasks.map( ( task, index ) => {

View File

@ -5,6 +5,8 @@
namespace Automattic\WooCommerce\Admin\Features\OnboardingTasks;
use Automattic\WooCommerce\Internal\Admin\WCAdminUser;
/**
* Task class.
*/
@ -414,6 +416,18 @@ abstract class Task {
return self::is_actioned();
}
/**
* Check if the task has been visited.
*
* @return bool
*/
public function is_visited() {
$user_id = get_current_user_id();
$response = WCAdminUser::get_user_data_field( $user_id, 'task_list_tracked_started_tasks' );
$tracked_tasks = $response ? json_decode( $response, true ) : array();
return isset( $tracked_tasks[ $this->get_id() ] ) && $tracked_tasks[ $this->get_id() ] > 0;
}
/**
* Get the task as JSON.
@ -440,6 +454,7 @@ abstract class Task {
'isDismissable' => $this->is_dismissable(),
'isSnoozed' => $this->is_snoozed(),
'isSnoozeable' => $this->is_snoozeable(),
'isVisited' => $this->is_visited(),
'snoozedUntil' => $this->get_snoozed_until(),
'additionalData' => self::convert_object_to_camelcase( $this->get_additional_data() ),
'eventPrefix' => $this->prefix_event( '' ),

View File

@ -47,6 +47,13 @@ class TaskList {
*/
public $hidden_id = '';
/**
* ID.
*
* @var boolean
*/
public $display_progress_header = false;
/**
* Title.
*
@ -96,25 +103,27 @@ class TaskList {
*/
public function __construct( $data = array() ) {
$defaults = array(
'id' => null,
'hidden_id' => null,
'title' => '',
'tasks' => array(),
'sort_by' => array(),
'event_prefix' => null,
'options' => array(),
'visible' => true,
'id' => null,
'hidden_id' => null,
'title' => '',
'tasks' => array(),
'sort_by' => array(),
'event_prefix' => null,
'options' => array(),
'visible' => true,
'display_progress_header' => false,
);
$data = wp_parse_args( $data, $defaults );
$this->id = $data['id'];
$this->hidden_id = $data['hidden_id'];
$this->title = $data['title'];
$this->sort_by = $data['sort_by'];
$this->event_prefix = $data['event_prefix'];
$this->options = $data['options'];
$this->visible = $data['visible'];
$this->id = $data['id'];
$this->hidden_id = $data['hidden_id'];
$this->title = $data['title'];
$this->sort_by = $data['sort_by'];
$this->event_prefix = $data['event_prefix'];
$this->options = $data['options'];
$this->visible = $data['visible'];
$this->display_progress_header = $data['display_progress_header'];
foreach ( $data['tasks'] as $task_name ) {
$class = 'Automattic\WooCommerce\Admin\Features\OnboardingTasks\Tasks\\' . $task_name;
@ -342,18 +351,19 @@ class TaskList {
public function get_json() {
$this->possibly_track_completion();
return array(
'id' => $this->get_list_id(),
'title' => $this->title,
'isHidden' => $this->is_hidden(),
'isVisible' => $this->is_visible(),
'isComplete' => $this->is_complete(),
'tasks' => array_map(
'id' => $this->get_list_id(),
'title' => $this->title,
'isHidden' => $this->is_hidden(),
'isVisible' => $this->is_visible(),
'isComplete' => $this->is_complete(),
'tasks' => array_map(
function( $task ) {
return $task->get_json();
},
$this->get_viewable_tasks()
),
'eventPrefix' => $this->prefix_event( '' ),
'eventPrefix' => $this->prefix_event( '' ),
'displayProgressHeader' => $this->display_progress_header,
);
}
}

View File

@ -98,10 +98,10 @@ class TaskLists {
self::add_list(
array(
'id' => 'setup_experiment_1',
'hidden_id' => 'setup',
'title' => __( 'Get ready to start selling', 'woocommerce-admin' ),
'tasks' => array(
'id' => 'setup_experiment_1',
'hidden_id' => 'setup',
'title' => __( 'Get ready to start selling', 'woocommerce-admin' ),
'tasks' => array(
'StoreDetails',
'Products',
'WooCommercePayments',
@ -111,11 +111,12 @@ class TaskLists {
'Marketing',
'Appearance',
),
'event_prefix' => 'tasklist_',
'options' => array(
'display_progress_header' => true,
'event_prefix' => 'tasklist_',
'options' => array(
'use_completed_title' => true,
),
'visible' => Features::is_enabled( 'tasklist-setup-experiment-1' ),
'visible' => Features::is_enabled( 'tasklist-setup-experiment-1' ),
)
);