Add conditional copy/content and CTA to expanded task items (https://github.com/woocommerce/woocommerce-admin/pull/6956)

* Add action button to TaskItem.

* Pass through click event.

* Add separate action and label to onClick.

* Add initial copy for task expansion.

* Expand one task at a time.

* Add descriptive text to the payments step.

* Set the first incomplete task current by default.

* Revert expansion behavior.

* Fix margins.

* Curate purchase products task content based on selections.

* Fix appearance task copy.

* Fix payment task copy.

* Add conditional tax step title.

* Indicated if task is expanded to click handlers.

* Automatically enable WC Tax from the expanded CTA.

* Restore additional text property.

* Fix task title xpath selector in E2E test.

* Fix automatic tax setup query param logic.

* Add changelog entries.
This commit is contained in:
Jeff Stieler 2021-06-01 14:04:21 -04:00 committed by GitHub
parent 9fbebae1a6
commit c23d02bcc0
10 changed files with 221 additions and 71 deletions

View File

@ -43,6 +43,14 @@ const taskDashboardSelect = ( select ) => {
const countryCode = getCountryCode( const countryCode = getCountryCode(
generalSettings.woocommerce_default_country generalSettings.woocommerce_default_country
); );
const {
woocommerce_store_address: storeAddress,
woocommerce_default_country: defaultCountry,
woocommerce_store_postcode: storePostCode,
} = generalSettings;
const hasCompleteAddress = Boolean(
storeAddress && defaultCountry && storePostCode
);
const activePlugins = getActivePlugins(); const activePlugins = getActivePlugins();
const installedPlugins = getInstalledPlugins(); const installedPlugins = getInstalledPlugins();
@ -65,6 +73,7 @@ const taskDashboardSelect = ( select ) => {
onboardingStatus, onboardingStatus,
profileItems, profileItems,
trackedCompletedTasks, trackedCompletedTasks,
hasCompleteAddress,
}; };
}; };
@ -85,6 +94,7 @@ const TaskDashboard = ( { userPreferences, query } ) => {
isTaskListComplete, isTaskListComplete,
isExtendedTaskListHidden, isExtendedTaskListHidden,
isExtendedTaskListComplete, isExtendedTaskListComplete,
hasCompleteAddress,
} = useSelect( taskDashboardSelect ); } = useSelect( taskDashboardSelect );
const [ isCartModalOpen, setIsCartModalOpen ] = useState( false ); const [ isCartModalOpen, setIsCartModalOpen ] = useState( false );
@ -173,6 +183,7 @@ const TaskDashboard = ( { userPreferences, query } ) => {
query, query,
toggleCartModal, toggleCartModal,
onTaskSelect, onTaskSelect,
hasCompleteAddress,
} ); } );
const { extension, setup: setupTasks } = allTasks; const { extension, setup: setupTasks } = allTasks;

View File

@ -32,6 +32,18 @@
} }
} }
.woocommerce-task__additional-info,
.woocommerce-task__estimated-time,
.woocommerce-task-list__item-content {
color: $gray-700;
font-weight: 400;
font-size: 12px;
}
.woocommerce-task-list__item-content {
font-size: 13px;
}
#wpbody-content { #wpbody-content {
position: relative; position: relative;
} }

View File

@ -53,6 +53,21 @@ export const TaskList = ( {
possiblyTrackCompletedTasks(); possiblyTrackCompletedTasks();
}, [ query ] ); }, [ query ] );
const visibleTasks = tasks.filter(
( task ) => task.visible && ! dismissedTasks.includes( task.key )
);
const completedTaskKeys = visibleTasks
.filter( ( task ) => task.completed )
.map( ( task ) => task.key );
const incompleteTasks = tasks.filter(
( task ) =>
task.visible &&
! task.completed &&
! dismissedTasks.includes( task.key )
);
const possiblyCompleteTaskList = () => { const possiblyCompleteTaskList = () => {
const taskListVariableName = `woocommerce_${ name }_complete`; const taskListVariableName = `woocommerce_${ name }_complete`;
const taskListToComplete = isComplete const taskListToComplete = isComplete
@ -64,8 +79,8 @@ export const TaskList = ( {
} }
if ( if (
( ! getIncompleteTasks().length && ! isComplete ) || ( ! incompleteTasks.length && ! isComplete ) ||
( getIncompleteTasks().length && isComplete ) ( incompleteTasks.length && isComplete )
) { ) {
updateOptions( { updateOptions( {
...taskListToComplete, ...taskListToComplete,
@ -73,26 +88,11 @@ export const TaskList = ( {
} }
}; };
const getCompletedTaskKeys = () => {
return getVisibleTasks()
.filter( ( task ) => task.completed )
.map( ( task ) => task.key );
};
const getIncompleteTasks = () => {
return tasks.filter(
( task ) =>
task.visible &&
! task.completed &&
! dismissedTasks.includes( task.key )
);
};
const getTrackedIncompletedTasks = ( const getTrackedIncompletedTasks = (
partialCompletedTasks, partialCompletedTasks,
allTrackedTask allTrackedTask
) => { ) => {
return getVisibleTasks() return visibleTasks
.filter( .filter(
( task ) => ( task ) =>
allTrackedTask.includes( task.key ) && allTrackedTask.includes( task.key ) &&
@ -102,7 +102,6 @@ export const TaskList = ( {
}; };
const possiblyTrackCompletedTasks = () => { const possiblyTrackCompletedTasks = () => {
const completedTaskKeys = getCompletedTaskKeys();
const trackedCompletedTasks = getTrackedCompletedTasks( const trackedCompletedTasks = getTrackedCompletedTasks(
completedTaskKeys, completedTaskKeys,
totalTrackedCompletedTasks totalTrackedCompletedTasks
@ -160,12 +159,6 @@ export const TaskList = ( {
} ); } );
}; };
const getVisibleTasks = () => {
return tasks.filter(
( task ) => task.visible && ! dismissedTasks.includes( task.key )
);
};
const recordTaskListView = () => { const recordTaskListView = () => {
if ( query.task ) { if ( query.task ) {
return; return;
@ -174,8 +167,6 @@ export const TaskList = ( {
const isCoreTaskList = name === 'task_list'; const isCoreTaskList = name === 'task_list';
const taskListName = isCoreTaskList ? 'tasklist' : 'extended_tasklist'; const taskListName = isCoreTaskList ? 'tasklist' : 'extended_tasklist';
const visibleTasks = getVisibleTasks();
recordEvent( `${ taskListName }_view`, { recordEvent( `${ taskListName }_view`, {
number_tasks: visibleTasks.length, number_tasks: visibleTasks.length,
store_connected: profileItems.wccom_connected, store_connected: profileItems.wccom_connected,
@ -196,8 +187,8 @@ export const TaskList = ( {
recordEvent( `${ taskListName }_completed`, { recordEvent( `${ taskListName }_completed`, {
action, action,
completed_task_count: getCompletedTaskKeys().length, completed_task_count: completedTaskKeys.length,
incomplete_task_count: getIncompleteTasks().length, incomplete_task_count: incompleteTasks.length,
} ); } );
updateOptions( { updateOptions( {
...updateOptionsParams, ...updateOptionsParams,
@ -223,7 +214,7 @@ export const TaskList = ( {
); );
}; };
const listTasks = getVisibleTasks().map( ( task ) => { const listTasks = visibleTasks.map( ( task ) => {
if ( ! task.onClick ) { if ( ! task.onClick ) {
task.onClick = ( e ) => { task.onClick = ( e ) => {
if ( e.target.nodeName === 'A' ) { if ( e.target.nodeName === 'A' ) {
@ -277,7 +268,7 @@ export const TaskList = ( {
<CardHeader size="medium"> <CardHeader size="medium">
<div className="wooocommerce-task-card__header"> <div className="wooocommerce-task-card__header">
<Text variant="title.small">{ listTitle }</Text> <Text variant="title.small">{ listTitle }</Text>
<Badge count={ getIncompleteTasks().length } /> <Badge count={ incompleteTasks.length } />
</div> </div>
{ renderMenu() } { renderMenu() }
</CardHeader> </CardHeader>
@ -294,6 +285,9 @@ export const TaskList = ( {
onDismiss={ () => dismissTask( task ) } onDismiss={ () => dismissTask( task ) }
time={ task.time } time={ task.time }
level={ task.level } level={ task.level }
action={ task.onClick }
actionLabel={ task.action }
additionalInfo={ task.additionalInfo }
/> />
) ) } ) ) }
</ListComp> </ListComp>

View File

@ -56,6 +56,7 @@ export function getAllTasks( {
query, query,
toggleCartModal, toggleCartModal,
onTaskSelect, onTaskSelect,
hasCompleteAddress,
} ) { } ) {
const { const {
hasPaymentGateway, hasPaymentGateway,
@ -84,6 +85,8 @@ export function getAllTasks( {
const woocommercePaymentsInstalled = const woocommercePaymentsInstalled =
installedPlugins.indexOf( 'woocommerce-payments' ) !== -1; installedPlugins.indexOf( 'woocommerce-payments' ) !== -1;
const woocommerceServicesActive =
activePlugins.indexOf( 'woocommerce-services' ) !== -1;
const { const {
completed: profilerCompleted, completed: profilerCompleted,
product_types: productTypes, product_types: productTypes,
@ -94,10 +97,11 @@ export function getAllTasks( {
businessExtensions || [] businessExtensions || []
).includes( 'woocommerce-payments' ); ).includes( 'woocommerce-payments' );
let purchaseAndInstallText = __( let purchaseAndInstallTitle = __(
'Add paid extensions to my store', 'Add paid extensions to my store',
'woocommerce-admin' 'woocommerce-admin'
); );
let purchaseAndInstallContent;
if ( uniqueItemsList.length === 1 ) { if ( uniqueItemsList.length === 1 ) {
const { name: itemName } = uniqueItemsList[ 0 ]; const { name: itemName } = uniqueItemsList[ 0 ];
@ -105,14 +109,64 @@ export function getAllTasks( {
'Add %s to my store', 'Add %s to my store',
'woocommerce-admin' 'woocommerce-admin'
); );
purchaseAndInstallText = sprintf( purchaseAndInstallFormat, itemName ); purchaseAndInstallTitle = sprintf( purchaseAndInstallFormat, itemName );
purchaseAndInstallContent = products.find(
( { label } ) => label === itemName
)?.description;
} else {
const uniqueProductNames = uniqueItemsList.map( ( { name } ) => name );
const lastProduct = uniqueProductNames.pop();
let firstProducts = uniqueProductNames.join( ', ' );
if ( uniqueProductNames.length > 1 ) {
firstProducts += ',';
}
/* translators: %1$s: list of product names comma separated, %2%s the last product name */
purchaseAndInstallContent = sprintf(
__(
'Good choice! You chose to add %1$s and %2$s to your store.',
'woocommerce-admin'
),
firstProducts,
lastProduct
);
}
const {
automatedTaxSupportedCountries = [],
taxJarActivated,
} = onboardingStatus;
const isTaxJarSupported =
! taxJarActivated && // WCS integration doesn't work with the official TaxJar plugin.
automatedTaxSupportedCountries.includes( countryCode );
const canUseAutomatedTaxes =
hasCompleteAddress && woocommerceServicesActive && isTaxJarSupported;
let taxAction = __( "Let's go", 'woocommerce-admin' );
let taxContent = __(
'Set your store location and configure tax rate settings.',
'woocommerce-admin'
);
if ( canUseAutomatedTaxes ) {
taxAction = __( 'Yes please', 'woocommerce-admin' );
taxContent = __(
'Good news! WooCommerce Services and Jetpack can automate your sales tax calculations for you.',
'woocommerce-admin'
);
} }
const tasks = [ const tasks = [
{ {
key: 'store_details', key: 'store_details',
title: __( 'Store details', 'woocommerce-admin' ), title: __( 'Store details', 'woocommerce-admin' ),
content: __(
'Your store address is required to set the origin country for shipping, currencies, and payment options.',
'woocommerce-admin'
),
container: null, container: null,
action: __( "Let's go", 'woocommerce-admin' ),
onClick: () => { onClick: () => {
onTaskSelect( 'store_details' ); onTaskSelect( 'store_details' );
getHistory().push( getNewPath( {}, '/setup-wizard', {} ) ); getHistory().push( getNewPath( {}, '/setup-wizard', {} ) );
@ -124,8 +178,10 @@ export function getAllTasks( {
}, },
{ {
key: 'purchase', key: 'purchase',
title: purchaseAndInstallText, title: purchaseAndInstallTitle,
content: purchaseAndInstallContent,
container: null, container: null,
action: __( 'Purchase & install now', 'woocommerce-admin' ),
onClick: () => { onClick: () => {
onTaskSelect( 'purchase' ); onTaskSelect( 'purchase' );
return remainingProducts.length ? toggleCartModal() : null; return remainingProducts.length ? toggleCartModal() : null;
@ -139,6 +195,10 @@ export function getAllTasks( {
{ {
key: 'products', key: 'products',
title: __( 'Add my products', 'woocommerce-admin' ), title: __( 'Add my products', 'woocommerce-admin' ),
content: __(
'Start by adding the first product to your store. You can add your products manually, via CSV, or import them from another service.',
'woocommerce-admin'
),
container: <Products />, container: <Products />,
onClick: () => { onClick: () => {
onTaskSelect( 'products' ); onTaskSelect( 'products' );
@ -155,6 +215,12 @@ export function getAllTasks( {
'Get paid with WooCommerce Payments', 'Get paid with WooCommerce Payments',
'woocommerce-admin' 'woocommerce-admin'
), ),
content: __(
"You're only one step away from getting paid. Verify your business details to start managing transactions with WooCommerce Payments.",
'woocommerce-admin'
),
action: __( 'Finish setup', 'woocommmerce-admin' ),
expanded: true,
container: <Fragment />, container: <Fragment />,
completed: wcPayIsConnected, completed: wcPayIsConnected,
onClick: async ( e ) => { onClick: async ( e ) => {
@ -196,6 +262,10 @@ export function getAllTasks( {
{ {
key: 'payments', key: 'payments',
title: __( 'Set up payments', 'woocommerce-admin' ), title: __( 'Set up payments', 'woocommerce-admin' ),
content: __(
'Choose payment providers and enable payment methods at checkout.',
'woocommerce-admin'
),
container: <Payments />, container: <Payments />,
completed: hasPaymentGateway, completed: hasPaymentGateway,
onClick: () => { onClick: () => {
@ -212,10 +282,19 @@ export function getAllTasks( {
{ {
key: 'tax', key: 'tax',
title: __( 'Set up tax', 'woocommerce-admin' ), title: __( 'Set up tax', 'woocommerce-admin' ),
content: taxContent,
container: <Tax />, container: <Tax />,
onClick: () => { action: taxAction,
onClick: ( e, args = {} ) => {
// The expanded item CTA allows us to enable
// automated taxes for eligible stores.
// Note: this will be initially part of an A/B test.
const { isExpanded } = args;
onTaskSelect( 'tax' ); onTaskSelect( 'tax' );
updateQueryString( { task: 'tax' } ); updateQueryString( {
task: 'tax',
auto: canUseAutomatedTaxes && isExpanded,
} );
}, },
completed: isTaxComplete, completed: isTaxComplete,
visible: true, visible: true,
@ -225,7 +304,12 @@ export function getAllTasks( {
{ {
key: 'shipping', key: 'shipping',
title: __( 'Set up shipping', 'woocommerce-admin' ), title: __( 'Set up shipping', 'woocommerce-admin' ),
content: __(
"Set your store location and where you'll ship to.",
'woocommerce-admin'
),
container: <Shipping />, container: <Shipping />,
action: __( "Let's go", 'woocommerce-admin' ),
onClick: () => { onClick: () => {
if ( shippingZonesCount > 0 ) { if ( shippingZonesCount > 0 ) {
window.location = getLinkTypeAndHref( { window.location = getLinkTypeAndHref( {
@ -247,7 +331,12 @@ export function getAllTasks( {
{ {
key: 'appearance', key: 'appearance',
title: __( 'Personalize my store', 'woocommerce-admin' ), title: __( 'Personalize my store', 'woocommerce-admin' ),
content: __(
'Add your logo, create a homepage, and start designing your store.',
'woocommerce-admin'
),
container: <Appearance />, container: <Appearance />,
action: __( "Let's go", 'woocommerce-admin' ),
onClick: () => { onClick: () => {
onTaskSelect( 'appearance' ); onTaskSelect( 'appearance' );
updateQueryString( { task: 'appearance' } ); updateQueryString( { task: 'appearance' } );

View File

@ -46,7 +46,13 @@ class Tax extends Component {
} }
componentDidMount() { componentDidMount() {
const { query } = this.props;
const { auto } = query;
this.reset(); this.reset();
if ( auto === 'true' ) {
this.enableAutomatedTax();
}
} }
reset() { reset() {
@ -396,6 +402,13 @@ class Tax extends Component {
return filter( steps, ( step ) => step.visible ); return filter( steps, ( step ) => step.visible );
} }
enableAutomatedTax() {
recordEvent( 'tasklist_tax_setup_automated_proceed', {
setup_automatically: true,
} );
this.updateAutomatedTax( true );
}
renderSuccessScreen() { renderSuccessScreen() {
const { isPending } = this.props; const { isPending } = this.props;
@ -427,12 +440,7 @@ class Tax extends Component {
disabled={ isPending } disabled={ isPending }
isPrimary isPrimary
isBusy={ isPending } isBusy={ isPending }
onClick={ () => { onClick={ this.enableAutomatedTax }
recordEvent( 'tasklist_tax_setup_automated_proceed', {
setup_automatically: true,
} );
this.updateAutomatedTax( true );
} }
> >
{ __( 'Yes please', 'woocommerce-admin' ) } { __( 'Yes please', 'woocommerce-admin' ) }
</Button> </Button>

View File

@ -1,6 +1,7 @@
# Unreleased # Unreleased
- Remove the use of Dashicons and replace with @wordpress/icons or gridicons #7020 - Remove the use of Dashicons and replace with @wordpress/icons or gridicons #7020
- Add expanded item text and CTA button. #6956
# 1.2.0 # 1.2.0

View File

@ -3,6 +3,7 @@ $task-alert-yellow: #f0b849;
.woocommerce-task-list__item { .woocommerce-task-list__item {
position: relative; position: relative;
padding-right: $gap-large;
&::before { &::before {
content: ''; content: '';
@ -56,6 +57,16 @@ $task-alert-yellow: #f0b849;
} }
} }
.woocommerce-task-list__item-content {
margin-top: $gap-smallest;
margin-bottom: $gap-smallest;
}
.woocommerce-task-list__item-action {
margin-top: $gap-smallest;
margin-bottom: $gap-smaller;
}
.woocommerce-task-list__item-after { .woocommerce-task-list__item-after {
margin-left: $gap; margin-left: $gap;
margin-right: $gap-large; margin-right: $gap-large;
@ -87,7 +98,8 @@ $task-alert-yellow: #f0b849;
color: $gray-700; color: $gray-700;
} }
.woocommerce-task-list__item-content { .woocommerce-task-list__item-content,
.woocommerce-task__estimated-time {
display: none; display: none;
} }
} }

View File

@ -24,17 +24,26 @@ const sanitizeHTML = ( html: string ) => {
type TaskLevel = 1 | 2 | 3; type TaskLevel = 1 | 2 | 3;
type ActionArgs = {
isExpanded?: boolean;
};
type TaskItemProps = { type TaskItemProps = {
title: string; title: string;
completed: boolean; completed: boolean;
onClick: () => void; onClick?: () => void;
isDismissable?: boolean; isDismissable?: boolean;
onDismiss?: () => void; onDismiss?: () => void;
additionalInfo?: string; additionalInfo?: string;
time?: string; time?: string;
content?: string; content: string;
expanded?: boolean; expanded?: boolean;
level?: TaskLevel; level?: TaskLevel;
action: (
event?: React.MouseEvent | React.KeyboardEvent,
args?: ActionArgs
) => void;
actionLabel?: string;
}; };
const OptionalTaskTooltip: React.FC< { const OptionalTaskTooltip: React.FC< {
@ -71,6 +80,8 @@ export const TaskItem: React.FC< TaskItemProps > = ( {
content, content,
expanded = false, expanded = false,
level = 3, level = 3,
action,
actionLabel,
} ) => { } ) => {
const className = classnames( 'woocommerce-task-list__item', { const className = classnames( 'woocommerce-task-list__item', {
complete: completed, complete: completed,
@ -97,32 +108,43 @@ export const TaskItem: React.FC< TaskItemProps > = ( {
</div> </div>
</OptionalTaskTooltip> </OptionalTaskTooltip>
<div className="woocommerce-task-list__item-text"> <div className="woocommerce-task-list__item-text">
<span className="woocommerce-task-list__item-title"> <Text as="div" variant={ completed ? 'body.small' : 'button' }>
<Text <span className="woocommerce-task-list__item-title">
as="div"
variant={ completed ? 'body.small' : 'button' }
>
{ title } { title }
{ additionalInfo && ( </span>
<div { expanded && (
className="woocommerce-task__additional-info" <div className="woocommerce-task-list__item-content">
dangerouslySetInnerHTML={ sanitizeHTML( { content }
additionalInfo </div>
) } ) }
></div> { additionalInfo && (
) } <div
{ expanded && content && ( className="woocommerce-task__additional-info"
<div className="woocommerce-task-list__item-content"> dangerouslySetInnerHTML={ sanitizeHTML(
{ content } additionalInfo
</div> ) }
) } ></div>
{ time && ! completed && ( ) }
<div className="woocommerce-task__estimated-time"> { expanded && ! completed && (
{ time } <Button
</div> className="woocommerce-task-list__item-action"
) } isPrimary
</Text> onClick={ (
</span> event: React.MouseEvent | React.KeyboardEvent
) => {
event.stopPropagation();
action( event, { isExpanded: true } );
} }
>
{ actionLabel || title }
</Button>
) }
{ time && (
<div className="woocommerce-task__estimated-time">
{ time }
</div>
) }
</Text>
</div> </div>
{ onDismiss && isDismissable && ! completed && ( { onDismiss && isDismissable && ! completed && (
<div className="woocommerce-task-list__item-after"> <div className="woocommerce-task-list__item-after">

View File

@ -93,6 +93,7 @@ Release and roadmap notes are available on the [WooCommerce Developers Blog](htt
- Add: Free extension list powered by remote config #6952 - Add: Free extension list powered by remote config #6952
- Add: Add PayPal to fallback payment gateways #7001 - Add: Add PayPal to fallback payment gateways #7001
- Add: Add a data store for WC Payments REST APIs #6918 - Add: Add a data store for WC Payments REST APIs #6918
- Add: Progressive setup checklist copy and call to action buttons. #6956
- Add: Add Paystack as fallback gateway #7025 - Add: Add Paystack as fallback gateway #7025
- Add: Add COD method to default payment gateway recommendations #7057 - Add: Add COD method to default payment gateway recommendations #7057
- Dev: Update package-lock to fix versioning of local packages. #6843 - Dev: Update package-lock to fix versioning of local packages. #6843

View File

@ -42,7 +42,7 @@ export class WcHomescreen extends BasePage {
} }
async clickOnTaskList( taskTitle: string ) { async clickOnTaskList( taskTitle: string ) {
const item = await waitForElementByText( 'div', taskTitle ); const item = await waitForElementByText( 'span', taskTitle );
if ( ! item ) { if ( ! item ) {
throw new Error( throw new Error(