woocommerce/plugins/woocommerce-admin/client/customize-store/index.tsx

202 lines
4.5 KiB
TypeScript
Raw Normal View History

/**
* External dependencies
*/
import { createMachine } from 'xstate';
import { useEffect, useMemo, useState } from '@wordpress/element';
import { useMachine, useSelector } from '@xstate/react';
/**
* Internal dependencies
*/
import { useFullScreen } from '~/utils';
import {
Intro,
events as introEvents,
services as introServices,
actions as introActions,
} from './intro';
import { DesignWithAi, events as designWithAiEvents } from './design-with-ai';
import { AssemblerHub, events as assemblerHubEvents } from './assembler-hub';
import { findComponentMeta } from '~/utils/xstate/find-component';
import {
CustomizeStoreComponentMeta,
CustomizeStoreComponent,
customizeStoreStateMachineContext,
} from './types';
import { ThemeCard } from './intro/theme-cards';
import './style.scss';
export type customizeStoreStateMachineEvents =
| introEvents
| designWithAiEvents
| assemblerHubEvents;
export const customizeStoreStateMachineServices = {
...introServices,
};
export const customizeStoreStateMachineActions = {
...introActions,
};
export const customizeStoreStateMachineDefinition = createMachine( {
id: 'customizeStore',
initial: 'intro',
predictableActionArguments: true,
preserveActionOrder: true,
schema: {
context: {} as customizeStoreStateMachineContext,
events: {} as customizeStoreStateMachineEvents,
services: {} as {
fetchThemeCards: { data: ThemeCard[] };
},
},
context: {
intro: {
themeCards: [] as ThemeCard[],
activeTheme: '',
},
} as customizeStoreStateMachineContext,
states: {
intro: {
id: 'intro',
initial: 'preIntro',
states: {
preIntro: {
invoke: {
src: 'fetchThemeCards',
onDone: {
target: 'intro',
actions: [ 'assignThemeCards' ],
},
},
},
intro: {
meta: {
component: Intro,
},
},
},
on: {
DESIGN_WITH_AI: {
target: 'designWithAi',
},
SELECTED_ACTIVE_THEME: {
target: 'assemblerHub',
},
CLICKED_ON_BREADCRUMB: {
target: 'backToHomescreen',
},
SELECTED_NEW_THEME: {
target: 'appearanceTask',
},
SELECTED_BROWSE_ALL_THEMES: {
target: 'appearanceTask',
},
},
},
designWithAi: {
initial: 'preDesignWithAi',
states: {
preDesignWithAi: {
always: {
target: 'designWithAi',
},
},
designWithAi: {
meta: {
component: DesignWithAi,
},
},
},
on: {
THEME_SUGGESTED: {
target: 'assemblerHub',
},
},
},
assemblerHub: {
meta: {
component: AssemblerHub,
},
on: {
FINISH_CUSTOMIZATION: {
target: 'backToHomescreen',
},
GO_BACK_TO_DESIGN_WITH_AI: {
target: 'designWithAi',
},
},
},
backToHomescreen: {},
appearanceTask: {},
},
} );
export const CustomizeStoreController = ( {
actionOverrides,
servicesOverrides,
}: {
actionOverrides: Partial< typeof customizeStoreStateMachineActions >;
servicesOverrides: Partial< typeof customizeStoreStateMachineServices >;
} ) => {
useFullScreen( [ 'woocommerce-customize-store' ] );
const augmentedStateMachine = useMemo( () => {
return customizeStoreStateMachineDefinition.withConfig( {
services: {
...customizeStoreStateMachineServices,
...servicesOverrides,
},
actions: {
...customizeStoreStateMachineActions,
...actionOverrides,
},
guards: {},
} );
}, [ actionOverrides, servicesOverrides ] );
const [ state, send, service ] = useMachine( augmentedStateMachine, {
devTools: process.env.NODE_ENV === 'development',
} );
// eslint-disable-next-line react-hooks/exhaustive-deps -- false positive due to function name match, this isn't from react std lib
const currentNodeMeta = useSelector( service, ( currentState ) =>
findComponentMeta< CustomizeStoreComponentMeta >(
currentState?.meta ?? undefined
)
);
const [ CurrentComponent, setCurrentComponent ] =
useState< CustomizeStoreComponent | null >( null );
useEffect( () => {
if ( currentNodeMeta?.component ) {
setCurrentComponent( () => currentNodeMeta?.component );
}
}, [ CurrentComponent, currentNodeMeta?.component ] );
const currentNodeCssLabel =
state.value instanceof Object
? Object.keys( state.value )[ 0 ]
: state.value;
return (
<>
<div
className={ `woocommerce-profile-wizard__container woocommerce-profile-wizard__step-${ currentNodeCssLabel }` }
>
{ CurrentComponent ? (
<CurrentComponent
sendEvent={ send }
context={ state.context }
/>
) : (
<div />
) }
</div>
</>
);
};
export default CustomizeStoreController;