CYS - AI Offline flow (#41656)
This commit is contained in:
parent
d8b2663def
commit
212c5bca3e
|
@ -154,6 +154,13 @@ export const AssemblerHub: CustomizeStoreComponent = ( props ) => {
|
|||
useEffect( () => {
|
||||
onBackButtonClicked( () => setShowExitModal( true ) );
|
||||
}, [] );
|
||||
// @ts-expect-error temp fix
|
||||
// Since we load the assember hub in an iframe, we don't have access to
|
||||
// xstate's context values.
|
||||
// This is the best workaround I can think of for now.
|
||||
// Set the aiOnline value from the parent window so that any child components
|
||||
// can access it.
|
||||
props.context.aiOnline = window.parent?.window.cys_aiOnline;
|
||||
|
||||
return (
|
||||
<>
|
||||
|
|
|
@ -2,9 +2,15 @@
|
|||
* External dependencies
|
||||
*/
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { useState } from '@wordpress/element';
|
||||
import { useContext, 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 = {
|
||||
|
@ -23,7 +29,47 @@ export const OnboardingTour = ( {
|
|||
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 }
|
||||
>
|
||||
<p>
|
||||
{ __(
|
||||
"We encountered some issues while generating content with AI. But don't worry — you can still customize the look and feel of your store, including adding your logo, and changing colors and layouts. 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={ {
|
||||
|
@ -87,15 +133,9 @@ export const OnboardingTour = ( {
|
|||
closeHandler: ( _steps, _currentStepIndex, source ) => {
|
||||
if ( source === 'done-btn' ) {
|
||||
// Click on "Take a tour" button
|
||||
recordEvent(
|
||||
'customize_your_store_assembler_hub_tour_start'
|
||||
);
|
||||
setShowWelcomeTour( false );
|
||||
takeTour();
|
||||
} else {
|
||||
recordEvent(
|
||||
'customize_your_store_assembler_hub_tour_skip'
|
||||
);
|
||||
onClose();
|
||||
skipTour();
|
||||
}
|
||||
},
|
||||
} }
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { createContext } from '@wordpress/element';
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import { recordEvent } from '@woocommerce/tracks';
|
||||
|
||||
|
@ -10,6 +11,13 @@ import { recordEvent } from '@woocommerce/tracks';
|
|||
import { OnboardingTour } from '../index';
|
||||
|
||||
jest.mock( '@woocommerce/tracks', () => ( { recordEvent: jest.fn() } ) );
|
||||
jest.mock( '../../', () => ( {
|
||||
CustomizeStoreContext: createContext( {
|
||||
context: {
|
||||
aiOnline: true,
|
||||
},
|
||||
} ),
|
||||
} ) );
|
||||
|
||||
describe( 'OnboardingTour', () => {
|
||||
let props: {
|
||||
|
|
|
@ -664,6 +664,21 @@
|
|||
box-shadow: none;
|
||||
}
|
||||
|
||||
&.ai-offline {
|
||||
.woocommerce-tour-kit-step {
|
||||
width: 430px;
|
||||
.components-card__header {
|
||||
background-image: none;
|
||||
height: auto;
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.tour-kit-frame__container {
|
||||
bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.woocommerce-tour-kit-step {
|
||||
width: 335px;
|
||||
border-radius: 8px;
|
||||
|
|
|
@ -8,7 +8,10 @@ import { AnyInterpreter, Sender } from 'xstate';
|
|||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { CustomizeStoreComponent } from '../types';
|
||||
import {
|
||||
CustomizeStoreComponent,
|
||||
customizeStoreStateMachineContext,
|
||||
} from '../types';
|
||||
import { designWithAiStateMachineDefinition } from './state-machine';
|
||||
import { findComponentMeta } from '~/utils/xstate/find-component';
|
||||
import {
|
||||
|
@ -33,10 +36,15 @@ export type DesignWithAiComponentMeta = {
|
|||
|
||||
export const DesignWithAiController = ( {
|
||||
parentMachine,
|
||||
parentContext,
|
||||
}: {
|
||||
parentMachine?: AnyInterpreter;
|
||||
sendEventToParent?: Sender< customizeStoreStateMachineEvents >;
|
||||
parentContext?: customizeStoreStateMachineContext;
|
||||
} ) => {
|
||||
// Assign aiOnline value from the parent context if it exists. Otherwise, ai is online by default.
|
||||
designWithAiStateMachineDefinition.context.aiOnline =
|
||||
parentContext?.aiOnline ?? true;
|
||||
const [ state, send, service ] = useMachine(
|
||||
designWithAiStateMachineDefinition,
|
||||
{
|
||||
|
@ -84,10 +92,16 @@ export const DesignWithAiController = ( {
|
|||
};
|
||||
|
||||
//loader should send event 'THEME_SUGGESTED' when it's done
|
||||
export const DesignWithAi: CustomizeStoreComponent = ( { parentMachine } ) => {
|
||||
export const DesignWithAi: CustomizeStoreComponent = ( {
|
||||
parentMachine,
|
||||
context,
|
||||
} ) => {
|
||||
return (
|
||||
<>
|
||||
<DesignWithAiController parentMachine={ parentMachine } />
|
||||
<DesignWithAiController
|
||||
parentMachine={ parentMachine }
|
||||
parentContext={ context }
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -205,7 +205,7 @@ export const updateStorePatterns = async (
|
|||
try {
|
||||
// TODO: Probably move this to a more appropriate place with a check. We should set this when the user granted permissions during the onboarding phase.
|
||||
await dispatch( OPTIONS_STORE_NAME ).updateOptions( {
|
||||
woocommerce_blocks_allow_ai_connection: true,
|
||||
woocommerce_blocks_allow_ai_connection: 'yes',
|
||||
} );
|
||||
|
||||
const { images } = await apiFetch< {
|
||||
|
@ -473,6 +473,17 @@ const saveAiResponseToOption = ( context: designWithAiStateMachineContext ) => {
|
|||
} );
|
||||
};
|
||||
|
||||
const resetPatterns = () => async () => {
|
||||
await dispatch( OPTIONS_STORE_NAME ).updateOptions( {
|
||||
woocommerce_blocks_allow_ai_connection: 'yes',
|
||||
} );
|
||||
|
||||
return await apiFetch( {
|
||||
path: '/wc/private/ai/patterns',
|
||||
method: 'DELETE',
|
||||
} );
|
||||
};
|
||||
|
||||
export const services = {
|
||||
browserPopstateHandler,
|
||||
queryAiEndpoint,
|
||||
|
@ -480,4 +491,5 @@ export const services = {
|
|||
updateStorePatterns,
|
||||
saveAiResponseToOption,
|
||||
installAndActivateTheme,
|
||||
resetPatterns,
|
||||
};
|
||||
|
|
|
@ -38,6 +38,10 @@ export const hasStepInUrl = (
|
|||
);
|
||||
};
|
||||
|
||||
export const isAiOnline = ( _ctx: designWithAiStateMachineContext ) => {
|
||||
return _ctx.aiOnline;
|
||||
};
|
||||
|
||||
export const designWithAiStateMachineDefinition = createMachine(
|
||||
{
|
||||
id: 'designWithAi',
|
||||
|
@ -84,6 +88,7 @@ export const designWithAiStateMachineDefinition = createMachine(
|
|||
apiCallLoader: {
|
||||
hasErrors: false,
|
||||
},
|
||||
aiOnline: true,
|
||||
},
|
||||
initial: 'navigate',
|
||||
states: {
|
||||
|
@ -272,8 +277,19 @@ export const designWithAiStateMachineDefinition = createMachine(
|
|||
type: 'parallel',
|
||||
states: {
|
||||
chooseColorPairing: {
|
||||
initial: 'pending',
|
||||
initial: 'executeOrSkip',
|
||||
states: {
|
||||
executeOrSkip: {
|
||||
always: [
|
||||
{
|
||||
target: 'pending',
|
||||
cond: 'isAiOnline',
|
||||
},
|
||||
{
|
||||
target: 'success',
|
||||
},
|
||||
],
|
||||
},
|
||||
pending: {
|
||||
invoke: {
|
||||
src: 'queryAiEndpoint',
|
||||
|
@ -307,8 +323,19 @@ export const designWithAiStateMachineDefinition = createMachine(
|
|||
},
|
||||
},
|
||||
chooseFontPairing: {
|
||||
initial: 'pending',
|
||||
initial: 'executeOrSkip',
|
||||
states: {
|
||||
executeOrSkip: {
|
||||
always: [
|
||||
{
|
||||
target: 'pending',
|
||||
cond: 'isAiOnline',
|
||||
},
|
||||
{
|
||||
target: 'success',
|
||||
},
|
||||
],
|
||||
},
|
||||
pending: {
|
||||
entry: [ 'assignFontPairing' ],
|
||||
always: {
|
||||
|
@ -319,8 +346,19 @@ export const designWithAiStateMachineDefinition = createMachine(
|
|||
},
|
||||
},
|
||||
updateStorePatterns: {
|
||||
initial: 'pending',
|
||||
initial: 'executeOrSkip',
|
||||
states: {
|
||||
executeOrSkip: {
|
||||
always: [
|
||||
{
|
||||
target: 'pending',
|
||||
cond: 'isAiOnline',
|
||||
},
|
||||
{
|
||||
target: 'resetPatterns',
|
||||
},
|
||||
],
|
||||
},
|
||||
pending: {
|
||||
invoke: {
|
||||
src: 'updateStorePatterns',
|
||||
|
@ -335,6 +373,20 @@ export const designWithAiStateMachineDefinition = createMachine(
|
|||
},
|
||||
},
|
||||
},
|
||||
resetPatterns: {
|
||||
invoke: {
|
||||
src: 'resetPatterns',
|
||||
onDone: {
|
||||
target: 'success',
|
||||
},
|
||||
onError: {
|
||||
actions: [
|
||||
'assignAPICallLoaderError',
|
||||
],
|
||||
target: '#toneOfVoice',
|
||||
},
|
||||
},
|
||||
},
|
||||
success: { type: 'final' },
|
||||
},
|
||||
},
|
||||
|
@ -429,6 +481,7 @@ export const designWithAiStateMachineDefinition = createMachine(
|
|||
services,
|
||||
guards: {
|
||||
hasStepInUrl,
|
||||
isAiOnline,
|
||||
},
|
||||
}
|
||||
);
|
||||
|
|
|
@ -39,6 +39,7 @@ export type designWithAiStateMachineContext = {
|
|||
// If we require more data from options, previously provided core profiler details,
|
||||
// we can retrieve them in preBusinessInfoDescription and then assign them here
|
||||
spawnSaveDescriptionToOptionRef?: ReturnType< typeof spawn >;
|
||||
aiOnline: boolean;
|
||||
};
|
||||
export type designWithAiStateMachineEvents =
|
||||
| { type: 'AI_WIZARD_CLOSED_BEFORE_COMPLETION'; payload: { step: string } }
|
||||
|
|
|
@ -160,6 +160,7 @@ export const customizeStoreStateMachineDefinition = createMachine( {
|
|||
transitionalScreen: {
|
||||
hasCompleteSurvey: false,
|
||||
},
|
||||
aiOnline: true,
|
||||
} as customizeStoreStateMachineContext,
|
||||
invoke: {
|
||||
src: 'browserPopstateHandler',
|
||||
|
@ -214,22 +215,54 @@ export const customizeStoreStateMachineDefinition = createMachine( {
|
|||
initial: 'preIntro',
|
||||
states: {
|
||||
preIntro: {
|
||||
invoke: {
|
||||
src: 'fetchIntroData',
|
||||
onError: {
|
||||
actions: 'assignFetchIntroDataError',
|
||||
target: 'intro',
|
||||
type: 'parallel',
|
||||
states: {
|
||||
checkAiStatus: {
|
||||
initial: 'pending',
|
||||
states: {
|
||||
pending: {
|
||||
invoke: {
|
||||
src: 'fetchAiStatus',
|
||||
onDone: {
|
||||
actions: 'assignAiStatus',
|
||||
target: 'success',
|
||||
},
|
||||
onError: {
|
||||
actions: 'assignAiOffline',
|
||||
target: 'success',
|
||||
},
|
||||
},
|
||||
},
|
||||
success: { type: 'final' },
|
||||
},
|
||||
},
|
||||
onDone: {
|
||||
target: 'intro',
|
||||
actions: [
|
||||
'assignThemeData',
|
||||
'assignActiveThemeHasMods',
|
||||
'assignCustomizeStoreCompleted',
|
||||
'assignCurrentThemeIsAiGenerated',
|
||||
],
|
||||
fetchIntroData: {
|
||||
initial: 'pending',
|
||||
states: {
|
||||
pending: {
|
||||
invoke: {
|
||||
src: 'fetchIntroData',
|
||||
onError: {
|
||||
actions:
|
||||
'assignFetchIntroDataError',
|
||||
target: 'success',
|
||||
},
|
||||
onDone: {
|
||||
target: 'success',
|
||||
actions: [
|
||||
'assignThemeData',
|
||||
'assignActiveThemeHasMods',
|
||||
'assignCustomizeStoreCompleted',
|
||||
'assignCurrentThemeIsAiGenerated',
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
success: { type: 'final' },
|
||||
},
|
||||
},
|
||||
},
|
||||
onDone: 'intro',
|
||||
},
|
||||
intro: {
|
||||
meta: {
|
||||
|
@ -285,8 +318,21 @@ export const customizeStoreStateMachineDefinition = createMachine( {
|
|||
},
|
||||
},
|
||||
assemblerHub: {
|
||||
initial: 'assemblerHub',
|
||||
initial: 'checkAiStatus',
|
||||
states: {
|
||||
checkAiStatus: {
|
||||
invoke: {
|
||||
src: 'fetchAiStatus',
|
||||
onDone: {
|
||||
actions: 'assignAiStatus',
|
||||
target: 'assemblerHub',
|
||||
},
|
||||
onError: {
|
||||
actions: 'assignAiOffline',
|
||||
target: 'assemblerHub',
|
||||
},
|
||||
},
|
||||
},
|
||||
assemblerHub: {
|
||||
entry: [
|
||||
{ type: 'updateQueryStep', step: 'assembler-hub' },
|
||||
|
@ -388,6 +434,12 @@ export const CustomizeStoreController = ( {
|
|||
( cond as { step: string | undefined } ).step
|
||||
);
|
||||
},
|
||||
isAiOnline: ( _ctx ) => {
|
||||
return _ctx.aiOnline;
|
||||
},
|
||||
isAiOffline: ( _ctx ) => {
|
||||
return ! _ctx.aiOnline;
|
||||
},
|
||||
},
|
||||
} );
|
||||
}, [ actionOverrides, servicesOverrides ] );
|
||||
|
|
|
@ -9,6 +9,7 @@ import { recordEvent } from '@woocommerce/tracks';
|
|||
*/
|
||||
import { customizeStoreStateMachineEvents } from '..';
|
||||
import {
|
||||
aiStatusResponse,
|
||||
customizeStoreStateMachineContext,
|
||||
RecommendThemesAPIResponse,
|
||||
} from '../types';
|
||||
|
@ -103,3 +104,37 @@ export const assignCurrentThemeIsAiGenerated = assign<
|
|||
return { ...context.intro, currentThemeIsAiGenerated };
|
||||
},
|
||||
} );
|
||||
|
||||
export const assignAiStatus = assign<
|
||||
customizeStoreStateMachineContext,
|
||||
customizeStoreStateMachineEvents // this is actually the wrong type for the event but I still don't know how to type this properly
|
||||
>( {
|
||||
aiOnline: ( _context, _event ) => {
|
||||
const indicator = ( _event as DoneInvokeEvent< aiStatusResponse > ).data
|
||||
.status.indicator;
|
||||
const status = indicator !== 'critical' && indicator !== 'major';
|
||||
// @ts-expect-error temp workaround;
|
||||
window.cys_aiOnline = status;
|
||||
|
||||
recordEvent( 'customize_your_store_ai_status', {
|
||||
online: status ? 'yes' : 'no',
|
||||
} );
|
||||
|
||||
return status;
|
||||
},
|
||||
} );
|
||||
|
||||
export const assignAiOffline = assign<
|
||||
customizeStoreStateMachineContext,
|
||||
customizeStoreStateMachineEvents // this is actually the wrong type for the event but I still don't know how to type this properly
|
||||
>( {
|
||||
aiOnline: () => {
|
||||
// @ts-expect-error temp workaround;
|
||||
window.cys_aiOnline = false;
|
||||
recordEvent( 'customize_your_store_ai_status', {
|
||||
online: 'no',
|
||||
} );
|
||||
|
||||
return false;
|
||||
},
|
||||
} );
|
||||
|
|
|
@ -307,38 +307,3 @@
|
|||
padding: 0.5rem 0.75rem;
|
||||
}
|
||||
}
|
||||
|
||||
.woocommerce-customize-store__design-change-warning-modal {
|
||||
width: 480px;
|
||||
max-width: 480px;
|
||||
|
||||
.components-modal__header {
|
||||
padding-top: 32px;
|
||||
}
|
||||
|
||||
p {
|
||||
padding: 16px 0 16px 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
a,
|
||||
button {
|
||||
text-decoration: none !important;
|
||||
}
|
||||
|
||||
.components-button {
|
||||
padding: 8px 16px;
|
||||
}
|
||||
|
||||
h1 {
|
||||
line-height: 28px;
|
||||
font-size: 20px;
|
||||
color: #1e1e1e;
|
||||
}
|
||||
|
||||
.woocommerce-customize-store__design-change-warning-modal-footer {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,6 +8,19 @@ import { resolveSelect } from '@wordpress/data';
|
|||
import { ONBOARDING_STORE_NAME, OPTIONS_STORE_NAME } from '@woocommerce/data';
|
||||
import apiFetch from '@wordpress/api-fetch';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { aiStatusResponse } from '../types';
|
||||
|
||||
export const fetchAiStatus = () => async (): Promise< aiStatusResponse > => {
|
||||
const response = await fetch(
|
||||
'https://status.openai.com/api/v2/status.json'
|
||||
);
|
||||
const data = await response.json();
|
||||
return data;
|
||||
};
|
||||
|
||||
export const fetchThemeCards = async () => {
|
||||
const themes = await apiFetch( {
|
||||
path: '/wc-admin/onboarding/themes/recommended',
|
||||
|
|
|
@ -44,6 +44,7 @@ describe( 'Intro Banners', () => {
|
|||
transitionalScreen: {
|
||||
hasCompleteSurvey: false,
|
||||
},
|
||||
aiOnline: true,
|
||||
} }
|
||||
currentState={ 'intro' }
|
||||
parentMachine={ null as unknown as AnyInterpreter }
|
||||
|
@ -81,6 +82,7 @@ describe( 'Intro Banners', () => {
|
|||
transitionalScreen: {
|
||||
hasCompleteSurvey: false,
|
||||
},
|
||||
aiOnline: true,
|
||||
} }
|
||||
currentState={ 'intro' }
|
||||
parentMachine={ null as unknown as AnyInterpreter }
|
||||
|
@ -124,6 +126,7 @@ describe( 'Intro Banners', () => {
|
|||
transitionalScreen: {
|
||||
hasCompleteSurvey: false,
|
||||
},
|
||||
aiOnline: true,
|
||||
} }
|
||||
currentState={ 'intro' }
|
||||
parentMachine={ null as unknown as AnyInterpreter }
|
||||
|
|
|
@ -42,6 +42,7 @@ describe( 'Intro Modals', () => {
|
|||
transitionalScreen: {
|
||||
hasCompleteSurvey: false,
|
||||
},
|
||||
aiOnline: true,
|
||||
} }
|
||||
currentState={ 'intro' }
|
||||
parentMachine={ null as unknown as AnyInterpreter }
|
||||
|
@ -96,6 +97,7 @@ describe( 'Intro Modals', () => {
|
|||
transitionalScreen: {
|
||||
hasCompleteSurvey: false,
|
||||
},
|
||||
aiOnline: true,
|
||||
} }
|
||||
currentState={ 'intro' }
|
||||
parentMachine={ null as unknown as AnyInterpreter }
|
||||
|
@ -148,6 +150,7 @@ describe( 'Intro Modals', () => {
|
|||
transitionalScreen: {
|
||||
hasCompleteSurvey: false,
|
||||
},
|
||||
aiOnline: true,
|
||||
} }
|
||||
currentState={ 'intro' }
|
||||
parentMachine={ null as unknown as AnyInterpreter }
|
||||
|
|
|
@ -180,3 +180,39 @@ body.woocommerce-customize-store.js.is-fullscreen-mode {
|
|||
transition: opacity 1.2s linear;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.woocommerce-customize-store__onboarding-welcome-modal,
|
||||
.woocommerce-customize-store__design-change-warning-modal {
|
||||
width: 480px;
|
||||
max-width: 480px;
|
||||
|
||||
.components-modal__header {
|
||||
padding-top: 32px;
|
||||
}
|
||||
|
||||
p {
|
||||
padding: 16px 0 16px 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
a,
|
||||
button {
|
||||
text-decoration: none !important;
|
||||
}
|
||||
|
||||
.components-button {
|
||||
padding: 8px 16px;
|
||||
}
|
||||
|
||||
h1 {
|
||||
line-height: 28px;
|
||||
font-size: 20px;
|
||||
color: #1e1e1e;
|
||||
}
|
||||
|
||||
.woocommerce-customize-store__design-change-warning-modal-footer {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -29,6 +29,12 @@ export type RecommendThemesAPIResponse = {
|
|||
};
|
||||
};
|
||||
|
||||
export type aiStatusResponse = {
|
||||
status: {
|
||||
indicator: 'major' | 'critical' | 'ok';
|
||||
};
|
||||
};
|
||||
|
||||
export type customizeStoreStateMachineContext = {
|
||||
themeConfiguration: Record< string, unknown >; // placeholder for theme configuration until we know what it looks like
|
||||
intro: {
|
||||
|
@ -42,4 +48,5 @@ export type customizeStoreStateMachineContext = {
|
|||
transitionalScreen: {
|
||||
hasCompleteSurvey: boolean;
|
||||
};
|
||||
aiOnline: boolean;
|
||||
};
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
Significance: minor
|
||||
Type: update
|
||||
|
||||
Tweaks for the CYS when the A.I is offline.
|
Loading…
Reference in New Issue