* Update task list to closer match new designs.

* Update TaskList placeholder to match the new design.

* Hook up store details task click.

* Don't show "skip" prompt for task list on new home screen.

* Update time estimates for tasks.

* Add progress indicator to TaskList.

* Use null container for store details task.

* Fix progress bar styling.

* Just use card menu for TaskList dismissal.

* Don't show TaskList on analytics overview if homepage feature is enabled.

* Initial refactor of TaskList to use WP card.

* Style TaskList items and card header.

* Style "hide" button for TaskList.

* Don't show TaskList after it's completion.

* Don't show TaskList placeholder when options are still loading.

Avoid a potential flash of the placeholder without showing the TaskList.

* Fix border radius on progress bar.

* Fix TaskList header style with homepage feature disabled.

* Remove defunct function.

* Remove unnecessary import.

* Some minor color tweaks (https://github.com/woocommerce/woocommerce-admin/pull/4436)

* Colors

* Only show time estimation for incomplete tasks

* Don't show chevron on completed tasks.

Co-authored-by: Jeff Stieler <jeff.m.stieler@gmail.com>

* Preload task list completed option, show placeholder when loading.

* Fix alignment of "hide this" button.

Co-authored-by: James Koster <james@jameskoster.co.uk>
This commit is contained in:
Jeff Stieler 2020-05-27 10:08:39 -06:00 committed by GitHub
parent 53dde217f6
commit cd146b388e
9 changed files with 272 additions and 236 deletions

View File

@ -302,7 +302,10 @@ class CustomizableDashboard extends Component {
render() {
const { query, taskListHidden, taskListComplete } = this.props;
const isTaskListEnabled = isOnboardingEnabled() && ! taskListHidden;
const isTaskListEnabled =
isOnboardingEnabled() &&
! taskListHidden &&
! window.wcAdminFeatures.homepage;
const isDashboardShown =
! isTaskListEnabled || ( ! query.task && taskListComplete );
@ -311,7 +314,7 @@ class CustomizableDashboard extends Component {
<Fragment>
{ isTaskListEnabled && (
<Suspense fallback={ <Spinner /> }>
<TaskList query={ query } inline={ isDashboardShown } />
<TaskList query={ query } />
</Suspense>
) }
{ isDashboardShown && this.renderDashboardReports() }

View File

@ -77,3 +77,17 @@
padding-bottom: 10px;
}
}
.woocommerce-task-dashboard__body {
.woocommerce-task-dashboard__container {
.woocommerce-task-card {
.components-card__header.is-size-large {
padding-bottom: 12px;
.woocommerce-card__menu {
margin-top: 8px;
}
}
}
}
}

View File

@ -15,11 +15,6 @@ import classnames from 'classnames';
import { get } from 'lodash';
import PropTypes from 'prop-types';
/**
* WooCommerce dependencies
*/
import { Spinner } from '@woocommerce/components';
/**
* Internal dependencies
*/
@ -57,8 +52,8 @@ export const Layout = ( props ) => {
};
}, [] );
const { query, requestingTaskList, taskListHidden } = props;
const isTaskListEnabled = taskListHidden === false;
const { query, requestingTaskList, taskListComplete, taskListHidden } = props;
const isTaskListEnabled = taskListHidden === false && ! taskListComplete;
const isDashboardShown = ! isTaskListEnabled || ! query.task;
const renderColumns = () => {
@ -105,11 +100,8 @@ export const Layout = ( props ) => {
}
return (
<Suspense fallback={ <Spinner /> }>
<TaskList
query={ query }
inline
/>
<Suspense fallback={ <TaskListPlaceholder /> }>
<TaskList query={ query } />
</Suspense>
);
};
@ -133,6 +125,10 @@ Layout.propTypes = {
* If the task list option is being fetched.
*/
requestingTaskList: PropTypes.bool.isRequired,
/**
* If the task list has been completed.
*/
taskListComplete: PropTypes.bool,
/**
* If the task list is hidden.
*/
@ -152,13 +148,16 @@ export default compose(
if ( isOnboardingEnabled() ) {
const options = getOptions( [
'woocommerce_task_list_complete',
'woocommerce_task_list_hidden',
] );
return {
requestingTaskList: isGetOptionsRequesting( [
'woocommerce_task_list_complete',
'woocommerce_task_list_hidden',
] ),
taskListComplete: get( options, [ 'woocommerce_task_list_complete' ] ),
taskListHidden: get( options, [ 'woocommerce_task_list_hidden' ] ) === 'yes',
};
}

View File

@ -61,4 +61,32 @@ describe( 'Homepage Layout', () => {
const taskList = await screen.findByText( '[TaskList]' )
expect( taskList ).toBeDefined();
} );
it( 'should not show TaskList when user has hidden', () => {
render(
<Layout
requestingTaskList={ false }
taskListComplete={ false }
taskListHidden
query={ {} }
/>
);
const taskList = screen.queryByText( '[TaskList]' )
expect( taskList ).toBeNull();
} );
it( 'should not show TaskList when it is complete', () => {
render(
<Layout
requestingTaskList={ false }
taskListComplete
taskListHidden={ false }
query={ {} }
/>
);
const taskList = screen.queryByText( '[TaskList]' )
expect( taskList ).toBeNull();
} );
} );

View File

@ -6,13 +6,20 @@ import { Component, cloneElement, Fragment } from '@wordpress/element';
import { get, isEqual } from 'lodash';
import { compose } from '@wordpress/compose';
import classNames from 'classnames';
import { Snackbar, Icon, Button, Modal } from '@wordpress/components';
import {
Button,
Card,
CardBody,
CardHeader,
Modal,
} from '@wordpress/components';
import { withDispatch } from '@wordpress/data';
import { Icon, check, chevronRight } from '@wordpress/icons';
/**
* WooCommerce dependencies
*/
import { Card, List, MenuItem, EllipsisMenu } from '@woocommerce/components';
import { H, List, EllipsisMenu } from '@woocommerce/components';
import { updateQueryString } from '@woocommerce/navigation';
import { PLUGINS_STORE_NAME } from '@woocommerce/data';
@ -193,56 +200,24 @@ class TaskDashboard extends Component {
return currentTask;
}
renderPrompt() {
if ( this.props.promptShown ) {
return null;
}
return (
<Snackbar className="woocommerce-task-card__prompt">
<div className="woocommerce-task-card__prompt-pointer" />
<div className="woocommerce-task-card__prompt-content">
<span>
{ __( 'Is this card useful?', 'woocommerce-admin' ) }
</span>
<div className="woocommerce-task-card__prompt-actions">
<Button
isLink
onClick={ () => this.hideTaskCard( 'hide_card' ) }
>
{ __( 'No, hide it', 'woocommerce-admin' ) }
</Button>
<Button isLink onClick={ () => this.keepTaskCard() }>
{ __( 'Yes, keep it', 'woocommerce-admin' ) }
</Button>
</div>
</div>
</Snackbar>
);
}
renderMenu() {
return (
<div className="woocommerce-card__menu woocommerce-card__header-item">
<EllipsisMenu
label={ __( 'Task List Options', 'woocommerce-admin' ) }
renderContent={ () => (
<div className="woocommerce-task-card__section-controls">
<MenuItem
isClickable
onInvoke={ () =>
<Button
onClick={ () =>
this.hideTaskCard( 'remove_card' )
}
>
<Icon
icon={ 'trash' }
label={ __( 'Remove block' ) }
/>
{ __( 'Remove this card', 'woocommerce-admin' ) }
</MenuItem>
{ __( 'Hide this', 'woocommerce-admin' ) }
</Button>
</div>
) }
/>
</div>
);
}
@ -319,38 +294,8 @@ class TaskDashboard extends Component {
);
}
onSkipStoreSetup = () => {
const completedTaskKeys = this.getTasks()
.filter( ( x ) => x.completed )
.map( ( x ) => x.key );
recordEvent( 'tasklist_skip', {
completed_tasks_count: completedTaskKeys.length,
completed_tasks: completedTaskKeys,
reason: 'skip',
} );
this.props.updateOptions( {
woocommerce_task_list_hidden: 'yes',
} );
};
renderSkipActions() {
return (
<div className="skip-actions">
<Button
isLink
className="is-secondary"
onClick={ this.onSkipStoreSetup }
>
{ __( 'Skip store setup', 'woocommerce-admin' ) }
</Button>
</div>
);
}
render() {
const { inline, query } = this.props;
const { query } = this.props;
const { isCartModalOpen, isWelcomeModalOpen } = this.state;
const currentTask = this.getCurrentTask();
const listTasks = this.getTasks().map( ( task ) => {
@ -358,14 +303,21 @@ class TaskDashboard extends Component {
task.completed ? 'is-complete' : null,
task.className
);
task.before = task.completed ? (
<i className="material-icons-outlined">check_circle</i>
task.before = (
<div className="woocommerce-task__icon">
{ task.completed && <Icon icon={ check } /> }
</div>
);
if ( ! task.completed ) {
task.after = task.time ? (
<span className="woocommerce-task-estimated-time">
{ task.time }
</span>
) : (
<i className="material-icons-outlined">{ task.icon }</i>
);
task.after = (
<i className="material-icons-outlined">chevron_right</i>
<Icon icon={ chevronRight } />
);
}
if ( ! task.onClick ) {
task.onClick = () => updateQueryString( { task: task.key } );
@ -373,6 +325,14 @@ class TaskDashboard extends Component {
return task;
} );
const numCompleteTasks = listTasks.filter( ( task ) => task.completed )
.length;
const progressBarClass = classNames(
'woocommerce-task-card__progress-bar',
{
completed: listTasks.length === numCompleteTasks,
}
);
return (
<Fragment>
@ -384,22 +344,28 @@ class TaskDashboard extends Component {
) : (
<Fragment>
<Card
size="large"
className="woocommerce-task-card"
title={ __(
'Set up your store and start selling',
'woocommerce-admin'
) }
description={ __(
'Below youll find a list of the most important steps to get your store up and running.',
'woocommerce-admin'
) }
menu={ inline && this.renderMenu() }
>
<progress
className={ progressBarClass }
max={ listTasks.length }
value={ numCompleteTasks }
/>
<CardHeader>
<H>
{ __(
'Store setup',
'woocommerce-admin'
) }
</H>
{ this.renderMenu() }
</CardHeader>
<CardBody>
<List items={ listTasks } />
</CardBody>
</Card>
{ inline && this.renderPrompt() }
{ isWelcomeModalOpen && this.renderWelcomeModal() }
{ this.renderSkipActions() }
</Fragment>
) }
</div>
@ -425,16 +391,10 @@ export default compose(
const profileItems = getProfileItems();
const options = getOptions( [
'woocommerce_task_list_prompt_shown',
'woocommerce_task_list_welcome_modal_dismissed',
'woocommerce_task_list_hidden',
'woocommerce_task_list_tracked_completed_tasks',
] );
const promptShown = get(
options,
[ 'woocommerce_task_list_prompt_shown' ],
false
);
const modalDismissed = get(
options,
[ 'woocommerce_task_list_welcome_modal_dismissed' ],
@ -466,7 +426,6 @@ export default compose(
return {
modalDismissed,
profileItems,
promptShown,
taskListPayments,
isJetpackConnected: isJetpackConnected(),
incompleteTasks,

View File

@ -18,9 +18,6 @@ const TaskListPlaceholder = ( props ) => {
<div className="woocommerce-card__title woocommerce-card__header-item">
<span className="is-placeholder" />
</div>
<div className="woocommerce-card__description woocommerce-card__header-item">
<span className="is-placeholder" />
</div>
</div>
</div>
<div className="woocommerce-card__body">
@ -35,10 +32,6 @@ const TaskListPlaceholder = ( props ) => {
<div className="woocommerce-list__item-title">
<span className="is-placeholder" />
</div>
<div className="woocommerce-list__item-content">
<span className="is-placeholder" />
</div>
</div>
<div className="woocommerce-list__item-after">
<span className="is-placeholder" />

View File

@ -1,15 +1,51 @@
.woocommerce-task-dashboard__body {
.woocommerce-card__description {
color: $studio-gray-50;
}
.woocommerce-task-card .woocommerce-card__body {
border-top: 1px solid $studio-gray-5;
.woocommerce-task-dashboard__container {
.woocommerce-task-card {
.components-card__header.is-size-large {
min-height: unset;
padding-top: 0;
display: grid;
grid-template-columns: auto 24px;
}
.components-card__body.is-size-large {
padding: 0;
}
.woocommerce-list__item.is-complete .woocommerce-task__icon {
background-color: theme(button); // $theme-color
}
.woocommerce-list__item:not(.is-complete) {
.woocommerce-task__icon {
border: 1px solid $core-grey-light-500;
background: $white;
}
}
.woocommerce-list__item-before .woocommerce-task__icon {
border-radius: 50%;
width: 32px;
height: 32px;
}
.woocommerce-list__item-before svg {
fill: $white;
position: relative;
top: 4px;
left: 5px;
}
.components-popover__content {
min-width: unset;
}
}
}
.woocommerce-card.is-narrow {
max-width: 680px;
margin-left: auto;
@ -20,63 +56,53 @@
.components-modal__frame .components-button.is-button {
height: 40px;
min-width: 106px;
margin: $gap $gap-smaller 0 0;
margin: 0;
justify-content: center;
font-size: 14px;
font-weight: 500;
display: inline-block;
&:not(.is-primary) {
color: $studio-pink-50;
}
}
.woocommerce-task-dashboard__container {
.woocommerce-task-card__prompt-actions {
.components-button:not(.components-icon-button) {
color: $studio-white;
}
}
.skip-actions {
text-align: center;
button.components-button.is-secondary {
color: $studio-gray-50;
margin: 0;
min-width: 0;
}
color: theme(button); // $theme-color
}
}
.woocommerce-list {
margin: 0;
.woocommerce-list__item.is-complete {
background: $studio-gray-0;
.woocommerce-list__item {
&:not(:first-child) {
border-top: 1px solid $light-gray-500;
}
.woocommerce-list__item-before i {
color: $studio-gray-20;
&:hover {
background: $light-gray-100;
.woocommerce-list__item-title {
color: color(theme(button) shade(25%));
}
}
}
.woocommerce-list__item-title {
color: $studio-gray-50;
color: theme(button); // $theme-color
}
.woocommerce-task-estimated-time {
color: $dark-gray-500;
}
.woocommerce-list__item.is-complete {
background: $light-gray-100;
.woocommerce-list__item-title {
color: $dark-gray-500;
}
.woocommerce-list__item-content {
display: none;
}
}
.woocommerce-list__item-before i {
width: 36px;
height: 36px;
font-size: 36px;
color: $studio-woocommerce-purple-60;
}
.woocommerce-list__item-after i {
color: $studio-gray-60;
}
}
.woocommerce-list__item-title {
@ -284,7 +310,7 @@
.components-button.is-primary {
min-width: 328px;
@include breakpoint( '<782px' ) {
@include breakpoint('<782px') {
min-width: auto;
}
}
@ -377,7 +403,7 @@
}
}
@include breakpoint( '<600px' ) {
@include breakpoint('<600px') {
.woocommerce-card__body {
flex-direction: column;
}
@ -443,8 +469,11 @@
}
}
.woocommerce-task-dashboard__container .woocommerce-task-payments .woocommerce-task-payments__actions {
.woocommerce-task-dashboard__container {
.woocommerce-task-payments
.woocommerce-task-payments__actions {
text-align: center;
}
button.components-button.is-primary {
margin: 0;
@ -541,18 +570,16 @@
}
.woocommerce-task-card__section-controls {
.dashicon {
margin: 0 $gap-smaller 0 0;
vertical-align: bottom;
}
.woocommerce-ellipsis-menu__item {
padding-bottom: 10px;
}
text-align: center;
}
.woocommerce-task-dashboard__container {
.woocommerce-task-card.is-loading {
.woocommerce-card__body {
border-top: 1px solid $studio-gray-5;
padding: 0;
}
.is-placeholder {
@include placeholder();
display: inline-block;
@ -566,13 +593,6 @@
}
}
.woocommerce-card__description {
.is-placeholder {
width: 90%;
height: 24px;
}
}
.woocommerce-list__item-before {
.is-placeholder {
height: 36px;
@ -589,20 +609,54 @@
width: 60%;
}
}
.woocommerce-list__item-content {
.is-placeholder {
height: 20px;
width: 80%;
}
}
}
.woocommerce-list__item-after {
.is-placeholder {
height: 24px;
width: 24px;
height: 18px;
width: 60px;
}
}
}
}
.woocommerce-task-dashboard__container .woocommerce-task-card__progress-bar {
-webkit-appearance: none;
-moz-appearance: none;
appearance: none;
border: 0;
border-top-left-radius: 2px;
border-top-right-radius: 2px;
height: 4px;
width: 100%;
vertical-align: top;
// Firefox
& {
background-color: $light-gray-300;
}
&::-moz-progress-bar {
background-color: $dark-gray-500;
border-top-left-radius: 2px;
border-top-right-radius: 2px;
}
// Chrome
&::-webkit-progress-bar {
background-color: $light-gray-300;
border-top-left-radius: 2px;
border-top-right-radius: 2px;
}
&::-webkit-progress-value {
background-color: $dark-gray-500;
border-top-left-radius: 2px;
}
}
.woocommerce-task-dashboard__container .woocommerce-task-card__progress-bar.completed {
&::-webkit-progress-value {
border-top-right-radius: 2px;
}
}

View File

@ -67,19 +67,28 @@ export function getAllTasks( {
);
const tasks = [
{
key: 'store_details',
title: __( 'Store details', 'woocommerce-admin' ),
container: null,
onClick: () => {
window.location = getAdminLink(
'admin.php?page=wc-admin&reset_profiler=1'
);
},
completed: profileItems.completed,
visible: true,
time: __( '4 minutes', 'woocommerce-admin' ),
},
{
key: 'purchase',
title: __( 'Purchase & install extensions', 'woocommerce-admin' ),
content: __(
'Purchase, install, and manage your extensions directly from your dashboard',
'wooocommerce-admin'
),
icon: 'extension',
container: null,
onClick: () =>
remainingProductIds.length ? toggleCartModal() : null,
visible: productIds.length,
completed: ! remainingProductIds.length,
time: __( '2 minutes', 'woocommerce-admin' ),
},
{
key: 'connect',
@ -87,75 +96,50 @@ export function getAllTasks( {
'Connect your store to WooCommerce.com',
'woocommerce-admin'
),
content: __(
'Install and manage your extensions directly from your Dashboard',
'wooocommerce-admin'
),
icon: 'extension',
container: <Connect query={ query } />,
visible:
profileItems.items_purchased && ! profileItems.wccom_connected,
completed: profileItems.wccom_connected,
time: __( '1 minute', 'woocommerce-admin' ),
},
{
key: 'products',
title: __( 'Add your first product', 'woocommerce-admin' ),
content: __(
'Add products manually, import from a sheet or migrate from another platform',
'wooocommerce-admin'
),
icon: 'add_box',
title: __( 'Add my products', 'woocommerce-admin' ),
container: <Products />,
completed: hasProducts,
visible: true,
time: __( '1 minute per product', 'woocommerce-admin' ),
},
{
key: 'appearance',
title: __( 'Personalize your store', 'woocommerce-admin' ),
content: __(
'Create a custom homepage and upload your logo',
'wooocommerce-admin'
),
icon: 'palette',
title: __( 'Personalize my store', 'woocommerce-admin' ),
container: <Appearance />,
completed: isAppearanceComplete,
visible: true,
time: __( '2 minutes', 'woocommerce-admin' ),
},
{
key: 'shipping',
title: __( 'Set up shipping', 'woocommerce-admin' ),
content: __(
'Configure some basic shipping rates to get started',
'wooocommerce-admin'
),
icon: 'local_shipping',
container: <Shipping />,
completed: shippingZonesCount > 0,
visible:
( profileItems.product_types &&
profileItems.product_types.includes( 'physical' ) ) ||
hasPhysicalProducts,
time: __( '1 minute', 'woocommerce-admin' ),
},
{
key: 'tax',
title: __( 'Set up tax', 'woocommerce-admin' ),
content: __(
'Choose how to configure tax rates - manually or automatically',
'wooocommerce-admin'
),
icon: 'account_balance',
container: <Tax />,
completed: isTaxComplete,
visible: true,
time: __( '1 minute', 'woocommerce-admin' ),
},
{
key: 'payments',
title: __( 'Set up payments', 'woocommerce-admin' ),
content: __(
'Select which payment providers youd like to use and configure them',
'wooocommerce-admin'
),
icon: 'payment',
container: <Payments />,
completed: paymentsCompleted || paymentsSkipped,
onClick: () => {
@ -168,6 +152,7 @@ export function getAllTasks( {
updateQueryString( { task: 'payments' } );
},
visible: true,
time: __( '2 minutes', 'woocommerce-admin' ),
},
];

View File

@ -500,8 +500,9 @@ class Onboarding {
* @return array
*/
public function preload_options( $options ) {
$options[] = 'woocommerce_task_list_hidden';
$options[] = 'woocommerce_task_list_complete';
$options[] = 'woocommerce_task_list_do_this_later';
$options[] = 'woocommerce_task_list_hidden';
if ( ! self::should_show_tasks() && ! self::should_show_profiler() ) {
return $options;