Merge pull request #32538 from woocommerce/feature/32158_complete_task_list_card_with_feedback
Feature/32158 complete task list card with feedback
This commit is contained in:
commit
e822a4a7f4
|
@ -0,0 +1,4 @@
|
|||
Significance: minor
|
||||
Type: add
|
||||
|
||||
Add new simple customer feedback component for inline CES feedback. #32538
|
|
@ -0,0 +1,4 @@
|
|||
Significance: minor
|
||||
Type: dev
|
||||
|
||||
Add TypeScript type support as part of the build process. #32538
|
|
@ -18,6 +18,7 @@
|
|||
},
|
||||
"main": "build/index.js",
|
||||
"module": "build-module/index.js",
|
||||
"types": "build-types",
|
||||
"react-native": "src/index",
|
||||
"dependencies": {
|
||||
"@woocommerce/experimental": "workspace:*",
|
||||
|
|
|
@ -4,13 +4,12 @@
|
|||
import { createElement, useState, useEffect } from '@wordpress/element';
|
||||
import PropTypes from 'prop-types';
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { compose } from '@wordpress/compose';
|
||||
import { withDispatch } from '@wordpress/data';
|
||||
import { useDispatch } from '@wordpress/data';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import CustomerFeedbackModal from './customer-feedback-modal';
|
||||
import { CustomerFeedbackModal } from './customer-feedback-modal';
|
||||
|
||||
const noop = () => {};
|
||||
|
||||
|
@ -23,16 +22,14 @@ const noop = () => {};
|
|||
* @param {Object} props Component props.
|
||||
* @param {Function} props.recordScoreCallback Function to call when the score should be recorded.
|
||||
* @param {string} props.label The label displayed in the modal.
|
||||
* @param {Function} props.createNotice Create a notice (snackbar).
|
||||
* @param {Function} props.onNoticeShownCallback Function to call when the notice is shown.
|
||||
* @param {Function} props.onNoticeDismissedCallback Function to call when the notice is dismissed.
|
||||
* @param {Function} props.onModalShownCallback Function to call when the modal is shown.
|
||||
* @param {Object} props.icon Icon (React component) to be shown on the notice.
|
||||
*/
|
||||
export function CustomerEffortScore( {
|
||||
function CustomerEffortScore( {
|
||||
recordScoreCallback,
|
||||
label,
|
||||
createNotice,
|
||||
onNoticeShownCallback = noop,
|
||||
onNoticeDismissedCallback = noop,
|
||||
onModalShownCallback = noop,
|
||||
|
@ -40,6 +37,7 @@ export function CustomerEffortScore( {
|
|||
} ) {
|
||||
const [ shouldCreateNotice, setShouldCreateNotice ] = useState( true );
|
||||
const [ visible, setVisible ] = useState( false );
|
||||
const { createNotice } = useDispatch( 'core/notices2' );
|
||||
|
||||
useEffect( () => {
|
||||
if ( ! shouldCreateNotice ) {
|
||||
|
@ -91,10 +89,6 @@ CustomerEffortScore.propTypes = {
|
|||
* The label displayed in the modal.
|
||||
*/
|
||||
label: PropTypes.string.isRequired,
|
||||
/**
|
||||
* Create a notice (snackbar).
|
||||
*/
|
||||
createNotice: PropTypes.func.isRequired,
|
||||
/**
|
||||
* The function to call when the notice is shown.
|
||||
*/
|
||||
|
@ -113,12 +107,4 @@ CustomerEffortScore.propTypes = {
|
|||
icon: PropTypes.element,
|
||||
};
|
||||
|
||||
export default compose(
|
||||
withDispatch( ( dispatch ) => {
|
||||
const { createNotice } = dispatch( 'core/notices2' );
|
||||
|
||||
return {
|
||||
createNotice,
|
||||
};
|
||||
} )
|
||||
)( CustomerEffortScore );
|
||||
export { CustomerEffortScore };
|
|
@ -26,13 +26,19 @@ import { __ } from '@wordpress/i18n';
|
|||
* @param {Object} props Component props.
|
||||
* @param {Function} props.recordScoreCallback Function to call when the results are sent.
|
||||
* @param {string} props.label Question to ask the customer.
|
||||
* @param {string} props.defaultScore Default score.
|
||||
* @param {Function} props.onCloseModal Callback for when user closes modal by clicking cancel.
|
||||
*/
|
||||
function CustomerFeedbackModal( {
|
||||
recordScoreCallback,
|
||||
label,
|
||||
defaultScore = NaN,
|
||||
onCloseModal,
|
||||
}: {
|
||||
recordScoreCallback: ( score: number, comments: string ) => void;
|
||||
label: string;
|
||||
defaultScore?: number;
|
||||
onCloseModal?: () => void;
|
||||
} ): JSX.Element | null {
|
||||
const options = [
|
||||
{
|
||||
|
@ -57,12 +63,17 @@ function CustomerFeedbackModal( {
|
|||
},
|
||||
];
|
||||
|
||||
const [ score, setScore ] = useState( NaN );
|
||||
const [ score, setScore ] = useState( defaultScore || NaN );
|
||||
const [ comments, setComments ] = useState( '' );
|
||||
const [ showNoScoreMessage, setShowNoScoreMessage ] = useState( false );
|
||||
const [ isOpen, setOpen ] = useState( true );
|
||||
|
||||
const closeModal = () => setOpen( false );
|
||||
const closeModal = () => {
|
||||
setOpen( false );
|
||||
if ( onCloseModal ) {
|
||||
onCloseModal();
|
||||
}
|
||||
};
|
||||
|
||||
const onRadioControlChange = ( value: string ) => {
|
||||
const valueAsInt = parseInt( value, 10 );
|
||||
|
@ -111,7 +122,7 @@ function CustomerFeedbackModal( {
|
|||
{ ( score === 1 || score === 2 ) && (
|
||||
<div className="woocommerce-customer-effort-score__comments">
|
||||
<TextareaControl
|
||||
label={ __( 'Comments (Optional)', 'woocommerce' ) }
|
||||
label={ __( 'Comments (optional)', 'woocommerce' ) }
|
||||
help={ __(
|
||||
'Your feedback will go to the WooCommerce development team',
|
||||
'woocommerce'
|
||||
|
@ -152,6 +163,8 @@ function CustomerFeedbackModal( {
|
|||
CustomerFeedbackModal.propTypes = {
|
||||
recordScoreCallback: PropTypes.func.isRequired,
|
||||
label: PropTypes.string.isRequired,
|
||||
defaultScore: PropTypes.number,
|
||||
onCloseModal: PropTypes.func,
|
||||
};
|
||||
|
||||
export default CustomerFeedbackModal;
|
||||
export { CustomerFeedbackModal };
|
||||
|
|
|
@ -7,7 +7,7 @@ import { createElement } from '@wordpress/element';
|
|||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import CustomerFeedbackModal from '../index';
|
||||
import { CustomerFeedbackModal } from '../index';
|
||||
|
||||
const mockRecordScoreCallback = jest.fn();
|
||||
|
||||
|
@ -59,7 +59,7 @@ describe( 'CustomerFeedbackModal', () => {
|
|||
await screen.findByRole( 'dialog' );
|
||||
|
||||
expect(
|
||||
screen.queryByLabelText( 'Comments (Optional)' )
|
||||
screen.queryByLabelText( 'Comments (optional)' )
|
||||
).not.toBeInTheDocument();
|
||||
} );
|
||||
|
||||
|
@ -80,7 +80,7 @@ describe( 'CustomerFeedbackModal', () => {
|
|||
fireEvent.click( screen.getByLabelText( labelText ) );
|
||||
|
||||
// Wait for comments field to show.
|
||||
await screen.findByLabelText( 'Comments (Optional)' );
|
||||
await screen.findByLabelText( 'Comments (optional)' );
|
||||
|
||||
// Select neutral score.
|
||||
fireEvent.click( screen.getByLabelText( 'Neutral' ) );
|
||||
|
@ -88,7 +88,7 @@ describe( 'CustomerFeedbackModal', () => {
|
|||
// Wait for comments field to hide.
|
||||
await waitFor( () => {
|
||||
expect(
|
||||
screen.queryByLabelText( 'Comments (Optional)' )
|
||||
screen.queryByLabelText( 'Comments (optional)' )
|
||||
).not.toBeInTheDocument();
|
||||
} );
|
||||
}
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
.customer-feedback-simple__container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
.components-button {
|
||||
line-height: 32px;
|
||||
font-size: 20px;
|
||||
border-radius: 3px;
|
||||
padding: 6px;
|
||||
|
||||
&:hover {
|
||||
background-color: $gray-100;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.customer-feedback-simple__selection {
|
||||
margin-left: $gap-small;
|
||||
}
|
|
@ -0,0 +1,94 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { createElement } from '@wordpress/element';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Button, Tooltip } from '@wordpress/components';
|
||||
import { Text } from '@woocommerce/experimental';
|
||||
import { __ } from '@wordpress/i18n';
|
||||
|
||||
type CustomerFeedbackSimpleProps = {
|
||||
onSelect: ( score: number ) => void;
|
||||
label: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* Provides a modal requesting customer feedback.
|
||||
*
|
||||
* A label is displayed in the modal asking the customer to score the
|
||||
* difficulty completing a task. A group of radio buttons, styled with
|
||||
* emoji facial expressions, are used to provide a score between 1 and 5.
|
||||
*
|
||||
* A low score triggers a comments field to appear.
|
||||
*
|
||||
* Upon completion, the score and comments is sent to a callback function.
|
||||
*
|
||||
* @param {Object} props Component props.
|
||||
* @param {Function} props.onSelect Function to call when the results are sent.
|
||||
* @param {string} props.label Question to ask the customer.
|
||||
*/
|
||||
const CustomerFeedbackSimple: React.FC< CustomerFeedbackSimpleProps > = ( {
|
||||
onSelect,
|
||||
label,
|
||||
} ) => {
|
||||
const options = [
|
||||
{
|
||||
tooltip: __( 'Very difficult', 'woocommerce' ),
|
||||
value: 1,
|
||||
emoji: '😞',
|
||||
},
|
||||
{
|
||||
tooltip: __( 'Difficult', 'woocommerce' ),
|
||||
value: 2,
|
||||
emoji: '🙁',
|
||||
},
|
||||
{
|
||||
tooltip: __( 'Neutral', 'woocommerce' ),
|
||||
value: 3,
|
||||
emoji: '😑',
|
||||
},
|
||||
{
|
||||
tooltip: __( 'Good', 'woocommerce' ),
|
||||
value: 4,
|
||||
emoji: '🙂',
|
||||
},
|
||||
{
|
||||
tooltip: __( 'Very good', 'woocommerce' ),
|
||||
value: 5,
|
||||
emoji: '😍',
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<div className="customer-feedback-simple__container">
|
||||
<Text variant="subtitle.small" as="p" size="13" lineHeight="16px">
|
||||
{ label }
|
||||
</Text>
|
||||
|
||||
<div className="customer-feedback-simple__selection">
|
||||
{ options.map( ( option ) => (
|
||||
<Tooltip
|
||||
text={ option.tooltip }
|
||||
key={ option.value }
|
||||
position="top center"
|
||||
>
|
||||
<Button
|
||||
onClick={ () => {
|
||||
onSelect( option.value );
|
||||
} }
|
||||
>
|
||||
{ option.emoji }
|
||||
</Button>
|
||||
</Tooltip>
|
||||
) ) }
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
CustomerFeedbackSimple.propTypes = {
|
||||
onSelect: PropTypes.func.isRequired,
|
||||
label: PropTypes.string.isRequired,
|
||||
};
|
||||
|
||||
export { CustomerFeedbackSimple };
|
|
@ -0,0 +1 @@
|
|||
export * from './customer-feedback-simple';
|
|
@ -0,0 +1,23 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { render, screen, fireEvent } from '@testing-library/react';
|
||||
import { createElement } from '@wordpress/element';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { CustomerFeedbackSimple } from '../index';
|
||||
|
||||
const mockOnSelectCallback = jest.fn();
|
||||
|
||||
describe( 'CustomerFeedbackSimple', () => {
|
||||
it( 'should trigger recordScoreCallback when item is selected', () => {
|
||||
render( <CustomerFeedbackSimple onSelect={ mockOnSelectCallback } /> );
|
||||
|
||||
// Select the option.
|
||||
fireEvent.click( screen.getAllByText( '🙂' )[ 0 ] );
|
||||
|
||||
expect( mockOnSelectCallback ).toHaveBeenCalledWith( 4 );
|
||||
} );
|
||||
} );
|
|
@ -0,0 +1,3 @@
|
|||
export * from './customer-effort-score';
|
||||
export * from './customer-feedback-simple';
|
||||
export * from './customer-feedback-modal';
|
|
@ -1,3 +1,5 @@
|
|||
@import 'customer-feedback-simple/customer-feedback-simple.scss';
|
||||
|
||||
.woocommerce-customer-effort-score__selection {
|
||||
margin: 1em 0;
|
||||
|
||||
|
@ -68,7 +70,7 @@
|
|||
}
|
||||
|
||||
input[value='3'] + label::before {
|
||||
content: '😐';
|
||||
content: '😑';
|
||||
}
|
||||
|
||||
input[value='4'] + label::before {
|
||||
|
@ -76,7 +78,7 @@
|
|||
}
|
||||
|
||||
input[value='5'] + label::before {
|
||||
content: '😁';
|
||||
content: '😍';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,22 +3,37 @@
|
|||
*/
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import { createElement } from '@wordpress/element';
|
||||
import { useDispatch } from '@wordpress/data';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { CustomerEffortScore } from '../index';
|
||||
import { CustomerEffortScore } from '../customer-effort-score';
|
||||
|
||||
const noop = () => {};
|
||||
|
||||
jest.mock( '@wordpress/data', () => {
|
||||
const originalModule = jest.requireActual( '@wordpress/data' );
|
||||
|
||||
return {
|
||||
__esModule: true,
|
||||
...originalModule,
|
||||
useDispatch: jest.fn().mockReturnValue( {
|
||||
createNotice: jest.fn(),
|
||||
} ),
|
||||
};
|
||||
} );
|
||||
|
||||
describe( 'CustomerEffortScore', () => {
|
||||
it( 'should call createNotice with appropriate parameters', async () => {
|
||||
const mockCreateNotice = jest.fn();
|
||||
useDispatch.mockReturnValue( {
|
||||
createNotice: mockCreateNotice,
|
||||
} );
|
||||
const icon = <span>icon</span>;
|
||||
|
||||
render(
|
||||
<CustomerEffortScore
|
||||
createNotice={ mockCreateNotice }
|
||||
recordScoreCallback={ noop }
|
||||
label={ 'label' }
|
||||
onNoticeDismissedCallback={ noop }
|
||||
|
@ -41,6 +56,9 @@ describe( 'CustomerEffortScore', () => {
|
|||
|
||||
it( 'should not call createNotice on rerender', async () => {
|
||||
const mockCreateNotice = jest.fn();
|
||||
useDispatch.mockReturnValue( {
|
||||
createNotice: mockCreateNotice,
|
||||
} );
|
||||
|
||||
const { rerender } = render(
|
||||
<CustomerEffortScore
|
||||
|
@ -53,7 +71,6 @@ describe( 'CustomerEffortScore', () => {
|
|||
// Simulate rerender by changing label prop.
|
||||
rerender(
|
||||
<CustomerEffortScore
|
||||
createNotice={ mockCreateNotice }
|
||||
recordScoreCallback={ noop }
|
||||
label={ 'label2' }
|
||||
/>
|
||||
|
@ -65,7 +82,6 @@ describe( 'CustomerEffortScore', () => {
|
|||
it( 'should not show dialog if no action is taken', async () => {
|
||||
render(
|
||||
<CustomerEffortScore
|
||||
createNotice={ noop }
|
||||
recordScoreCallback={ noop }
|
||||
label={ 'label' }
|
||||
/>
|
||||
|
@ -91,10 +107,12 @@ describe( 'CustomerEffortScore', () => {
|
|||
// Modal shown callback should also be called.
|
||||
expect( mockOnModalShownCallback ).toHaveBeenCalled();
|
||||
};
|
||||
useDispatch.mockReturnValue( {
|
||||
createNotice,
|
||||
} );
|
||||
|
||||
render(
|
||||
<CustomerEffortScore
|
||||
createNotice={ createNotice }
|
||||
recordScoreCallback={ noop }
|
||||
label={ 'label' }
|
||||
onModalShownCallback={ mockOnModalShownCallback }
|
||||
|
|
|
@ -2,6 +2,9 @@
|
|||
"extends": "../tsconfig",
|
||||
"compilerOptions": {
|
||||
"rootDir": "src",
|
||||
"outDir": "build-module"
|
||||
"outDir": "build-module",
|
||||
"declaration": true,
|
||||
"declarationMap": true,
|
||||
"declarationDir": "./build-types"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
Significance: minor
|
||||
Type: enhancement
|
||||
|
||||
Added TypeScript options selectors and action in onboarding store for keeping the completed task list. #32158
|
|
@ -105,6 +105,7 @@ import { WPDataSelectors } from './types';
|
|||
import { PaymentSelectors } from './payment-gateways/selectors';
|
||||
import { PluginSelectors } from './plugins/selectors';
|
||||
import { OnboardingSelectors } from './onboarding/selectors';
|
||||
import { OptionsSelectors } from './options/types';
|
||||
|
||||
// As we add types to all the package selectors we can fill out these unknown types with real ones. See one
|
||||
// of the already typed selectors for an example of how you can do this.
|
||||
|
@ -121,7 +122,7 @@ export type WCSelectorType< T > = T extends typeof REVIEWS_STORE_NAME
|
|||
: T extends typeof USER_STORE_NAME
|
||||
? WPDataSelectors
|
||||
: T extends typeof OPTIONS_STORE_NAME
|
||||
? WPDataSelectors
|
||||
? OptionsSelectors
|
||||
: T extends typeof NAVIGATION_STORE_NAME
|
||||
? WPDataSelectors
|
||||
: T extends typeof NOTES_STORE_NAME
|
||||
|
|
|
@ -34,6 +34,8 @@ const TYPES = {
|
|||
ACTION_TASK_REQUEST: 'ACTION_TASK_REQUEST',
|
||||
ACTION_TASK_SUCCESS: 'ACTION_TASK_SUCCESS',
|
||||
VISITED_TASK: 'VISITED_TASK',
|
||||
KEEP_COMPLETED_TASKS_REQUEST: 'KEEP_COMPLETED_TASKS_REQUEST',
|
||||
KEEP_COMPLETED_TASKS_SUCCESS: 'KEEP_COMPLETED_TASKS_SUCCESS',
|
||||
};
|
||||
|
||||
export default TYPES;
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
* External dependencies
|
||||
*/
|
||||
import { apiFetch } from '@wordpress/data-controls';
|
||||
import { controls } from '@wordpress/data';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
|
@ -9,6 +10,7 @@ import { apiFetch } from '@wordpress/data-controls';
|
|||
import TYPES from './action-types';
|
||||
import { WC_ADMIN_NAMESPACE } from '../constants';
|
||||
import { DeprecatedTasks } from './deprecated-tasks';
|
||||
import { STORE_NAME as OPTIONS_STORE_NAME } from '../options/constants';
|
||||
|
||||
export function getFreeExtensionsError( error ) {
|
||||
return {
|
||||
|
@ -203,6 +205,14 @@ export function optimisticallyCompleteTaskRequest( taskId ) {
|
|||
};
|
||||
}
|
||||
|
||||
export function keepCompletedTaskListSuccess( taskListId, keepCompletedList ) {
|
||||
return {
|
||||
type: TYPES.KEEP_COMPLETED_TASKS_SUCCESS,
|
||||
taskListId,
|
||||
keepCompletedTaskList: keepCompletedList,
|
||||
};
|
||||
}
|
||||
|
||||
export function visitedTask( taskId ) {
|
||||
return {
|
||||
type: TYPES.VISITED_TASK,
|
||||
|
@ -260,6 +270,20 @@ export function getProductTypesError( error ) {
|
|||
};
|
||||
}
|
||||
|
||||
export function* keepCompletedTaskList( taskListId ) {
|
||||
const updateOptionsParams = {
|
||||
woocommerce_task_list_keep_completed: 'yes',
|
||||
};
|
||||
const response = yield controls.dispatch(
|
||||
OPTIONS_STORE_NAME,
|
||||
'updateOptions',
|
||||
updateOptionsParams
|
||||
);
|
||||
if ( response && response.success ) {
|
||||
yield keepCompletedTaskListSuccess( taskListId, 'yes' );
|
||||
}
|
||||
}
|
||||
|
||||
export function* updateProfileItems( items ) {
|
||||
yield setIsRequesting( 'updateProfileItems', true );
|
||||
yield setError( 'updateProfileItems', null );
|
||||
|
|
|
@ -72,6 +72,7 @@ const onboarding = (
|
|||
taskListId,
|
||||
taskList,
|
||||
taskLists,
|
||||
keepCompletedTaskList,
|
||||
}
|
||||
) => {
|
||||
switch ( type ) {
|
||||
|
@ -372,6 +373,17 @@ const onboarding = (
|
|||
[ taskListId ]: taskList,
|
||||
},
|
||||
};
|
||||
case TYPES.KEEP_COMPLETED_TASKS_SUCCESS:
|
||||
return {
|
||||
...state,
|
||||
taskLists: {
|
||||
...state.taskLists,
|
||||
[ taskListId ]: {
|
||||
...state.taskLists[ taskListId ],
|
||||
keepCompletedTaskList,
|
||||
},
|
||||
},
|
||||
};
|
||||
case TYPES.OPTIMISTICALLY_COMPLETE_TASK_REQUEST:
|
||||
return {
|
||||
...state,
|
||||
|
|
|
@ -51,6 +51,7 @@ export type TaskListType = {
|
|||
eventPrefix: string;
|
||||
displayProgressHeader: boolean;
|
||||
keepCompletedTaskList: 'yes' | 'no';
|
||||
showCESFeedback?: boolean;
|
||||
sections?: TaskListSection[];
|
||||
isToggleable?: boolean;
|
||||
isCollapsible?: boolean;
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { WPDataSelector, WPDataSelectors } from '../types';
|
||||
import {
|
||||
getOptionsRequestingError,
|
||||
isOptionsUpdating,
|
||||
getOptionsUpdatingError,
|
||||
} from './selectors';
|
||||
|
||||
export type OptionsSelectors = {
|
||||
getOption: < T = string >( option: string ) => T;
|
||||
// getOption: getOption;
|
||||
getOptionsRequestingError: WPDataSelector<
|
||||
typeof getOptionsRequestingError
|
||||
>;
|
||||
isOptionsUpdating: WPDataSelector< typeof isOptionsUpdating >;
|
||||
getOptionsUpdatingError: WPDataSelector< typeof getOptionsUpdatingError >;
|
||||
} & WPDataSelectors;
|
|
@ -4,7 +4,7 @@
|
|||
import { useState } from '@wordpress/element';
|
||||
import PropTypes from 'prop-types';
|
||||
import { recordEvent } from '@woocommerce/tracks';
|
||||
import CustomerEffortScore from '@woocommerce/customer-effort-score';
|
||||
import { CustomerEffortScore } from '@woocommerce/customer-effort-score';
|
||||
import { compose } from '@wordpress/compose';
|
||||
import { withSelect, withDispatch } from '@wordpress/data';
|
||||
import { OPTIONS_STORE_NAME, WEEK } from '@woocommerce/data';
|
||||
|
@ -117,7 +117,7 @@ function CustomerEffortScoreTracks( {
|
|||
|
||||
return (
|
||||
<CustomerEffortScore
|
||||
recordScoreCallback={ recordScore }
|
||||
onSelect={ recordScore }
|
||||
label={ label }
|
||||
onNoticeShownCallback={ onNoticeShown }
|
||||
onNoticeDismissedCallback={ onNoticeDismissed }
|
||||
|
|
|
@ -26,6 +26,10 @@ export type TaskListProps = TaskListType & {
|
|||
query: {
|
||||
task?: string;
|
||||
};
|
||||
eventName?: string;
|
||||
twoColumns?: boolean;
|
||||
keepCompletedTaskList?: 'yes' | 'no';
|
||||
cesHeader?: boolean;
|
||||
};
|
||||
|
||||
export const TaskList: React.FC< TaskListProps > = ( {
|
||||
|
|
|
@ -11,6 +11,7 @@ import {
|
|||
OPTIONS_STORE_NAME,
|
||||
TaskListType,
|
||||
TaskType,
|
||||
WCDataSelector,
|
||||
} from '@woocommerce/data';
|
||||
import { useExperiment } from '@woocommerce/explat';
|
||||
import { recordEvent } from '@woocommerce/tracks';
|
||||
|
@ -66,14 +67,16 @@ export const Tasks: React.FC< TasksProps > = ( { query } ) => {
|
|||
'woocommerce_tasklist_progression'
|
||||
);
|
||||
|
||||
const { isResolving, taskLists } = useSelect( ( select ) => {
|
||||
const { isResolving, taskLists } = useSelect(
|
||||
( select: WCDataSelector ) => {
|
||||
return {
|
||||
isResolving: ! select(
|
||||
ONBOARDING_STORE_NAME
|
||||
).hasFinishedResolution( 'getTaskLists' ),
|
||||
taskLists: select( ONBOARDING_STORE_NAME ).getTaskLists(),
|
||||
};
|
||||
} );
|
||||
}
|
||||
);
|
||||
|
||||
const getCurrentTask = () => {
|
||||
if ( ! task ) {
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
<svg width="453" height="73" viewBox="0 0 453 73" fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg">
|
||||
<rect width="3.88561" height="12.2119" rx="1.94281" transform="matrix(-0.825943 0.563754 0.563755 0.825942 404.047 48.594)" fill="#64CA43"/>
|
||||
<rect width="3.05264" height="9.59401" rx="1.52632" transform="matrix(-0.672177 0.740391 0.740391 0.672176 417.97 13.4998)" fill="#FF2D55"/>
|
||||
<rect width="3.64276" height="11.4487" rx="1.82138" transform="matrix(-0.6382 -0.769871 -0.769872 0.638198 391.795 24.9504)" fill="#117AC9"/>
|
||||
<rect width="3.88561" height="12.2119" rx="1.9428" transform="matrix(-0.404372 -0.914595 -0.914595 0.404371 170.94 14.6914)" fill="#FF8085"/>
|
||||
<rect width="5.34271" height="16.7914" rx="2.67136" transform="matrix(0.39264 0.919692 0.919692 -0.392642 328.119 50.6055)" fill="#FF8085"/>
|
||||
<circle r="3.43422" transform="matrix(-0.949193 -0.314694 -0.314694 0.949193 433.694 60.544)" fill="#F0B849"/>
|
||||
<ellipse rx="2.28948" ry="2.28948" transform="matrix(-0.949193 -0.314695 -0.314693 0.949194 449.993 44.0008)" fill="#BF5AF2"/>
|
||||
<ellipse rx="1.52632" ry="1.52632" transform="matrix(-0.949194 -0.314692 -0.314695 0.949193 373.339 63.31)" fill="#BF5AF2"/>
|
||||
<ellipse rx="2.28948" ry="2.28948" transform="matrix(-0.949194 -0.314692 -0.314695 0.949193 160.713 54.097)" fill="#09B585"/>
|
||||
<rect x="314.273" y="17.2192" width="5.34271" height="16.7914" rx="2.67136" transform="rotate(-51.7958 314.273 17.2192)" fill="#984A9C"/>
|
||||
<rect width="3.88561" height="12.2119" rx="1.9428" transform="matrix(0.618465 -0.785812 0.78581 0.618467 27.061 34.741)" fill="#64CA43"/>
|
||||
<rect width="3.64276" height="11.4487" rx="1.82138" transform="matrix(-0.988881 -0.148711 0.148714 -0.98888 267.602 27.863)" fill="#E7C037"/>
|
||||
<rect width="3.00682" height="9.45" rx="1.50341" transform="matrix(0.226971 0.973902 -0.973902 0.226968 212.204 51)" fill="#E7C037"/>
|
||||
<rect width="3.88561" height="12.2119" rx="1.9428" transform="matrix(0.78581 0.618468 -0.618465 0.785812 269.396 56.8789)" fill="#3361CC"/>
|
||||
<circle cx="90.527" cy="45.6926" r="3.43422" transform="rotate(-1.79578 90.527 45.6926)" fill="#F0B849"/>
|
||||
<circle cx="59.8596" cy="27.1158" r="2.28948" transform="rotate(-1.79576 59.8596 27.1158)" fill="#BF5AF2"/>
|
||||
<circle cx="307.109" cy="60.7663" r="1.52632" transform="rotate(-1.79574 307.109 60.7663)" fill="#F0C930"/>
|
||||
<circle cx="357.311" cy="28.5444" r="1.52632" transform="rotate(-1.79574 357.311 28.5444)" fill="#F0C930"/>
|
||||
<ellipse cx="237.248" cy="47.3674" rx="1.52632" ry="1.52632" transform="rotate(-1.79578 237.248 47.3674)" fill="#3361CC"/>
|
||||
<circle cx="290.869" cy="39.9329" r="1.9079" transform="rotate(-1.79577 290.869 39.9329)" fill="#37E688"/>
|
||||
<rect width="3.88561" height="12.2119" rx="1.9428" transform="matrix(0.336735 -0.941599 0.941599 0.336737 108.684 60.751)" fill="#64CA43"/>
|
||||
<rect x="131.252" y="25.1282" width="3.88561" height="12.2119" rx="1.9428" transform="rotate(5.81869 131.252 25.1282)" fill="#3361CC"/>
|
||||
<ellipse rx="3.43422" ry="3.43422" transform="matrix(0.827262 -0.561816 0.561811 0.827266 21.4569 67.7751)" fill="#F0B849"/>
|
||||
<circle cx="195.819" cy="33.1654" r="2.28948" transform="rotate(-34.1813 195.819 33.1654)" fill="#BF5AF2"/>
|
||||
<circle r="1.52632" transform="matrix(0.827266 -0.56181 0.561818 0.827261 64.254 65.9745)" fill="#3361CC"/>
|
||||
<ellipse rx="1.9079" ry="1.9079" transform="matrix(0.827265 -0.561812 0.561815 0.827263 2.58724 48.3031)" fill="#37E688"/>
|
||||
<ellipse rx="1.9079" ry="1.9079" transform="matrix(0.827265 -0.561812 0.561815 0.827263 27.9769 15.6493)" fill="#F0C930"/>
|
||||
<ellipse cx="231.367" cy="21.336" rx="2.28948" ry="2.28948" transform="rotate(-34.1813 231.367 21.336)" fill="#09B585"/>
|
||||
<ellipse rx="2.28948" ry="2.28948" transform="matrix(0.827267 -0.561809 0.561819 0.82726 100.164 15.4271)" fill="#FF3B30"/>
|
||||
</svg>
|
After Width: | Height: | Size: 3.8 KiB |
|
@ -0,0 +1,27 @@
|
|||
$ces-feedback-height: 64px;
|
||||
|
||||
.wooocommerce-task-card__header .wooocommerce-task-card__header-subtitle {
|
||||
color: $gray-700;
|
||||
margin-bottom: $gap-large;
|
||||
}
|
||||
|
||||
.wooocommerce-task-card__finished-header-image {
|
||||
max-width: 75%;
|
||||
}
|
||||
|
||||
.customer-feedback-simple__container {
|
||||
height: $ces-feedback-height;
|
||||
}
|
||||
|
||||
.woocommerce-task-card__header-menu {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
}
|
||||
|
||||
.wooocommerce-task-card__header-ces-feedback {
|
||||
height: $ces-feedback-height;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
|
@ -0,0 +1,249 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import classnames from 'classnames';
|
||||
import { useEffect, useState } from '@wordpress/element';
|
||||
import { EllipsisMenu } from '@woocommerce/components';
|
||||
import { recordEvent } from '@woocommerce/tracks';
|
||||
import { useDispatch, useSelect } from '@wordpress/data';
|
||||
import { OPTIONS_STORE_NAME, WCDataSelector, WEEK } from '@woocommerce/data';
|
||||
import { Button, Card, CardHeader } from '@wordpress/components';
|
||||
import { Text } from '@woocommerce/experimental';
|
||||
import {
|
||||
CustomerFeedbackModal,
|
||||
CustomerFeedbackSimple,
|
||||
} from '@woocommerce/customer-effort-score';
|
||||
import { __ } from '@wordpress/i18n';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import './completed-header.scss';
|
||||
import HeaderImage from './completed-celebration-header.svg';
|
||||
|
||||
type TaskListCompletedHeaderProps = {
|
||||
hideTasks: () => void;
|
||||
keepTasks: () => void;
|
||||
customerEffortScore: boolean;
|
||||
};
|
||||
|
||||
const ADMIN_INSTALL_TIMESTAMP_OPTION_NAME =
|
||||
'woocommerce_admin_install_timestamp';
|
||||
const SHOWN_FOR_ACTIONS_OPTION_NAME = 'woocommerce_ces_shown_for_actions';
|
||||
const CUSTOMER_EFFORT_SCORE_ACTION = 'store_setup';
|
||||
const ALLOW_TRACKING_OPTION_NAME = 'woocommerce_allow_tracking';
|
||||
|
||||
function getStoreAgeInWeeks( adminInstallTimestamp: number ) {
|
||||
if ( adminInstallTimestamp === 0 ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Date.now() is ms since Unix epoch, adminInstallTimestamp is in
|
||||
// seconds since Unix epoch.
|
||||
const storeAgeInMs = Date.now() - adminInstallTimestamp * 1000;
|
||||
const storeAgeInWeeks = Math.round( storeAgeInMs / WEEK );
|
||||
|
||||
return storeAgeInWeeks;
|
||||
}
|
||||
|
||||
export const TaskListCompletedHeader: React.FC< TaskListCompletedHeaderProps > = ( {
|
||||
hideTasks,
|
||||
keepTasks,
|
||||
customerEffortScore,
|
||||
} ) => {
|
||||
const { updateOptions } = useDispatch( OPTIONS_STORE_NAME );
|
||||
const [ showCesModal, setShowCesModal ] = useState( false );
|
||||
const [ hasSubmittedScore, setHasSubmittedScore ] = useState( false );
|
||||
const [ score, setScore ] = useState( NaN );
|
||||
const [ hideCustomerEffortScore, setHideCustomerEffortScore ] = useState(
|
||||
false
|
||||
);
|
||||
const {
|
||||
storeAgeInWeeks,
|
||||
cesShownForActions,
|
||||
canShowCustomerEffortScore,
|
||||
} = useSelect( ( select: WCDataSelector ) => {
|
||||
const { getOption, hasFinishedResolution } = select(
|
||||
OPTIONS_STORE_NAME
|
||||
);
|
||||
|
||||
if ( customerEffortScore ) {
|
||||
const allowTracking = getOption( ALLOW_TRACKING_OPTION_NAME );
|
||||
const adminInstallTimestamp: number =
|
||||
getOption( ADMIN_INSTALL_TIMESTAMP_OPTION_NAME ) || 0;
|
||||
const cesActions = getOption< string[] >(
|
||||
SHOWN_FOR_ACTIONS_OPTION_NAME
|
||||
);
|
||||
const loadingOptions =
|
||||
! hasFinishedResolution( 'getOption', [
|
||||
SHOWN_FOR_ACTIONS_OPTION_NAME,
|
||||
] ) ||
|
||||
! hasFinishedResolution( 'getOption', [
|
||||
ADMIN_INSTALL_TIMESTAMP_OPTION_NAME,
|
||||
] );
|
||||
return {
|
||||
storeAgeInWeeks: getStoreAgeInWeeks( adminInstallTimestamp ),
|
||||
cesShownForActions: cesActions,
|
||||
canShowCustomerEffortScore:
|
||||
! loadingOptions &&
|
||||
allowTracking &&
|
||||
! ( cesActions || [] ).includes( 'store_setup' ),
|
||||
loading: loadingOptions,
|
||||
};
|
||||
}
|
||||
return {};
|
||||
} );
|
||||
|
||||
useEffect( () => {
|
||||
if ( hasSubmittedScore ) {
|
||||
setTimeout( () => {
|
||||
setHideCustomerEffortScore( true );
|
||||
}, 1200 );
|
||||
}
|
||||
}, [ hasSubmittedScore ] );
|
||||
|
||||
const submitScore = ( recordedScore: number, comments?: string ) => {
|
||||
recordEvent( 'ces_feedback', {
|
||||
action: CUSTOMER_EFFORT_SCORE_ACTION,
|
||||
score: recordedScore,
|
||||
comments: comments || '',
|
||||
store_age: storeAgeInWeeks,
|
||||
} );
|
||||
updateOptions( {
|
||||
[ SHOWN_FOR_ACTIONS_OPTION_NAME ]: [
|
||||
CUSTOMER_EFFORT_SCORE_ACTION,
|
||||
...( cesShownForActions || [] ),
|
||||
],
|
||||
} );
|
||||
setHasSubmittedScore( true );
|
||||
};
|
||||
|
||||
const recordScore = ( recordedScore: number ) => {
|
||||
if ( recordedScore > 2 ) {
|
||||
setScore( recordedScore );
|
||||
submitScore( recordedScore );
|
||||
} else {
|
||||
setScore( recordedScore );
|
||||
setShowCesModal( true );
|
||||
recordEvent( 'ces_view', {
|
||||
action: CUSTOMER_EFFORT_SCORE_ACTION,
|
||||
store_age: storeAgeInWeeks,
|
||||
} );
|
||||
}
|
||||
};
|
||||
|
||||
const recordModalScore = ( recordedScore: number, comments: string ) => {
|
||||
setShowCesModal( false );
|
||||
submitScore( recordedScore, comments );
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
className={ classnames(
|
||||
'woocommerce-task-dashboard__container two-column-experiment'
|
||||
) }
|
||||
>
|
||||
<Card
|
||||
size="large"
|
||||
className="woocommerce-task-card woocommerce-homescreen-card completed"
|
||||
>
|
||||
<CardHeader size="medium">
|
||||
<div className="wooocommerce-task-card__header">
|
||||
<img
|
||||
src={ HeaderImage }
|
||||
alt="Completed"
|
||||
className="wooocommerce-task-card__finished-header-image"
|
||||
/>
|
||||
|
||||
<Text size="title" as="h2" lineHeight={ 1.4 }>
|
||||
{ __(
|
||||
"You've completed store setup",
|
||||
'woocommerce'
|
||||
) }
|
||||
</Text>
|
||||
<Text
|
||||
variant="subtitle.small"
|
||||
as="p"
|
||||
size="13"
|
||||
lineHeight="16px"
|
||||
className="wooocommerce-task-card__header-subtitle"
|
||||
>
|
||||
{ __(
|
||||
'Congratulations! Take a moment to celebrate and look out for the first sale.',
|
||||
'woocommerce'
|
||||
) }
|
||||
</Text>
|
||||
<div className="woocommerce-task-card__header-menu">
|
||||
<EllipsisMenu
|
||||
label={ __(
|
||||
'Task List Options',
|
||||
'woocommerce'
|
||||
) }
|
||||
renderContent={ () => (
|
||||
<div className="woocommerce-task-card__section-controls">
|
||||
<Button
|
||||
onClick={ () => keepTasks() }
|
||||
>
|
||||
{ __(
|
||||
'Show setup task list',
|
||||
'woocommerce'
|
||||
) }
|
||||
</Button>
|
||||
<Button
|
||||
onClick={ () => hideTasks() }
|
||||
>
|
||||
{ __(
|
||||
'Hide this',
|
||||
'woocommerce'
|
||||
) }
|
||||
</Button>
|
||||
</div>
|
||||
) }
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</CardHeader>
|
||||
{ canShowCustomerEffortScore &&
|
||||
! hideCustomerEffortScore &&
|
||||
! hasSubmittedScore && (
|
||||
<CustomerFeedbackSimple
|
||||
label={ __(
|
||||
'How was your experience?',
|
||||
'woocommerce'
|
||||
) }
|
||||
onSelect={ recordScore }
|
||||
/>
|
||||
) }
|
||||
{ hasSubmittedScore && ! hideCustomerEffortScore && (
|
||||
<div className="wooocommerce-task-card__header-ces-feedback">
|
||||
<Text
|
||||
variant="subtitle.small"
|
||||
as="p"
|
||||
size="13"
|
||||
lineHeight="16px"
|
||||
>
|
||||
🙌{ ' ' }
|
||||
{ __(
|
||||
'We appreciate your feedback!',
|
||||
'woocommerce'
|
||||
) }
|
||||
</Text>
|
||||
</div>
|
||||
) }
|
||||
</Card>
|
||||
</div>
|
||||
{ showCesModal ? (
|
||||
<CustomerFeedbackModal
|
||||
label={ __( 'How was your experience?', 'woocommerce' ) }
|
||||
defaultScore={ score }
|
||||
recordScoreCallback={ recordModalScore }
|
||||
onCloseModal={ () => {
|
||||
setScore( NaN );
|
||||
setShowCesModal( false );
|
||||
} }
|
||||
/>
|
||||
) : null }
|
||||
</>
|
||||
);
|
||||
};
|
|
@ -89,6 +89,7 @@ const TaskDashboard = ( { query, twoColumns } ) => {
|
|||
dismissedTasks={ dismissedTasks || [] }
|
||||
isComplete={ isTaskListComplete }
|
||||
query={ query }
|
||||
cesHeader={ false }
|
||||
tasks={ setupTasks }
|
||||
title={ __( 'Get ready to start selling', 'woocommerce' ) }
|
||||
/>
|
||||
|
|
|
@ -24,6 +24,7 @@ import { TaskListProps } from '~/tasks/task-list';
|
|||
import { ProgressHeader } from '~/task-lists/progress-header';
|
||||
import { SectionPanelTitle } from './section-panel-title';
|
||||
import { TaskListItem } from './task-list-item';
|
||||
import { TaskListCompletedHeader } from './completed-header';
|
||||
|
||||
type PanelBodyProps = Omit< PanelBody.Props, 'title' | 'onToggle' > & {
|
||||
title: string | React.ReactNode | undefined;
|
||||
|
@ -40,6 +41,7 @@ export const SectionedTaskList: React.FC< TaskListProps > = ( {
|
|||
isComplete,
|
||||
sections,
|
||||
displayProgressHeader,
|
||||
cesHeader = true,
|
||||
} ) => {
|
||||
const { updateOptions } = useDispatch( OPTIONS_STORE_NAME );
|
||||
const { profileItems } = useSelect( ( select ) => {
|
||||
|
@ -115,14 +117,22 @@ export const SectionedTaskList: React.FC< TaskListProps > = ( {
|
|||
return <div className="woocommerce-task-dashboard__container"></div>;
|
||||
}
|
||||
|
||||
if ( isComplete && ! keepCompletedTaskList ) {
|
||||
if ( isComplete && keepCompletedTaskList !== 'yes' ) {
|
||||
return (
|
||||
<>
|
||||
{ cesHeader ? (
|
||||
<TaskListCompletedHeader
|
||||
hideTasks={ hideTasks }
|
||||
keepTasks={ keepTasks }
|
||||
customerEffortScore={ true }
|
||||
/>
|
||||
) : (
|
||||
<TaskListCompleted
|
||||
hideTasks={ hideTasks }
|
||||
keepTasks={ keepTasks }
|
||||
twoColumns={ false }
|
||||
/>
|
||||
) }
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -81,9 +81,8 @@
|
|||
}
|
||||
|
||||
h2 {
|
||||
font-size: 1.5em;
|
||||
font-weight: normal;
|
||||
margin-top: 22px;
|
||||
margin-top: $gap-large;
|
||||
margin-bottom: $gap-small;
|
||||
}
|
||||
|
||||
.wooocommerce-task-card__header {
|
||||
|
@ -92,7 +91,7 @@
|
|||
}
|
||||
|
||||
button.is-secondary {
|
||||
margin-right: 12px;
|
||||
margin-right: $gap-small;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -18,6 +18,7 @@ import {
|
|||
useUserPreferences,
|
||||
getVisibleTasks,
|
||||
TaskListType,
|
||||
WCDataSelector,
|
||||
} from '@woocommerce/data';
|
||||
import { recordEvent } from '@woocommerce/tracks';
|
||||
import { List } from '@woocommerce/experimental';
|
||||
|
@ -33,6 +34,7 @@ import DismissModal from './dismiss-modal';
|
|||
import TaskListCompleted from './completed';
|
||||
import { ProgressHeader } from '~/task-lists/progress-header';
|
||||
import { TaskListItemTwoColumn } from './task-list-item-two-column';
|
||||
import { TaskListCompletedHeader } from './completed-header';
|
||||
|
||||
export type TaskListProps = TaskListType & {
|
||||
eventName?: string;
|
||||
|
@ -40,6 +42,7 @@ export type TaskListProps = TaskListType & {
|
|||
query: {
|
||||
task?: string;
|
||||
};
|
||||
cesHeader?: boolean;
|
||||
};
|
||||
|
||||
export const TaskList: React.FC< TaskListProps > = ( {
|
||||
|
@ -52,16 +55,20 @@ export const TaskList: React.FC< TaskListProps > = ( {
|
|||
keepCompletedTaskList,
|
||||
isComplete,
|
||||
displayProgressHeader,
|
||||
cesHeader = true,
|
||||
} ) => {
|
||||
const listEventPrefix = eventName ? eventName + '_' : eventPrefix;
|
||||
const { updateOptions } = useDispatch( OPTIONS_STORE_NAME );
|
||||
const { profileItems } = useSelect( ( select ) => {
|
||||
const { profileItems } = useSelect( ( select: WCDataSelector ) => {
|
||||
const { getProfileItems } = select( ONBOARDING_STORE_NAME );
|
||||
return {
|
||||
profileItems: getProfileItems(),
|
||||
};
|
||||
} );
|
||||
const { hideTaskList, visitedTask } = useDispatch( ONBOARDING_STORE_NAME );
|
||||
const {
|
||||
hideTaskList,
|
||||
visitedTask,
|
||||
keepCompletedTaskList: keepCompletedTasks,
|
||||
} = useDispatch( ONBOARDING_STORE_NAME );
|
||||
const userPreferences = useUserPreferences();
|
||||
const [ headerData, setHeaderData ] = useState< {
|
||||
task?: TaskType;
|
||||
|
@ -108,13 +115,7 @@ export const TaskList: React.FC< TaskListProps > = ( {
|
|||
};
|
||||
|
||||
const keepTasks = () => {
|
||||
const updateOptionsParams = {
|
||||
woocommerce_task_list_keep_completed: 'yes',
|
||||
};
|
||||
|
||||
updateOptions( {
|
||||
...updateOptionsParams,
|
||||
} );
|
||||
keepCompletedTasks( id );
|
||||
};
|
||||
|
||||
const renderMenu = () => {
|
||||
|
@ -226,14 +227,22 @@ export const TaskList: React.FC< TaskListProps > = ( {
|
|||
return <div className="woocommerce-task-dashboard__container"></div>;
|
||||
}
|
||||
|
||||
if ( isComplete && ! keepCompletedTaskList ) {
|
||||
if ( isComplete && keepCompletedTaskList !== 'yes' ) {
|
||||
return (
|
||||
<>
|
||||
{ cesHeader ? (
|
||||
<TaskListCompletedHeader
|
||||
hideTasks={ hideTasks }
|
||||
keepTasks={ keepTasks }
|
||||
customerEffortScore={ true }
|
||||
/>
|
||||
) : (
|
||||
<TaskListCompleted
|
||||
hideTasks={ hideTasks }
|
||||
keepTasks={ keepTasks }
|
||||
twoColumns={ false }
|
||||
/>
|
||||
) }
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue