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

CYS - Ensure the offline modal is displayed whenever AI is unavailable #42949

Merged
merged 6 commits into from
Dec 21, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import {
useViewportMatch,
} from '@wordpress/compose';
import { __ } from '@wordpress/i18n';
import { useState, useContext } from '@wordpress/element';
import { useState, useContext, useEffect } from '@wordpress/element';
import {
// @ts-ignore No types for this exist yet.
__unstableMotion as motion,
Expand Down Expand Up @@ -47,17 +47,54 @@ import { OnboardingTour, useOnboardingTour } from './onboarding-tour';
import { HighlightedBlockContextProvider } from './context/highlighted-block-context';
import { Transitional } from '../transitional';
import { CustomizeStoreContext } from './';
import { recordEvent } from '@woocommerce/tracks';
import { AiOfflineModal } from '~/customize-store/assembler-hub/onboarding-tour/ai-offline-modal';
import { useQuery } from '@woocommerce/navigation';

const { useGlobalStyle } = unlock( blockEditorPrivateApis );

const ANIMATION_DURATION = 0.5;

export const Layout = () => {
const [ logoBlockIds, setLogoBlockIds ] = useState< Array< string > >( [] );

const { sendEvent, currentState, context } = useContext(
CustomizeStoreContext
);

const aiOnline = context.aiOnline;
const { customizing } = useQuery();

const [ showAiOfflineModal, setShowAiOfflineModal ] = useState(
! aiOnline && customizing !== 'true'
);

useEffect( () => {
setShowAiOfflineModal( ! aiOnline && customizing !== 'true' );
}, [ aiOnline, customizing ] );

// This ensures the edited entity id and type are initialized properly.
useInitEditedEntityFromURL();
const { shouldTourBeShown, isResizeHandleVisible, ...onboardingTourProps } =
useOnboardingTour();
const {
shouldTourBeShown,
isResizeHandleVisible,
setShowWelcomeTour,
onClose,
...onboardingTourProps
} = useOnboardingTour();

const takeTour = () => {
// Click on "Take a tour" button
recordEvent( 'customize_your_store_assembler_hub_tour_start' );
setShowWelcomeTour( false );
setShowAiOfflineModal( false );
};

const skipTour = () => {
recordEvent( 'customize_your_store_assembler_hub_tour_skip' );
onClose();
setShowAiOfflineModal( false );
};

const isMobileViewport = useViewportMatch( 'medium', '<' );
const disableMotion = useReducedMotion();
Expand All @@ -71,9 +108,6 @@ export const Layout = () => {
const { record: template } = useEditedEntityRecord();
const { id: templateId, type: templateType } = template;

const { sendEvent, currentState, context } = useContext(
CustomizeStoreContext
);
const [ isSurveyOpen, setSurveyOpen ] = useState( false );
const editor = <Editor isLoading={ isEditorLoading } />;

Expand Down Expand Up @@ -206,8 +240,23 @@ export const Layout = () => {
) }
</div>
</div>
{ ! isEditorLoading && shouldTourBeShown && (
<OnboardingTour { ...onboardingTourProps } />
{ ! isEditorLoading &&
shouldTourBeShown &&
! showAiOfflineModal && (
<OnboardingTour
skipTour={ skipTour }
takeTour={ takeTour }
onClose={ onClose }
{ ...onboardingTourProps }
/>
) }

{ ! isEditorLoading && showAiOfflineModal && (
<AiOfflineModal
shouldTourBeShown={ shouldTourBeShown }
skipTour={ skipTour }
takeTour={ takeTour }
/>
) }
</EntityProvider>
</EntityProvider>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/**
* External dependencies
*/
import { Button, Modal } from '@wordpress/components';
import { __ } from '@wordpress/i18n';

type AiOfflineModalProps = {
skipTour: () => void;
takeTour: () => void;
shouldTourBeShown: boolean;
};

export const AiOfflineModal = ( {
skipTour,
takeTour,
shouldTourBeShown,
}: AiOfflineModalProps ) => {
return (
<Modal
className="woocommerce-customize-store__onboarding-welcome-modal"
title={ __( 'Welcome to your store!', 'woocommerce' ) }
onRequestClose={ skipTour }
shouldCloseOnClickOutside={ false }
>
<span className="woocommerce-customize-store__title">
{ __(
'Our AI tool had a few issues generating your content.',
'woocommerce'
) }
</span>
<p>
{ __(
"But don't let that stop you! Start customizing the look and feel of your store by adding your logo and selecting your colors and layout. ",
'woocommerce'
) }
{ shouldTourBeShown &&
__(
"Take a quick tour to discover what's possible.",
'woocommerce'
) }
</p>
{ shouldTourBeShown && (
<div className="woocommerce-customize-store__design-change-warning-modal-footer">
<Button onClick={ skipTour } variant="link">
{ __( 'Skip', 'woocommerce' ) }
</Button>
<Button onClick={ takeTour } variant="primary">
{ __( 'Take a tour', 'woocommerce' ) }
</Button>
</div>
) }
</Modal>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -2,80 +2,34 @@
* External dependencies
*/
import { __ } from '@wordpress/i18n';
import { useContext, useState } from '@wordpress/element';
import { useState } from '@wordpress/element';
import { TourKit, TourKitTypes } from '@woocommerce/components';
import { recordEvent } from '@woocommerce/tracks';
import { Button, Modal } from '@wordpress/components';

/**
* Internal dependencies
*/
import { CustomizeStoreContext } from '..';
export * from './use-onboarding-tour';

type OnboardingTourProps = {
onClose: () => void;
skipTour: () => void;
takeTour: () => void;
showWelcomeTour: boolean;
setShowWelcomeTour: ( show: boolean ) => void;
setIsResizeHandleVisible: ( isVisible: boolean ) => void;
};

export const OnboardingTour = ( {
onClose,
setShowWelcomeTour,
skipTour,
takeTour,
showWelcomeTour,
setIsResizeHandleVisible,
}: OnboardingTourProps ) => {
const [ placement, setPlacement ] =
useState< TourKitTypes.WooConfig[ 'placement' ] >( 'left' );

const { context } = useContext( CustomizeStoreContext );
const aiOnline = context.aiOnline;

if ( showWelcomeTour ) {
const takeTour = () => {
// Click on "Take a tour" button
recordEvent( 'customize_your_store_assembler_hub_tour_start' );
setShowWelcomeTour( false );
};

const skipTour = () => {
recordEvent( 'customize_your_store_assembler_hub_tour_skip' );
onClose();
};

if ( ! aiOnline ) {
return (
<Modal
className="woocommerce-customize-store__onboarding-welcome-modal"
title={ __( 'Welcome to your store!', 'woocommerce' ) }
onRequestClose={ skipTour }
shouldCloseOnClickOutside={ false }
>
<span className="woocommerce-customize-store__title">
{ __(
'Our AI tool had a few issues generating your content.',
'woocommerce'
) }
</span>
<p>
{ __(
"But don't let that stop you! Start customizing the look and feel of your store by adding your logo and selecting your colors and layout. Take a quick tour to discover what's possible.",
'woocommerce'
) }
</p>
<div className="woocommerce-customize-store__design-change-warning-modal-footer">
<Button onClick={ skipTour } variant="link">
{ __( 'Skip', 'woocommerce' ) }
</Button>
<Button onClick={ takeTour } variant="primary">
{ __( 'Take a tour', 'woocommerce' ) }
</Button>
</div>
</Modal>
);
}

return (
<TourKit
config={ {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ jest.mock( '../../', () => ( {
describe( 'OnboardingTour', () => {
let props: {
onClose: jest.Mock;
skipTour: jest.Mock;
takeTour: jest.Mock;
setShowWelcomeTour: jest.Mock;
showWelcomeTour: boolean;
setIsResizeHandleVisible: ( isVisible: boolean ) => void;
Expand All @@ -30,6 +32,8 @@ describe( 'OnboardingTour', () => {
beforeEach( () => {
props = {
onClose: jest.fn(),
skipTour: jest.fn(),
takeTour: jest.fn(),
setShowWelcomeTour: jest.fn(),
showWelcomeTour: true,
setIsResizeHandleVisible: jest.fn(),
Expand Down Expand Up @@ -61,9 +65,7 @@ describe( 'OnboardingTour', () => {
} )
.click();

expect( recordEvent ).toHaveBeenCalledWith(
'customize_your_store_assembler_hub_tour_start'
);
expect( props.takeTour ).toHaveBeenCalled();
} );

it( 'should record an event when clicking on "Skip" button', () => {
Expand All @@ -75,12 +77,10 @@ describe( 'OnboardingTour', () => {
} )
.click();

expect( recordEvent ).toHaveBeenCalledWith(
'customize_your_store_assembler_hub_tour_skip'
);
expect( props.skipTour ).toHaveBeenCalled();
} );

it( 'should record an event when clicking on "Skip" button', () => {
it( 'should record an event when clicking on the "Close Tour" button', () => {
render( <OnboardingTour { ...props } showWelcomeTour={ false } /> );

screen
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -237,7 +237,11 @@ export const ExistingAiThemeBanner = ( {
recordEvent( 'customize_your_store_intro_customize_click' );
navigateOrParent(
window,
getNewPath( {}, '/customize-store/assembler-hub', {} )
getNewPath(
{ customizing: true },
'/customize-store/assembler-hub',
{}
)
);
} }
bannerButtonText={ __( 'Customize', 'woocommerce' ) }
Expand Down
4 changes: 4 additions & 0 deletions plugins/woocommerce/changelog/42949-show-ai-offline-modal
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Significance: minor
Type: enhancement

Ensure the offline modal is displayed whenever AI is unavailable and not just on the initial store setup.