From 7aa37d609fd7d549ddd2d02a2e9da7a299075ace Mon Sep 17 00:00:00 2001 From: Lourens Schep Date: Thu, 7 Apr 2022 16:56:20 -0300 Subject: [PATCH 01/14] Added new completed header and customer feedback component --- .../src/customer-effort-score.js | 124 +++++++++++++++++ .../customer-feedback-simple.scss | 20 +++ .../customer-feedback-simple.tsx | 128 ++++++++++++++++++ .../src/customer-feedback-simple/index.ts | 1 + .../customer-feedback-simple/test/index.js | 96 +++++++++++++ .../js/customer-effort-score/src/index.js | 126 +---------------- .../js/customer-effort-score/src/style.scss | 2 + .../customer-effort-score/src/test/index.js | 2 +- .../client/tasks/task-list.tsx | 4 + .../woocommerce-admin/client/tasks/tasks.tsx | 22 +-- .../completed-celebration-header.svg | 32 +++++ .../completed-header-with-ces.scss | 14 ++ .../completed-header-with-ces.tsx | 107 +++++++++++++++ .../client/two-column-tasks/index.js | 1 + .../client/two-column-tasks/task-list.tsx | 45 ++++-- 15 files changed, 579 insertions(+), 145 deletions(-) create mode 100644 packages/js/customer-effort-score/src/customer-effort-score.js create mode 100644 packages/js/customer-effort-score/src/customer-feedback-simple/customer-feedback-simple.scss create mode 100644 packages/js/customer-effort-score/src/customer-feedback-simple/customer-feedback-simple.tsx create mode 100644 packages/js/customer-effort-score/src/customer-feedback-simple/index.ts create mode 100644 packages/js/customer-effort-score/src/customer-feedback-simple/test/index.js create mode 100644 plugins/woocommerce-admin/client/two-column-tasks/completed-celebration-header.svg create mode 100644 plugins/woocommerce-admin/client/two-column-tasks/completed-header-with-ces.scss create mode 100644 plugins/woocommerce-admin/client/two-column-tasks/completed-header-with-ces.tsx diff --git a/packages/js/customer-effort-score/src/customer-effort-score.js b/packages/js/customer-effort-score/src/customer-effort-score.js new file mode 100644 index 00000000000..6563247067d --- /dev/null +++ b/packages/js/customer-effort-score/src/customer-effort-score.js @@ -0,0 +1,124 @@ +/** + * External dependencies + */ +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'; + +/** + * Internal dependencies + */ +import CustomerFeedbackModal from './customer-feedback-modal'; + +const noop = () => {}; + +/** + * Use `CustomerEffortScore` to gather a customer effort score. + * + * NOTE: This should live in @woocommerce/customer-effort-score to allow + * reuse. + * + * @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( { + recordScoreCallback, + label, + createNotice, + onNoticeShownCallback = noop, + onNoticeDismissedCallback = noop, + onModalShownCallback = noop, + icon, +} ) { + const [ shouldCreateNotice, setShouldCreateNotice ] = useState( true ); + const [ visible, setVisible ] = useState( false ); + + useEffect( () => { + if ( ! shouldCreateNotice ) { + return; + } + + createNotice( 'success', label, { + actions: [ + { + label: __( 'Give feedback', 'woocommerce' ), + onClick: () => { + setVisible( true ); + onModalShownCallback(); + }, + }, + ], + icon, + explicitDismiss: true, + onDismiss: onNoticeDismissedCallback, + } ); + + setShouldCreateNotice( false ); + + onNoticeShownCallback(); + }, [ shouldCreateNotice ] ); + + if ( shouldCreateNotice ) { + return null; + } + + if ( ! visible ) { + return null; + } + + return ( + + ); +} + +CustomerEffortScore.propTypes = { + /** + * The function to call to record the score. + */ + recordScoreCallback: PropTypes.func.isRequired, + /** + * 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. + */ + onNoticeShownCallback: PropTypes.func, + /** + * The function to call when the notice is dismissed. + */ + onNoticeDismissedCallback: PropTypes.func, + /** + * The function to call when the modal is shown. + */ + onModalShownCallback: PropTypes.func, + /** + * Icon (React component) to be displayed. + */ + icon: PropTypes.element, +}; + +export default compose( + withDispatch( ( dispatch ) => { + const { createNotice } = dispatch( 'core/notices2' ); + + return { + createNotice, + }; + } ) +)( CustomerEffortScore ); diff --git a/packages/js/customer-effort-score/src/customer-feedback-simple/customer-feedback-simple.scss b/packages/js/customer-effort-score/src/customer-feedback-simple/customer-feedback-simple.scss new file mode 100644 index 00000000000..2ceae46af00 --- /dev/null +++ b/packages/js/customer-effort-score/src/customer-feedback-simple/customer-feedback-simple.scss @@ -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; +} diff --git a/packages/js/customer-effort-score/src/customer-feedback-simple/customer-feedback-simple.tsx b/packages/js/customer-effort-score/src/customer-feedback-simple/customer-feedback-simple.tsx new file mode 100644 index 00000000000..698fce1cd2d --- /dev/null +++ b/packages/js/customer-effort-score/src/customer-feedback-simple/customer-feedback-simple.tsx @@ -0,0 +1,128 @@ +/** + * External dependencies + */ +import { + createElement, + Fragment, + useEffect, + useState, +} 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 = { + recordScoreCallback: ( 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.recordScoreCallback Function to call when the results are sent. + * @param {string} props.label Question to ask the customer. + */ +const CustomerFeedbackSimple: React.FC< CustomerFeedbackSimpleProps > = ( { + recordScoreCallback, + 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: '😍', + }, + ]; + + const [ score, setScore ] = useState( NaN ); + + useEffect( () => { + if ( score !== NaN ) { + recordScoreCallback( score ); + } + }, [ score ] ); + + return ( +
+ { isNaN( score ) ? ( + + + { label } + + +
+ { options.map( ( option ) => ( + + + + ) ) } +
+
+ ) : null } + + { score >= 3 && ( +
+ + 🙌{ ' ' } + { __( 'We appreciate your feedback!', 'woocommerce' ) } + +
+ ) } +
+ ); +}; + +CustomerFeedbackSimple.propTypes = { + recordScoreCallback: PropTypes.func.isRequired, + label: PropTypes.string.isRequired, +}; + +export { CustomerFeedbackSimple }; diff --git a/packages/js/customer-effort-score/src/customer-feedback-simple/index.ts b/packages/js/customer-effort-score/src/customer-feedback-simple/index.ts new file mode 100644 index 00000000000..740d002960a --- /dev/null +++ b/packages/js/customer-effort-score/src/customer-feedback-simple/index.ts @@ -0,0 +1 @@ +export * from './customer-feedback-simple'; diff --git a/packages/js/customer-effort-score/src/customer-feedback-simple/test/index.js b/packages/js/customer-effort-score/src/customer-feedback-simple/test/index.js new file mode 100644 index 00000000000..429ccd75034 --- /dev/null +++ b/packages/js/customer-effort-score/src/customer-feedback-simple/test/index.js @@ -0,0 +1,96 @@ +/** + * External dependencies + */ +import { render, screen, waitFor, fireEvent } from '@testing-library/react'; +import { createElement } from '@wordpress/element'; + +/** + * Internal dependencies + */ +import CustomerFeedbackModal from '../index'; + +const mockRecordScoreCallback = jest.fn(); + +describe( 'CustomerFeedbackModal', () => { + it( 'should close modal when cancel button pressed', async () => { + render( + + ); + + // Wait for the modal to render. + await screen.findByRole( 'dialog' ); + + // Press cancel button. + fireEvent.click( screen.getByRole( 'button', { name: /cancel/i } ) ); + + expect( screen.queryByRole( 'dialog' ) ).not.toBeInTheDocument(); + } ); + + it( 'should halt with an error when submitting without a score', async () => { + render( + + ); + + await screen.findByRole( 'dialog' ); // Wait for the modal to render. + + fireEvent.click( screen.getByRole( 'button', { name: /send/i } ) ); // Press send button. + + // Wait for error message. + await screen.findByRole( 'alert' ); + + expect( screen.getByRole( 'dialog' ) ).toBeInTheDocument(); + } ); + + it( 'should disable the comments field initially', async () => { + render( + + ); + + // Wait for the modal to render. + await screen.findByRole( 'dialog' ); + + expect( + screen.queryByLabelText( 'Comments (Optional)' ) + ).not.toBeInTheDocument(); + } ); + + it.each( [ 'Very difficult', 'Somewhat difficult' ] )( + 'should toggle the comments field when %s is selected', + async ( labelText ) => { + render( + + ); + + // Wait for the modal to render. + await screen.findByRole( 'dialog' ); + + // Select the option. + fireEvent.click( screen.getByLabelText( labelText ) ); + + // Wait for comments field to show. + await screen.findByLabelText( 'Comments (Optional)' ); + + // Select neutral score. + fireEvent.click( screen.getByLabelText( 'Neutral' ) ); + + // Wait for comments field to hide. + await waitFor( () => { + expect( + screen.queryByLabelText( 'Comments (Optional)' ) + ).not.toBeInTheDocument(); + } ); + } + ); +} ); diff --git a/packages/js/customer-effort-score/src/index.js b/packages/js/customer-effort-score/src/index.js index 6563247067d..a89db2c591c 100644 --- a/packages/js/customer-effort-score/src/index.js +++ b/packages/js/customer-effort-score/src/index.js @@ -1,124 +1,2 @@ -/** - * External dependencies - */ -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'; - -/** - * Internal dependencies - */ -import CustomerFeedbackModal from './customer-feedback-modal'; - -const noop = () => {}; - -/** - * Use `CustomerEffortScore` to gather a customer effort score. - * - * NOTE: This should live in @woocommerce/customer-effort-score to allow - * reuse. - * - * @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( { - recordScoreCallback, - label, - createNotice, - onNoticeShownCallback = noop, - onNoticeDismissedCallback = noop, - onModalShownCallback = noop, - icon, -} ) { - const [ shouldCreateNotice, setShouldCreateNotice ] = useState( true ); - const [ visible, setVisible ] = useState( false ); - - useEffect( () => { - if ( ! shouldCreateNotice ) { - return; - } - - createNotice( 'success', label, { - actions: [ - { - label: __( 'Give feedback', 'woocommerce' ), - onClick: () => { - setVisible( true ); - onModalShownCallback(); - }, - }, - ], - icon, - explicitDismiss: true, - onDismiss: onNoticeDismissedCallback, - } ); - - setShouldCreateNotice( false ); - - onNoticeShownCallback(); - }, [ shouldCreateNotice ] ); - - if ( shouldCreateNotice ) { - return null; - } - - if ( ! visible ) { - return null; - } - - return ( - - ); -} - -CustomerEffortScore.propTypes = { - /** - * The function to call to record the score. - */ - recordScoreCallback: PropTypes.func.isRequired, - /** - * 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. - */ - onNoticeShownCallback: PropTypes.func, - /** - * The function to call when the notice is dismissed. - */ - onNoticeDismissedCallback: PropTypes.func, - /** - * The function to call when the modal is shown. - */ - onModalShownCallback: PropTypes.func, - /** - * Icon (React component) to be displayed. - */ - icon: PropTypes.element, -}; - -export default compose( - withDispatch( ( dispatch ) => { - const { createNotice } = dispatch( 'core/notices2' ); - - return { - createNotice, - }; - } ) -)( CustomerEffortScore ); +export * from './customer-effort-score'; +export * from './customer-feedback-simple'; diff --git a/packages/js/customer-effort-score/src/style.scss b/packages/js/customer-effort-score/src/style.scss index 8c65b7005bf..0c0101b05cf 100644 --- a/packages/js/customer-effort-score/src/style.scss +++ b/packages/js/customer-effort-score/src/style.scss @@ -1,3 +1,5 @@ +@import 'customer-feedback-simple/customer-feedback-simple.scss'; + .woocommerce-customer-effort-score__selection { margin: 1em 0; diff --git a/packages/js/customer-effort-score/src/test/index.js b/packages/js/customer-effort-score/src/test/index.js index fb016334a81..3a580062675 100644 --- a/packages/js/customer-effort-score/src/test/index.js +++ b/packages/js/customer-effort-score/src/test/index.js @@ -7,7 +7,7 @@ import { createElement } from '@wordpress/element'; /** * Internal dependencies */ -import { CustomerEffortScore } from '../index'; +import { CustomerEffortScore } from '../customer-effort-score'; const noop = () => {}; diff --git a/plugins/woocommerce-admin/client/tasks/task-list.tsx b/plugins/woocommerce-admin/client/tasks/task-list.tsx index c35fa4a0386..96034880cf5 100644 --- a/plugins/woocommerce-admin/client/tasks/task-list.tsx +++ b/plugins/woocommerce-admin/client/tasks/task-list.tsx @@ -26,6 +26,10 @@ export type TaskListProps = TaskListType & { query: { task?: string; }; + eventName?: string; + twoColumns?: boolean; + keepCompletedTaskList?: boolean; + cesHeader?: boolean; }; export const TaskList: React.FC< TaskListProps > = ( { diff --git a/plugins/woocommerce-admin/client/tasks/tasks.tsx b/plugins/woocommerce-admin/client/tasks/tasks.tsx index d261d7d7991..151067e6528 100644 --- a/plugins/woocommerce-admin/client/tasks/tasks.tsx +++ b/plugins/woocommerce-admin/client/tasks/tasks.tsx @@ -11,6 +11,7 @@ import { OPTIONS_STORE_NAME, TaskListType, TaskType, + WCDataSelector, } from '@woocommerce/data'; import { useExperiment } from '@woocommerce/explat'; import { recordEvent } from '@woocommerce/tracks'; @@ -29,6 +30,7 @@ import TwoColumnTaskListPlaceholder from '../two-column-tasks/placeholder'; import '../two-column-tasks/style.scss'; import { getAdminSetting } from '~/utils/admin-settings'; import { SectionedTaskListPlaceholder } from '~/two-column-tasks/sectioned-task-list-placeholder'; +import { ALLOW_TRACKING_OPTION_NAME } from '../two-column-tasks/task-list'; export type TasksProps = { query: { task?: string }; @@ -66,14 +68,18 @@ export const Tasks: React.FC< TasksProps > = ( { query } ) => { 'woocommerce_tasklist_progression' ); - const { isResolving, taskLists } = useSelect( ( select ) => { - return { - isResolving: ! select( - ONBOARDING_STORE_NAME - ).hasFinishedResolution( 'getTaskLists' ), - taskLists: select( ONBOARDING_STORE_NAME ).getTaskLists(), - }; - } ); + const { isResolving, taskLists } = useSelect( + ( select: WCDataSelector ) => { + const { getOption } = select( OPTIONS_STORE_NAME ); + getOption( ALLOW_TRACKING_OPTION_NAME ); + return { + isResolving: ! select( + ONBOARDING_STORE_NAME + ).hasFinishedResolution( 'getTaskLists' ), + taskLists: select( ONBOARDING_STORE_NAME ).getTaskLists(), + }; + } + ); const getCurrentTask = () => { if ( ! task ) { diff --git a/plugins/woocommerce-admin/client/two-column-tasks/completed-celebration-header.svg b/plugins/woocommerce-admin/client/two-column-tasks/completed-celebration-header.svg new file mode 100644 index 00000000000..85ae25d3dcf --- /dev/null +++ b/plugins/woocommerce-admin/client/two-column-tasks/completed-celebration-header.svg @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/plugins/woocommerce-admin/client/two-column-tasks/completed-header-with-ces.scss b/plugins/woocommerce-admin/client/two-column-tasks/completed-header-with-ces.scss new file mode 100644 index 00000000000..428843d1fb1 --- /dev/null +++ b/plugins/woocommerce-admin/client/two-column-tasks/completed-header-with-ces.scss @@ -0,0 +1,14 @@ +.wooocommerce-task-card__header-subtitle { + color: $gray-700; + margin-bottom: $gap-large; +} + +.customer-feedback-simple__container { + height: 64px; +} + +.woocommerce-task-card__header-menu { + position: absolute; + right: $gap-large; + top: $gap; +} \ No newline at end of file diff --git a/plugins/woocommerce-admin/client/two-column-tasks/completed-header-with-ces.tsx b/plugins/woocommerce-admin/client/two-column-tasks/completed-header-with-ces.tsx new file mode 100644 index 00000000000..00c007d33b4 --- /dev/null +++ b/plugins/woocommerce-admin/client/two-column-tasks/completed-header-with-ces.tsx @@ -0,0 +1,107 @@ +/** + * External dependencies + */ +import classnames from 'classnames'; +import { Button, Card, CardHeader } from '@wordpress/components'; +import { Text } from '@woocommerce/experimental'; +import { CustomerFeedbackSimple } from '@woocommerce/customer-effort-score'; +import { __ } from '@wordpress/i18n'; + +/** + * Internal dependencies + */ +import './completed-header-with-ces.scss'; +import HeaderImage from './completed-celebration-header.svg'; +import React from 'react'; +import { EllipsisMenu } from '@woocommerce/components'; + +type TaskListCompletedHeaderProps = { + hideTasks: ( event: string ) => void; + keepTasks: () => void; + allowTracking: boolean; +}; + +export const TaskListCompletedHeaderWithCES: React.FC< TaskListCompletedHeaderProps > = ( { + hideTasks, + keepTasks, + allowTracking, +} ) => { + const recordScoreCallback = ( score: number ) => {}; + + return ( + <> +
+ + +
+ Completed + +

+ { __( + "You've completed store setup", + 'woocommerce' + ) } +

+ + { __( + 'Congratulations! Take a moment to celebrate and look out for the first sale.', + 'woocommerce' + ) } + +
+ ( +
+ + +
+ ) } + /> +
+
+
+ { allowTracking && ( + + ) } +
+
+ + ); +}; diff --git a/plugins/woocommerce-admin/client/two-column-tasks/index.js b/plugins/woocommerce-admin/client/two-column-tasks/index.js index 83f6d48e51c..81c8ca7203a 100644 --- a/plugins/woocommerce-admin/client/two-column-tasks/index.js +++ b/plugins/woocommerce-admin/client/two-column-tasks/index.js @@ -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' ) } /> diff --git a/plugins/woocommerce-admin/client/two-column-tasks/task-list.tsx b/plugins/woocommerce-admin/client/two-column-tasks/task-list.tsx index 10d78958ea0..98f4f84a9df 100644 --- a/plugins/woocommerce-admin/client/two-column-tasks/task-list.tsx +++ b/plugins/woocommerce-admin/client/two-column-tasks/task-list.tsx @@ -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,9 @@ 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 { TaskListCompletedHeaderWithCES } from './completed-header-with-ces'; + +export const ALLOW_TRACKING_OPTION_NAME = 'woocommerce_allow_tracking'; export type TaskListProps = TaskListType & { eventName?: string; @@ -52,15 +56,24 @@ 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 { getProfileItems } = select( ONBOARDING_STORE_NAME ); - return { - profileItems: getProfileItems(), - }; - } ); + const { createNotice } = useDispatch( 'core/notices' ); + const { updateOptions, dismissTask, undoDismissTask } = useDispatch( + OPTIONS_STORE_NAME + ); + const { profileItems, allowTracking } = useSelect( + ( select: WCDataSelector ) => { + const { getProfileItems } = select( ONBOARDING_STORE_NAME ); + const { getOption } = select( OPTIONS_STORE_NAME ); + return { + allowTracking: + getOption( ALLOW_TRACKING_OPTION_NAME ) === 'yes', + profileItems: getProfileItems(), + }; + } + ); const { hideTaskList, visitedTask } = useDispatch( ONBOARDING_STORE_NAME ); const userPreferences = useUserPreferences(); const [ headerData, setHeaderData ] = useState< { @@ -229,11 +242,19 @@ export const TaskList: React.FC< TaskListProps > = ( { if ( isComplete && ! keepCompletedTaskList ) { return ( <> - + { cesHeader ? ( + + ) : ( + + ) } ); } From 4b27f831f7ec036f32a6a58907a78acc7877b81b Mon Sep 17 00:00:00 2001 From: Lourens Schep Date: Fri, 8 Apr 2022 14:28:43 -0300 Subject: [PATCH 02/14] Add CES submission --- .../src/customer-effort-score.js | 12 ++- .../src/customer-feedback-modal/index.tsx | 7 +- .../customer-feedback-simple.tsx | 12 ++- .../js/customer-effort-score/src/index.js | 1 + .../customer-effort-score-tracks.js | 2 +- .../completed-header-with-ces.scss | 2 +- .../completed-header-with-ces.tsx | 94 +++++++++++++++++-- 7 files changed, 110 insertions(+), 20 deletions(-) diff --git a/packages/js/customer-effort-score/src/customer-effort-score.js b/packages/js/customer-effort-score/src/customer-effort-score.js index 6563247067d..50b8d9fc0fe 100644 --- a/packages/js/customer-effort-score/src/customer-effort-score.js +++ b/packages/js/customer-effort-score/src/customer-effort-score.js @@ -10,7 +10,7 @@ import { withDispatch } from '@wordpress/data'; /** * Internal dependencies */ -import CustomerFeedbackModal from './customer-feedback-modal'; +import { CustomerFeedbackModal } from './customer-feedback-modal'; const noop = () => {}; @@ -29,7 +29,7 @@ const noop = () => {}; * @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 CustomerEffortScoreComponent( { recordScoreCallback, label, createNotice, @@ -82,7 +82,7 @@ export function CustomerEffortScore( { ); } -CustomerEffortScore.propTypes = { +CustomerEffortScoreComponent.propTypes = { /** * The function to call to record the score. */ @@ -113,7 +113,7 @@ CustomerEffortScore.propTypes = { icon: PropTypes.element, }; -export default compose( +const CustomerEffortScore = compose( withDispatch( ( dispatch ) => { const { createNotice } = dispatch( 'core/notices2' ); @@ -121,4 +121,6 @@ export default compose( createNotice, }; } ) -)( CustomerEffortScore ); +)( CustomerEffortScoreComponent ); + +export { CustomerEffortScore }; diff --git a/packages/js/customer-effort-score/src/customer-feedback-modal/index.tsx b/packages/js/customer-effort-score/src/customer-feedback-modal/index.tsx index 109febcf7cd..6cd4541bee4 100644 --- a/packages/js/customer-effort-score/src/customer-feedback-modal/index.tsx +++ b/packages/js/customer-effort-score/src/customer-feedback-modal/index.tsx @@ -30,9 +30,11 @@ import { __ } from '@wordpress/i18n'; function CustomerFeedbackModal( { recordScoreCallback, label, + defaultScore = NaN, }: { recordScoreCallback: ( score: number, comments: string ) => void; label: string; + defaultScore?: number; } ): JSX.Element | null { const options = [ { @@ -57,7 +59,7 @@ 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 ); @@ -152,6 +154,7 @@ function CustomerFeedbackModal( { CustomerFeedbackModal.propTypes = { recordScoreCallback: PropTypes.func.isRequired, label: PropTypes.string.isRequired, + score: PropTypes.number, }; -export default CustomerFeedbackModal; +export { CustomerFeedbackModal }; diff --git a/packages/js/customer-effort-score/src/customer-feedback-simple/customer-feedback-simple.tsx b/packages/js/customer-effort-score/src/customer-feedback-simple/customer-feedback-simple.tsx index 698fce1cd2d..ed162706871 100644 --- a/packages/js/customer-effort-score/src/customer-feedback-simple/customer-feedback-simple.tsx +++ b/packages/js/customer-effort-score/src/customer-feedback-simple/customer-feedback-simple.tsx @@ -15,6 +15,7 @@ import { __ } from '@wordpress/i18n'; type CustomerFeedbackSimpleProps = { recordScoreCallback: ( score: number ) => void; label: string; + showFeedback?: boolean; }; /** @@ -31,10 +32,12 @@ type CustomerFeedbackSimpleProps = { * @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.showFeedback To show feedback message. */ const CustomerFeedbackSimple: React.FC< CustomerFeedbackSimpleProps > = ( { recordScoreCallback, label, + showFeedback = false, } ) => { const options = [ { @@ -67,14 +70,14 @@ const CustomerFeedbackSimple: React.FC< CustomerFeedbackSimpleProps > = ( { const [ score, setScore ] = useState( NaN ); useEffect( () => { - if ( score !== NaN ) { + if ( ! isNaN( score ) ) { recordScoreCallback( score ); } }, [ score ] ); return (
- { isNaN( score ) ? ( + { isNaN( score ) && ! showFeedback ? ( = ( { ) ) }
- ) : null } - - { score >= 3 && ( + ) : (
= ( { CustomerFeedbackSimple.propTypes = { recordScoreCallback: PropTypes.func.isRequired, label: PropTypes.string.isRequired, + showFeedback: PropTypes.bool, }; export { CustomerFeedbackSimple }; diff --git a/packages/js/customer-effort-score/src/index.js b/packages/js/customer-effort-score/src/index.js index a89db2c591c..8699d826dac 100644 --- a/packages/js/customer-effort-score/src/index.js +++ b/packages/js/customer-effort-score/src/index.js @@ -1,2 +1,3 @@ export * from './customer-effort-score'; export * from './customer-feedback-simple'; +export * from './customer-feedback-modal'; diff --git a/plugins/woocommerce-admin/client/customer-effort-score-tracks/customer-effort-score-tracks.js b/plugins/woocommerce-admin/client/customer-effort-score-tracks/customer-effort-score-tracks.js index 3d37a61d4fa..b55b39548e3 100644 --- a/plugins/woocommerce-admin/client/customer-effort-score-tracks/customer-effort-score-tracks.js +++ b/plugins/woocommerce-admin/client/customer-effort-score-tracks/customer-effort-score-tracks.js @@ -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'; diff --git a/plugins/woocommerce-admin/client/two-column-tasks/completed-header-with-ces.scss b/plugins/woocommerce-admin/client/two-column-tasks/completed-header-with-ces.scss index 428843d1fb1..e784d47201e 100644 --- a/plugins/woocommerce-admin/client/two-column-tasks/completed-header-with-ces.scss +++ b/plugins/woocommerce-admin/client/two-column-tasks/completed-header-with-ces.scss @@ -1,4 +1,4 @@ -.wooocommerce-task-card__header-subtitle { +.wooocommerce-task-card__header .wooocommerce-task-card__header-subtitle { color: $gray-700; margin-bottom: $gap-large; } diff --git a/plugins/woocommerce-admin/client/two-column-tasks/completed-header-with-ces.tsx b/plugins/woocommerce-admin/client/two-column-tasks/completed-header-with-ces.tsx index 00c007d33b4..a322354f10a 100644 --- a/plugins/woocommerce-admin/client/two-column-tasks/completed-header-with-ces.tsx +++ b/plugins/woocommerce-admin/client/two-column-tasks/completed-header-with-ces.tsx @@ -2,9 +2,17 @@ * External dependencies */ import classnames from 'classnames'; +import { useState } from '@wordpress/element'; +import { EllipsisMenu } from '@woocommerce/components'; +import { recordEvent } from '@woocommerce/tracks'; +import { useDispatch, useSelect } from '@wordpress/data'; +import { OPTIONS_STORE_NAME, WEEK } from '@woocommerce/data'; import { Button, Card, CardHeader } from '@wordpress/components'; import { Text } from '@woocommerce/experimental'; -import { CustomerFeedbackSimple } from '@woocommerce/customer-effort-score'; +import { + CustomerFeedbackModal, + CustomerFeedbackSimple, +} from '@woocommerce/customer-effort-score'; import { __ } from '@wordpress/i18n'; /** @@ -12,21 +20,87 @@ import { __ } from '@wordpress/i18n'; */ import './completed-header-with-ces.scss'; import HeaderImage from './completed-celebration-header.svg'; -import React from 'react'; -import { EllipsisMenu } from '@woocommerce/components'; type TaskListCompletedHeaderProps = { - hideTasks: ( event: string ) => void; + hideTasks: () => void; keepTasks: () => void; allowTracking: boolean; }; +const ADMIN_INSTALL_TIMESTAMP_OPTION_NAME = + 'woocommerce_admin_install_timestamp'; +const SHOWN_FOR_ACTIONS_OPTION_NAME = 'woocommerce_ces_shown_for_actions'; +const CES_ACTION = 'store_setup'; + +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 TaskListCompletedHeaderWithCES: React.FC< TaskListCompletedHeaderProps > = ( { hideTasks, keepTasks, allowTracking, } ) => { - const recordScoreCallback = ( score: number ) => {}; + const { updateOptions } = useDispatch( OPTIONS_STORE_NAME ); + const [ showCesModal, setShowCesModal ] = useState( false ); + const [ score, setScore ] = useState( NaN ); + const [ showFeedback, setShowFeedback ] = useState( false ); + const { storeAgeInWeeks, cesShownForActions } = useSelect( ( select ) => { + const { getOption } = select( OPTIONS_STORE_NAME ); + + const adminInstallTimestamp: number = + getOption( ADMIN_INSTALL_TIMESTAMP_OPTION_NAME ) || 0; + return { + storeAgeInWeeks: getStoreAgeInWeeks( adminInstallTimestamp ), + cesShownForActions: + getOption( SHOWN_FOR_ACTIONS_OPTION_NAME ) || [], + }; + } ); + + const recordScore = ( score: number ) => { + if ( score > 2 ) { + recordEvent( 'ces_feedback', { + action: CES_ACTION, + score, + store_age: storeAgeInWeeks, + } ); + updateOptions( { + [ SHOWN_FOR_ACTIONS_OPTION_NAME ]: [ + CES_ACTION, + ...cesShownForActions, + ], + } ); + } else { + setScore( score ); + setShowCesModal( true ); + } + }; + + const recordModalScore = ( score: number, comments: string ) => { + setShowCesModal( false ); + recordEvent( 'ces_feedback', { + action: 'store_setup', + score, + comments: comments || '', + store_age: storeAgeInWeeks, + } ); + updateOptions( { + [ SHOWN_FOR_ACTIONS_OPTION_NAME ]: [ + CES_ACTION, + ...cesShownForActions, + ], + } ); + setShowFeedback( true ); + }; return ( <> @@ -97,11 +171,19 @@ export const TaskListCompletedHeaderWithCES: React.FC< TaskListCompletedHeaderPr 'How was your experience?', 'woocommerce' ) } - recordScoreCallback={ recordScoreCallback } + recordScoreCallback={ recordScore } + showFeedback={ showFeedback } /> ) }
+ { showCesModal ? ( + + ) : null } ); }; From 45dfda2b65831da8981db16e3871cd3d96ee4e58 Mon Sep 17 00:00:00 2001 From: Lourens Schep Date: Fri, 8 Apr 2022 15:37:22 -0300 Subject: [PATCH 03/14] Move CES show logic to the back end, so there is no loading delay --- packages/js/data/src/onboarding/types.ts | 1 + .../client/tasks/task-list.tsx | 2 +- .../woocommerce-admin/client/tasks/tasks.tsx | 3 -- .../completed-header-with-ces.tsx | 39 +++++++++++-------- .../client/two-column-tasks/task-list.tsx | 24 +++++------- .../Features/OnboardingTasks/TaskList.php | 15 +++++++ 6 files changed, 49 insertions(+), 35 deletions(-) diff --git a/packages/js/data/src/onboarding/types.ts b/packages/js/data/src/onboarding/types.ts index 5bc46bb1237..25ddbab9b88 100644 --- a/packages/js/data/src/onboarding/types.ts +++ b/packages/js/data/src/onboarding/types.ts @@ -51,6 +51,7 @@ export type TaskListType = { eventPrefix: string; displayProgressHeader: boolean; keepCompletedTaskList: 'yes' | 'no'; + showCESFeedback: boolean; sections?: TaskListSection[]; isToggleable?: boolean; isCollapsible?: boolean; diff --git a/plugins/woocommerce-admin/client/tasks/task-list.tsx b/plugins/woocommerce-admin/client/tasks/task-list.tsx index 96034880cf5..170834fa92f 100644 --- a/plugins/woocommerce-admin/client/tasks/task-list.tsx +++ b/plugins/woocommerce-admin/client/tasks/task-list.tsx @@ -28,7 +28,7 @@ export type TaskListProps = TaskListType & { }; eventName?: string; twoColumns?: boolean; - keepCompletedTaskList?: boolean; + keepCompletedTaskList?: 'yes' | 'no'; cesHeader?: boolean; }; diff --git a/plugins/woocommerce-admin/client/tasks/tasks.tsx b/plugins/woocommerce-admin/client/tasks/tasks.tsx index 151067e6528..2f3a4198ccf 100644 --- a/plugins/woocommerce-admin/client/tasks/tasks.tsx +++ b/plugins/woocommerce-admin/client/tasks/tasks.tsx @@ -30,7 +30,6 @@ import TwoColumnTaskListPlaceholder from '../two-column-tasks/placeholder'; import '../two-column-tasks/style.scss'; import { getAdminSetting } from '~/utils/admin-settings'; import { SectionedTaskListPlaceholder } from '~/two-column-tasks/sectioned-task-list-placeholder'; -import { ALLOW_TRACKING_OPTION_NAME } from '../two-column-tasks/task-list'; export type TasksProps = { query: { task?: string }; @@ -70,8 +69,6 @@ export const Tasks: React.FC< TasksProps > = ( { query } ) => { const { isResolving, taskLists } = useSelect( ( select: WCDataSelector ) => { - const { getOption } = select( OPTIONS_STORE_NAME ); - getOption( ALLOW_TRACKING_OPTION_NAME ); return { isResolving: ! select( ONBOARDING_STORE_NAME diff --git a/plugins/woocommerce-admin/client/two-column-tasks/completed-header-with-ces.tsx b/plugins/woocommerce-admin/client/two-column-tasks/completed-header-with-ces.tsx index a322354f10a..871c9ad7e4f 100644 --- a/plugins/woocommerce-admin/client/two-column-tasks/completed-header-with-ces.tsx +++ b/plugins/woocommerce-admin/client/two-column-tasks/completed-header-with-ces.tsx @@ -24,7 +24,7 @@ import HeaderImage from './completed-celebration-header.svg'; type TaskListCompletedHeaderProps = { hideTasks: () => void; keepTasks: () => void; - allowTracking: boolean; + showCES: boolean; }; const ADMIN_INSTALL_TIMESTAMP_OPTION_NAME = @@ -48,7 +48,7 @@ function getStoreAgeInWeeks( adminInstallTimestamp: number ) { export const TaskListCompletedHeaderWithCES: React.FC< TaskListCompletedHeaderProps > = ( { hideTasks, keepTasks, - allowTracking, + showCES, } ) => { const { updateOptions } = useDispatch( OPTIONS_STORE_NAME ); const [ showCesModal, setShowCesModal ] = useState( false ); @@ -57,20 +57,23 @@ export const TaskListCompletedHeaderWithCES: React.FC< TaskListCompletedHeaderPr const { storeAgeInWeeks, cesShownForActions } = useSelect( ( select ) => { const { getOption } = select( OPTIONS_STORE_NAME ); - const adminInstallTimestamp: number = - getOption( ADMIN_INSTALL_TIMESTAMP_OPTION_NAME ) || 0; - return { - storeAgeInWeeks: getStoreAgeInWeeks( adminInstallTimestamp ), - cesShownForActions: - getOption( SHOWN_FOR_ACTIONS_OPTION_NAME ) || [], - }; + if ( showCES ) { + const adminInstallTimestamp: number = + getOption( ADMIN_INSTALL_TIMESTAMP_OPTION_NAME ) || 0; + return { + storeAgeInWeeks: getStoreAgeInWeeks( adminInstallTimestamp ), + cesShownForActions: + getOption( SHOWN_FOR_ACTIONS_OPTION_NAME ) || [], + }; + } + return {}; } ); - const recordScore = ( score: number ) => { - if ( score > 2 ) { + const recordScore = ( recordedScore: number ) => { + if ( recordedScore > 2 ) { recordEvent( 'ces_feedback', { action: CES_ACTION, - score, + score: recordedScore, store_age: storeAgeInWeeks, } ); updateOptions( { @@ -80,16 +83,20 @@ export const TaskListCompletedHeaderWithCES: React.FC< TaskListCompletedHeaderPr ], } ); } else { - setScore( score ); + setScore( recordedScore ); setShowCesModal( true ); + recordEvent( 'ces_view', { + action: CES_ACTION, + store_age: storeAgeInWeeks, + } ); } }; - const recordModalScore = ( score: number, comments: string ) => { + const recordModalScore = ( recordedScore: number, comments: string ) => { setShowCesModal( false ); recordEvent( 'ces_feedback', { action: 'store_setup', - score, + score: recordedScore, comments: comments || '', store_age: storeAgeInWeeks, } ); @@ -165,7 +172,7 @@ export const TaskListCompletedHeaderWithCES: React.FC< TaskListCompletedHeaderPr - { allowTracking && ( + { showCES && ( = ( { isComplete, displayProgressHeader, cesHeader = true, + showCESFeedback = false, } ) => { const listEventPrefix = eventName ? eventName + '_' : eventPrefix; const { createNotice } = useDispatch( 'core/notices' ); const { updateOptions, dismissTask, undoDismissTask } = useDispatch( OPTIONS_STORE_NAME ); - const { profileItems, allowTracking } = useSelect( - ( select: WCDataSelector ) => { - const { getProfileItems } = select( ONBOARDING_STORE_NAME ); - const { getOption } = select( OPTIONS_STORE_NAME ); - return { - allowTracking: - getOption( ALLOW_TRACKING_OPTION_NAME ) === 'yes', - profileItems: getProfileItems(), - }; - } - ); + const { profileItems } = useSelect( ( select: WCDataSelector ) => { + const { getProfileItems } = select( ONBOARDING_STORE_NAME ); + return { + profileItems: getProfileItems(), + }; + } ); const { hideTaskList, visitedTask } = useDispatch( ONBOARDING_STORE_NAME ); const userPreferences = useUserPreferences(); const [ headerData, setHeaderData ] = useState< { @@ -239,14 +233,14 @@ export const TaskList: React.FC< TaskListProps > = ( { return
; } - if ( isComplete && ! keepCompletedTaskList ) { + if ( isComplete && keepCompletedTaskList !== 'yes' ) { return ( <> { cesHeader ? ( ) : ( is_complete() ) { + return false; + } + $allow_tracking = 'yes' === get_option( 'woocommerce_allow_tracking' ); + $shown_ces_actions = get_option( 'woocommerce_ces_shown_for_actions', array() ); + return $allow_tracking && ! in_array( 'store_setup', $shown_ces_actions, true ); + } + /** * Remove reminder bar four weeks after store creation. */ @@ -415,6 +429,7 @@ class TaskList { }, $this->sections ), + 'showCESFeedback' => $this->show_ces_feedback(), ); } } From b441a84d9d88a8e74ede4c27e3135ecb50fe7730 Mon Sep 17 00:00:00 2001 From: Lourens Schep Date: Thu, 21 Apr 2022 16:40:48 -0300 Subject: [PATCH 04/14] Keep score displayed if user cancels out of CES Modal --- .../src/customer-effort-score.js | 24 +----- .../src/customer-feedback-modal/index.tsx | 16 +++- .../src/customer-feedback-modal/test/index.js | 8 +- .../customer-feedback-simple.tsx | 18 ++-- .../customer-feedback-simple/test/index.js | 85 ++----------------- .../customer-effort-score/src/test/index.js | 26 +++++- .../js/data/src/onboarding/action-types.js | 2 + packages/js/data/src/onboarding/actions.js | 24 ++++++ packages/js/data/src/onboarding/reducer.js | 12 +++ .../completed-header-with-ces.scss | 14 +-- .../completed-header-with-ces.tsx | 52 ++++++------ .../two-column-tasks/sectioned-task-list.tsx | 23 +++-- .../client/two-column-tasks/task-list.tsx | 19 +++-- 13 files changed, 164 insertions(+), 159 deletions(-) diff --git a/packages/js/customer-effort-score/src/customer-effort-score.js b/packages/js/customer-effort-score/src/customer-effort-score.js index 50b8d9fc0fe..962ff91449d 100644 --- a/packages/js/customer-effort-score/src/customer-effort-score.js +++ b/packages/js/customer-effort-score/src/customer-effort-score.js @@ -4,8 +4,7 @@ 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 @@ -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. */ -function CustomerEffortScoreComponent( { +function CustomerEffortScore( { recordScoreCallback, label, - createNotice, onNoticeShownCallback = noop, onNoticeDismissedCallback = noop, onModalShownCallback = noop, @@ -40,6 +37,7 @@ function CustomerEffortScoreComponent( { } ) { const [ shouldCreateNotice, setShouldCreateNotice ] = useState( true ); const [ visible, setVisible ] = useState( false ); + const { createNotice } = useDispatch( 'core/notices2' ); useEffect( () => { if ( ! shouldCreateNotice ) { @@ -82,7 +80,7 @@ function CustomerEffortScoreComponent( { ); } -CustomerEffortScoreComponent.propTypes = { +CustomerEffortScore.propTypes = { /** * The function to call to record the score. */ @@ -91,10 +89,6 @@ CustomerEffortScoreComponent.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,14 +107,4 @@ CustomerEffortScoreComponent.propTypes = { icon: PropTypes.element, }; -const CustomerEffortScore = compose( - withDispatch( ( dispatch ) => { - const { createNotice } = dispatch( 'core/notices2' ); - - return { - createNotice, - }; - } ) -)( CustomerEffortScoreComponent ); - export { CustomerEffortScore }; diff --git a/packages/js/customer-effort-score/src/customer-feedback-modal/index.tsx b/packages/js/customer-effort-score/src/customer-feedback-modal/index.tsx index 6cd4541bee4..f167740a90a 100644 --- a/packages/js/customer-effort-score/src/customer-feedback-modal/index.tsx +++ b/packages/js/customer-effort-score/src/customer-feedback-modal/index.tsx @@ -26,15 +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 = [ { @@ -64,7 +68,12 @@ function CustomerFeedbackModal( { 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 ); @@ -113,7 +122,7 @@ function CustomerFeedbackModal( { { ( score === 1 || score === 2 ) && (
{ 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(); } ); } diff --git a/packages/js/customer-effort-score/src/customer-feedback-simple/customer-feedback-simple.tsx b/packages/js/customer-effort-score/src/customer-feedback-simple/customer-feedback-simple.tsx index ed162706871..a9d54cc1e3e 100644 --- a/packages/js/customer-effort-score/src/customer-feedback-simple/customer-feedback-simple.tsx +++ b/packages/js/customer-effort-score/src/customer-feedback-simple/customer-feedback-simple.tsx @@ -15,6 +15,7 @@ import { __ } from '@wordpress/i18n'; type CustomerFeedbackSimpleProps = { recordScoreCallback: ( score: number ) => void; label: string; + feedbackScore?: number; showFeedback?: boolean; }; @@ -32,12 +33,14 @@ type CustomerFeedbackSimpleProps = { * @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.showFeedback To show feedback message. + * @param {number} props.feedbackScore Feedback score. + * @param {boolean} props.showFeedback Show feedback. */ const CustomerFeedbackSimple: React.FC< CustomerFeedbackSimpleProps > = ( { recordScoreCallback, label, - showFeedback = false, + feedbackScore = NaN, + showFeedback, } ) => { const options = [ { @@ -67,7 +70,13 @@ const CustomerFeedbackSimple: React.FC< CustomerFeedbackSimpleProps > = ( { }, ]; - const [ score, setScore ] = useState( NaN ); + const [ score, setScore ] = useState( feedbackScore || NaN ); + + useEffect( () => { + if ( feedbackScore !== score ) { + setScore( feedbackScore ); + } + }, [ feedbackScore ] ); useEffect( () => { if ( ! isNaN( score ) ) { @@ -77,7 +86,7 @@ const CustomerFeedbackSimple: React.FC< CustomerFeedbackSimpleProps > = ( { return (
- { isNaN( score ) && ! showFeedback ? ( + { ! showFeedback ? ( = ( { CustomerFeedbackSimple.propTypes = { recordScoreCallback: PropTypes.func.isRequired, label: PropTypes.string.isRequired, - showFeedback: PropTypes.bool, }; export { CustomerFeedbackSimple }; diff --git a/packages/js/customer-effort-score/src/customer-feedback-simple/test/index.js b/packages/js/customer-effort-score/src/customer-feedback-simple/test/index.js index 429ccd75034..c35ad327219 100644 --- a/packages/js/customer-effort-score/src/customer-feedback-simple/test/index.js +++ b/packages/js/customer-effort-score/src/customer-feedback-simple/test/index.js @@ -1,96 +1,27 @@ /** * External dependencies */ -import { render, screen, waitFor, fireEvent } from '@testing-library/react'; +import { render, screen, fireEvent } from '@testing-library/react'; import { createElement } from '@wordpress/element'; /** * Internal dependencies */ -import CustomerFeedbackModal from '../index'; +import { CustomerFeedbackSimple } from '../index'; const mockRecordScoreCallback = jest.fn(); -describe( 'CustomerFeedbackModal', () => { - it( 'should close modal when cancel button pressed', async () => { +describe( 'CustomerFeedbackSimple', () => { + it( 'should trigger recordScoreCallback when item is selected', () => { render( - ); - // Wait for the modal to render. - await screen.findByRole( 'dialog' ); + // Select the option. + fireEvent.click( screen.getAllByText( '🙂' )[ 0 ] ); - // Press cancel button. - fireEvent.click( screen.getByRole( 'button', { name: /cancel/i } ) ); - - expect( screen.queryByRole( 'dialog' ) ).not.toBeInTheDocument(); + expect( mockRecordScoreCallback ).toHaveBeenCalledWith( 4 ); } ); - - it( 'should halt with an error when submitting without a score', async () => { - render( - - ); - - await screen.findByRole( 'dialog' ); // Wait for the modal to render. - - fireEvent.click( screen.getByRole( 'button', { name: /send/i } ) ); // Press send button. - - // Wait for error message. - await screen.findByRole( 'alert' ); - - expect( screen.getByRole( 'dialog' ) ).toBeInTheDocument(); - } ); - - it( 'should disable the comments field initially', async () => { - render( - - ); - - // Wait for the modal to render. - await screen.findByRole( 'dialog' ); - - expect( - screen.queryByLabelText( 'Comments (Optional)' ) - ).not.toBeInTheDocument(); - } ); - - it.each( [ 'Very difficult', 'Somewhat difficult' ] )( - 'should toggle the comments field when %s is selected', - async ( labelText ) => { - render( - - ); - - // Wait for the modal to render. - await screen.findByRole( 'dialog' ); - - // Select the option. - fireEvent.click( screen.getByLabelText( labelText ) ); - - // Wait for comments field to show. - await screen.findByLabelText( 'Comments (Optional)' ); - - // Select neutral score. - fireEvent.click( screen.getByLabelText( 'Neutral' ) ); - - // Wait for comments field to hide. - await waitFor( () => { - expect( - screen.queryByLabelText( 'Comments (Optional)' ) - ).not.toBeInTheDocument(); - } ); - } - ); } ); diff --git a/packages/js/customer-effort-score/src/test/index.js b/packages/js/customer-effort-score/src/test/index.js index 3a580062675..bfb47ad05aa 100644 --- a/packages/js/customer-effort-score/src/test/index.js +++ b/packages/js/customer-effort-score/src/test/index.js @@ -3,6 +3,7 @@ */ import { render, screen } from '@testing-library/react'; import { createElement } from '@wordpress/element'; +import { useDispatch } from '@wordpress/data'; /** * Internal dependencies @@ -11,14 +12,28 @@ 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 = icon; render( { it( 'should not call createNotice on rerender', async () => { const mockCreateNotice = jest.fn(); + useDispatch.mockReturnValue( { + createNotice: mockCreateNotice, + } ); const { rerender } = render( { // Simulate rerender by changing label prop. rerender( @@ -65,7 +82,6 @@ describe( 'CustomerEffortScore', () => { it( 'should not show dialog if no action is taken', async () => { render( @@ -91,10 +107,12 @@ describe( 'CustomerEffortScore', () => { // Modal shown callback should also be called. expect( mockOnModalShownCallback ).toHaveBeenCalled(); }; + useDispatch.mockReturnValue( { + createNotice, + } ); render( { 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, diff --git a/plugins/woocommerce-admin/client/two-column-tasks/completed-header-with-ces.scss b/plugins/woocommerce-admin/client/two-column-tasks/completed-header-with-ces.scss index e784d47201e..3360e1fcd7f 100644 --- a/plugins/woocommerce-admin/client/two-column-tasks/completed-header-with-ces.scss +++ b/plugins/woocommerce-admin/client/two-column-tasks/completed-header-with-ces.scss @@ -1,14 +1,14 @@ .wooocommerce-task-card__header .wooocommerce-task-card__header-subtitle { - color: $gray-700; - margin-bottom: $gap-large; + color: $gray-700; + margin-bottom: $gap-large; } .customer-feedback-simple__container { - height: 64px; + height: 64px; } .woocommerce-task-card__header-menu { - position: absolute; - right: $gap-large; - top: $gap; -} \ No newline at end of file + position: absolute; + right: 0; + top: 0; +} diff --git a/plugins/woocommerce-admin/client/two-column-tasks/completed-header-with-ces.tsx b/plugins/woocommerce-admin/client/two-column-tasks/completed-header-with-ces.tsx index 871c9ad7e4f..05d596aec1b 100644 --- a/plugins/woocommerce-admin/client/two-column-tasks/completed-header-with-ces.tsx +++ b/plugins/woocommerce-admin/client/two-column-tasks/completed-header-with-ces.tsx @@ -52,8 +52,8 @@ export const TaskListCompletedHeaderWithCES: React.FC< TaskListCompletedHeaderPr } ) => { const { updateOptions } = useDispatch( OPTIONS_STORE_NAME ); const [ showCesModal, setShowCesModal ] = useState( false ); + const [ submittedScore, setSubmittedScore ] = useState( false ); const [ score, setScore ] = useState( NaN ); - const [ showFeedback, setShowFeedback ] = useState( false ); const { storeAgeInWeeks, cesShownForActions } = useSelect( ( select ) => { const { getOption } = select( OPTIONS_STORE_NAME ); @@ -69,19 +69,26 @@ export const TaskListCompletedHeaderWithCES: React.FC< TaskListCompletedHeaderPr return {}; } ); + const submitScore = ( recordedScore: number, comments?: string ) => { + recordEvent( 'ces_feedback', { + action: CES_ACTION, + score: recordedScore, + comments: comments || '', + store_age: storeAgeInWeeks, + } ); + updateOptions( { + [ SHOWN_FOR_ACTIONS_OPTION_NAME ]: [ + CES_ACTION, + ...cesShownForActions, + ], + } ); + setSubmittedScore( true ); + }; + const recordScore = ( recordedScore: number ) => { if ( recordedScore > 2 ) { - recordEvent( 'ces_feedback', { - action: CES_ACTION, - score: recordedScore, - store_age: storeAgeInWeeks, - } ); - updateOptions( { - [ SHOWN_FOR_ACTIONS_OPTION_NAME ]: [ - CES_ACTION, - ...cesShownForActions, - ], - } ); + setScore( recordedScore ); + submitScore( recordedScore ); } else { setScore( recordedScore ); setShowCesModal( true ); @@ -94,19 +101,7 @@ export const TaskListCompletedHeaderWithCES: React.FC< TaskListCompletedHeaderPr const recordModalScore = ( recordedScore: number, comments: string ) => { setShowCesModal( false ); - recordEvent( 'ces_feedback', { - action: 'store_setup', - score: recordedScore, - comments: comments || '', - store_age: storeAgeInWeeks, - } ); - updateOptions( { - [ SHOWN_FOR_ACTIONS_OPTION_NAME ]: [ - CES_ACTION, - ...cesShownForActions, - ], - } ); - setShowFeedback( true ); + submitScore( recordedScore, comments ); }; return ( @@ -178,8 +173,9 @@ export const TaskListCompletedHeaderWithCES: React.FC< TaskListCompletedHeaderPr 'How was your experience?', 'woocommerce' ) } + showFeedback={ submittedScore } recordScoreCallback={ recordScore } - showFeedback={ showFeedback } + feedbackScore={ score } /> ) } @@ -189,6 +185,10 @@ export const TaskListCompletedHeaderWithCES: React.FC< TaskListCompletedHeaderPr label={ __( 'How was your experience?', 'woocommerce' ) } defaultScore={ score } recordScoreCallback={ recordModalScore } + onCloseModal={ () => { + setScore( NaN ); + setShowCesModal( false ); + } } /> ) : null } diff --git a/plugins/woocommerce-admin/client/two-column-tasks/sectioned-task-list.tsx b/plugins/woocommerce-admin/client/two-column-tasks/sectioned-task-list.tsx index 0e70b47c7c9..f734f15cb9a 100644 --- a/plugins/woocommerce-admin/client/two-column-tasks/sectioned-task-list.tsx +++ b/plugins/woocommerce-admin/client/two-column-tasks/sectioned-task-list.tsx @@ -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 { TaskListCompletedHeaderWithCES } from './completed-header-with-ces'; type PanelBodyProps = Omit< PanelBody.Props, 'title' | 'onToggle' > & { title: string | React.ReactNode | undefined; @@ -40,6 +41,8 @@ export const SectionedTaskList: React.FC< TaskListProps > = ( { isComplete, sections, displayProgressHeader, + cesHeader = true, + showCESFeedback = false, } ) => { const { updateOptions } = useDispatch( OPTIONS_STORE_NAME ); const { profileItems } = useSelect( ( select ) => { @@ -115,14 +118,22 @@ export const SectionedTaskList: React.FC< TaskListProps > = ( { return
; } - if ( isComplete && ! keepCompletedTaskList ) { + if ( isComplete && keepCompletedTaskList !== 'yes' ) { return ( <> - + { cesHeader ? ( + + ) : ( + + ) } ); } diff --git a/plugins/woocommerce-admin/client/two-column-tasks/task-list.tsx b/plugins/woocommerce-admin/client/two-column-tasks/task-list.tsx index c0e5216013f..9e9ff2f777a 100644 --- a/plugins/woocommerce-admin/client/two-column-tasks/task-list.tsx +++ b/plugins/woocommerce-admin/client/two-column-tasks/task-list.tsx @@ -68,7 +68,11 @@ export const TaskList: React.FC< TaskListProps > = ( { 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; @@ -115,13 +119,14 @@ export const TaskList: React.FC< TaskListProps > = ( { }; const keepTasks = () => { - const updateOptionsParams = { - woocommerce_task_list_keep_completed: 'yes', - }; + keepCompletedTasks( id ); + // const updateOptionsParams = { + // woocommerce_task_list_keep_completed: 'yes', + // }; - updateOptions( { - ...updateOptionsParams, - } ); + // updateOptions( { + // ...updateOptionsParams, + // } ); }; const renderMenu = () => { From fe1e814a418c20a5fc45c09a111933ad759c4bad Mon Sep 17 00:00:00 2001 From: Lourens Schep Date: Fri, 22 Apr 2022 17:21:34 -0300 Subject: [PATCH 05/14] Fix customer effort types and add timeout to thank you message --- .../js/customer-effort-score/package.json | 1 + .../src/{index.js => index.ts} | 0 .../js/customer-effort-score/tsconfig.json | 5 ++- packages/js/data/src/index.ts | 3 +- packages/js/data/src/onboarding/types.ts | 2 +- packages/js/data/src/options/types.ts | 19 ++++++++ .../completed-header-with-ces.tsx | 45 ++++++++++++------- .../client/two-column-tasks/task-list.tsx | 8 +--- 8 files changed, 58 insertions(+), 25 deletions(-) rename packages/js/customer-effort-score/src/{index.js => index.ts} (100%) create mode 100644 packages/js/data/src/options/types.ts diff --git a/packages/js/customer-effort-score/package.json b/packages/js/customer-effort-score/package.json index d481a36d154..14362431945 100644 --- a/packages/js/customer-effort-score/package.json +++ b/packages/js/customer-effort-score/package.json @@ -18,6 +18,7 @@ }, "main": "build/index.js", "module": "build-module/index.js", + "types": "build-types", "react-native": "src/index", "dependencies": { "@woocommerce/experimental": "workspace:*", diff --git a/packages/js/customer-effort-score/src/index.js b/packages/js/customer-effort-score/src/index.ts similarity index 100% rename from packages/js/customer-effort-score/src/index.js rename to packages/js/customer-effort-score/src/index.ts diff --git a/packages/js/customer-effort-score/tsconfig.json b/packages/js/customer-effort-score/tsconfig.json index 6ac6ac42d21..ea9f201d401 100644 --- a/packages/js/customer-effort-score/tsconfig.json +++ b/packages/js/customer-effort-score/tsconfig.json @@ -2,6 +2,9 @@ "extends": "../tsconfig", "compilerOptions": { "rootDir": "src", - "outDir": "build-module" + "outDir": "build-module", + "declaration": true, + "declarationMap": true, + "declarationDir": "./build-types" } } diff --git a/packages/js/data/src/index.ts b/packages/js/data/src/index.ts index bd7b1e8551d..d0d02cae419 100644 --- a/packages/js/data/src/index.ts +++ b/packages/js/data/src/index.ts @@ -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 diff --git a/packages/js/data/src/onboarding/types.ts b/packages/js/data/src/onboarding/types.ts index 25ddbab9b88..817f3f7cf93 100644 --- a/packages/js/data/src/onboarding/types.ts +++ b/packages/js/data/src/onboarding/types.ts @@ -51,7 +51,7 @@ export type TaskListType = { eventPrefix: string; displayProgressHeader: boolean; keepCompletedTaskList: 'yes' | 'no'; - showCESFeedback: boolean; + showCESFeedback?: boolean; sections?: TaskListSection[]; isToggleable?: boolean; isCollapsible?: boolean; diff --git a/packages/js/data/src/options/types.ts b/packages/js/data/src/options/types.ts new file mode 100644 index 00000000000..abfadb138c9 --- /dev/null +++ b/packages/js/data/src/options/types.ts @@ -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; diff --git a/plugins/woocommerce-admin/client/two-column-tasks/completed-header-with-ces.tsx b/plugins/woocommerce-admin/client/two-column-tasks/completed-header-with-ces.tsx index 05d596aec1b..d46b2634ef9 100644 --- a/plugins/woocommerce-admin/client/two-column-tasks/completed-header-with-ces.tsx +++ b/plugins/woocommerce-admin/client/two-column-tasks/completed-header-with-ces.tsx @@ -2,11 +2,11 @@ * External dependencies */ import classnames from 'classnames'; -import { useState } from '@wordpress/element'; +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, WEEK } from '@woocommerce/data'; +import { OPTIONS_STORE_NAME, WCDataSelector, WEEK } from '@woocommerce/data'; import { Button, Card, CardHeader } from '@wordpress/components'; import { Text } from '@woocommerce/experimental'; import { @@ -54,20 +54,35 @@ export const TaskListCompletedHeaderWithCES: React.FC< TaskListCompletedHeaderPr const [ showCesModal, setShowCesModal ] = useState( false ); const [ submittedScore, setSubmittedScore ] = useState( false ); const [ score, setScore ] = useState( NaN ); - const { storeAgeInWeeks, cesShownForActions } = useSelect( ( select ) => { - const { getOption } = select( OPTIONS_STORE_NAME ); + const [ hideCES, setHideCES ] = useState( false ); + const { storeAgeInWeeks, cesShownForActions } = useSelect( + ( select: WCDataSelector ) => { + const { getOption } = select( OPTIONS_STORE_NAME ); - if ( showCES ) { - const adminInstallTimestamp: number = - getOption( ADMIN_INSTALL_TIMESTAMP_OPTION_NAME ) || 0; - return { - storeAgeInWeeks: getStoreAgeInWeeks( adminInstallTimestamp ), - cesShownForActions: - getOption( SHOWN_FOR_ACTIONS_OPTION_NAME ) || [], - }; + if ( showCES ) { + const adminInstallTimestamp: number = + getOption( ADMIN_INSTALL_TIMESTAMP_OPTION_NAME ) || 0; + return { + storeAgeInWeeks: getStoreAgeInWeeks( + adminInstallTimestamp + ), + cesShownForActions: + getOption< string[] >( + SHOWN_FOR_ACTIONS_OPTION_NAME + ) || [], + }; + } + return {}; } - return {}; - } ); + ); + + useEffect( () => { + if ( submittedScore ) { + setTimeout( () => { + setHideCES( true ); + }, 1200 ); + } + }, [ submittedScore ] ); const submitScore = ( recordedScore: number, comments?: string ) => { recordEvent( 'ces_feedback', { @@ -167,7 +182,7 @@ export const TaskListCompletedHeaderWithCES: React.FC< TaskListCompletedHeaderPr
- { showCES && ( + { showCES && ! hideCES && ( = ( { @@ -120,13 +121,6 @@ export const TaskList: React.FC< TaskListProps > = ( { const keepTasks = () => { keepCompletedTasks( id ); - // const updateOptionsParams = { - // woocommerce_task_list_keep_completed: 'yes', - // }; - - // updateOptions( { - // ...updateOptionsParams, - // } ); }; const renderMenu = () => { From 03cb7e5a42f3bfd91b9b23762b932b21b587ec16 Mon Sep 17 00:00:00 2001 From: Lourens Schep Date: Mon, 25 Apr 2022 11:35:46 -0300 Subject: [PATCH 06/14] Fix completed image display in two column layout --- .../client/two-column-tasks/completed-header-with-ces.scss | 4 ++++ .../client/two-column-tasks/completed-header-with-ces.tsx | 6 +++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/plugins/woocommerce-admin/client/two-column-tasks/completed-header-with-ces.scss b/plugins/woocommerce-admin/client/two-column-tasks/completed-header-with-ces.scss index 3360e1fcd7f..6f5cd79b880 100644 --- a/plugins/woocommerce-admin/client/two-column-tasks/completed-header-with-ces.scss +++ b/plugins/woocommerce-admin/client/two-column-tasks/completed-header-with-ces.scss @@ -3,6 +3,10 @@ margin-bottom: $gap-large; } +.wooocommerce-task-card__finished-header-image { + max-width: 75%; +} + .customer-feedback-simple__container { height: 64px; } diff --git a/plugins/woocommerce-admin/client/two-column-tasks/completed-header-with-ces.tsx b/plugins/woocommerce-admin/client/two-column-tasks/completed-header-with-ces.tsx index d46b2634ef9..1fe63daf63e 100644 --- a/plugins/woocommerce-admin/client/two-column-tasks/completed-header-with-ces.tsx +++ b/plugins/woocommerce-admin/client/two-column-tasks/completed-header-with-ces.tsx @@ -132,7 +132,11 @@ export const TaskListCompletedHeaderWithCES: React.FC< TaskListCompletedHeaderPr >
- Completed + Completed

{ __( From 780fbffc686ce52d3666578335c47e8a21e725af Mon Sep 17 00:00:00 2001 From: Lourens Schep Date: Tue, 10 May 2022 09:48:43 -0300 Subject: [PATCH 07/14] Fix merge conflict type issue --- .../client/two-column-tasks/completed-header-with-ces.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/woocommerce-admin/client/two-column-tasks/completed-header-with-ces.tsx b/plugins/woocommerce-admin/client/two-column-tasks/completed-header-with-ces.tsx index 1fe63daf63e..3b3939eee40 100644 --- a/plugins/woocommerce-admin/client/two-column-tasks/completed-header-with-ces.tsx +++ b/plugins/woocommerce-admin/client/two-column-tasks/completed-header-with-ces.tsx @@ -94,7 +94,7 @@ export const TaskListCompletedHeaderWithCES: React.FC< TaskListCompletedHeaderPr updateOptions( { [ SHOWN_FOR_ACTIONS_OPTION_NAME ]: [ CES_ACTION, - ...cesShownForActions, + ...( cesShownForActions || [] ), ], } ); setSubmittedScore( true ); From d1bf5c571932c1eef35d8215f14cb75905753668 Mon Sep 17 00:00:00 2001 From: Lourens Schep Date: Mon, 16 May 2022 10:03:46 -0300 Subject: [PATCH 08/14] Address the majority of the PR feedback --- .../customer-feedback-simple.tsx | 96 +++++-------------- .../customer-feedback-simple/test/index.js | 10 +- .../customer-effort-score-tracks.js | 2 +- ...er-with-ces.scss => completed-header.scss} | 11 ++- ...ader-with-ces.tsx => completed-header.tsx} | 72 ++++++++++---- .../two-column-tasks/sectioned-task-list.tsx | 7 +- .../client/two-column-tasks/style.scss | 7 +- .../client/two-column-tasks/task-list.tsx | 11 +-- .../Features/OnboardingTasks/TaskList.php | 15 --- 9 files changed, 100 insertions(+), 131 deletions(-) rename plugins/woocommerce-admin/client/two-column-tasks/{completed-header-with-ces.scss => completed-header.scss} (60%) rename plugins/woocommerce-admin/client/two-column-tasks/{completed-header-with-ces.tsx => completed-header.tsx} (74%) diff --git a/packages/js/customer-effort-score/src/customer-feedback-simple/customer-feedback-simple.tsx b/packages/js/customer-effort-score/src/customer-feedback-simple/customer-feedback-simple.tsx index a9d54cc1e3e..cbf0e75b06c 100644 --- a/packages/js/customer-effort-score/src/customer-feedback-simple/customer-feedback-simple.tsx +++ b/packages/js/customer-effort-score/src/customer-feedback-simple/customer-feedback-simple.tsx @@ -1,22 +1,15 @@ /** * External dependencies */ -import { - createElement, - Fragment, - useEffect, - useState, -} from '@wordpress/element'; +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 = { - recordScoreCallback: ( score: number ) => void; + onSelect: ( score: number ) => void; label: string; - feedbackScore?: number; - showFeedback?: boolean; }; /** @@ -30,17 +23,13 @@ type CustomerFeedbackSimpleProps = { * * Upon completion, the score and comments is sent to a callback function. * - * @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 {number} props.feedbackScore Feedback score. - * @param {boolean} props.showFeedback Show feedback. + * @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 > = ( { - recordScoreCallback, + onSelect, label, - feedbackScore = NaN, - showFeedback, } ) => { const options = [ { @@ -70,68 +59,35 @@ const CustomerFeedbackSimple: React.FC< CustomerFeedbackSimpleProps > = ( { }, ]; - const [ score, setScore ] = useState( feedbackScore || NaN ); - - useEffect( () => { - if ( feedbackScore !== score ) { - setScore( feedbackScore ); - } - }, [ feedbackScore ] ); - - useEffect( () => { - if ( ! isNaN( score ) ) { - recordScoreCallback( score ); - } - }, [ score ] ); - return (
- { ! showFeedback ? ( - - - { label } - + + { label } + -
- { options.map( ( option ) => ( - - - - ) ) } -
-
- ) : ( -
- + { options.map( ( option ) => ( + - 🙌{ ' ' } - { __( 'We appreciate your feedback!', 'woocommerce' ) } - -
- ) } + + + ) ) } +

); }; CustomerFeedbackSimple.propTypes = { - recordScoreCallback: PropTypes.func.isRequired, + onSelect: PropTypes.func.isRequired, label: PropTypes.string.isRequired, }; diff --git a/packages/js/customer-effort-score/src/customer-feedback-simple/test/index.js b/packages/js/customer-effort-score/src/customer-feedback-simple/test/index.js index c35ad327219..a3133135edd 100644 --- a/packages/js/customer-effort-score/src/customer-feedback-simple/test/index.js +++ b/packages/js/customer-effort-score/src/customer-feedback-simple/test/index.js @@ -9,19 +9,15 @@ import { createElement } from '@wordpress/element'; */ import { CustomerFeedbackSimple } from '../index'; -const mockRecordScoreCallback = jest.fn(); +const mockOnSelectCallback = jest.fn(); describe( 'CustomerFeedbackSimple', () => { it( 'should trigger recordScoreCallback when item is selected', () => { - render( - - ); + render( ); // Select the option. fireEvent.click( screen.getAllByText( '🙂' )[ 0 ] ); - expect( mockRecordScoreCallback ).toHaveBeenCalledWith( 4 ); + expect( mockOnSelectCallback ).toHaveBeenCalledWith( 4 ); } ); } ); diff --git a/plugins/woocommerce-admin/client/customer-effort-score-tracks/customer-effort-score-tracks.js b/plugins/woocommerce-admin/client/customer-effort-score-tracks/customer-effort-score-tracks.js index b55b39548e3..7433e931a93 100644 --- a/plugins/woocommerce-admin/client/customer-effort-score-tracks/customer-effort-score-tracks.js +++ b/plugins/woocommerce-admin/client/customer-effort-score-tracks/customer-effort-score-tracks.js @@ -117,7 +117,7 @@ function CustomerEffortScoreTracks( { return ( void; keepTasks: () => void; - showCES: boolean; + enableCES: boolean; }; const ADMIN_INSTALL_TIMESTAMP_OPTION_NAME = 'woocommerce_admin_install_timestamp'; const SHOWN_FOR_ACTIONS_OPTION_NAME = 'woocommerce_ces_shown_for_actions'; const CES_ACTION = 'store_setup'; +const ALLOW_TRACKING_OPTION_NAME = 'woocommerce_allow_tracking'; function getStoreAgeInWeeks( adminInstallTimestamp: number ) { if ( adminInstallTimestamp === 0 ) { @@ -45,31 +46,46 @@ function getStoreAgeInWeeks( adminInstallTimestamp: number ) { return storeAgeInWeeks; } -export const TaskListCompletedHeaderWithCES: React.FC< TaskListCompletedHeaderProps > = ( { +export const TaskListCompletedHeader: React.FC< TaskListCompletedHeaderProps > = ( { hideTasks, keepTasks, - showCES, + enableCES, } ) => { const { updateOptions } = useDispatch( OPTIONS_STORE_NAME ); const [ showCesModal, setShowCesModal ] = useState( false ); - const [ submittedScore, setSubmittedScore ] = useState( false ); + const [ hasSubmittedScore, setHasSubmittedScore ] = useState( false ); const [ score, setScore ] = useState( NaN ); const [ hideCES, setHideCES ] = useState( false ); - const { storeAgeInWeeks, cesShownForActions } = useSelect( + const { storeAgeInWeeks, cesShownForActions, showCES } = useSelect( ( select: WCDataSelector ) => { - const { getOption } = select( OPTIONS_STORE_NAME ); + const { getOption, hasFinishedResolution } = select( + OPTIONS_STORE_NAME + ); - if ( showCES ) { + if ( enableCES ) { + 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: - getOption< string[] >( - SHOWN_FOR_ACTIONS_OPTION_NAME - ) || [], + cesShownForActions: cesActions, + showCES: + ! loadingOptions && + allowTracking && + ! ( cesActions || [] ).includes( 'store_setup' ), + loading: loadingOptions, }; } return {}; @@ -77,12 +93,12 @@ export const TaskListCompletedHeaderWithCES: React.FC< TaskListCompletedHeaderPr ); useEffect( () => { - if ( submittedScore ) { + if ( hasSubmittedScore ) { setTimeout( () => { setHideCES( true ); }, 1200 ); } - }, [ submittedScore ] ); + }, [ hasSubmittedScore ] ); const submitScore = ( recordedScore: number, comments?: string ) => { recordEvent( 'ces_feedback', { @@ -97,7 +113,7 @@ export const TaskListCompletedHeaderWithCES: React.FC< TaskListCompletedHeaderPr ...( cesShownForActions || [] ), ], } ); - setSubmittedScore( true ); + setHasSubmittedScore( true ); }; const recordScore = ( recordedScore: number ) => { @@ -138,12 +154,12 @@ export const TaskListCompletedHeaderWithCES: React.FC< TaskListCompletedHeaderPr className="wooocommerce-task-card__finished-header-image" /> -

+ { __( "You've completed store setup", 'woocommerce' ) } -

+
- { showCES && ! hideCES && ( + { showCES && ! hideCES && ! hasSubmittedScore && ( ) } + { hasSubmittedScore && ! hideCES && ( +
+ + 🙌{ ' ' } + { __( + 'We appreciate your feedback!', + 'woocommerce' + ) } + +
+ ) } { showCesModal ? ( diff --git a/plugins/woocommerce-admin/client/two-column-tasks/sectioned-task-list.tsx b/plugins/woocommerce-admin/client/two-column-tasks/sectioned-task-list.tsx index f734f15cb9a..ed0de1e6fea 100644 --- a/plugins/woocommerce-admin/client/two-column-tasks/sectioned-task-list.tsx +++ b/plugins/woocommerce-admin/client/two-column-tasks/sectioned-task-list.tsx @@ -24,7 +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 { TaskListCompletedHeaderWithCES } from './completed-header-with-ces'; +import { TaskListCompletedHeader } from './completed-header'; type PanelBodyProps = Omit< PanelBody.Props, 'title' | 'onToggle' > & { title: string | React.ReactNode | undefined; @@ -42,7 +42,6 @@ export const SectionedTaskList: React.FC< TaskListProps > = ( { sections, displayProgressHeader, cesHeader = true, - showCESFeedback = false, } ) => { const { updateOptions } = useDispatch( OPTIONS_STORE_NAME ); const { profileItems } = useSelect( ( select ) => { @@ -122,10 +121,10 @@ export const SectionedTaskList: React.FC< TaskListProps > = ( { return ( <> { cesHeader ? ( - ) : ( = ( { isComplete, displayProgressHeader, cesHeader = true, - showCESFeedback = false, } ) => { const listEventPrefix = eventName ? eventName + '_' : eventPrefix; - const { createNotice } = useDispatch( 'core/notices' ); - const { updateOptions, dismissTask, undoDismissTask } = useDispatch( - OPTIONS_STORE_NAME - ); const { profileItems } = useSelect( ( select: WCDataSelector ) => { const { getProfileItems } = select( ONBOARDING_STORE_NAME ); return { @@ -236,10 +231,10 @@ export const TaskList: React.FC< TaskListProps > = ( { return ( <> { cesHeader ? ( - ) : ( is_complete() ) { - return false; - } - $allow_tracking = 'yes' === get_option( 'woocommerce_allow_tracking' ); - $shown_ces_actions = get_option( 'woocommerce_ces_shown_for_actions', array() ); - return $allow_tracking && ! in_array( 'store_setup', $shown_ces_actions, true ); - } - /** * Remove reminder bar four weeks after store creation. */ @@ -429,7 +415,6 @@ class TaskList { }, $this->sections ), - 'showCESFeedback' => $this->show_ces_feedback(), ); } } From e820eb06db53d0f5afe94467e291f68e4d97094f Mon Sep 17 00:00:00 2001 From: Lourens Schep Date: Mon, 16 May 2022 10:05:53 -0300 Subject: [PATCH 09/14] Update emoji's --- .../src/customer-feedback-simple/customer-feedback-simple.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/js/customer-effort-score/src/customer-feedback-simple/customer-feedback-simple.tsx b/packages/js/customer-effort-score/src/customer-feedback-simple/customer-feedback-simple.tsx index cbf0e75b06c..bbabcfda706 100644 --- a/packages/js/customer-effort-score/src/customer-feedback-simple/customer-feedback-simple.tsx +++ b/packages/js/customer-effort-score/src/customer-feedback-simple/customer-feedback-simple.tsx @@ -35,7 +35,7 @@ const CustomerFeedbackSimple: React.FC< CustomerFeedbackSimpleProps > = ( { { tooltip: __( 'Very difficult', 'woocommerce' ), value: 1, - emoji: '😔', + emoji: '😞', }, { tooltip: __( 'Difficult', 'woocommerce' ), From 2291de40c77cc2eacdbe01de603097f4eb4d4b51 Mon Sep 17 00:00:00 2001 From: Lourens Schep Date: Mon, 16 May 2022 10:27:35 -0300 Subject: [PATCH 10/14] Make sure Modal emoji's match --- packages/js/customer-effort-score/src/style.scss | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/js/customer-effort-score/src/style.scss b/packages/js/customer-effort-score/src/style.scss index 0c0101b05cf..6c7397a20f8 100644 --- a/packages/js/customer-effort-score/src/style.scss +++ b/packages/js/customer-effort-score/src/style.scss @@ -70,7 +70,7 @@ } input[value='3'] + label::before { - content: '😐'; + content: '😑'; } input[value='4'] + label::before { @@ -78,7 +78,7 @@ } input[value='5'] + label::before { - content: '😁'; + content: '😍'; } } } From 137ec24174a2e0ad54c65646bd3845377dfef00f Mon Sep 17 00:00:00 2001 From: Lourens Schep Date: Mon, 16 May 2022 10:36:30 -0300 Subject: [PATCH 11/14] Add changelogs for customer-effort-score package --- .../feature-32158_complete_task_list_card_with_feedback | 4 ++++ ...ture-32158_complete_task_list_card_with_feedback_enable_ts | 4 ++++ 2 files changed, 8 insertions(+) create mode 100644 packages/js/customer-effort-score/changelog/feature-32158_complete_task_list_card_with_feedback create mode 100644 packages/js/customer-effort-score/changelog/feature-32158_complete_task_list_card_with_feedback_enable_ts diff --git a/packages/js/customer-effort-score/changelog/feature-32158_complete_task_list_card_with_feedback b/packages/js/customer-effort-score/changelog/feature-32158_complete_task_list_card_with_feedback new file mode 100644 index 00000000000..c0d5a961114 --- /dev/null +++ b/packages/js/customer-effort-score/changelog/feature-32158_complete_task_list_card_with_feedback @@ -0,0 +1,4 @@ +Significance: minor +Type: add + +Add new simple customer feedback component for inline CES feedback. #32538 diff --git a/packages/js/customer-effort-score/changelog/feature-32158_complete_task_list_card_with_feedback_enable_ts b/packages/js/customer-effort-score/changelog/feature-32158_complete_task_list_card_with_feedback_enable_ts new file mode 100644 index 00000000000..90abcdfb65d --- /dev/null +++ b/packages/js/customer-effort-score/changelog/feature-32158_complete_task_list_card_with_feedback_enable_ts @@ -0,0 +1,4 @@ +Significance: minor +Type: dev + +Add TypeScript type support as part of the build process. #32538 From 3ba939a27a555e12873cd984d1ee45712ba34e50 Mon Sep 17 00:00:00 2001 From: Lourens Schep Date: Mon, 16 May 2022 10:54:07 -0300 Subject: [PATCH 12/14] Add changelog for data package --- .../feature-32158_option_types_and_tasklist_completed | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 packages/js/data/changelog/feature-32158_option_types_and_tasklist_completed diff --git a/packages/js/data/changelog/feature-32158_option_types_and_tasklist_completed b/packages/js/data/changelog/feature-32158_option_types_and_tasklist_completed new file mode 100644 index 00000000000..072fafbd651 --- /dev/null +++ b/packages/js/data/changelog/feature-32158_option_types_and_tasklist_completed @@ -0,0 +1,4 @@ +Significance: minor +Type: enhancement + +Added TypeScript options selectors and action in onboarding store for keeping the completed task list. #32158 From 5494f4970db5cbc68ff46175b42f34ab775ce92d Mon Sep 17 00:00:00 2001 From: Lourens Schep Date: Tue, 17 May 2022 09:59:11 -0300 Subject: [PATCH 13/14] Renamed some of the variables to remove some ambiguity --- .../two-column-tasks/completed-header.tsx | 108 +++++++++--------- .../two-column-tasks/sectioned-task-list.tsx | 2 +- .../client/two-column-tasks/task-list.tsx | 2 +- 3 files changed, 58 insertions(+), 54 deletions(-) diff --git a/plugins/woocommerce-admin/client/two-column-tasks/completed-header.tsx b/plugins/woocommerce-admin/client/two-column-tasks/completed-header.tsx index 36c57d33c6e..8cc37698ec3 100644 --- a/plugins/woocommerce-admin/client/two-column-tasks/completed-header.tsx +++ b/plugins/woocommerce-admin/client/two-column-tasks/completed-header.tsx @@ -24,13 +24,13 @@ import HeaderImage from './completed-celebration-header.svg'; type TaskListCompletedHeaderProps = { hideTasks: () => void; keepTasks: () => void; - enableCES: boolean; + showCustomerEffortScore: boolean; }; const ADMIN_INSTALL_TIMESTAMP_OPTION_NAME = 'woocommerce_admin_install_timestamp'; const SHOWN_FOR_ACTIONS_OPTION_NAME = 'woocommerce_ces_shown_for_actions'; -const CES_ACTION = 'store_setup'; +const CUSTOMER_EFFORT_SCORE_ACTION = 'store_setup'; const ALLOW_TRACKING_OPTION_NAME = 'woocommerce_allow_tracking'; function getStoreAgeInWeeks( adminInstallTimestamp: number ) { @@ -49,67 +49,69 @@ function getStoreAgeInWeeks( adminInstallTimestamp: number ) { export const TaskListCompletedHeader: React.FC< TaskListCompletedHeaderProps > = ( { hideTasks, keepTasks, - enableCES, + showCustomerEffortScore, } ) => { const { updateOptions } = useDispatch( OPTIONS_STORE_NAME ); const [ showCesModal, setShowCesModal ] = useState( false ); const [ hasSubmittedScore, setHasSubmittedScore ] = useState( false ); const [ score, setScore ] = useState( NaN ); - const [ hideCES, setHideCES ] = useState( false ); - const { storeAgeInWeeks, cesShownForActions, showCES } = useSelect( - ( select: WCDataSelector ) => { - const { getOption, hasFinishedResolution } = select( - OPTIONS_STORE_NAME - ); - - if ( enableCES ) { - 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, - showCES: - ! loadingOptions && - allowTracking && - ! ( cesActions || [] ).includes( 'store_setup' ), - loading: loadingOptions, - }; - } - return {}; - } + const [ hideCustomerEffortScore, setHideCustomerEffortScore ] = useState( + false ); + const { + storeAgeInWeeks, + cesShownForActions, + canShowCustomerEffortScore, + } = useSelect( ( select: WCDataSelector ) => { + const { getOption, hasFinishedResolution } = select( + OPTIONS_STORE_NAME + ); + + if ( showCustomerEffortScore ) { + 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( () => { - setHideCES( true ); + setHideCustomerEffortScore( true ); }, 1200 ); } }, [ hasSubmittedScore ] ); const submitScore = ( recordedScore: number, comments?: string ) => { recordEvent( 'ces_feedback', { - action: CES_ACTION, + action: CUSTOMER_EFFORT_SCORE_ACTION, score: recordedScore, comments: comments || '', store_age: storeAgeInWeeks, } ); updateOptions( { [ SHOWN_FOR_ACTIONS_OPTION_NAME ]: [ - CES_ACTION, + CUSTOMER_EFFORT_SCORE_ACTION, ...( cesShownForActions || [] ), ], } ); @@ -124,7 +126,7 @@ export const TaskListCompletedHeader: React.FC< TaskListCompletedHeaderProps > = setScore( recordedScore ); setShowCesModal( true ); recordEvent( 'ces_view', { - action: CES_ACTION, + action: CUSTOMER_EFFORT_SCORE_ACTION, store_age: storeAgeInWeeks, } ); } @@ -202,16 +204,18 @@ export const TaskListCompletedHeader: React.FC< TaskListCompletedHeaderProps > = - { showCES && ! hideCES && ! hasSubmittedScore && ( - - ) } - { hasSubmittedScore && ! hideCES && ( + { canShowCustomerEffortScore && + ! hideCustomerEffortScore && + ! hasSubmittedScore && ( + + ) } + { hasSubmittedScore && ! hideCustomerEffortScore && (
= ( { ) : ( = ( { ) : ( Date: Tue, 17 May 2022 14:25:06 -0300 Subject: [PATCH 14/14] Rename showCustomerEffortScore prop --- .../client/two-column-tasks/completed-header.tsx | 6 +++--- .../client/two-column-tasks/sectioned-task-list.tsx | 2 +- .../woocommerce-admin/client/two-column-tasks/task-list.tsx | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/plugins/woocommerce-admin/client/two-column-tasks/completed-header.tsx b/plugins/woocommerce-admin/client/two-column-tasks/completed-header.tsx index 8cc37698ec3..2082d98dfdd 100644 --- a/plugins/woocommerce-admin/client/two-column-tasks/completed-header.tsx +++ b/plugins/woocommerce-admin/client/two-column-tasks/completed-header.tsx @@ -24,7 +24,7 @@ import HeaderImage from './completed-celebration-header.svg'; type TaskListCompletedHeaderProps = { hideTasks: () => void; keepTasks: () => void; - showCustomerEffortScore: boolean; + customerEffortScore: boolean; }; const ADMIN_INSTALL_TIMESTAMP_OPTION_NAME = @@ -49,7 +49,7 @@ function getStoreAgeInWeeks( adminInstallTimestamp: number ) { export const TaskListCompletedHeader: React.FC< TaskListCompletedHeaderProps > = ( { hideTasks, keepTasks, - showCustomerEffortScore, + customerEffortScore, } ) => { const { updateOptions } = useDispatch( OPTIONS_STORE_NAME ); const [ showCesModal, setShowCesModal ] = useState( false ); @@ -67,7 +67,7 @@ export const TaskListCompletedHeader: React.FC< TaskListCompletedHeaderProps > = OPTIONS_STORE_NAME ); - if ( showCustomerEffortScore ) { + if ( customerEffortScore ) { const allowTracking = getOption( ALLOW_TRACKING_OPTION_NAME ); const adminInstallTimestamp: number = getOption( ADMIN_INSTALL_TIMESTAMP_OPTION_NAME ) || 0; diff --git a/plugins/woocommerce-admin/client/two-column-tasks/sectioned-task-list.tsx b/plugins/woocommerce-admin/client/two-column-tasks/sectioned-task-list.tsx index bece55ab9f5..d4fcbb67e8b 100644 --- a/plugins/woocommerce-admin/client/two-column-tasks/sectioned-task-list.tsx +++ b/plugins/woocommerce-admin/client/two-column-tasks/sectioned-task-list.tsx @@ -124,7 +124,7 @@ export const SectionedTaskList: React.FC< TaskListProps > = ( { ) : ( = ( { ) : (