Add/35300 ces feedback product mvp (#35690)
* Add ability to show CES modal through share Feedback button * Make use of showCesModal in footer * Update CES action for share feedback * Update changes to support second CES question * Add changelog * Address some PR feedback
This commit is contained in:
parent
0e8fbe083d
commit
e7dd1a0be9
|
@ -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 (
|
||||
<CustomerFeedbackModal
|
||||
title={ visibleCESModalData.label }
|
||||
firstQuestion={ visibleCESModalData.firstQuestion }
|
||||
secondQuestion={ visibleCESModalData.secondQuestion }
|
||||
recordScoreCallback={ ( ...args ) => {
|
||||
recordScore( ...args );
|
||||
hideCesModal();
|
||||
} }
|
||||
onCloseModal={ () => hideCesModal() }
|
||||
/>
|
||||
);
|
||||
};
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
*/
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -1,3 +1,7 @@
|
|||
export function getCesSurveyQueue( state ) {
|
||||
return state.queue;
|
||||
}
|
||||
|
||||
export function getVisibleCESModalData( state ) {
|
||||
return state.showCESModal ? state.cesModalData : undefined;
|
||||
}
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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: <span>🌟</span>,
|
||||
}
|
||||
);
|
||||
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: <span>🌟</span>,
|
||||
}
|
||||
);
|
||||
setShowFeedbackModal( false );
|
||||
};
|
||||
|
||||
const showCESFooter =
|
||||
! isLoading && allowTracking && cesAction && cesAction !== 'hide';
|
||||
|
||||
|
@ -158,24 +143,6 @@ export const ProductMVPCESFooter: React.FC = () => {
|
|||
</div>
|
||||
</WooFooterItem>
|
||||
) }
|
||||
{ showFeedbackModal && (
|
||||
<CustomerFeedbackModal
|
||||
title={ __(
|
||||
"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'
|
||||
) }
|
||||
recordScoreCallback={ recordScore }
|
||||
onCloseModal={ () => setShowFeedbackModal( false ) }
|
||||
/>
|
||||
) }
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -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 {
|
|||
</Suspense>
|
||||
) }
|
||||
<Footer />
|
||||
<CustomerEffortScoreModalContainer />
|
||||
</div>
|
||||
<PluginArea scope="woocommerce-admin" />
|
||||
{ window.wcAdminFeatures.navigation && (
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
*/
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { DropdownMenu, MenuItem } from '@wordpress/components';
|
||||
import { useDispatch } from '@wordpress/data';
|
||||
import { getAdminLink } from '@woocommerce/settings';
|
||||
import { moreVertical } from '@wordpress/icons';
|
||||
import { Product } from '@woocommerce/data';
|
||||
|
@ -14,10 +15,12 @@ import { useFormContext } from '@woocommerce/components';
|
|||
import { ClassicEditorIcon } from './images/classic-editor-icon';
|
||||
import { FeedbackIcon } from './images/feedback-icon';
|
||||
import { WooHeaderItem } from '~/header/utils';
|
||||
import { STORE_KEY as CES_STORE_KEY } from '~/customer-effort-score-tracks/data/constants';
|
||||
import './product-more-menu.scss';
|
||||
|
||||
export const ProductMoreMenu = () => {
|
||||
const { values } = useFormContext< Product >();
|
||||
const { showCesModal } = useDispatch( CES_STORE_KEY );
|
||||
|
||||
const classEditorUrl = values.id
|
||||
? getAdminLink( `post.php?post=${ values.id }&action=edit` )
|
||||
|
@ -36,6 +39,28 @@ export const ProductMoreMenu = () => {
|
|||
<MenuItem
|
||||
onClick={ () => {
|
||||
// @todo This should open the CES modal.
|
||||
showCesModal(
|
||||
{
|
||||
action: 'new_product',
|
||||
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'
|
||||
),
|
||||
},
|
||||
{},
|
||||
{
|
||||
type: 'snackbar',
|
||||
icon: <span>🌟</span>,
|
||||
}
|
||||
);
|
||||
onClose();
|
||||
} }
|
||||
icon={ <FeedbackIcon /> }
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
Significance: minor
|
||||
Type: add
|
||||
|
||||
Add CES feedback functionality to the share feedback button within the Product MVP.
|
Loading…
Reference in New Issue