From 822d55eebcf1b875073bcc7d8275e3bd08388447 Mon Sep 17 00:00:00 2001 From: Oleg Vavilov Date: Tue, 17 Jun 2025 23:40:22 +0300 Subject: [PATCH 1/2] [UI] Allow to hide the Tour panel #286 --- frontend/src/App/constants.ts | 1 + frontend/src/App/slice.ts | 26 +++++++++++- frontend/src/App/types.ts | 1 + .../layouts/AppLayout/TutorialPanel/hooks.ts | 4 +- .../layouts/AppLayout/TutorialPanel/index.tsx | 42 ++++++++++++++++++- 5 files changed, 69 insertions(+), 5 deletions(-) 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..f2d7478eb0 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 showStartUp = true; let activeMode = getThemeMode(); try { @@ -18,6 +19,14 @@ const getInitialState = (): IAppState => { console.log(e); } + try { + showStartUp = + !localStorage.getItem(TUTORIAL_SHOW_STARTUP_STORAGE_KEY) || + localStorage.getItem(TUTORIAL_SHOW_STARTUP_STORAGE_KEY) === 'true'; + } catch (e) { + console.log(e); + } + try { const modeStorageData = localStorage.getItem(MODE_STORAGE_KEY); @@ -53,6 +62,7 @@ const getInitialState = (): IAppState => { discordCompleted: false, tallyCompleted: false, quickStartCompleted: false, + showStartUp, }, }; }; @@ -138,6 +148,19 @@ export const appSlice = createSlice({ ...action.payload, }; }, + + setShowAtStartup: (state, action: PayloadAction) => { + state.tutorialPanel = { + ...state.tutorialPanel, + showStartUp: action.payload, + }; + + try { + localStorage.setItem(TUTORIAL_SHOW_STARTUP_STORAGE_KEY, JSON.stringify(action.payload)); + } catch (e) { + console.log(e); + } + }, }, }); @@ -152,6 +175,7 @@ export const { setToolsTab, openTutorialPanel, updateTutorialPanelState, + setShowAtStartup, } = 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..e2b61c005e 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; + showStartUp: boolean; }; } diff --git a/frontend/src/layouts/AppLayout/TutorialPanel/hooks.ts b/frontend/src/layouts/AppLayout/TutorialPanel/hooks.ts index 2cdf5e5ebf..d718a1ff76 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, showStartUp } = useAppSelector(selectTutorialPanel); const { data: userBillingData } = useGetUserBillingInfoQuery({ username: useName ?? '' }, { skip: !useName }); @@ -48,7 +48,7 @@ export const useTutorials = () => { }), ); - if ((userBillingData.balance <= 0 || runsData.length === 0) && process.env.UI_VERSION === 'sky') { + if ((userBillingData.balance <= 0 || runsData.length === 0) && showStartUp && 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..aaed5396ab 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, setShowAtStartup } 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 { showStartUp } = useAppSelector(selectTutorialPanel); + + const onChangeShowStartUp = (value: boolean) => { + dispatch(setShowAtStartup(value)); + }; + + const renderShowAtStartup = () => { + return ( + + onChangeShowStartUp(detail.checked)} checked={showStartUp}> + 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()); + } + }, [showStartUp]); return ; }; From 68aa0231c1c985cbf099f220bdedd5d69354aa61 Mon Sep 17 00:00:00 2001 From: Oleg Vavilov Date: Wed, 25 Jun 2025 21:51:31 +0300 Subject: [PATCH 2/2] [UI] Allow to hide the Tour panel #286 Fixes after review --- frontend/src/App/slice.ts | 20 +++++++++------- frontend/src/App/types.ts | 2 +- .../layouts/AppLayout/TutorialPanel/hooks.ts | 23 +++++++++++-------- .../layouts/AppLayout/TutorialPanel/index.tsx | 10 ++++---- 4 files changed, 32 insertions(+), 23 deletions(-) diff --git a/frontend/src/App/slice.ts b/frontend/src/App/slice.ts index f2d7478eb0..dc53eef413 100644 --- a/frontend/src/App/slice.ts +++ b/frontend/src/App/slice.ts @@ -10,7 +10,7 @@ import { IAppState, ToolsTabs } from './types'; const getInitialState = (): IAppState => { let authData = null; let storageData = null; - let showStartUp = true; + let hideStartUp: null | boolean = null; let activeMode = getThemeMode(); try { @@ -20,9 +20,13 @@ const getInitialState = (): IAppState => { } try { - showStartUp = - !localStorage.getItem(TUTORIAL_SHOW_STARTUP_STORAGE_KEY) || - localStorage.getItem(TUTORIAL_SHOW_STARTUP_STORAGE_KEY) === 'true'; + 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); } @@ -62,7 +66,7 @@ const getInitialState = (): IAppState => { discordCompleted: false, tallyCompleted: false, quickStartCompleted: false, - showStartUp, + hideStartUp, }, }; }; @@ -149,10 +153,10 @@ export const appSlice = createSlice({ }; }, - setShowAtStartup: (state, action: PayloadAction) => { + setHideAtStartup: (state, action: PayloadAction) => { state.tutorialPanel = { ...state.tutorialPanel, - showStartUp: action.payload, + hideStartUp: action.payload, }; try { @@ -175,7 +179,7 @@ export const { setToolsTab, openTutorialPanel, updateTutorialPanelState, - setShowAtStartup, + 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 e2b61c005e..6b6f87a4d0 100644 --- a/frontend/src/App/types.ts +++ b/frontend/src/App/types.ts @@ -37,6 +37,6 @@ export interface IAppState { discordCompleted: boolean; tallyCompleted: boolean; quickStartCompleted: boolean; - showStartUp: boolean; + hideStartUp: boolean | null; }; } diff --git a/frontend/src/layouts/AppLayout/TutorialPanel/hooks.ts b/frontend/src/layouts/AppLayout/TutorialPanel/hooks.ts index d718a1ff76..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, showStartUp } = + 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) && showStartUp && 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 aaed5396ab..9a614d14e4 100644 --- a/frontend/src/layouts/AppLayout/TutorialPanel/index.tsx +++ b/frontend/src/layouts/AppLayout/TutorialPanel/index.tsx @@ -5,7 +5,7 @@ import { Box, Toggle, TutorialPanel as TutorialPanelGeneric, TutorialPanelProps import { useAppDispatch, useAppSelector } from 'hooks'; -import { selectTutorialPanel, setShowAtStartup } from 'App/slice'; +import { selectTutorialPanel, setHideAtStartup } from 'App/slice'; import { tutorialPanelI18nStrings } from './constants'; import { useTutorials } from './hooks'; @@ -18,16 +18,16 @@ export const TutorialPanel: React.FC = () => { const dispatch = useAppDispatch(); const { tutorials } = useTutorials(); const tutorialRootRef = useRef(null); - const { showStartUp } = useAppSelector(selectTutorialPanel); + const { hideStartUp } = useAppSelector(selectTutorialPanel); const onChangeShowStartUp = (value: boolean) => { - dispatch(setShowAtStartup(value)); + dispatch(setHideAtStartup(!value)); }; const renderShowAtStartup = () => { return ( - onChangeShowStartUp(detail.checked)} checked={showStartUp}> + onChangeShowStartUp(detail.checked)} checked={!hideStartUp}> Show at startup @@ -48,7 +48,7 @@ export const TutorialPanel: React.FC = () => { if (tutorialRootRef.current) { tutorialRootRef.current.render(renderShowAtStartup()); } - }, [showStartUp]); + }, [hideStartUp]); return ; };