* Task list completed functionality

* Hook up banner functionality

* Handle PR feedback: Add pointer/arrow, adjust banner hide behavior, some minor code changes.
This commit is contained in:
Justin Shreve 2019-10-11 08:55:35 -04:00 committed by GitHub
parent bfba456a46
commit 0785f3c97b
7 changed files with 332 additions and 161 deletions

View File

@ -5,7 +5,7 @@
import { __, sprintf } from '@wordpress/i18n'; import { __, sprintf } from '@wordpress/i18n';
import { Component, Fragment } from '@wordpress/element'; import { Component, Fragment } from '@wordpress/element';
import { compose } from '@wordpress/compose'; import { compose } from '@wordpress/compose';
import { partial } from 'lodash'; import { partial, filter, get } from 'lodash';
import { IconButton, Icon, Dropdown, Button } from '@wordpress/components'; import { IconButton, Icon, Dropdown, Button } from '@wordpress/components';
import { withDispatch } from '@wordpress/data'; import { withDispatch } from '@wordpress/data';
@ -22,6 +22,8 @@ import defaultSections from './default-sections';
import Section from './section'; import Section from './section';
import withSelect from 'wc-api/with-select'; import withSelect from 'wc-api/with-select';
import { recordEvent } from 'lib/tracks'; import { recordEvent } from 'lib/tracks';
import TaskList from './task-list';
import { getTasks } from './task-list/tasks';
class CustomizableDashboard extends Component { class CustomizableDashboard extends Component {
constructor( props ) { constructor( props ) {
@ -189,14 +191,28 @@ class CustomizableDashboard extends Component {
} }
render() { render() {
const { query, path } = this.props; const { query, path, taskListHidden, taskListCompleted } = this.props;
const { sections } = this.state; const { sections } = this.state;
const visibleSectionKeys = sections const visibleSectionKeys = sections
.filter( section => section.isVisible ) .filter( section => section.isVisible )
.map( section => section.key ); .map( section => section.key );
if (
window.wcAdminFeatures.onboarding &&
wcSettings.onboarding &&
! taskListHidden &&
( query.task || ! taskListCompleted )
) {
return <TaskList query={ query } />;
}
return ( return (
<Fragment> <Fragment>
{ window.wcAdminFeatures.onboarding &&
wcSettings.onboarding &&
! taskListHidden &&
taskListCompleted && <TaskList query={ query } inline /> }
<ReportFilters query={ query } path={ path } /> <ReportFilters query={ query } path={ path } />
{ sections.map( ( section, index ) => { { sections.map( ( section, index ) => {
if ( section.isVisible ) { if ( section.isVisible ) {
@ -226,11 +242,32 @@ class CustomizableDashboard extends Component {
} }
export default compose( export default compose(
withSelect( select => { withSelect( ( select, props ) => {
const { getCurrentUserData } = select( 'wc-api' ); const { getCurrentUserData, getProfileItems, getOptions } = select( 'wc-api' );
const userData = getCurrentUserData(); const userData = getCurrentUserData();
const profileItems = getProfileItems();
const taskListHidden =
'yes' ===
get(
getOptions( [ 'woocommerce_task_list_hidden' ] ),
[ 'woocommerce_task_list_hidden' ],
'no'
);
const tasks = getTasks( {
profileItems,
options: getOptions( [ 'woocommerce_onboarding_payments' ] ),
query: props.query,
} );
const visibleTasks = filter( tasks, task => task.visible );
const completedTasks = filter( tasks, task => task.visible && task.completed );
const taskListCompleted = visibleTasks.length === completedTasks.length;
return { return {
taskListHidden,
taskListCompleted,
userPrefSections: userData.dashboard_sections, userPrefSections: userData.dashboard_sections,
}; };
} ), } ),

View File

@ -2,7 +2,7 @@
/** /**
* External dependencies * External dependencies
*/ */
import { Component, Fragment } from '@wordpress/element'; import { Component } from '@wordpress/element';
import { compose } from '@wordpress/compose'; import { compose } from '@wordpress/compose';
/** /**
@ -10,12 +10,6 @@ import { compose } from '@wordpress/compose';
*/ */
import './style.scss'; import './style.scss';
import CustomizableDashboard from './customizable'; import CustomizableDashboard from './customizable';
import DashboardCharts from './dashboard-charts';
import Leaderboards from './leaderboards';
import { ReportFilters } from '@woocommerce/components';
import { getSetting } from '@woocommerce/wc-admin-settings';
import StorePerformance from './store-performance';
import TaskList from './task-list';
import ProfileWizard from './profile-wizard'; import ProfileWizard from './profile-wizard';
import withSelect from 'wc-api/with-select'; import withSelect from 'wc-api/with-select';
@ -27,27 +21,11 @@ class Dashboard extends Component {
return <ProfileWizard query={ query } />; return <ProfileWizard query={ query } />;
} }
const { taskListHidden } = getSetting( 'onboarding', {} );
// @todo This should be replaced by a check of tasks from the REST API response from #1897.
if ( window.wcAdminFeatures.onboarding && ! taskListHidden ) {
return <TaskList query={ query } />;
}
// @todo When the customizable dashboard is ready to be launched, we can pull `CustomizableDashboard`'s render
// method into `index.js`, and replace both this feature check, and the existing dashboard below.
if ( window.wcAdminFeatures[ 'analytics-dashboard/customizable' ] ) { if ( window.wcAdminFeatures[ 'analytics-dashboard/customizable' ] ) {
return <CustomizableDashboard query={ query } path={ path } />; return <CustomizableDashboard query={ query } path={ path } />;
} }
return ( return null;
<Fragment>
<ReportFilters query={ query } path={ path } />
<StorePerformance query={ query } hiddenBlocks={ [] } />
<DashboardCharts query={ query } path={ path } hiddenBlocks={ [] } />
<Leaderboards query={ query } hiddenBlocks={ [] } />
</Fragment>
);
} }
} }

View File

@ -6,40 +6,23 @@ import { __ } from '@wordpress/i18n';
import { Component, Fragment } from '@wordpress/element'; import { Component, Fragment } from '@wordpress/element';
import { filter, get } from 'lodash'; import { filter, get } from 'lodash';
import { compose } from '@wordpress/compose'; import { compose } from '@wordpress/compose';
import classNames from 'classnames';
import { Snackbar, Icon, Button } from '@wordpress/components';
import { withDispatch } from '@wordpress/data';
/** /**
* WooCommerce dependencies * WooCommerce dependencies
*/ */
import { Card, List } from '@woocommerce/components'; import { Card, List, MenuItem, EllipsisMenu } from '@woocommerce/components';
import { updateQueryString } from '@woocommerce/navigation'; import { updateQueryString } from '@woocommerce/navigation';
import { getSetting } from '@woocommerce/wc-admin-settings';
/** /**
* Internal depdencies * Internal depdencies
*/ */
import './style.scss'; import './style.scss';
import Appearance from './tasks/appearance';
import Connect from './tasks/connect';
import Products from './tasks/products';
import Shipping from './tasks/shipping';
import Tax from './tasks/tax';
import Payments from './tasks/payments';
import withSelect from 'wc-api/with-select'; import withSelect from 'wc-api/with-select';
import { recordEvent } from 'lib/tracks'; import { recordEvent } from 'lib/tracks';
import { getTasks } from './tasks';
const {
customLogo,
hasHomepage,
hasPhysicalProducts,
hasProducts,
shippingZonesCount,
} = getSetting( 'onboarding', {
customLogo: '',
hasHomePage: false,
hasPhysicalProducts: false,
hasProducts: false,
shippingZonesCount: 0,
} );
class TaskDashboard extends Component { class TaskDashboard extends Component {
componentDidMount() { componentDidMount() {
@ -59,108 +42,36 @@ class TaskDashboard extends Component {
return; return;
} }
const { profileItems } = this.props; const { profileItems } = this.props;
const tasks = filter( this.getTasks(), task => task.visible ); const tasks = filter( this.props.tasks, task => task.visible );
recordEvent( 'tasklist_view', { recordEvent( 'tasklist_view', {
number_tasks: tasks.length, number_tasks: tasks.length,
store_connected: profileItems.wccom_connected, store_connected: profileItems.wccom_connected,
} ); } );
} }
getTasks() { keepTaskCard() {
const { profileItems, query, paymentsCompleted } = this.props; recordEvent( 'tasklist_completed', {
action: 'keep_card',
} );
return [ this.props.updateOptions( {
{ woocommerce_task_list_prompt_shown: true,
key: 'connect', } );
title: __( 'Connect your store to WooCommerce.com', 'woocommerce-admin' ), }
content: __(
'Install and manage your extensions directly from your Dashboard', hideTaskCard( action ) {
'wooocommerce-admin' recordEvent( 'tasklist_completed', {
), action,
before: <i className="material-icons-outlined">extension</i>, } );
after: <i className="material-icons-outlined">chevron_right</i>, this.props.updateOptions( {
onClick: () => updateQueryString( { task: 'connect' } ), woocommerce_task_list_hidden: 'yes',
container: <Connect query={ query } />, woocommerce_task_list_prompt_shown: true,
visible: profileItems.items_purchased && ! profileItems.wccom_connected, } );
},
{
key: 'products',
title: __( 'Add your first product', 'woocommerce-admin' ),
content: __(
'Add products manually, import from a sheet or migrate from another platform',
'wooocommerce-admin'
),
before: hasProducts ? (
<i className="material-icons-outlined">check_circle</i>
) : (
<i className="material-icons-outlined">add_box</i>
),
after: <i className="material-icons-outlined">chevron_right</i>,
onClick: () => updateQueryString( { task: 'products' } ),
container: <Products />,
className: hasProducts ? 'is-complete' : null,
visible: true,
},
{
key: 'appearance',
title: __( 'Personalize your store', 'woocommerce-admin' ),
content: __( 'Create a custom homepage and upload your logo', 'wooocommerce-admin' ),
before: <i className="material-icons-outlined">palette</i>,
after: <i className="material-icons-outlined">chevron_right</i>,
onClick: () => updateQueryString( { task: 'appearance' } ),
container: <Appearance />,
className: customLogo && hasHomepage ? 'is-complete' : null,
visible: true,
},
{
key: 'shipping',
title: __( 'Set up shipping', 'woocommerce-admin' ),
content: __( 'Configure some basic shipping rates to get started', 'wooocommerce-admin' ),
before:
shippingZonesCount > 0 ? (
<i className="material-icons-outlined">check_circle</i>
) : (
<i className="material-icons-outlined">local_shipping</i>
),
after: <i className="material-icons-outlined">chevron_right</i>,
onClick: () => updateQueryString( { task: 'shipping' } ),
container: <Shipping />,
className: shippingZonesCount > 0 ? 'is-complete' : null,
visible: profileItems.product_types.includes( 'physical' ) || hasPhysicalProducts,
},
{
key: 'tax',
title: __( 'Set up tax', 'woocommerce-admin' ),
content: __(
'Choose how to configure tax rates - manually or automatically',
'wooocommerce-admin'
),
before: <i className="material-icons-outlined">account_balance</i>,
after: <i className="material-icons-outlined">chevron_right</i>,
onClick: () => updateQueryString( { task: 'tax' } ),
container: <Tax />,
visible: true,
},
{
key: 'payments',
title: __( 'Set up payments', 'woocommerce-admin' ),
content: __(
'Select which payment providers youd like to use and configure them',
'wooocommerce-admin'
),
before: <i className="material-icons-outlined">payment</i>,
after: <i className="material-icons-outlined">chevron_right</i>,
onClick: () => updateQueryString( { task: 'payments' } ),
container: <Payments />,
className: paymentsCompleted ? 'is-complete' : null,
visible: true,
},
];
} }
getCurrentTask() { getCurrentTask() {
const { task } = this.props.query; const { task } = this.props.query;
const currentTask = this.getTasks().find( s => s.key === task ); const currentTask = this.props.tasks.find( s => s.key === task );
if ( ! currentTask ) { if ( ! currentTask ) {
return null; return null;
@ -169,9 +80,59 @@ class TaskDashboard extends Component {
return currentTask; return currentTask;
} }
renderPrompt() {
if ( this.props.promptShown ) {
return null;
}
return (
<Snackbar className="woocommerce-task-card__prompt">
<div>
<div className="woocommerce-task-card__prompt-pointer" />
<span>{ __( 'Is this card useful?', 'woocommerce-admin' ) }</span>
</div>
<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>
</Snackbar>
);
}
renderMenu() {
return (
<EllipsisMenu
label={ __( 'Task List Options', 'woocommerce-admin' ) }
renderContent={ () => (
<div className="woocommerce-task-card__section-controls">
<MenuItem isClickable onInvoke={ () => this.hideTaskCard( 'remove_card' ) }>
<Icon icon={ 'trash' } label={ __( 'Remove block' ) } />
{ __( 'Remove this card', 'woocommerce-admin' ) }
</MenuItem>
</div>
) }
/>
);
}
render() { render() {
const currentTask = this.getCurrentTask(); const currentTask = this.getCurrentTask();
const tasks = filter( this.getTasks(), task => task.visible ); const tasks = filter( this.props.tasks, task => task.visible ).map( task => {
task.className = classNames( task.completed ? 'is-complete' : null, task.className );
task.before = task.completed ? (
<i className="material-icons-outlined">check_circle</i>
) : (
<i className="material-icons-outlined">{ task.icon }</i>
);
task.after = <i className="material-icons-outlined">chevron_right</i>;
task.onClick = () => updateQueryString( { task: task.key } );
return task;
} );
return ( return (
<Fragment> <Fragment>
@ -179,6 +140,7 @@ class TaskDashboard extends Component {
{ currentTask ? ( { currentTask ? (
currentTask.container currentTask.container
) : ( ) : (
<Fragment>
<Card <Card
className="woocommerce-task-card" className="woocommerce-task-card"
title={ __( 'Set up your store and start selling', 'woocommerce-admin' ) } title={ __( 'Set up your store and start selling', 'woocommerce-admin' ) }
@ -186,9 +148,12 @@ class TaskDashboard extends Component {
'Below youll find a list of the most important steps to get your store up and running.', 'Below youll find a list of the most important steps to get your store up and running.',
'woocommerce-admin' 'woocommerce-admin'
) } ) }
menu={ this.props.inline && this.renderMenu() }
> >
<List items={ tasks } /> <List items={ tasks } />
</Card> </Card>
{ this.props.inline && this.renderPrompt() }
</Fragment>
) } ) }
</div> </div>
</Fragment> </Fragment>
@ -197,17 +162,32 @@ class TaskDashboard extends Component {
} }
export default compose( export default compose(
withSelect( select => { withSelect( ( select, props ) => {
const { getProfileItems, getOptions } = select( 'wc-api' ); const { getProfileItems, getOptions } = select( 'wc-api' );
const profileItems = getProfileItems(); const profileItems = getProfileItems();
const options = getOptions( [ 'woocommerce_onboarding_payments' ] ); const promptShown = get(
const paymentsCompleted = get( getOptions( [ 'woocommerce_task_list_prompt_shown' ] ),
options, [ 'woocommerce_task_list_prompt_shown' ],
[ 'woocommerce_onboarding_payments', 'completed' ],
false false
); );
return { profileItems, paymentsCompleted }; const tasks = getTasks( {
profileItems,
options: getOptions( [ 'woocommerce_onboarding_payments' ] ),
query: props.query,
} );
return {
profileItems,
promptShown,
tasks,
};
} ),
withDispatch( dispatch => {
const { updateOptions } = dispatch( 'wc-api' );
return {
updateOptions,
};
} ) } )
)( TaskDashboard ); )( TaskDashboard );

View File

@ -255,3 +255,57 @@
margin-bottom: $gap-smallest; margin-bottom: $gap-smallest;
} }
} }
.woocommerce-task-card__prompt {
width: 100%;
min-width: 100%;
margin-bottom: $gap-large;
margin-top: -$gap-smallest;
cursor: default;
.components-snackbar__content span {
margin-left: -$gap-large;
}
.woocommerce-task-card__prompt-actions {
button.is-link,
button.is-link:active,
button.is-link:focus {
color: $studio-white;
margin-left: $gap-large;
background: transparent;
}
button.is-link:hover {
color: $studio-white;
}
}
.woocommerce-task-card__prompt-pointer {
border-bottom: 10px solid $core-grey-dark-700; /* Snackbar color */
border-left: 10px solid transparent;
border-right: 10px solid transparent;
position: relative;
width: 0;
height: 0;
display: inline-block;
top: -30px;
}
&:hover {
.woocommerce-task-card__prompt-pointer {
border-bottom-color: $core-grey-dark-900; /* Snackbar hover */
}
}
}
.woocommerce-task-card__section-controls {
.dashicon {
margin: 0 $gap-smaller 0 0;
vertical-align: bottom;
}
.woocommerce-ellipsis-menu__item {
padding-bottom: 10px;
}
}

View File

@ -0,0 +1,116 @@
/**
* External dependencies
*
* @format
*/
import { __ } from '@wordpress/i18n';
import { get } from 'lodash';
/**
* WooCommerce dependencies
*/
import { getSetting } from '@woocommerce/wc-admin-settings';
/**
* Internal dependencies
*/
import Appearance from './tasks/appearance';
import Connect from './tasks/connect';
import Products from './tasks/products';
import Shipping from './tasks/shipping';
import Tax from './tasks/tax';
import Payments from './tasks/payments';
export function getTasks( { profileItems, options, query } ) {
const {
customLogo,
hasHomepage,
hasPhysicalProducts,
hasProducts,
isTaxComplete,
shippingZonesCount,
} = getSetting( 'onboarding', {
customLogo: '',
hasHomePage: false,
hasPhysicalProducts: false,
hasProducts: false,
isTaxComplete: false,
shippingZonesCount: 0,
} );
const paymentsCompleted = get(
options,
[ 'woocommerce_onboarding_payments', 'completed' ],
false
);
return [
{
key: 'connect',
title: __( '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,
},
{
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',
container: <Products />,
completed: hasProducts,
visible: true,
},
{
key: 'appearance',
title: __( 'Personalize your store', 'woocommerce-admin' ),
content: __( 'Create a custom homepage and upload your logo', 'wooocommerce-admin' ),
icon: 'palette',
container: <Appearance />,
completed: customLogo && hasHomepage,
visible: true,
},
{
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.includes( 'physical' ) || hasPhysicalProducts,
},
{
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,
},
{
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,
visible: true,
},
];
}

View File

@ -327,7 +327,6 @@ class Onboarding {
$settings['onboarding'] = array( $settings['onboarding'] = array(
'industries' => self::get_allowed_industries(), 'industries' => self::get_allowed_industries(),
'profile' => $profile, 'profile' => $profile,
'taskListHidden' => ! $this->should_show_tasks(),
); );
// Only fetch if the onboarding wizard is incomplete. // Only fetch if the onboarding wizard is incomplete.
@ -352,9 +351,13 @@ class Onboarding {
* @return array * @return array
*/ */
public function preload_options( $options ) { public function preload_options( $options ) {
$options[] = 'woocommerce_task_list_hidden';
if ( ! $this->should_show_tasks() && ! $this->should_show_profiler() ) { if ( ! $this->should_show_tasks() && ! $this->should_show_profiler() ) {
return $options; return $options;
} }
$options[] = 'woocommerce_task_list_prompt_shown';
$options[] = 'woocommerce_onboarding_payments'; $options[] = 'woocommerce_onboarding_payments';
$options[] = 'woocommerce_allow_tracking'; $options[] = 'woocommerce_allow_tracking';
$options[] = 'woocommerce_stripe_settings'; $options[] = 'woocommerce_stripe_settings';

View File

@ -8,6 +8,8 @@
namespace Automattic\WooCommerce\Admin\Features; namespace Automattic\WooCommerce\Admin\Features;
use Automattic\WooCommerce\Admin\API\Reports\Taxes\Stats\DataStore;
/** /**
* Contains the logic for completing onboarding tasks. * Contains the logic for completing onboarding tasks.
*/ */
@ -79,6 +81,7 @@ class OnboardingTasks {
) )
) > 0; ) > 0;
$settings['onboarding']['hasProducts'] = self::check_task_completion( 'products' ); $settings['onboarding']['hasProducts'] = self::check_task_completion( 'products' );
$settings['onboarding']['isTaxComplete'] = 'yes' === get_option( 'wc_connect_taxes_enabled' ) || count( DataStore::get_taxes( array() ) ) > 0;
$settings['onboarding']['shippingZonesCount'] = count( \WC_Shipping_Zones::get_zones() ); $settings['onboarding']['shippingZonesCount'] = count( \WC_Shipping_Zones::get_zones() );
return $settings; return $settings;