diff --git a/plugins/woocommerce-admin/client/customer-effort-score-tracks/customer-effort-score-modal-container.tsx b/plugins/woocommerce-admin/client/customer-effort-score-tracks/customer-effort-score-modal-container.tsx new file mode 100644 index 00000000000..051b7a351de --- /dev/null +++ b/plugins/woocommerce-admin/client/customer-effort-score-tracks/customer-effort-score-modal-container.tsx @@ -0,0 +1,87 @@ +/** + * External dependencies + */ +import { __ } from '@wordpress/i18n'; +import { useDispatch, useSelect } from '@wordpress/data'; +import { CustomerFeedbackModal } from '@woocommerce/customer-effort-score'; +import { recordEvent } from '@woocommerce/tracks'; +import { OPTIONS_STORE_NAME } from '@woocommerce/data'; + +/** + * Internal dependencies + */ +import { getStoreAgeInWeeks } from './utils'; +import { ADMIN_INSTALL_TIMESTAMP_OPTION_NAME } from './constants'; +import { STORE_KEY } from './data/constants'; + +export const PRODUCT_MVP_CES_ACTION_OPTION_NAME = + 'woocommerce_ces_product_mvp_ces_action'; + +export const CustomerEffortScoreModalContainer: React.FC = () => { + const { createSuccessNotice } = useDispatch( 'core/notices' ); + const { hideCesModal } = useDispatch( STORE_KEY ); + const { + storeAgeInWeeks, + resolving: isLoading, + visibleCESModalData, + } = useSelect( ( select ) => { + const { getOption, hasFinishedResolution } = + select( OPTIONS_STORE_NAME ); + const { getVisibleCESModalData } = select( STORE_KEY ); + + const adminInstallTimestamp = + ( getOption( ADMIN_INSTALL_TIMESTAMP_OPTION_NAME ) as number ) || 0; + + const resolving = + adminInstallTimestamp === null || + ! hasFinishedResolution( 'getOption', [ + ADMIN_INSTALL_TIMESTAMP_OPTION_NAME, + ] ); + + return { + storeAgeInWeeks: getStoreAgeInWeeks( adminInstallTimestamp ), + visibleCESModalData: getVisibleCESModalData(), + resolving, + }; + } ); + + const recordScore = ( + score: number, + secondScore: number, + comments: string + ) => { + recordEvent( 'ces_feedback', { + action: visibleCESModalData.action, + score, + score_second_question: secondScore ?? null, + score_combined: score + ( secondScore ?? 0 ), + comments: comments || '', + store_age: storeAgeInWeeks, + } ); + createSuccessNotice( + visibleCESModalData.onSubmitLabel || + __( + "Thanks for the feedback. We'll put it to good use!", + 'woocommerce' + ), + visibleCESModalData.onSubmitNoticeProps || {} + ); + }; + + if ( ! visibleCESModalData || isLoading ) { + return null; + } + + return ( + { + recordScore( ...args ); + hideCesModal(); + } } + onCloseModal={ () => hideCesModal() } + /> + ); +}; diff --git a/plugins/woocommerce-admin/client/customer-effort-score-tracks/data/action-types.js b/plugins/woocommerce-admin/client/customer-effort-score-tracks/data/action-types.js index d3960abc423..74e552b9faf 100644 --- a/plugins/woocommerce-admin/client/customer-effort-score-tracks/data/action-types.js +++ b/plugins/woocommerce-admin/client/customer-effort-score-tracks/data/action-types.js @@ -1,6 +1,8 @@ const TYPES = { SET_CES_SURVEY_QUEUE: 'SET_CES_SURVEY_QUEUE', ADD_CES_SURVEY: 'ADD_CES_SURVEY', + SHOW_CES_MODAL: 'SHOW_CES_MODAL', + HIDE_CES_MODAL: 'HIDE_CES_MODAL', }; export default TYPES; diff --git a/plugins/woocommerce-admin/client/customer-effort-score-tracks/data/actions.js b/plugins/woocommerce-admin/client/customer-effort-score-tracks/data/actions.js index 49ef813327b..cfff81e1d2a 100644 --- a/plugins/woocommerce-admin/client/customer-effort-score-tracks/data/actions.js +++ b/plugins/woocommerce-admin/client/customer-effort-score-tracks/data/actions.js @@ -56,6 +56,36 @@ export function addCesSurvey( { }; } +/** + * Add show CES modal. + * + * @param {Object} surveyProps props for CES survey, similar to addCesSurvey. + * @param {Object} props object for optional props + * @param {Object} onSubmitNoticeProps object for on submit notice props. + */ +export function showCesModal( + surveyProps = {}, + props = {}, + onSubmitNoticeProps = {} +) { + return { + type: TYPES.SHOW_CES_MODAL, + surveyProps, + onsubmit_label: surveyProps.onsubmitLabel || '', + props, + onSubmitNoticeProps, + }; +} + +/** + * Hide CES Modal. + */ +export function hideCesModal() { + return { + type: TYPES.HIDE_CES_MODAL, + }; +} + /** * Add a new CES survey track for the pages in Analytics menu */ diff --git a/plugins/woocommerce-admin/client/customer-effort-score-tracks/data/reducer.js b/plugins/woocommerce-admin/client/customer-effort-score-tracks/data/reducer.js index 96c96dc424a..be98c28f74d 100644 --- a/plugins/woocommerce-admin/client/customer-effort-score-tracks/data/reducer.js +++ b/plugins/woocommerce-admin/client/customer-effort-score-tracks/data/reducer.js @@ -5,6 +5,8 @@ import TYPES from './action-types'; const DEFAULT_STATE = { queue: [], + cesModalData: undefined, + showCESModal: false, }; const reducer = ( state = DEFAULT_STATE, action ) => { @@ -14,6 +16,27 @@ const reducer = ( state = DEFAULT_STATE, action ) => { ...state, queue: action.queue, }; + case TYPES.HIDE_CES_MODAL: + return { + ...state, + showCESModal: false, + cesModalData: undefined, + }; + case TYPES.SHOW_CES_MODAL: + const cesModalData = { + action: action.surveyProps.action, + label: action.surveyProps.label, + onSubmitLabel: action.onSubmitLabel, + firstQuestion: action.surveyProps.firstQuestion, + secondQuestion: action.surveyProps.secondQuestion, + onSubmitNoticeProps: action.onSubmitNoticeProps || {}, + props: action.props, + }; + return { + ...state, + showCESModal: true, + cesModalData, + }; case TYPES.ADD_CES_SURVEY: // Prevent duplicate const hasDuplicate = state.queue.filter( diff --git a/plugins/woocommerce-admin/client/customer-effort-score-tracks/data/selectors.js b/plugins/woocommerce-admin/client/customer-effort-score-tracks/data/selectors.js index ab18e6631af..e4823d8e126 100644 --- a/plugins/woocommerce-admin/client/customer-effort-score-tracks/data/selectors.js +++ b/plugins/woocommerce-admin/client/customer-effort-score-tracks/data/selectors.js @@ -1,3 +1,7 @@ export function getCesSurveyQueue( state ) { return state.queue; } + +export function getVisibleCESModalData( state ) { + return state.showCESModal ? state.cesModalData : undefined; +} diff --git a/plugins/woocommerce-admin/client/customer-effort-score-tracks/index.js b/plugins/woocommerce-admin/client/customer-effort-score-tracks/index.js index 58b35851d2e..bd9b84cbbe4 100644 --- a/plugins/woocommerce-admin/client/customer-effort-score-tracks/index.js +++ b/plugins/woocommerce-admin/client/customer-effort-score-tracks/index.js @@ -1,2 +1,3 @@ export { default as CustomerEffortScoreTracks } from './customer-effort-score-tracks'; export { default as CustomerEffortScoreTracksContainer } from './customer-effort-score-tracks-container'; +export * from './customer-effort-score-modal-container.tsx'; diff --git a/plugins/woocommerce-admin/client/customer-effort-score-tracks/product-mvp-ces-footer.tsx b/plugins/woocommerce-admin/client/customer-effort-score-tracks/product-mvp-ces-footer.tsx index 6b93ac67290..3c1ea3809e3 100644 --- a/plugins/woocommerce-admin/client/customer-effort-score-tracks/product-mvp-ces-footer.tsx +++ b/plugins/woocommerce-admin/client/customer-effort-score-tracks/product-mvp-ces-footer.tsx @@ -2,36 +2,30 @@ * External dependencies */ import { __ } from '@wordpress/i18n'; -import { useState } from '@wordpress/element'; import { Button } from '@wordpress/components'; import { useDispatch, useSelect } from '@wordpress/data'; import { closeSmall } from '@wordpress/icons'; import { Pill } from '@woocommerce/components'; -import { CustomerFeedbackModal } from '@woocommerce/customer-effort-score'; -import { recordEvent } from '@woocommerce/tracks'; import { OPTIONS_STORE_NAME } from '@woocommerce/data'; /** * Internal dependencies */ import './product-mvp-ces-footer.scss'; -import { getStoreAgeInWeeks } from './utils'; import { - ADMIN_INSTALL_TIMESTAMP_OPTION_NAME, ALLOW_TRACKING_OPTION_NAME, SHOWN_FOR_ACTIONS_OPTION_NAME, } from './constants'; import { WooFooterItem } from '~/layout/footer'; +import { STORE_KEY } from './data/constants'; export const PRODUCT_MVP_CES_ACTION_OPTION_NAME = 'woocommerce_ces_product_mvp_ces_action'; export const ProductMVPCESFooter: React.FC = () => { - const [ showFeedbackModal, setShowFeedbackModal ] = useState( false ); - const { createSuccessNotice } = useDispatch( 'core/notices' ); + const { showCesModal } = useDispatch( STORE_KEY ); const { updateOptions } = useDispatch( OPTIONS_STORE_NAME ); const { - storeAgeInWeeks, cesAction, allowTracking, cesShownForActions, @@ -47,9 +41,6 @@ export const ProductMVPCESFooter: React.FC = () => { const shownForActions = ( getOption( SHOWN_FOR_ACTIONS_OPTION_NAME ) as string[] ) || []; - const adminInstallTimestamp = - ( getOption( ADMIN_INSTALL_TIMESTAMP_OPTION_NAME ) as number ) || 0; - const allowTrackingOption = getOption( ALLOW_TRACKING_OPTION_NAME ) || 'no'; @@ -60,10 +51,6 @@ export const ProductMVPCESFooter: React.FC = () => { ! hasFinishedResolution( 'getOption', [ PRODUCT_MVP_CES_ACTION_OPTION_NAME, ] ) || - adminInstallTimestamp === null || - ! hasFinishedResolution( 'getOption', [ - ADMIN_INSTALL_TIMESTAMP_OPTION_NAME, - ] ) || ! hasFinishedResolution( 'getOption', [ ALLOW_TRACKING_OPTION_NAME, ] ); @@ -71,14 +58,38 @@ export const ProductMVPCESFooter: React.FC = () => { return { cesShownForActions: shownForActions, allowTracking: allowTrackingOption === 'yes', - storeAgeInWeeks: getStoreAgeInWeeks( adminInstallTimestamp ), cesAction: action, resolving, }; } ); const shareFeedback = () => { - setShowFeedbackModal( true ); + showCesModal( + { + action: cesAction, + label: __( + "How's your experience with the product editor?", + 'woocommerce' + ), + firstQuestion: __( + 'The product editing screen is easy to use', + 'woocommerce' + ), + secondQuestion: __( + "The product editing screen's functionality meets my needs", + 'woocommerce' + ), + onsubmitLabel: __( + "Thanks for the feedback. We'll put it to good use!", + 'woocommerce' + ), + }, + {}, + { + type: 'snackbar', + icon: 🌟, + } + ); updateOptions( { [ SHOWN_FOR_ACTIONS_OPTION_NAME ]: [ cesAction, @@ -93,32 +104,6 @@ export const ProductMVPCESFooter: React.FC = () => { } ); }; - const recordScore = ( - score: number, - secondScore: number, - comments: string - ) => { - recordEvent( 'ces_feedback', { - action: cesAction, - score, - score_second_question: secondScore ?? null, - score_combined: score + ( secondScore ?? 0 ), - comments: comments || '', - store_age: storeAgeInWeeks, - } ); - createSuccessNotice( - __( - "Thanks for the feedback. We'll put it to good use!", - 'woocommerce' - ), - { - type: 'snackbar', - icon: 🌟, - } - ); - setShowFeedbackModal( false ); - }; - const showCESFooter = ! isLoading && allowTracking && cesAction && cesAction !== 'hide'; @@ -158,24 +143,6 @@ export const ProductMVPCESFooter: React.FC = () => { ) } - { showFeedbackModal && ( - setShowFeedbackModal( false ) } - /> - ) } ); }; diff --git a/plugins/woocommerce-admin/client/layout/index.js b/plugins/woocommerce-admin/client/layout/index.js index 8de16df60e2..7e4f299b638 100644 --- a/plugins/woocommerce-admin/client/layout/index.js +++ b/plugins/woocommerce-admin/client/layout/index.js @@ -37,6 +37,7 @@ import { Header } from '../header'; import { Footer } from './footer'; import Notices from './notices'; import TransientNotices from './transient-notices'; +import { CustomerEffortScoreModalContainer } from '../customer-effort-score-tracks'; import { getAdminSetting } from '~/utils/admin-settings'; import '~/activity-panel'; import '~/mobile-banner'; @@ -248,6 +249,7 @@ class _Layout extends Component { ) }