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(
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 installedPlugins = getInstalledPlugins();
@ -65,6 +73,7 @@ const taskDashboardSelect = ( select ) => {
onboardingStatus,
profileItems,
trackedCompletedTasks,
hasCompleteAddress,
};
};
@ -85,6 +94,7 @@ const TaskDashboard = ( { userPreferences, query } ) => {
isTaskListComplete,
isExtendedTaskListHidden,
isExtendedTaskListComplete,
hasCompleteAddress,
} = useSelect( taskDashboardSelect );
const [ isCartModalOpen, setIsCartModalOpen ] = useState( false );
@ -173,6 +183,7 @@ const TaskDashboard = ( { userPreferences, query } ) => {
query,
toggleCartModal,
onTaskSelect,
hasCompleteAddress,
} );
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 {
position: relative;
}

View File

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

View File

@ -56,6 +56,7 @@ export function getAllTasks( {
query,
toggleCartModal,
onTaskSelect,
hasCompleteAddress,
} ) {
const {
hasPaymentGateway,
@ -84,6 +85,8 @@ export function getAllTasks( {
const woocommercePaymentsInstalled =
installedPlugins.indexOf( 'woocommerce-payments' ) !== -1;
const woocommerceServicesActive =
activePlugins.indexOf( 'woocommerce-services' ) !== -1;
const {
completed: profilerCompleted,
product_types: productTypes,
@ -94,10 +97,11 @@ export function getAllTasks( {
businessExtensions || []
).includes( 'woocommerce-payments' );
let purchaseAndInstallText = __(
let purchaseAndInstallTitle = __(
'Add paid extensions to my store',
'woocommerce-admin'
);
let purchaseAndInstallContent;
if ( uniqueItemsList.length === 1 ) {
const { name: itemName } = uniqueItemsList[ 0 ];
@ -105,14 +109,64 @@ export function getAllTasks( {
'Add %s to my store',
'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 = [
{
key: 'store_details',
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,
action: __( "Let's go", 'woocommerce-admin' ),
onClick: () => {
onTaskSelect( 'store_details' );
getHistory().push( getNewPath( {}, '/setup-wizard', {} ) );
@ -124,8 +178,10 @@ export function getAllTasks( {
},
{
key: 'purchase',
title: purchaseAndInstallText,
title: purchaseAndInstallTitle,
content: purchaseAndInstallContent,
container: null,
action: __( 'Purchase & install now', 'woocommerce-admin' ),
onClick: () => {
onTaskSelect( 'purchase' );
return remainingProducts.length ? toggleCartModal() : null;
@ -139,6 +195,10 @@ export function getAllTasks( {
{
key: 'products',
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 />,
onClick: () => {
onTaskSelect( 'products' );
@ -155,6 +215,12 @@ export function getAllTasks( {
'Get paid with WooCommerce Payments',
'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 />,
completed: wcPayIsConnected,
onClick: async ( e ) => {
@ -196,6 +262,10 @@ export function getAllTasks( {
{
key: 'payments',
title: __( 'Set up payments', 'woocommerce-admin' ),
content: __(
'Choose payment providers and enable payment methods at checkout.',
'woocommerce-admin'
),
container: <Payments />,
completed: hasPaymentGateway,
onClick: () => {
@ -212,10 +282,19 @@ export function getAllTasks( {
{
key: 'tax',
title: __( 'Set up tax', 'woocommerce-admin' ),
content: taxContent,
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' );
updateQueryString( { task: 'tax' } );
updateQueryString( {
task: 'tax',
auto: canUseAutomatedTaxes && isExpanded,
} );
},
completed: isTaxComplete,
visible: true,
@ -225,7 +304,12 @@ export function getAllTasks( {
{
key: 'shipping',
title: __( 'Set up shipping', 'woocommerce-admin' ),
content: __(
"Set your store location and where you'll ship to.",
'woocommerce-admin'
),
container: <Shipping />,
action: __( "Let's go", 'woocommerce-admin' ),
onClick: () => {
if ( shippingZonesCount > 0 ) {
window.location = getLinkTypeAndHref( {
@ -247,7 +331,12 @@ export function getAllTasks( {
{
key: 'appearance',
title: __( 'Personalize my store', 'woocommerce-admin' ),
content: __(
'Add your logo, create a homepage, and start designing your store.',
'woocommerce-admin'
),
container: <Appearance />,
action: __( "Let's go", 'woocommerce-admin' ),
onClick: () => {
onTaskSelect( 'appearance' );
updateQueryString( { task: 'appearance' } );

View File

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

View File

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

View File

@ -3,6 +3,7 @@ $task-alert-yellow: #f0b849;
.woocommerce-task-list__item {
position: relative;
padding-right: $gap-large;
&::before {
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 {
margin-left: $gap;
margin-right: $gap-large;
@ -87,7 +98,8 @@ $task-alert-yellow: #f0b849;
color: $gray-700;
}
.woocommerce-task-list__item-content {
.woocommerce-task-list__item-content,
.woocommerce-task__estimated-time {
display: none;
}
}

View File

@ -24,17 +24,26 @@ const sanitizeHTML = ( html: string ) => {
type TaskLevel = 1 | 2 | 3;
type ActionArgs = {
isExpanded?: boolean;
};
type TaskItemProps = {
title: string;
completed: boolean;
onClick: () => void;
onClick?: () => void;
isDismissable?: boolean;
onDismiss?: () => void;
additionalInfo?: string;
time?: string;
content?: string;
content: string;
expanded?: boolean;
level?: TaskLevel;
action: (
event?: React.MouseEvent | React.KeyboardEvent,
args?: ActionArgs
) => void;
actionLabel?: string;
};
const OptionalTaskTooltip: React.FC< {
@ -71,6 +80,8 @@ export const TaskItem: React.FC< TaskItemProps > = ( {
content,
expanded = false,
level = 3,
action,
actionLabel,
} ) => {
const className = classnames( 'woocommerce-task-list__item', {
complete: completed,
@ -97,32 +108,43 @@ export const TaskItem: React.FC< TaskItemProps > = ( {
</div>
</OptionalTaskTooltip>
<div className="woocommerce-task-list__item-text">
<span className="woocommerce-task-list__item-title">
<Text
as="div"
variant={ completed ? 'body.small' : 'button' }
>
<Text as="div" variant={ completed ? 'body.small' : 'button' }>
<span className="woocommerce-task-list__item-title">
{ title }
{ additionalInfo && (
<div
className="woocommerce-task__additional-info"
dangerouslySetInnerHTML={ sanitizeHTML(
additionalInfo
) }
></div>
) }
{ expanded && content && (
<div className="woocommerce-task-list__item-content">
{ content }
</div>
) }
{ time && ! completed && (
<div className="woocommerce-task__estimated-time">
{ time }
</div>
) }
</Text>
</span>
</span>
{ expanded && (
<div className="woocommerce-task-list__item-content">
{ content }
</div>
) }
{ additionalInfo && (
<div
className="woocommerce-task__additional-info"
dangerouslySetInnerHTML={ sanitizeHTML(
additionalInfo
) }
></div>
) }
{ expanded && ! completed && (
<Button
className="woocommerce-task-list__item-action"
isPrimary
onClick={ (
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>
{ onDismiss && isDismissable && ! completed && (
<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: Add PayPal to fallback payment gateways #7001
- 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 COD method to default payment gateway recommendations #7057
- 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 ) {
const item = await waitForElementByText( 'div', taskTitle );
const item = await waitForElementByText( 'span', taskTitle );
if ( ! item ) {
throw new Error(