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 }
>
);
};