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

Product Editor Onboarding: Show spotlight for first time visitors #38590

Merged
merged 13 commits into from Jun 6, 2023
12 changes: 8 additions & 4 deletions plugins/woocommerce-admin/client/products/product-page.tsx
Expand Up @@ -17,6 +17,7 @@ import { useParams } from 'react-router-dom';
import { useProductEntityRecord } from './hooks/use-product-entity-record';
import './fills/product-block-editor-fills';
import './product-page.scss';
import BlockEditorTourWrapper from './tour/block-editor/block-editor-tour-wrapper';

declare const productBlockEditorSettings: ProductEditorSettings;

Expand All @@ -36,9 +37,12 @@ export default function ProductPage() {
}

return (
<Editor
product={ product }
settings={ productBlockEditorSettings || {} }
/>
<>
<Editor
product={ product }
settings={ productBlockEditorSettings || {} }
/>
<BlockEditorTourWrapper />
</>
);
}
@@ -0,0 +1,12 @@
/**
* Internal dependencies
*/
import BlockEditorTour from './block-editor-tour';
import { useBlockEditorTourOptions } from './use-block-editor-tour-options';

const BlockEditorTourWrapper = () => {
const blockEditorTourProps = useBlockEditorTourOptions();
return <BlockEditorTour { ...blockEditorTourProps } />;
};

export default BlockEditorTourWrapper;
@@ -0,0 +1,211 @@
/**
* External dependencies
*/
import { TourKit } from '@woocommerce/components';
import { Guide } from '@wordpress/components';
import { __ } from '@wordpress/i18n';
import { recordEvent } from '@woocommerce/tracks';
import { useEffect } from '@wordpress/element';

/**
* Internal dependencies
*/

import './style.scss';

interface Props {
isTourOpen: boolean;
dismissModal: () => void;
openGuide: () => void;
closeGuide: () => void;
isGuideOpen: boolean;
}

const BlockEditorTour = ( {
isTourOpen,
dismissModal,
openGuide,
isGuideOpen,
closeGuide,
}: Props ) => {
useEffect( () => {
if ( isTourOpen ) {
recordEvent( 'block_product_editor_spotlight_view' );
}
}, [ isTourOpen ] );

if ( isTourOpen ) {
return (
<TourKit
config={ {
Copy link
Contributor

Choose a reason for hiding this comment

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

In the design the tourkit also includes a purple box at the top, is it possible to add this?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

For the background color in the TourKit, we would have to change the component to allow customizing the CardHeader, which is the part where the X button is, and where we would change the background color and increase its size.

Do you think I should implement this?

Copy link
Contributor

Choose a reason for hiding this comment

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

Do you think I should implement this?

Yeah, but you can do this as part of a follow up.

steps: [
{
meta: {
name: 'woocommerce-block-editor-tour',
primaryButton: {
text: __(
'View highlights',
'woocommerce'
),
},
descriptions: {
desktop: __(
"We designed a brand new product editing experience to let you focus on what's important.",
'woocommerce'
),
},
heading: __(
'Meet a streamlined product form',
Copy link
Contributor

Choose a reason for hiding this comment

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

This is missing the beta pill

'woocommerce'
),
},
referenceElements: {
desktop: '#adminmenuback',
},
},
],
closeHandler: ( _steps, _currentStepIndex, source ) => {
dismissModal();
if ( source === 'done-btn' ) {
recordEvent(
'block_product_editor_spotlight_view_highlights'
);
openGuide();
} else {
recordEvent(
'block_product_editor_spotlight_dismissed'
);
}
},
options: {
effects: {
arrowIndicator: false,
overlay: false,
liveResize: {
rootElementSelector: '#adminmenuback',
resize: true,
},
},
portalParentElement:
document.getElementById( 'wpbody' ),
popperModifiers: [
{
name: 'bottom-left',
enabled: true,
phase: 'beforeWrite',
requires: [ 'computeStyles' ],
fn: ( { state } ) => {
state.styles.popper.top = 'auto';
state.styles.popper.left = 'auto';
state.styles.popper.bottom = '10px';
state.styles.popper.transform =
'translate3d(10px, 0px, 0px)';
},
},
],
},
} }
/>
);
} else if ( isGuideOpen ) {
return (
<Guide
Copy link
Contributor

Choose a reason for hiding this comment

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

Could we move this into it's own component - block-editor-guide? and use it within the block editor tour wrapper?

className="woocommerce-block-editor-guide"
finishButtonText={ __( 'Close', 'woocommerce' ) }
onFinish={ () => {
Copy link
Contributor

Choose a reason for hiding this comment

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

Hmmm it sucks there is only a single callback and their is no way of deciphering what step we are on.

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 agree. Can we ask for this feature for the component, perhaps?

recordEvent( 'block_product_editor_spotlight_completed' );
closeGuide();
} }
pages={ [
{
content: (
<>
<h1 className="woocommerce-block-editor-guide__heading">
{ __(
'Refreshed, streamlined interface',
'woocommerce'
) }
</h1>
<p className="woocommerce-block-editor-guide__text">
{ __(
'Experience a simpler, more focused interface with a modern design that enhances usability.',
'woocommerce'
) }
</p>
</>
),
image: (
<div className="woocommerce-block-editor-guide__background1"></div>
),
},
{
content: (
<>
<h1 className="woocommerce-block-editor-guide__heading">
{ __(
'Content-rich product descriptions',
'woocommerce'
) }
</h1>
<p className="woocommerce-block-editor-guide__text">
{ __(
'Create compelling product pages with blocks, media, images, videos, and any content you desire to engage customers.',
'woocommerce'
) }
</p>
</>
),
image: (
<div className="woocommerce-block-editor-guide__background2"></div>
),
},
{
content: (
<>
<h1 className="woocommerce-block-editor-guide__heading">
{ __(
'Improved speed & performance',
'woocommerce'
) }
</h1>
<p className="woocommerce-block-editor-guide__text">
{ __(
'Enjoy a seamless experience without page reloads. Our modern technology ensures reliability and lightning-fast performance.',
'woocommerce'
) }
</p>
</>
),
image: (
<div className="woocommerce-block-editor-guide__background3"></div>
),
},
{
content: (
<>
<h1 className="woocommerce-block-editor-guide__heading">
{ __(
'More features are on the way',
'woocommerce'
) }
</h1>
<p className="woocommerce-block-editor-guide__text">
{ __(
'While we currently support physical products, exciting updates are coming to accommodate more types, like digital products, variations, and more. Stay tuned!',
'woocommerce'
) }
</p>
</>
),
image: (
<div className="woocommerce-block-editor-guide__background4"></div>
),
},
] }
/>
);
}
return null;
};

export default BlockEditorTour;
@@ -0,0 +1,54 @@
.woocommerce-block-editor-guide {
&__background1 {
height: 220px;
background-color: #f2edff;
}
&__background2 {
height: 220px;
background-color: #dfd1fb;
}
&__background3 {
height: 220px;
background-color: #cfb9f6;
}
&__background4 {
height: 220px;
Copy link
Contributor

Choose a reason for hiding this comment

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

Could we turn the height into a variable here, incase we change it, and forget one of them.

background-color: #bea0f2;
}
&.components-modal__frame {
width: 320px;
Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe we should use max-width here instead?

}
&__heading,
&__text {
margin: 12px 24px;
}

&__heading {
font-size: 24px;
line-height: 30px;
}

&__text {
height: 80px;
Copy link
Contributor

Choose a reason for hiding this comment

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

Is this manual height necessary?

}
.components-guide {
&__page-control {
margin-top: 12px;
}
&__footer {
width: unset;
margin: 32px 24px;
.components-guide__back-button {
left: 0;
padding: 6px 12px;
}
.components-guide__forward-button {
right: 0;
padding: 6px 12px;
}
.components-guide__finish-button {
right: 0;
}
}
}
}
@@ -0,0 +1,44 @@
/**
* External dependencies
*/
import { OPTIONS_STORE_NAME } from '@woocommerce/data';
import { useSelect, useDispatch } from '@wordpress/data';
import { useState } from '@wordpress/element';

export const BLOCK_EDITOR_TOUR_SHOWN_OPTION =
'woocommerce_block_product_tour_shown';

export const useBlockEditorTourOptions = () => {
const { updateOptions } = useDispatch( OPTIONS_STORE_NAME );
const [ isGuideOpen, setIsGuideOpen ] = useState( false );
Copy link
Contributor

Choose a reason for hiding this comment

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

This local state would prevent us from re-triggering the guide from the options menu, as is needed in: #38522

Could we move this state into a useContext?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Hey...

I payed attention to that requirement.

That's why I split into BlockEditorTour and BlockEditorTourWrapper. I figured that the BlockEditorTour component could be used directly from the About menu without using this hook, as this hook is more related to the option persisted in the back-end.

I made another refactor now and extracted the BlockEditorGuide component. I think we can just call the component directly and it will show up. Let me know if you find a problem with this approach.

Copy link
Contributor

Choose a reason for hiding this comment

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

I made another refactor now and extracted the BlockEditorGuide component. I think we can just call the component directly and it will show up. Let me know if you find a problem with this approach.

That seems good to me :) That seems lower effort and easier then adding a whole context.

const { isTourOpen, isTourClosed } = useSelect( ( select ) => {
const { getOption, hasFinishedResolution } =
select( OPTIONS_STORE_NAME );

const tourClosed =
getOption( BLOCK_EDITOR_TOUR_SHOWN_OPTION ) === 'yes' ||
! hasFinishedResolution( 'getOption', [
BLOCK_EDITOR_TOUR_SHOWN_OPTION,
] );

return {
isTourClosed: tourClosed,
isTourOpen: ! tourClosed,
};
} );

const dismissModal = () => {
updateOptions( {
[ BLOCK_EDITOR_TOUR_SHOWN_OPTION ]: 'yes',
} );
};

return {
dismissModal,
isTourOpen,
isTourClosed,
openGuide: () => setIsGuideOpen( true ),
closeGuide: () => setIsGuideOpen( false ),
isGuideOpen,
};
};
4 changes: 4 additions & 0 deletions plugins/woocommerce/changelog/add-block-tour
@@ -0,0 +1,4 @@
Significance: minor
Type: add

Show spotlight for first time visitors of block product editor
1 change: 1 addition & 0 deletions plugins/woocommerce/src/Admin/API/Options.php
Expand Up @@ -202,6 +202,7 @@ public static function get_default_option_permissions() {
'woocommerce_weight_unit',
'woocommerce_ces_product_mvp_ces_action',
'woocommerce_product_tour_modal_hidden',
'woocommerce_block_product_tour_shown',
'woocommerce_revenue_report_date_tour_shown',
'woocommerce_date_type',
'date_format',
Expand Down