From c281ddb820558e55ad2c6fad0aac69521387ed79 Mon Sep 17 00:00:00 2001 From: Lourens Schep Date: Mon, 21 Mar 2022 10:52:04 -0300 Subject: [PATCH] Add progress header bar to task list experiment --- .../js/data/src/onboarding/action-types.js | 1 + packages/js/data/src/onboarding/actions.js | 7 ++ packages/js/data/src/onboarding/reducer.js | 8 ++ packages/js/data/src/onboarding/types.ts | 4 +- .../task-lists/progress-header/index.ts | 1 + .../progress-header/progress-header.scss | 53 +++++++++ .../progress-header/progress-header.tsx | 105 ++++++++++++++++++ .../client/tasks/task-list-item.tsx | 2 + .../client/tasks/task-list-menu.tsx | 9 +- .../client/tasks/task-list.tsx | 5 + .../woocommerce-admin/client/tasks/tasks.tsx | 2 + .../client/two-column-tasks/task-list.tsx | 7 +- .../Admin/Features/OnboardingTasks/Task.php | 15 +++ .../Features/OnboardingTasks/TaskList.php | 54 +++++---- .../Features/OnboardingTasks/TaskLists.php | 15 +-- 15 files changed, 255 insertions(+), 33 deletions(-) create mode 100644 plugins/woocommerce-admin/client/task-lists/progress-header/index.ts create mode 100644 plugins/woocommerce-admin/client/task-lists/progress-header/progress-header.scss create mode 100644 plugins/woocommerce-admin/client/task-lists/progress-header/progress-header.tsx diff --git a/packages/js/data/src/onboarding/action-types.js b/packages/js/data/src/onboarding/action-types.js index cb3363686e1..b492840fd6b 100644 --- a/packages/js/data/src/onboarding/action-types.js +++ b/packages/js/data/src/onboarding/action-types.js @@ -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; diff --git a/packages/js/data/src/onboarding/actions.js b/packages/js/data/src/onboarding/actions.js index 271c76674a1..5ee1161493e 100644 --- a/packages/js/data/src/onboarding/actions.js +++ b/packages/js/data/src/onboarding/actions.js @@ -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, diff --git a/packages/js/data/src/onboarding/reducer.js b/packages/js/data/src/onboarding/reducer.js index fbe05845ca9..499b890dda1 100644 --- a/packages/js/data/src/onboarding/reducer.js +++ b/packages/js/data/src/onboarding/reducer.js @@ -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, diff --git a/packages/js/data/src/onboarding/types.ts b/packages/js/data/src/onboarding/types.ts index 1a359d85735..f60f284a659 100644 --- a/packages/js/data/src/onboarding/types.ts +++ b/packages/js/data/src/onboarding/types.ts @@ -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; }; diff --git a/plugins/woocommerce-admin/client/task-lists/progress-header/index.ts b/plugins/woocommerce-admin/client/task-lists/progress-header/index.ts new file mode 100644 index 00000000000..5839d2e8937 --- /dev/null +++ b/plugins/woocommerce-admin/client/task-lists/progress-header/index.ts @@ -0,0 +1 @@ +export * from './progress-header'; diff --git a/plugins/woocommerce-admin/client/task-lists/progress-header/progress-header.scss b/plugins/woocommerce-admin/client/task-lists/progress-header/progress-header.scss new file mode 100644 index 00000000000..e51ab9b22a7 --- /dev/null +++ b/plugins/woocommerce-admin/client/task-lists/progress-header/progress-header.scss @@ -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; + } +} diff --git a/plugins/woocommerce-admin/client/task-lists/progress-header/progress-header.tsx b/plugins/woocommerce-admin/client/task-lists/progress-header/progress-header.tsx new file mode 100644 index 00000000000..5bfbe2f35f7 --- /dev/null +++ b/plugins/woocommerce-admin/client/task-lists/progress-header/progress-header.tsx @@ -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 ( +
+ +
+

+ { progressTitle } +

+

+ { 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 + ) } +

+ +
+
+ ); +}; diff --git a/plugins/woocommerce-admin/client/tasks/task-list-item.tsx b/plugins/woocommerce-admin/client/tasks/task-list-item.tsx index 2349327c194..b2dbee6fd0d 100644 --- a/plugins/woocommerce-admin/client/tasks/task-list-item.tsx +++ b/plugins/woocommerce-admin/client/tasks/task-list-item.tsx @@ -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 || {} ), diff --git a/plugins/woocommerce-admin/client/tasks/task-list-menu.tsx b/plugins/woocommerce-admin/client/tasks/task-list-menu.tsx index 214a18c97af..ea7b76a1389 100644 --- a/plugins/woocommerce-admin/client/tasks/task-list-menu.tsx +++ b/plugins/woocommerce-admin/client/tasks/task-list-menu.tsx @@ -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={ () => (
) } diff --git a/plugins/woocommerce-admin/client/tasks/task-list.tsx b/plugins/woocommerce-admin/client/tasks/task-list.tsx index 165095f35bd..8f1db159593 100644 --- a/plugins/woocommerce-admin/client/tasks/task-list.tsx +++ b/plugins/woocommerce-admin/client/tasks/task-list.tsx @@ -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 ? ( + + ) : null } = ( { 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 } /> { isToggleable && ( diff --git a/plugins/woocommerce-admin/client/two-column-tasks/task-list.tsx b/plugins/woocommerce-admin/client/two-column-tasks/task-list.tsx index 670bcd15071..21bef6d0ebb 100644 --- a/plugins/woocommerce-admin/client/two-column-tasks/task-list.tsx +++ b/plugins/woocommerce-admin/client/two-column-tasks/task-list.tsx @@ -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 ? ( + + ) : null }
= ( { headerData ) }
- { renderMenu() } + { ! displayProgressHeader && renderMenu() } { visibleTasks.map( ( task, index ) => { diff --git a/plugins/woocommerce/src/Admin/Features/OnboardingTasks/Task.php b/plugins/woocommerce/src/Admin/Features/OnboardingTasks/Task.php index 0f8a10c7908..09792c07076 100644 --- a/plugins/woocommerce/src/Admin/Features/OnboardingTasks/Task.php +++ b/plugins/woocommerce/src/Admin/Features/OnboardingTasks/Task.php @@ -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( '' ), diff --git a/plugins/woocommerce/src/Admin/Features/OnboardingTasks/TaskList.php b/plugins/woocommerce/src/Admin/Features/OnboardingTasks/TaskList.php index 0dc497ec541..f2cf197a77d 100644 --- a/plugins/woocommerce/src/Admin/Features/OnboardingTasks/TaskList.php +++ b/plugins/woocommerce/src/Admin/Features/OnboardingTasks/TaskList.php @@ -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, ); } } diff --git a/plugins/woocommerce/src/Admin/Features/OnboardingTasks/TaskLists.php b/plugins/woocommerce/src/Admin/Features/OnboardingTasks/TaskLists.php index d46e53359ec..fcd4a8ebd6b 100644 --- a/plugins/woocommerce/src/Admin/Features/OnboardingTasks/TaskLists.php +++ b/plugins/woocommerce/src/Admin/Features/OnboardingTasks/TaskLists.php @@ -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' ), ) );