diff --git a/frontend/src/App/constants.ts b/frontend/src/App/constants.ts index 727fae1581..d861606c36 100644 --- a/frontend/src/App/constants.ts +++ b/frontend/src/App/constants.ts @@ -1,2 +1,3 @@ export const AUTH_DATA_STORAGE_KEY = 'authData'; export const MODE_STORAGE_KEY = 'mode'; +export const TUTORIAL_SHOW_STARTUP_STORAGE_KEY = 'tutorial-show-startup'; diff --git a/frontend/src/App/slice.ts b/frontend/src/App/slice.ts index bee2e4ecb9..dc53eef413 100644 --- a/frontend/src/App/slice.ts +++ b/frontend/src/App/slice.ts @@ -2,7 +2,7 @@ import type { RootState } from 'store'; import { applyMode, Mode } from '@cloudscape-design/global-styles'; import { createSlice, PayloadAction } from '@reduxjs/toolkit'; -import { AUTH_DATA_STORAGE_KEY, MODE_STORAGE_KEY } from './constants'; +import { AUTH_DATA_STORAGE_KEY, MODE_STORAGE_KEY, TUTORIAL_SHOW_STARTUP_STORAGE_KEY } from './constants'; import { getThemeMode } from './helpers'; import { IAppState, ToolsTabs } from './types'; @@ -10,6 +10,7 @@ import { IAppState, ToolsTabs } from './types'; const getInitialState = (): IAppState => { let authData = null; let storageData = null; + let hideStartUp: null | boolean = null; let activeMode = getThemeMode(); try { @@ -18,6 +19,18 @@ const getInitialState = (): IAppState => { console.log(e); } + try { + hideStartUp = (() => { + if (!localStorage.getItem(TUTORIAL_SHOW_STARTUP_STORAGE_KEY)) { + return null; + } + + return localStorage.getItem(TUTORIAL_SHOW_STARTUP_STORAGE_KEY) === 'true'; + })(); + } catch (e) { + console.log(e); + } + try { const modeStorageData = localStorage.getItem(MODE_STORAGE_KEY); @@ -53,6 +66,7 @@ const getInitialState = (): IAppState => { discordCompleted: false, tallyCompleted: false, quickStartCompleted: false, + hideStartUp, }, }; }; @@ -138,6 +152,19 @@ export const appSlice = createSlice({ ...action.payload, }; }, + + setHideAtStartup: (state, action: PayloadAction) => { + state.tutorialPanel = { + ...state.tutorialPanel, + hideStartUp: action.payload, + }; + + try { + localStorage.setItem(TUTORIAL_SHOW_STARTUP_STORAGE_KEY, JSON.stringify(action.payload)); + } catch (e) { + console.log(e); + } + }, }, }); @@ -152,6 +179,7 @@ export const { setToolsTab, openTutorialPanel, updateTutorialPanelState, + setHideAtStartup, } = appSlice.actions; export const selectUserData = (state: RootState) => state.app.userData; export const selectAuthToken = (state: RootState) => state.app.authData?.token; diff --git a/frontend/src/App/types.ts b/frontend/src/App/types.ts index 15a415e1db..6b6f87a4d0 100644 --- a/frontend/src/App/types.ts +++ b/frontend/src/App/types.ts @@ -37,5 +37,6 @@ export interface IAppState { discordCompleted: boolean; tallyCompleted: boolean; quickStartCompleted: boolean; + hideStartUp: boolean | null; }; } diff --git a/frontend/src/layouts/AppLayout/TutorialPanel/hooks.ts b/frontend/src/layouts/AppLayout/TutorialPanel/hooks.ts index 2cdf5e5ebf..911b7b1083 100644 --- a/frontend/src/layouts/AppLayout/TutorialPanel/hooks.ts +++ b/frontend/src/layouts/AppLayout/TutorialPanel/hooks.ts @@ -29,7 +29,7 @@ export const useTutorials = () => { const dispatch = useAppDispatch(); const { billingUrl } = useSideNavigation(); const useName = useAppSelector(selectUserName); - const { billingCompleted, configureCLICompleted, discordCompleted, tallyCompleted, quickStartCompleted } = + const { billingCompleted, configureCLICompleted, discordCompleted, tallyCompleted, quickStartCompleted, hideStartUp } = useAppSelector(selectTutorialPanel); const { data: userBillingData } = useGetUserBillingInfoQuery({ username: useName ?? '' }, { skip: !useName }); @@ -41,14 +41,19 @@ export const useTutorials = () => { useEffect(() => { if (userBillingData && runsData && !completeIsChecked.current) { - dispatch( - updateTutorialPanelState({ - billingCompleted: userBillingData.balance > 0, - configureCLICompleted: runsData.length > 0, - }), - ); - - if ((userBillingData.balance <= 0 || runsData.length === 0) && process.env.UI_VERSION === 'sky') { + const billingCompleted = userBillingData.balance > 0; + const configureCLICompleted = runsData.length > 0; + + let tempHideStartUp = hideStartUp; + + if (hideStartUp === null) { + tempHideStartUp = billingCompleted && configureCLICompleted; + } + + // Set hideStartUp without updating localstorage + dispatch(updateTutorialPanelState({ billingCompleted, configureCLICompleted, hideStartUp: tempHideStartUp })); + + if (!tempHideStartUp && process.env.UI_VERSION === 'sky') { dispatch(openTutorialPanel()); } diff --git a/frontend/src/layouts/AppLayout/TutorialPanel/index.tsx b/frontend/src/layouts/AppLayout/TutorialPanel/index.tsx index 1d20f341bd..9a614d14e4 100644 --- a/frontend/src/layouts/AppLayout/TutorialPanel/index.tsx +++ b/frontend/src/layouts/AppLayout/TutorialPanel/index.tsx @@ -1,6 +1,11 @@ -import React from 'react'; +import React, { useLayoutEffect, useRef } from 'react'; +import { createRoot, Root } from 'react-dom/client'; -import { TutorialPanel as TutorialPanelGeneric, TutorialPanelProps } from 'components'; +import { Box, Toggle, TutorialPanel as TutorialPanelGeneric, TutorialPanelProps } from 'components'; + +import { useAppDispatch, useAppSelector } from 'hooks'; + +import { selectTutorialPanel, setHideAtStartup } from 'App/slice'; import { tutorialPanelI18nStrings } from './constants'; import { useTutorials } from './hooks'; @@ -10,7 +15,40 @@ export interface Props extends Partial { } export const TutorialPanel: React.FC = () => { + const dispatch = useAppDispatch(); const { tutorials } = useTutorials(); + const tutorialRootRef = useRef(null); + const { hideStartUp } = useAppSelector(selectTutorialPanel); + + const onChangeShowStartUp = (value: boolean) => { + dispatch(setHideAtStartup(!value)); + }; + + const renderShowAtStartup = () => { + return ( + + onChangeShowStartUp(detail.checked)} checked={!hideStartUp}> + Show at startup + + + ); + }; + + useLayoutEffect(() => { + const tutorialPanelElement = document.querySelector('[class*="awsui_tutorial-panel"]'); + + if (tutorialPanelElement && !tutorialRootRef.current) { + const divElement = document.createElement('div'); + tutorialPanelElement.appendChild(divElement); + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + tutorialRootRef.current = createRoot(divElement); + } + + if (tutorialRootRef.current) { + tutorialRootRef.current.render(renderShowAtStartup()); + } + }, [hideStartUp]); return ; };