1570 lines
37 KiB
TypeScript
1570 lines
37 KiB
TypeScript
/**
|
|
* External dependencies
|
|
*/
|
|
import {
|
|
createMachine,
|
|
assign,
|
|
fromPromise,
|
|
spawnChild,
|
|
raise,
|
|
assertEvent,
|
|
enqueueActions,
|
|
DoneActorEvent,
|
|
fromCallback,
|
|
} from 'xstate5';
|
|
import { useMachine, useSelector } from '@xstate5/react';
|
|
import { useMemo } from '@wordpress/element';
|
|
import { resolveSelect, dispatch } from '@wordpress/data';
|
|
import {
|
|
updateQueryString,
|
|
getQuery,
|
|
getNewPath,
|
|
} from '@woocommerce/navigation';
|
|
import {
|
|
ExtensionList,
|
|
OPTIONS_STORE_NAME,
|
|
COUNTRIES_STORE_NAME,
|
|
Country,
|
|
ONBOARDING_STORE_NAME,
|
|
Extension,
|
|
GeolocationResponse,
|
|
PLUGINS_STORE_NAME,
|
|
SETTINGS_STORE_NAME,
|
|
USER_STORE_NAME,
|
|
WCUser,
|
|
} from '@woocommerce/data';
|
|
import { initializeExPlat } from '@woocommerce/explat';
|
|
import { CountryStateOption } from '@woocommerce/onboarding';
|
|
import { getAdminLink } from '@woocommerce/settings';
|
|
import CurrencyFactory from '@woocommerce/currency';
|
|
|
|
/**
|
|
* Internal dependencies
|
|
*/
|
|
import { findComponentMeta } from '~/utils/xstate/find-component';
|
|
import { IntroOptIn } from './pages/IntroOptIn';
|
|
import {
|
|
UserProfile,
|
|
BusinessChoice,
|
|
SellingOnlineAnswer,
|
|
SellingPlatform,
|
|
} from './pages/UserProfile';
|
|
import {
|
|
BusinessInfo,
|
|
IndustryChoice,
|
|
POSSIBLY_DEFAULT_STORE_NAMES,
|
|
} from './pages/BusinessInfo';
|
|
import { BusinessLocation } from './pages/BusinessLocation';
|
|
import { getCountryStateOptions } from './services/country';
|
|
import { CoreProfilerLoader } from './components/loader/Loader';
|
|
import { Plugins } from './pages/Plugins';
|
|
import { getPluginSlug, useFullScreen } from '~/utils';
|
|
import './style.scss';
|
|
import {
|
|
InstalledPlugin,
|
|
PluginInstallError,
|
|
pluginInstallerMachine,
|
|
} from './services/installAndActivatePlugins';
|
|
import { ProfileSpinner } from './components/profile-spinner/profile-spinner';
|
|
import recordTracksActions from './actions/tracks';
|
|
import { ComponentMeta } from './types';
|
|
import { getCountryCode } from '~/dashboard/utils';
|
|
import { getAdminSetting } from '~/utils/admin-settings';
|
|
import { useXStateInspect } from '~/xstate';
|
|
import { useComponentFromXStateService } from '~/utils/xstate/useComponentFromService';
|
|
import {
|
|
CoreProfilerEvents,
|
|
BusinessLocationEvent,
|
|
UserProfileEvent,
|
|
BusinessInfoEvent,
|
|
IntroOptInEvent,
|
|
PluginsInstallationRequestedEvent,
|
|
} from './events';
|
|
|
|
export type CoreProfilerPageComponent = {
|
|
navigationProgress: number | undefined;
|
|
sendEvent: ( event: CoreProfilerEvents ) => void;
|
|
context: CoreProfilerStateMachineContext;
|
|
};
|
|
|
|
export type OnboardingProfile = {
|
|
business_choice: BusinessChoice;
|
|
industry: Array< IndustryChoice >;
|
|
selling_online_answer: SellingOnlineAnswer | null;
|
|
selling_platforms: SellingPlatform[] | null;
|
|
skip?: boolean;
|
|
is_store_country_set: boolean | null;
|
|
store_email?: string;
|
|
is_agree_marketing?: boolean;
|
|
};
|
|
|
|
export type CoreProfilerStateMachineContext = {
|
|
optInDataSharing: boolean;
|
|
userProfile: {
|
|
businessChoice?: BusinessChoice;
|
|
sellingOnlineAnswer?: SellingOnlineAnswer | null;
|
|
sellingPlatforms?: SellingPlatform[] | null;
|
|
skipped?: boolean;
|
|
};
|
|
pluginsAvailable: ExtensionList[ 'plugins' ] | [];
|
|
pluginsSelected: string[]; // extension slugs
|
|
pluginsInstallationErrors: PluginInstallError[];
|
|
geolocatedLocation: GeolocationResponse | undefined;
|
|
businessInfo: {
|
|
storeName?: string | undefined;
|
|
industry?: string | undefined;
|
|
location?: string;
|
|
};
|
|
countries: CountryStateOption[];
|
|
loader: {
|
|
progress?: number;
|
|
className?: string;
|
|
useStages?: string;
|
|
stageIndex?: number;
|
|
};
|
|
onboardingProfile: OnboardingProfile;
|
|
jetpackAuthUrl?: string;
|
|
currentUserEmail: string | undefined;
|
|
};
|
|
|
|
const getAllowTrackingOption = fromPromise( async () =>
|
|
resolveSelect( OPTIONS_STORE_NAME ).getOption(
|
|
'woocommerce_allow_tracking'
|
|
)
|
|
);
|
|
|
|
const handleTrackingOption = assign( {
|
|
optInDataSharing: ( {
|
|
event,
|
|
}: {
|
|
event: DoneActorEvent< 'no' | 'yes' | undefined >;
|
|
} ) => event.output !== 'no',
|
|
} );
|
|
|
|
const getStoreNameOption = fromPromise( async () =>
|
|
resolveSelect( OPTIONS_STORE_NAME ).getOption( 'blogname' )
|
|
);
|
|
|
|
const handleStoreNameOption = assign( {
|
|
businessInfo: ( {
|
|
context,
|
|
event,
|
|
}: {
|
|
context: CoreProfilerStateMachineContext;
|
|
event: DoneActorEvent< string | undefined >;
|
|
} ) => {
|
|
return {
|
|
...context.businessInfo,
|
|
storeName: POSSIBLY_DEFAULT_STORE_NAMES.includes( event.output ) // if its empty or the default, show empty to the user
|
|
? undefined
|
|
: event.output,
|
|
};
|
|
},
|
|
} );
|
|
|
|
const getStoreCountryOption = fromPromise( async () =>
|
|
resolveSelect( OPTIONS_STORE_NAME ).getOption(
|
|
'woocommerce_default_country'
|
|
)
|
|
);
|
|
|
|
const handleStoreCountryOption = assign( {
|
|
businessInfo: ( {
|
|
context,
|
|
event,
|
|
}: {
|
|
context: CoreProfilerStateMachineContext;
|
|
event: DoneActorEvent< string | undefined >;
|
|
} ) => {
|
|
return {
|
|
...context.businessInfo,
|
|
location: event.output,
|
|
};
|
|
},
|
|
} );
|
|
|
|
const preFetchOptions = fromPromise( async ( { input }: { input: string[] } ) =>
|
|
Promise.all( [
|
|
input.map( ( optionName: string ) =>
|
|
resolveSelect( OPTIONS_STORE_NAME ).getOption( optionName )
|
|
),
|
|
] )
|
|
);
|
|
|
|
const getCountries = fromPromise( async () =>
|
|
resolveSelect( COUNTRIES_STORE_NAME ).getCountries()
|
|
);
|
|
|
|
const handleCountries = assign( {
|
|
countries: ( { event }: { event: DoneActorEvent< Country[] > } ) => {
|
|
return getCountryStateOptions( event.output );
|
|
},
|
|
} );
|
|
|
|
const getOnboardingProfileOption = fromPromise( async () =>
|
|
resolveSelect( OPTIONS_STORE_NAME ).getOption(
|
|
'woocommerce_onboarding_profile'
|
|
)
|
|
);
|
|
|
|
const handleOnboardingProfileOption = assign( {
|
|
userProfile: ( {
|
|
event,
|
|
}: {
|
|
event: DoneActorEvent< OnboardingProfile | undefined >;
|
|
} ) => {
|
|
if ( ! event.output ) {
|
|
return {};
|
|
}
|
|
|
|
const {
|
|
business_choice: businessChoice,
|
|
selling_online_answer: sellingOnlineAnswer,
|
|
selling_platforms: sellingPlatforms,
|
|
...rest
|
|
} = event.output;
|
|
return {
|
|
...rest,
|
|
businessChoice,
|
|
sellingOnlineAnswer,
|
|
sellingPlatforms,
|
|
};
|
|
},
|
|
} );
|
|
|
|
const getCurrentUserEmail = fromPromise( async () => {
|
|
const currentUser: WCUser< 'email' > = await resolveSelect(
|
|
USER_STORE_NAME
|
|
).getCurrentUser();
|
|
return currentUser?.email;
|
|
} );
|
|
|
|
const assignCurrentUserEmail = assign( {
|
|
currentUserEmail: ( {
|
|
event,
|
|
}: {
|
|
event: DoneActorEvent< string | undefined >;
|
|
} ) => {
|
|
if (
|
|
event.output &&
|
|
event.output.length > 0 &&
|
|
event.output !== 'wordpress@example.com' // wordpress default prefilled email address
|
|
) {
|
|
return event.output;
|
|
}
|
|
return undefined;
|
|
},
|
|
} );
|
|
|
|
const assignOnboardingProfile = assign( {
|
|
onboardingProfile: ( {
|
|
event,
|
|
}: {
|
|
event: DoneActorEvent< OnboardingProfile | undefined >;
|
|
} ) => event.output,
|
|
} );
|
|
|
|
const getGeolocation = fromPromise(
|
|
async ( { input }: { input: CoreProfilerStateMachineContext } ) => {
|
|
if ( input.optInDataSharing ) {
|
|
return resolveSelect( COUNTRIES_STORE_NAME ).geolocate();
|
|
}
|
|
return undefined;
|
|
}
|
|
);
|
|
|
|
const handleGeolocation = assign( {
|
|
geolocatedLocation: ( {
|
|
event,
|
|
}: {
|
|
event: DoneActorEvent< GeolocationResponse >;
|
|
} ) => {
|
|
return event.output;
|
|
},
|
|
} );
|
|
|
|
const redirectToWooHome = raise( { type: 'REDIRECT_TO_WOO_HOME' } );
|
|
|
|
const exitToWooHome = fromPromise( async () => {
|
|
if ( window.wcAdminFeatures[ 'launch-your-store' ] ) {
|
|
await dispatch( ONBOARDING_STORE_NAME ).coreProfilerCompleted();
|
|
}
|
|
window.location.href = getNewPath( {}, '/', {} );
|
|
} );
|
|
|
|
const redirectToJetpackAuthPage = ( {
|
|
context,
|
|
event,
|
|
}: {
|
|
context: CoreProfilerStateMachineContext;
|
|
event: { output: { url: string } };
|
|
} ) => {
|
|
const url = new URL( event.output.url );
|
|
url.searchParams.set( 'installed_ext_success', '1' );
|
|
const selectedPlugin = context.pluginsSelected.find(
|
|
( plugin ) => plugin === 'jetpack' || plugin === 'jetpack-boost'
|
|
);
|
|
|
|
if ( selectedPlugin ) {
|
|
const pluginName =
|
|
selectedPlugin === 'jetpack' ? 'jetpack-ai' : 'jetpack-boost';
|
|
url.searchParams.set( 'plugin_name', pluginName );
|
|
}
|
|
|
|
window.location.href = url.toString();
|
|
};
|
|
|
|
const updateTrackingOption = fromPromise(
|
|
async ( { input }: { input: CoreProfilerStateMachineContext } ) => {
|
|
await new Promise< void >( ( resolve ) => {
|
|
setTimeout( resolve, 500 );
|
|
if (
|
|
input.optInDataSharing &&
|
|
typeof window.wcTracks.enable === 'function'
|
|
) {
|
|
window.wcTracks.enable( () => {
|
|
initializeExPlat();
|
|
resolve(); // resolve the promise only after explat is enabled by the callback
|
|
} );
|
|
} else {
|
|
if ( ! input.optInDataSharing ) {
|
|
window.wcTracks.isEnabled = false;
|
|
}
|
|
resolve();
|
|
}
|
|
} );
|
|
|
|
const trackingValue = input.optInDataSharing ? 'yes' : 'no';
|
|
dispatch( OPTIONS_STORE_NAME ).updateOptions( {
|
|
woocommerce_allow_tracking: trackingValue,
|
|
} );
|
|
}
|
|
);
|
|
|
|
const updateOnboardingProfileOption = fromPromise(
|
|
async ( { input }: { input: CoreProfilerStateMachineContext } ) => {
|
|
const { businessChoice, sellingOnlineAnswer, sellingPlatforms } =
|
|
input.userProfile;
|
|
|
|
return dispatch( OPTIONS_STORE_NAME ).updateOptions( {
|
|
woocommerce_onboarding_profile: {
|
|
...input.onboardingProfile,
|
|
business_choice: businessChoice,
|
|
selling_online_answer: sellingOnlineAnswer,
|
|
selling_platforms: sellingPlatforms,
|
|
},
|
|
} );
|
|
}
|
|
);
|
|
|
|
const updateBusinessLocation = ( countryAndState: string ) => {
|
|
return dispatch( OPTIONS_STORE_NAME ).updateOptions( {
|
|
woocommerce_default_country: countryAndState,
|
|
} );
|
|
};
|
|
|
|
const updateStoreCurrency = async ( countryAndState: string ) => {
|
|
const { general: settings = {} } = await resolveSelect(
|
|
SETTINGS_STORE_NAME
|
|
).getSettings( 'general' );
|
|
|
|
const countryCode = getCountryCode( countryAndState ) as string;
|
|
const { currencySymbols = {}, localeInfo = {} } = getAdminSetting(
|
|
'onboarding',
|
|
{}
|
|
);
|
|
const currencySettings = CurrencyFactory().getDataForCountry(
|
|
countryCode,
|
|
localeInfo,
|
|
currencySymbols
|
|
) as {
|
|
code: string;
|
|
symbolPosition: string;
|
|
thousandSeparator: string;
|
|
decimalSeparator: string;
|
|
precision: string;
|
|
};
|
|
|
|
if ( Object.keys( currencySettings ).length === 0 ) {
|
|
return;
|
|
}
|
|
|
|
return dispatch( SETTINGS_STORE_NAME ).updateAndPersistSettingsForGroup(
|
|
'general',
|
|
{
|
|
general: {
|
|
...settings,
|
|
woocommerce_currency: currencySettings.code,
|
|
woocommerce_currency_pos: currencySettings.symbolPosition,
|
|
woocommerce_price_thousand_sep:
|
|
currencySettings.thousandSeparator,
|
|
woocommerce_price_decimal_sep:
|
|
currencySettings.decimalSeparator,
|
|
woocommerce_price_num_decimals: currencySettings.precision,
|
|
},
|
|
}
|
|
);
|
|
};
|
|
|
|
const assignStoreLocation = assign( {
|
|
businessInfo: ( {
|
|
event,
|
|
context,
|
|
}: {
|
|
context: CoreProfilerStateMachineContext;
|
|
event: BusinessLocationEvent;
|
|
} ) => {
|
|
return {
|
|
...context.businessInfo,
|
|
location: event.payload.storeLocation,
|
|
};
|
|
},
|
|
} );
|
|
|
|
const assignUserProfile = assign( {
|
|
userProfile: ( { event }: { event: UserProfileEvent } ) =>
|
|
event.payload.userProfile,
|
|
} );
|
|
|
|
const updateBusinessInfo = fromPromise(
|
|
async ( {
|
|
input,
|
|
}: {
|
|
input: { payload: BusinessInfoEvent[ 'payload' ] };
|
|
} ) => {
|
|
const refreshedOnboardingProfile = ( await resolveSelect(
|
|
OPTIONS_STORE_NAME
|
|
).getOption( 'woocommerce_onboarding_profile' ) ) as OnboardingProfile;
|
|
|
|
await updateStoreCurrency( input.payload.storeLocation );
|
|
|
|
return dispatch( OPTIONS_STORE_NAME ).updateOptions( {
|
|
blogname: input.payload.storeName,
|
|
woocommerce_default_country: input.payload.storeLocation,
|
|
woocommerce_onboarding_profile: {
|
|
...refreshedOnboardingProfile,
|
|
is_store_country_set: true,
|
|
industry: [ input.payload.industry ],
|
|
is_agree_marketing: input.payload.isOptInMarketing,
|
|
store_email:
|
|
input.payload.storeEmailAddress.length > 0
|
|
? input.payload.storeEmailAddress
|
|
: null,
|
|
},
|
|
} );
|
|
}
|
|
);
|
|
|
|
const promiseDelay = ( milliseconds: number ) => {
|
|
return new Promise( ( resolve ) => {
|
|
setTimeout( resolve, milliseconds );
|
|
} );
|
|
};
|
|
|
|
const assignOptInDataSharing = assign( {
|
|
optInDataSharing: ( { event }: { event: IntroOptInEvent } ) =>
|
|
event.payload.optInDataSharing,
|
|
} );
|
|
|
|
const preFetchIsJetpackConnected = assign( {
|
|
isJetpackConnectedRef: ( { spawn } ) =>
|
|
spawn(
|
|
fromPromise( async () =>
|
|
resolveSelect( PLUGINS_STORE_NAME ).isJetpackConnected()
|
|
)
|
|
),
|
|
} );
|
|
|
|
const preFetchJetpackAuthUrl = assign( {
|
|
jetpackAuthUrlRef: ( { spawn } ) =>
|
|
spawn(
|
|
fromPromise( async () =>
|
|
resolveSelect( ONBOARDING_STORE_NAME ).getJetpackAuthUrl( {
|
|
redirectUrl: getAdminLink( 'admin.php?page=wc-admin' ),
|
|
from: 'woocommerce-core-profiler',
|
|
} )
|
|
)
|
|
),
|
|
} );
|
|
|
|
const preFetchGetPlugins = fromPromise( async () =>
|
|
resolveSelect( ONBOARDING_STORE_NAME ).getFreeExtensions()
|
|
);
|
|
|
|
const getPlugins = fromPromise( async () => {
|
|
dispatch( ONBOARDING_STORE_NAME ).invalidateResolution(
|
|
'getFreeExtensions'
|
|
);
|
|
const extensionsBundles = await resolveSelect(
|
|
ONBOARDING_STORE_NAME
|
|
).getFreeExtensions();
|
|
return (
|
|
extensionsBundles.find(
|
|
( bundle ) => bundle.key === 'obw/core-profiler'
|
|
)?.plugins || []
|
|
);
|
|
} );
|
|
|
|
/** Special callback that is used to trigger a navigation event if the user uses the browser's back or forward buttons */
|
|
const browserPopstateHandler = fromCallback( ( { sendBack } ) => {
|
|
const popstateHandler = () => {
|
|
sendBack( { type: 'EXTERNAL_URL_UPDATE' } );
|
|
};
|
|
window.addEventListener( 'popstate', popstateHandler );
|
|
return () => {
|
|
window.removeEventListener( 'popstate', popstateHandler );
|
|
};
|
|
} );
|
|
|
|
const handlePlugins = assign( {
|
|
pluginsAvailable: ( {
|
|
event,
|
|
}: {
|
|
event: DoneActorEvent< Extension[] >;
|
|
} ) => {
|
|
return event.output;
|
|
},
|
|
} );
|
|
|
|
const updateQueryStep = ( _: unknown, params: { step: string } ) => {
|
|
const { step } = getQuery() as { step: string };
|
|
// only update the query string if it has changed
|
|
if ( params.step !== step ) {
|
|
updateQueryString( { step: params.step } );
|
|
}
|
|
};
|
|
|
|
const assignPluginsSelected = assign( {
|
|
pluginsSelected: ( {
|
|
event,
|
|
}: {
|
|
event: PluginsInstallationRequestedEvent;
|
|
} ) => {
|
|
return event.payload.pluginsSelected.map( getPluginSlug );
|
|
},
|
|
} );
|
|
|
|
const updateLoaderProgressWithPluginInstall = assign( {
|
|
loader: ( { event } ) => {
|
|
assertEvent( event, 'PLUGIN_INSTALLED_AND_ACTIVATED' );
|
|
const progress = event.payload.progressPercentage;
|
|
|
|
let stageIndex = 0;
|
|
|
|
if ( progress > 60 ) {
|
|
stageIndex = 2;
|
|
} else if ( progress > 30 ) {
|
|
stageIndex = 1;
|
|
}
|
|
|
|
return {
|
|
useStages: 'plugins',
|
|
progress,
|
|
stageIndex,
|
|
};
|
|
},
|
|
} );
|
|
|
|
const skipFlowUpdateBusinessLocation = fromPromise(
|
|
async ( {
|
|
input: context,
|
|
}: {
|
|
input: CoreProfilerStateMachineContext;
|
|
} ) => {
|
|
const skipped = dispatch( ONBOARDING_STORE_NAME ).updateProfileItems( {
|
|
skipped: true,
|
|
} );
|
|
const businessLocation = updateBusinessLocation(
|
|
context.businessInfo.location as string
|
|
);
|
|
const currencyUpdate = updateStoreCurrency(
|
|
context.businessInfo.location as string
|
|
);
|
|
|
|
return Promise.all( [ skipped, businessLocation, currencyUpdate ] );
|
|
}
|
|
);
|
|
|
|
export const getJetpackIsConnected = fromPromise( async () => {
|
|
return resolveSelect( PLUGINS_STORE_NAME ).isJetpackConnected();
|
|
} );
|
|
|
|
export const preFetchActions = {
|
|
preFetchIsJetpackConnected,
|
|
preFetchJetpackAuthUrl,
|
|
};
|
|
|
|
const coreProfilerMachineActions = {
|
|
...preFetchActions,
|
|
...recordTracksActions,
|
|
handlePlugins,
|
|
updateQueryStep,
|
|
handleTrackingOption,
|
|
handleGeolocation,
|
|
handleStoreNameOption,
|
|
handleStoreCountryOption,
|
|
assignOptInDataSharing,
|
|
assignStoreLocation,
|
|
assignPluginsSelected,
|
|
assignUserProfile,
|
|
handleCountries,
|
|
handleOnboardingProfileOption,
|
|
assignOnboardingProfile,
|
|
assignCurrentUserEmail,
|
|
redirectToWooHome,
|
|
redirectToJetpackAuthPage,
|
|
updateLoaderProgressWithPluginInstall,
|
|
};
|
|
|
|
const coreProfilerMachineActors = {
|
|
preFetchGetPlugins,
|
|
preFetchOptions,
|
|
getAllowTrackingOption,
|
|
getStoreNameOption,
|
|
getStoreCountryOption,
|
|
getCountries,
|
|
getGeolocation,
|
|
getOnboardingProfileOption,
|
|
getCurrentUserEmail,
|
|
getPlugins,
|
|
getJetpackIsConnected,
|
|
browserPopstateHandler,
|
|
updateBusinessInfo,
|
|
updateTrackingOption,
|
|
updateOnboardingProfileOption,
|
|
skipFlowUpdateBusinessLocation,
|
|
pluginInstallerMachine,
|
|
exitToWooHome,
|
|
};
|
|
export const coreProfilerStateMachineDefinition = createMachine( {
|
|
id: 'coreProfiler',
|
|
initial: 'navigate',
|
|
types: {} as {
|
|
context: CoreProfilerStateMachineContext;
|
|
events: CoreProfilerEvents;
|
|
},
|
|
invoke: {
|
|
id: 'browserPopstateHandler',
|
|
src: 'browserPopstateHandler',
|
|
},
|
|
on: {
|
|
EXTERNAL_URL_UPDATE: {
|
|
target: '#navigate',
|
|
},
|
|
REDIRECT_TO_WOO_HOME: {
|
|
target: '#redirectingToWooHome',
|
|
},
|
|
},
|
|
context: {
|
|
// these are safe default values if for some reason the steps fail to complete correctly
|
|
// actual defaults displayed to the user should be handled in the steps themselves
|
|
optInDataSharing: false,
|
|
userProfile: { skipped: true },
|
|
geolocatedLocation: undefined,
|
|
businessInfo: {
|
|
storeName: undefined,
|
|
industry: undefined,
|
|
storeCountryPreviouslySet: false,
|
|
location: 'US:CA',
|
|
},
|
|
countries: [] as CountryStateOption[],
|
|
pluginsAvailable: [],
|
|
pluginsInstallationErrors: [],
|
|
pluginsSelected: [],
|
|
loader: {},
|
|
onboardingProfile: {} as OnboardingProfile,
|
|
jetpackAuthUrl: undefined,
|
|
currentUserEmail: undefined,
|
|
} as CoreProfilerStateMachineContext,
|
|
states: {
|
|
navigate: {
|
|
id: 'navigate',
|
|
always: [
|
|
/**
|
|
* The 'navigate' state forwards the progress to whichever step is
|
|
* specified in the query string. If no step is specified, it will
|
|
* default to introOptIn.
|
|
*
|
|
* Each top level state must be responsible for populating their own
|
|
* context data dependencies, as it is possible that they are the
|
|
* first state to be loaded due to the navigation jump.
|
|
*
|
|
* Each top level state must also be responsible for updating the
|
|
* query string to reflect their own state, using the 'updateQueryStep'
|
|
* action.
|
|
*/
|
|
{
|
|
target: '#introOptIn',
|
|
guard: {
|
|
type: 'hasStepInUrl',
|
|
params: { step: 'intro-opt-in' },
|
|
},
|
|
},
|
|
{
|
|
target: '#userProfile',
|
|
guard: {
|
|
type: 'hasStepInUrl',
|
|
params: { step: 'user-profile' },
|
|
},
|
|
},
|
|
{
|
|
target: '#businessInfo',
|
|
guard: {
|
|
type: 'hasStepInUrl',
|
|
params: { step: 'business-info' },
|
|
},
|
|
},
|
|
{
|
|
target: '#plugins',
|
|
guard: {
|
|
type: 'hasStepInUrl',
|
|
params: { step: 'plugins' },
|
|
},
|
|
},
|
|
{
|
|
target: '#skipGuidedSetup',
|
|
guard: {
|
|
type: 'hasStepInUrl',
|
|
params: { step: 'skip-guided-setup' },
|
|
},
|
|
},
|
|
{
|
|
target: 'introOptIn',
|
|
},
|
|
],
|
|
},
|
|
introOptIn: {
|
|
id: 'introOptIn',
|
|
initial: 'preIntroOptIn',
|
|
states: {
|
|
preIntroOptIn: {
|
|
entry: [
|
|
// these prefetch tasks are spawned actors in the background and do not block progression of the state machine
|
|
spawnChild( 'preFetchGetPlugins' ),
|
|
spawnChild( 'getCountries' ),
|
|
spawnChild( 'preFetchOptions', {
|
|
id: 'prefetch-options',
|
|
input: [
|
|
'blogname',
|
|
'woocommerce_onboarding_profile',
|
|
'woocommerce_default_country',
|
|
],
|
|
} ),
|
|
],
|
|
type: 'parallel',
|
|
states: {
|
|
// if we have any other init tasks to do in parallel, add them as a parallel state here.
|
|
// this blocks the introOptIn UI from loading keep that in mind when adding new tasks here
|
|
trackingOption: {
|
|
initial: 'fetching',
|
|
states: {
|
|
fetching: {
|
|
invoke: {
|
|
systemId: 'getAllowTrackingOption',
|
|
src: 'getAllowTrackingOption',
|
|
onDone: [
|
|
{
|
|
actions: [
|
|
'handleTrackingOption',
|
|
],
|
|
target: 'done',
|
|
},
|
|
],
|
|
onError: {
|
|
target: 'done', // leave it as initialised default on error
|
|
},
|
|
},
|
|
},
|
|
done: {
|
|
type: 'final',
|
|
},
|
|
},
|
|
},
|
|
},
|
|
onDone: {
|
|
target: 'introOptIn',
|
|
},
|
|
meta: {
|
|
progress: 0,
|
|
},
|
|
},
|
|
introOptIn: {
|
|
on: {
|
|
INTRO_COMPLETED: {
|
|
target: 'postIntroOptIn',
|
|
actions: [ 'assignOptInDataSharing' ],
|
|
},
|
|
INTRO_SKIPPED: {
|
|
// if the user skips the intro, we set the optInDataSharing to false and go to the Business Location page
|
|
target: '#skipGuidedSetup',
|
|
actions: [
|
|
'assignOptInDataSharing',
|
|
spawnChild( 'updateTrackingOption ', {
|
|
input: ( {
|
|
event,
|
|
}: {
|
|
event: CoreProfilerEvents;
|
|
} ) => {
|
|
assertEvent( event, 'INTRO_SKIPPED' );
|
|
return event.payload;
|
|
},
|
|
} ),
|
|
],
|
|
},
|
|
},
|
|
meta: {
|
|
progress: 20,
|
|
component: IntroOptIn,
|
|
},
|
|
},
|
|
postIntroOptIn: {
|
|
invoke: {
|
|
src: 'updateTrackingOption',
|
|
input: ( { context } ) => context,
|
|
onDone: {
|
|
actions: [ 'recordTracksIntroCompleted' ],
|
|
target: '#userProfile',
|
|
},
|
|
onError: {
|
|
actions: [ 'recordTracksIntroCompleted' ],
|
|
target: '#userProfile',
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
userProfile: {
|
|
id: 'userProfile',
|
|
initial: 'preUserProfile',
|
|
states: {
|
|
preUserProfile: {
|
|
invoke: {
|
|
src: 'getOnboardingProfileOption',
|
|
onDone: [
|
|
{
|
|
actions: [
|
|
'handleOnboardingProfileOption',
|
|
'assignOnboardingProfile',
|
|
],
|
|
target: 'userProfile',
|
|
},
|
|
],
|
|
onError: {
|
|
target: 'userProfile',
|
|
},
|
|
},
|
|
},
|
|
userProfile: {
|
|
meta: {
|
|
progress: 40,
|
|
component: UserProfile,
|
|
},
|
|
entry: [
|
|
{
|
|
type: 'recordTracksStepViewed',
|
|
params: { step: 'user_profile' },
|
|
},
|
|
{
|
|
type: 'updateQueryStep',
|
|
params: { step: 'user-profile' },
|
|
},
|
|
spawnChild( 'getGeolocation', {
|
|
input: ( {
|
|
context,
|
|
}: {
|
|
context: CoreProfilerStateMachineContext;
|
|
} ) => context,
|
|
} ),
|
|
],
|
|
on: {
|
|
USER_PROFILE_COMPLETED: {
|
|
target: 'postUserProfile',
|
|
actions: [
|
|
'assignUserProfile',
|
|
{ type: 'recordTracksUserProfileCompleted' },
|
|
],
|
|
},
|
|
USER_PROFILE_SKIPPED: {
|
|
target: 'postUserProfile',
|
|
actions: [
|
|
'assignUserProfile',
|
|
{
|
|
type: 'recordTracksStepSkipped',
|
|
params: { step: 'user_profile' },
|
|
},
|
|
],
|
|
},
|
|
},
|
|
},
|
|
postUserProfile: {
|
|
entry: spawnChild( 'updateOnboardingProfileOption', {
|
|
id: 'updateOnboardingProfileOption',
|
|
input: ( {
|
|
context,
|
|
}: {
|
|
context: CoreProfilerStateMachineContext;
|
|
} ) => context,
|
|
} ),
|
|
always: {
|
|
target: '#businessInfo',
|
|
},
|
|
},
|
|
},
|
|
},
|
|
businessInfo: {
|
|
id: 'businessInfo',
|
|
initial: 'preBusinessInfo',
|
|
entry: [
|
|
{ type: 'updateQueryStep', params: { step: 'business-info' } },
|
|
],
|
|
states: {
|
|
preBusinessInfo: {
|
|
type: 'parallel',
|
|
states: {
|
|
geolocation: {
|
|
initial: 'checkDataOptIn',
|
|
states: {
|
|
checkDataOptIn: {
|
|
invoke: {
|
|
src: 'getAllowTrackingOption',
|
|
onDone: [
|
|
{
|
|
actions: [
|
|
'handleTrackingOption',
|
|
],
|
|
target: 'fetching',
|
|
},
|
|
],
|
|
onError: {
|
|
target: 'done', // leave it as initialised default on error
|
|
},
|
|
},
|
|
},
|
|
fetching: {
|
|
invoke: {
|
|
input: ( { context } ) => context,
|
|
src: 'getGeolocation',
|
|
onDone: {
|
|
target: 'done',
|
|
actions: 'handleGeolocation',
|
|
},
|
|
onError: {
|
|
target: 'done',
|
|
},
|
|
},
|
|
},
|
|
done: {
|
|
type: 'final',
|
|
},
|
|
},
|
|
},
|
|
storeCountryOption: {
|
|
initial: 'fetching',
|
|
states: {
|
|
fetching: {
|
|
invoke: {
|
|
src: 'getStoreCountryOption',
|
|
onDone: [
|
|
{
|
|
actions: [
|
|
'handleStoreCountryOption',
|
|
],
|
|
target: 'done',
|
|
},
|
|
],
|
|
onError: {
|
|
target: 'done',
|
|
},
|
|
},
|
|
},
|
|
done: {
|
|
type: 'final',
|
|
},
|
|
},
|
|
},
|
|
onboardingProfileOption: {
|
|
initial: 'fetching',
|
|
states: {
|
|
fetching: {
|
|
invoke: {
|
|
src: 'getOnboardingProfileOption',
|
|
onDone: [
|
|
{
|
|
actions: [
|
|
'assignOnboardingProfile',
|
|
],
|
|
target: 'done',
|
|
},
|
|
],
|
|
onError: {
|
|
target: 'done',
|
|
},
|
|
},
|
|
},
|
|
done: {
|
|
type: 'final',
|
|
},
|
|
},
|
|
},
|
|
storeNameOption: {
|
|
initial: 'fetching',
|
|
states: {
|
|
fetching: {
|
|
invoke: {
|
|
src: 'getStoreNameOption',
|
|
onDone: [
|
|
{
|
|
actions: [
|
|
'handleStoreNameOption',
|
|
],
|
|
target: 'done',
|
|
},
|
|
],
|
|
onError: {
|
|
target: 'done', // leave it as initialised default on error
|
|
},
|
|
},
|
|
},
|
|
done: {
|
|
type: 'final',
|
|
},
|
|
},
|
|
},
|
|
countries: {
|
|
initial: 'fetching',
|
|
states: {
|
|
fetching: {
|
|
invoke: {
|
|
systemId: 'getCountries',
|
|
src: 'getCountries',
|
|
onDone: {
|
|
target: 'done',
|
|
actions: 'handleCountries',
|
|
},
|
|
},
|
|
},
|
|
done: {
|
|
type: 'final',
|
|
},
|
|
},
|
|
},
|
|
currentUserEmail: {
|
|
initial: 'fetching',
|
|
states: {
|
|
fetching: {
|
|
invoke: {
|
|
src: 'getCurrentUserEmail',
|
|
onDone: {
|
|
target: 'done',
|
|
actions: [
|
|
'assignCurrentUserEmail',
|
|
],
|
|
},
|
|
onError: {
|
|
target: 'done',
|
|
},
|
|
},
|
|
},
|
|
done: {
|
|
type: 'final',
|
|
},
|
|
},
|
|
},
|
|
},
|
|
// onDone is reached when child parallel states fo fetching are resolved (reached final states)
|
|
onDone: {
|
|
target: 'businessInfo',
|
|
},
|
|
},
|
|
businessInfo: {
|
|
meta: {
|
|
progress: 60,
|
|
component: BusinessInfo,
|
|
},
|
|
entry: [
|
|
{
|
|
type: 'recordTracksStepViewed',
|
|
params: { step: 'business_info' },
|
|
},
|
|
],
|
|
on: {
|
|
BUSINESS_INFO_COMPLETED: {
|
|
target: 'postBusinessInfo',
|
|
actions: [
|
|
'recordTracksBusinessInfoCompleted',
|
|
'recordTracksIsEmailChanged',
|
|
],
|
|
},
|
|
},
|
|
},
|
|
postBusinessInfo: {
|
|
invoke: {
|
|
src: 'updateBusinessInfo',
|
|
input: ( { event } ) => event,
|
|
onDone: {
|
|
target: '#plugins',
|
|
},
|
|
onError: {
|
|
target: '#plugins',
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
skipGuidedSetup: {
|
|
id: 'skipGuidedSetup',
|
|
initial: 'preSkipFlowBusinessLocation',
|
|
entry: [
|
|
{
|
|
type: 'updateQueryStep',
|
|
params: { step: 'skip-guided-setup' },
|
|
},
|
|
],
|
|
states: {
|
|
preSkipFlowBusinessLocation: {
|
|
invoke: {
|
|
src: 'getCountries',
|
|
onDone: [
|
|
{
|
|
actions: [ 'handleCountries' ],
|
|
target: 'skipFlowBusinessLocation',
|
|
},
|
|
],
|
|
onError: {
|
|
target: 'skipFlowBusinessLocation',
|
|
},
|
|
},
|
|
},
|
|
skipFlowBusinessLocation: {
|
|
on: {
|
|
BUSINESS_LOCATION_COMPLETED: {
|
|
target: 'postSkipFlowBusinessLocation',
|
|
actions: [
|
|
'assignStoreLocation',
|
|
'recordTracksSkipBusinessLocationCompleted',
|
|
],
|
|
},
|
|
},
|
|
entry: [
|
|
{
|
|
type: 'recordTracksStepViewed',
|
|
params: { step: 'skip_business_location' },
|
|
},
|
|
],
|
|
meta: {
|
|
progress: 80,
|
|
component: BusinessLocation,
|
|
},
|
|
},
|
|
postSkipFlowBusinessLocation: {
|
|
initial: 'updateBusinessLocation',
|
|
states: {
|
|
updateBusinessLocation: {
|
|
entry: assign( {
|
|
loader: {
|
|
progress: 10,
|
|
useStages: 'skippedGuidedSetup',
|
|
},
|
|
} ),
|
|
invoke: {
|
|
input: ( { context } ) => context,
|
|
src: 'skipFlowUpdateBusinessLocation',
|
|
onDone: {
|
|
target: 'progress20',
|
|
},
|
|
},
|
|
},
|
|
// Although we don't need to wait 3 seconds for the following states
|
|
// We will display 20% and 80% progress for 1.5 seconds each
|
|
// for the sake of user experience.
|
|
progress20: {
|
|
entry: assign( {
|
|
loader: {
|
|
progress: 20,
|
|
useStages: 'skippedGuidedSetup',
|
|
},
|
|
} ),
|
|
after: {
|
|
1500: {
|
|
target: 'progress80',
|
|
},
|
|
},
|
|
},
|
|
progress80: {
|
|
entry: assign( {
|
|
loader: {
|
|
progress: 80,
|
|
useStages: 'skippedGuidedSetup',
|
|
stageIndex: 1,
|
|
},
|
|
} ),
|
|
after: {
|
|
1500: {
|
|
actions: [ 'redirectToWooHome' ],
|
|
},
|
|
},
|
|
},
|
|
},
|
|
meta: {
|
|
component: CoreProfilerLoader,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
plugins: {
|
|
id: 'plugins',
|
|
initial: 'prePlugins',
|
|
states: {
|
|
prePlugins: {
|
|
invoke: {
|
|
src: 'getPlugins',
|
|
onDone: [
|
|
{
|
|
target: 'pluginsSkipped',
|
|
guard: ( {
|
|
event,
|
|
}: {
|
|
event: DoneActorEvent< Extension[] >;
|
|
} ) => {
|
|
// Skip the plugins page
|
|
// When there is 0 plugin returned from the server
|
|
// Or all the plugins are activated already.
|
|
return (
|
|
event.output.length === 0 ||
|
|
event.output.every(
|
|
( plugin: Extension ) =>
|
|
plugin.is_activated
|
|
)
|
|
);
|
|
},
|
|
},
|
|
{ target: 'plugins', actions: 'handlePlugins' },
|
|
],
|
|
onError: {
|
|
target: 'pluginsSkipped',
|
|
},
|
|
},
|
|
meta: {
|
|
progress: 70,
|
|
},
|
|
},
|
|
pluginsSkipped: {
|
|
entry: assign( {
|
|
loader: {
|
|
progress: 80,
|
|
},
|
|
} ),
|
|
invoke: {
|
|
src: fromPromise( () => {
|
|
dispatch(
|
|
ONBOARDING_STORE_NAME
|
|
).updateProfileItems( {
|
|
is_plugins_page_skipped: true,
|
|
completed: true,
|
|
} );
|
|
return promiseDelay( 3000 );
|
|
} ),
|
|
onDone: [
|
|
{
|
|
target: 'isJetpackConnected',
|
|
guard: 'hasJetpackSelected',
|
|
},
|
|
{ actions: [ 'redirectToWooHome' ] },
|
|
],
|
|
},
|
|
meta: {
|
|
component: CoreProfilerLoader,
|
|
},
|
|
},
|
|
plugins: {
|
|
entry: [
|
|
{
|
|
type: 'recordTracksStepViewed',
|
|
params: { step: 'plugins' },
|
|
},
|
|
{
|
|
type: 'updateQueryStep',
|
|
params: { step: 'plugins' },
|
|
},
|
|
],
|
|
on: {
|
|
PLUGINS_PAGE_SKIPPED: {
|
|
actions: [
|
|
{
|
|
type: 'recordTracksStepSkipped',
|
|
params: { step: 'plugins' },
|
|
},
|
|
],
|
|
target: 'pluginsSkipped',
|
|
},
|
|
PLUGINS_LEARN_MORE_LINK_CLICKED: {
|
|
actions: [
|
|
{
|
|
type: 'recordTracksPluginsLearnMoreLinkClicked',
|
|
params: { step: 'plugins' },
|
|
},
|
|
],
|
|
},
|
|
PLUGINS_INSTALLATION_REQUESTED: {
|
|
target: 'installPlugins',
|
|
actions: [
|
|
'assignPluginsSelected',
|
|
'recordTracksPluginsInstallationRequest',
|
|
],
|
|
},
|
|
},
|
|
meta: {
|
|
progress: 80,
|
|
component: Plugins,
|
|
},
|
|
},
|
|
postPluginInstallation: {
|
|
invoke: {
|
|
input: ( { event } ) => {
|
|
assertEvent(
|
|
event,
|
|
'PLUGINS_INSTALLATION_COMPLETED'
|
|
);
|
|
return event;
|
|
},
|
|
src: fromPromise( async ( { input: event } ) => {
|
|
return await dispatch(
|
|
ONBOARDING_STORE_NAME
|
|
).updateProfileItems( {
|
|
business_extensions:
|
|
event.payload.installationCompletedResult.installedPlugins.map(
|
|
( extension: InstalledPlugin ) =>
|
|
extension.plugin
|
|
),
|
|
completed: true,
|
|
} );
|
|
} ),
|
|
onDone: [
|
|
{
|
|
target: 'isJetpackConnected',
|
|
guard: 'hasJetpackSelected',
|
|
},
|
|
{ actions: 'redirectToWooHome' },
|
|
],
|
|
},
|
|
meta: {
|
|
component: CoreProfilerLoader,
|
|
progress: 100,
|
|
},
|
|
},
|
|
isJetpackConnected: {
|
|
invoke: {
|
|
src: 'getJetpackIsConnected',
|
|
onDone: [
|
|
{
|
|
target: 'sendToJetpackAuthPage',
|
|
guard: ( { event } ) => {
|
|
return ! event.output.data;
|
|
},
|
|
},
|
|
{ actions: 'redirectToWooHome' },
|
|
],
|
|
},
|
|
meta: {
|
|
component: CoreProfilerLoader,
|
|
progress: 100,
|
|
},
|
|
},
|
|
sendToJetpackAuthPage: {
|
|
invoke: {
|
|
src: fromPromise( async () => {
|
|
if (
|
|
window.wcAdminFeatures[ 'launch-your-store' ]
|
|
) {
|
|
await dispatch(
|
|
ONBOARDING_STORE_NAME
|
|
).coreProfilerCompleted();
|
|
}
|
|
return await resolveSelect(
|
|
ONBOARDING_STORE_NAME
|
|
).getJetpackAuthUrl( {
|
|
redirectUrl: getAdminLink(
|
|
'admin.php?page=wc-admin'
|
|
),
|
|
from: 'woocommerce-core-profiler',
|
|
} );
|
|
} ),
|
|
onDone: {
|
|
actions: enqueueActions( ( { enqueue, check } ) => {
|
|
if (
|
|
check(
|
|
( { event } ) => event.output.success
|
|
)
|
|
) {
|
|
enqueue( {
|
|
type: 'redirectToJetpackAuthPage',
|
|
} );
|
|
} else {
|
|
enqueue( { type: 'redirectToWooHome' } );
|
|
}
|
|
} ),
|
|
},
|
|
},
|
|
meta: {
|
|
component: CoreProfilerLoader,
|
|
progress: 100,
|
|
},
|
|
},
|
|
installPlugins: {
|
|
on: {
|
|
PLUGIN_INSTALLED_AND_ACTIVATED: {
|
|
actions: [
|
|
'updateLoaderProgressWithPluginInstall',
|
|
],
|
|
},
|
|
PLUGINS_INSTALLATION_COMPLETED_WITH_ERRORS: {
|
|
target: 'prePlugins',
|
|
actions: [
|
|
assign( {
|
|
pluginsInstallationErrors: ( { event } ) =>
|
|
event.payload.errors,
|
|
} ),
|
|
{
|
|
type: 'recordFailedPluginInstallations',
|
|
},
|
|
],
|
|
},
|
|
PLUGINS_INSTALLATION_COMPLETED: {
|
|
target: 'postPluginInstallation',
|
|
actions: [
|
|
{
|
|
type: 'recordSuccessfulPluginInstallation',
|
|
},
|
|
],
|
|
},
|
|
},
|
|
entry: enqueueActions( ( { enqueue, check } ) => {
|
|
if ( check( { type: 'hasJetpackSelected' } ) ) {
|
|
enqueue( 'preFetchIsJetpackConnected' );
|
|
enqueue( 'preFetchJetpackAuthUrl' );
|
|
}
|
|
enqueue(
|
|
assign( {
|
|
loader: {
|
|
progress: 10,
|
|
useStages: 'plugins',
|
|
},
|
|
} )
|
|
);
|
|
} ),
|
|
invoke: {
|
|
src: 'pluginInstallerMachine',
|
|
input: ( { context } ) => {
|
|
return {
|
|
selectedPlugins: context.pluginsSelected,
|
|
pluginsAvailable: context.pluginsAvailable,
|
|
};
|
|
},
|
|
},
|
|
meta: {
|
|
component: CoreProfilerLoader,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
settingUpStore: {},
|
|
redirectingToWooHome: {
|
|
id: 'redirectingToWooHome',
|
|
invoke: {
|
|
src: 'exitToWooHome',
|
|
},
|
|
},
|
|
},
|
|
} );
|
|
|
|
export const CoreProfilerController = ( {
|
|
actionOverrides,
|
|
servicesOverrides,
|
|
}: {
|
|
actionOverrides: Partial< typeof coreProfilerMachineActions >;
|
|
servicesOverrides: Partial< typeof coreProfilerMachineActors >;
|
|
} ) => {
|
|
const augmentedStateMachine = useMemo( () => {
|
|
// When adding extensibility, this is the place to manipulate the state machine definition.
|
|
return coreProfilerStateMachineDefinition.provide( {
|
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
// @ts-ignore -- there seems to be a flaky error here - it fails sometimes and then not on recompile, will need to investigate further.
|
|
actions: {
|
|
...coreProfilerMachineActions,
|
|
...actionOverrides,
|
|
},
|
|
actors: {
|
|
...coreProfilerMachineActors,
|
|
...servicesOverrides,
|
|
},
|
|
guards: {
|
|
hasStepInUrl: ( _, params ) => {
|
|
const { step } = getQuery() as { step: string };
|
|
return (
|
|
!! step && step === ( params as { step: string } ).step
|
|
);
|
|
},
|
|
hasJetpackSelected: ( { context } ) => {
|
|
return (
|
|
context.pluginsSelected.find(
|
|
( plugin ) =>
|
|
plugin === 'jetpack' ||
|
|
plugin === 'jetpack-boost'
|
|
) !== undefined ||
|
|
context.pluginsAvailable.find(
|
|
( plugin: Extension ) =>
|
|
( plugin.key === 'jetpack' ||
|
|
plugin.key === 'jetpack-boost' ) &&
|
|
plugin.is_activated
|
|
) !== undefined
|
|
);
|
|
},
|
|
},
|
|
} );
|
|
}, [ actionOverrides, servicesOverrides ] );
|
|
|
|
const { xstateV5Inspector } = useXStateInspect( 'V5' );
|
|
|
|
const [ state, send, service ] = useMachine( augmentedStateMachine, {
|
|
inspect: xstateV5Inspector,
|
|
} );
|
|
|
|
// 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< ComponentMeta >(
|
|
currentState?.getMeta() ?? undefined
|
|
)
|
|
);
|
|
|
|
const navigationProgress = currentNodeMeta?.progress;
|
|
|
|
const currentNodeCssLabel =
|
|
state.value instanceof Object
|
|
? Object.keys( state.value )[ 0 ]
|
|
: state.value;
|
|
|
|
useFullScreen( [ 'woocommerce-profile-wizard__body' ] );
|
|
|
|
const [ CurrentComponent ] =
|
|
useComponentFromXStateService< CoreProfilerPageComponent >( service );
|
|
|
|
return (
|
|
<>
|
|
<div
|
|
className={ `woocommerce-profile-wizard__container woocommerce-profile-wizard__step-${ currentNodeCssLabel }` }
|
|
>
|
|
{ CurrentComponent ? (
|
|
<CurrentComponent
|
|
navigationProgress={ navigationProgress }
|
|
sendEvent={ send }
|
|
context={ state.context }
|
|
/>
|
|
) : (
|
|
<ProfileSpinner />
|
|
) }
|
|
</div>
|
|
</>
|
|
);
|
|
};
|
|
export default CoreProfilerController;
|