Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Show feedback footer on product editor page #38599

Merged
merged 34 commits into from Jun 9, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
c322000
Footer component
mattsherman Jun 2, 2023
60e7f5a
Add Footer to interface skeleton
mattsherman Jun 2, 2023
e65e8aa
Show footer on mobile
mattsherman Jun 3, 2023
91066a5
Footer styles
mattsherman Jun 3, 2023
bb7376e
Update feedback footer styling
mattsherman Jun 3, 2023
44bd9b2
Update label for close button
mattsherman Jun 3, 2023
f2fcdf1
Changelog
mattsherman Jun 3, 2023
2af6753
Changelog
mattsherman Jun 3, 2023
ce171a2
Pass product to Footer
mattsherman Jun 5, 2023
3706f21
Old editor - pass product to footer
mattsherman Jun 5, 2023
59f61e5
Add Tracks events
mattsherman Jun 5, 2023
39bbfc8
Remove unused constant
mattsherman Jun 5, 2023
a8478ca
Update option name for tracking if feedback bar should be shown
mattsherman Jun 5, 2023
e27f0ad
Rename product editor feedback hook
mattsherman Jun 5, 2023
114d54e
Clean up feedback bar component naming
mattsherman Jun 5, 2023
c2aeef6
Clean up feedback bar naming in old new editor
mattsherman Jun 5, 2023
97ca651
Add hideFeedback to useFeedbackBar hook
mattsherman Jun 5, 2023
ceedba3
Rename const
mattsherman Jun 5, 2023
6e3b3c6
Changelog
mattsherman Jun 5, 2023
300c8c0
Hook feedback bar up to draft and publish
mattsherman Jun 5, 2023
5b963c4
Remove unused functions from useFeedbackBar hook
mattsherman Jun 5, 2023
f008ee2
Update use of useFeedbackBar hook in old new editor
mattsherman Jun 5, 2023
38e6f51
Do not expose showFeedbackBar from useFeedbackBar
mattsherman Jun 5, 2023
3eddfea
Remove obsolete option to disable editor
mattsherman Jun 5, 2023
6c5f0b3
Clean up logic for if feedback bar should be shown
mattsherman Jun 6, 2023
38e29e0
Add comment to padding
mattsherman Jun 6, 2023
45fa71f
Rename to maybeShowFeedbackBar
mattsherman Jun 6, 2023
390cb3a
Rename to maybeShowFeedbackBar in old new editor
mattsherman Jun 6, 2023
3ee0a03
Move shouldShowFeedbackBar to hook
mattsherman Jun 6, 2023
7fd27f3
Create useCustomerEffortScoreModal hook
mattsherman Jun 7, 2023
9e35af3
Move additional logic to useFeedbackBar hook
mattsherman Jun 7, 2023
08b7a18
Tweak useFeedbackBar so it handles when previously shown is updated
mattsherman Jun 7, 2023
4c61728
Add wcTracks to window global type
mattsherman Jun 8, 2023
4d218c6
Use window.wcTracks.isEnable instead of fetching option
mattsherman Jun 8, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
@@ -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', {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not something we need to do in this PR, but just thinking out loud about a helper util in the future: something like recordProductEditorEvent( eventName ) might be nice that handles prefixing with product_editor_ and adding in entity props such as the product_type.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like the idea of auto-adding entity props.

I'm not sure about prefixing the Tracks event name... doing dynamic generation of event page makes it harder to find events in code when searching. But, I'm not totally opposed to it.

...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);
mattsherman marked this conversation as resolved.
Show resolved Hide resolved
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 } />
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Optional, but it might be nice to move the FeedbackBar and ProductMVPFeedbackModalContainer to a fill for WooFooterItem.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FeedbackBar actually already is a fill. It feels odd to include it here, but seemed like the most natural place to do so. Any suggestions?

<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.