Skip to content

Commit

Permalink
Show feedback footer on product editor page (#38599)
Browse files Browse the repository at this point in the history
  • Loading branch information
mattsherman committed Jun 9, 2023
1 parent f163880 commit dadc0eb
Show file tree
Hide file tree
Showing 34 changed files with 452 additions and 260 deletions.
@@ -0,0 +1,4 @@
Significance: patch
Type: dev

Remove unused constant.
Expand Up @@ -15,9 +15,6 @@ import { getStoreAgeInWeeks } from '../../utils';
import { STORE_KEY } from '../../store';
import { ADMIN_INSTALL_TIMESTAMP_OPTION_NAME } from '../../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 );
Expand Down
1 change: 1 addition & 0 deletions packages/js/customer-effort-score/src/hooks/index.ts
@@ -1 +1,2 @@
export * from './use-customer-effort-score-modal';
export * from './use-customer-effort-score-exit-page-tracker';
@@ -0,0 +1,71 @@
/**
* External dependencies
*/
import { resolveSelect, useDispatch, useSelect } from '@wordpress/data';
import { OPTIONS_STORE_NAME } from '@woocommerce/data';

/**
* Internal dependencies
*/
import { SHOWN_FOR_ACTIONS_OPTION_NAME } from '../../constants';
import { STORE_KEY } from '../../store';

export const useCustomerEffortScoreModal = () => {
const { showCesModal: _showCesModal, showProductMVPFeedbackModal } =
useDispatch( STORE_KEY );
const { updateOptions } = useDispatch( OPTIONS_STORE_NAME );

const { wasPreviouslyShown, isLoading } = useSelect( ( select ) => {
const { getOption, hasFinishedResolution } =
select( OPTIONS_STORE_NAME );

const shownForActionsOption =
( getOption( SHOWN_FOR_ACTIONS_OPTION_NAME ) as string[] ) || [];

const resolving = ! hasFinishedResolution( 'getOption', [
SHOWN_FOR_ACTIONS_OPTION_NAME,
] );

return {
wasPreviouslyShown: ( action: string ) => {
return shownForActionsOption.includes( action );
},
isLoading: resolving,
};
} );

const markCesAsShown = async ( action: string ) => {
const { getOption } = resolveSelect( OPTIONS_STORE_NAME );

const shownForActionsOption =
( ( await getOption(
SHOWN_FOR_ACTIONS_OPTION_NAME
) ) as string[] ) || [];

updateOptions( {
[ SHOWN_FOR_ACTIONS_OPTION_NAME ]: [
action,
...shownForActionsOption,
],
} );
};

const showCesModal = (
surveyProps = {},
props = {},
onSubmitNoticeProps = {},
tracksProps = {}
) => {
_showCesModal( surveyProps, props, onSubmitNoticeProps, tracksProps );
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore We don't have type definitions for this.
markCesAsShown( surveyProps.action );
};

return {
wasPreviouslyShown,
isLoading,
showCesModal,
showProductMVPFeedbackModal,
};
};
@@ -0,0 +1,4 @@
Significance: minor
Type: add

Show feedback bar for the product editor.
2 changes: 2 additions & 0 deletions packages/js/product-editor/src/components/editor/editor.tsx
Expand Up @@ -34,6 +34,7 @@ import { FullscreenMode, InterfaceSkeleton } from '@wordpress/interface';
/**
* Internal dependencies
*/
import { Footer } from '../footer';
import { Header } from '../header';
import { BlockEditor } from '../block-editor';
import { ValidationProvider } from '../../contexts/validation-context';
Expand Down Expand Up @@ -85,6 +86,7 @@ export function Editor( { product, settings }: EditorProps ) {
<PluginArea scope="woocommerce-product-block-editor" />
</>
}
footer={ <Footer product={ product } /> }
/>

<Popover.Slot />
Expand Down
@@ -0,0 +1,135 @@
/**
* External dependencies
*/
import { __ } from '@wordpress/i18n';
import { Button } from '@wordpress/components';
import {
createElement,
createInterpolateElement,
Fragment,
} from '@wordpress/element';
import { closeSmall } from '@wordpress/icons';
import { WooFooterItem } from '@woocommerce/admin-layout';
import { Pill } from '@woocommerce/components';
import { useCustomerEffortScoreModal } from '@woocommerce/customer-effort-score';
import { Product } from '@woocommerce/data';
import { recordEvent } from '@woocommerce/tracks';

/**
* Internal dependencies
*/
import { PRODUCT_EDITOR_FEEDBACK_CES_ACTION } from '../../constants';
import { useFeedbackBar } from '../../hooks/use-feedback-bar';

export type FeedbackBarProps = {
product: Partial< Product >;
};

export function FeedbackBar( { product }: FeedbackBarProps ) {
const { hideFeedbackBar, shouldShowFeedbackBar } = useFeedbackBar();
const { showCesModal, showProductMVPFeedbackModal } =
useCustomerEffortScoreModal();

const getProductTracksProps = () => {
const tracksProps = {
product_type: product.type,
};

return tracksProps;
};

const onShareFeedbackClick = () => {
recordEvent( 'product_editor_feedback_bar_share_feedback_click', {
...getProductTracksProps(),
} );

showCesModal(
{
action: PRODUCT_EDITOR_FEEDBACK_CES_ACTION,
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'
),
onsubmitLabel: __(
"Thanks for the feedback. We'll put it to good use!",
'woocommerce'
),
shouldShowComments: () => true,
},
{},
{
type: 'snackbar',
icon: <span>🌟</span>,
}
);
};

const onTurnOffEditorClick = () => {
recordEvent( 'product_editor_feedback_bar_turnoff_editor_click', {
...getProductTracksProps(),
} );

hideFeedbackBar();

showProductMVPFeedbackModal();
};

const onHideFeedbackBarClick = () => {
recordEvent( 'product_editor_feedback_bar_dismiss_click', {
...getProductTracksProps(),
} );

hideFeedbackBar();
};

return (
<>
{ shouldShowFeedbackBar && (
<WooFooterItem>
<div className="woocommerce-product-mvp-ces-footer">
<Pill>Beta</Pill>
<div className="woocommerce-product-mvp-ces-footer__message">
{ createInterpolateElement(
__(
'How is your experience with the new product form? <span><shareButton>Share feedback</shareButton> or <turnOffButton>turn it off</turnOffButton></span>',
'woocommerce'
),
{
span: (
<span className="woocommerce-product-mvp-ces-footer__message-buttons" />
),
shareButton: (
<Button
variant="link"
onClick={ onShareFeedbackClick }
/>
),
turnOffButton: (
<Button
onClick={ onTurnOffEditorClick }
variant="link"
/>
),
}
) }
</div>
<Button
className="woocommerce-product-mvp-ces-footer__close-button"
icon={ closeSmall }
label={ __( 'Hide this message', 'woocommerce' ) }
onClick={ onHideFeedbackBarClick }
></Button>
</div>
</WooFooterItem>
) }
</>
);
}
@@ -0,0 +1 @@
export * from './feedback-bar';
42 changes: 42 additions & 0 deletions packages/js/product-editor/src/components/feedback-bar/style.scss
@@ -0,0 +1,42 @@
.woocommerce-product-mvp-ces-footer {
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
// the left/right padding is set to this to match the padding of
// .block-editor-block-list__layout.is-root-container and
// .editor-styles-wrapper combined
padding: $gap calc(2 * $gap + $gap-small);
gap: $gap;

@include breakpoint(">782px") {
padding: $gap 0;
max-width: 650px;
margin: 0 auto;
}

.woocommerce-pill {
background-color: $studio-yellow-5;
border: 0;
font-size: 1em;
}

&__close-button {
padding: 0;
}

&__message {
flex: 1;
flex-wrap: wrap;
align-items: center;
white-space: pre-wrap;
}

&__message-buttons {
white-space: nowrap;

button.is-link {
text-decoration: none;
}
}
}
33 changes: 33 additions & 0 deletions packages/js/product-editor/src/components/footer/footer.tsx
@@ -0,0 +1,33 @@
/**
* External dependencies
*/
import { createElement } from '@wordpress/element';
import { __ } from '@wordpress/i18n';
import { WooFooterItem } from '@woocommerce/admin-layout';
import { Product } from '@woocommerce/data';

/**
* Internal dependencies
*/
import { FeedbackBar } from '../feedback-bar';
import { ProductMVPFeedbackModalContainer } from '../product-mvp-feedback-modal-container';

export type FooterProps = {
product: Partial< Product >;
};

export function Footer( { product }: FooterProps ) {
return (
<div
className="woocommerce-product-footer"
role="region"
aria-label={ __( 'Product Editor bottom bar.', 'woocommerce' ) }
tabIndex={ -1 }
>
<WooFooterItem.Slot name="product" />

<FeedbackBar product={ product } />
<ProductMVPFeedbackModalContainer productId={ product.id } />
</div>
);
}
1 change: 1 addition & 0 deletions packages/js/product-editor/src/components/footer/index.ts
@@ -0,0 +1 @@
export * from './footer';
3 changes: 3 additions & 0 deletions packages/js/product-editor/src/components/footer/style.scss
@@ -0,0 +1,3 @@
.woocommerce-product-footer {
width: 100%;
}
Expand Up @@ -16,6 +16,7 @@ import { getProductErrorMessage } from '../../../utils/get-product-error-message
import { recordProductEvent } from '../../../utils/record-product-event';
import { usePublish } from '../hooks/use-publish';
import { PublishButtonProps } from './types';
import { useFeedbackBar } from '../../../hooks/use-feedback-bar';

export function PublishButton( {
productStatus,
Expand All @@ -24,6 +25,8 @@ export function PublishButton( {
const { createSuccessNotice, createErrorNotice } =
useDispatch( 'core/notices' );

const { maybeShowFeedbackBar } = useFeedbackBar();

const publishButtonProps = usePublish( {
productStatus,
...props,
Expand Down Expand Up @@ -54,6 +57,8 @@ export function PublishButton( {

createSuccessNotice( noticeContent, noticeOptions );

maybeShowFeedbackBar();

if ( productStatus === 'auto-draft' ) {
const url = getNewPath( {}, `/product/${ savedProduct.id }` );
navigateTo( { url } );
Expand Down
Expand Up @@ -15,6 +15,7 @@ import { getProductErrorMessage } from '../../../utils/get-product-error-message
import { recordProductEvent } from '../../../utils/record-product-event';
import { useSaveDraft } from '../hooks/use-save-draft';
import { SaveDraftButtonProps } from './types';
import { useFeedbackBar } from '../../../hooks/use-feedback-bar';

export function SaveDraftButton( {
productStatus,
Expand All @@ -23,6 +24,8 @@ export function SaveDraftButton( {
const { createSuccessNotice, createErrorNotice } =
useDispatch( 'core/notices' );

const { maybeShowFeedbackBar } = useFeedbackBar();

const saveDraftButtonProps = useSaveDraft( {
productStatus,
...props,
Expand All @@ -33,6 +36,8 @@ export function SaveDraftButton( {
__( 'Product saved as draft.', 'woocommerce' )
);

maybeShowFeedbackBar();

if ( productStatus === 'auto-draft' ) {
const url = getNewPath( {}, `/product/${ savedProduct.id }` );
navigateTo( { url } );
Expand Down
2 changes: 1 addition & 1 deletion packages/js/product-editor/src/components/index.ts
Expand Up @@ -20,7 +20,7 @@ export {
BlockIcon as __experimentalBlockIcon,
BlockIconProps,
} from './block-icon';
export { ProductMVPCESFooter as __experimentalProductMVPCESFooter } from './product-mvp-ces-footer';
export { FeedbackBar as __experimentalProductMVPCESFooter } from './feedback-bar';
export { ProductMVPFeedbackModal as __experimentalProductMVPFeedbackModal } from './product-mvp-feedback-modal';
export { ProductMVPFeedbackModalContainer as __experimentalProductMVPFeedbackModalContainer } from './product-mvp-feedback-modal-container';
export {
Expand Down

This file was deleted.

0 comments on commit dadc0eb

Please sign in to comment.