diff --git a/frontend/src/component/demo/Demo.tsx b/frontend/src/component/demo/Demo.tsx index cd2384f684..df0eaeb575 100644 --- a/frontend/src/component/demo/Demo.tsx +++ b/frontend/src/component/demo/Demo.tsx @@ -14,7 +14,8 @@ const defaultProgress = { welcomeOpen: true, expanded: true, topic: -1, - steps: [0], + step: 0, + stepsCompletion: Array(TOPICS.length).fill(0), }; interface IDemoProps { @@ -26,7 +27,7 @@ export const Demo = ({ children }: IDemoProps): JSX.Element => { const { trackEvent } = usePlausibleTracker(); const { value: storedProgress, setValue: setStoredProgress } = - createLocalStorage('Tutorial:v1', defaultProgress); + createLocalStorage('Tutorial:v1.1', defaultProgress); const [welcomeOpen, setWelcomeOpen] = useState( storedProgress.welcomeOpen ?? defaultProgress.welcomeOpen @@ -40,8 +41,11 @@ export const Demo = ({ children }: IDemoProps): JSX.Element => { const [topic, setTopic] = useState( storedProgress.topic ?? defaultProgress.topic ); - const [steps, setSteps] = useState( - storedProgress.steps ?? defaultProgress.steps + const [step, setStep] = useState( + storedProgress.step ?? defaultProgress.step + ); + const [stepsCompletion, setStepsCompletion] = useState( + storedProgress.stepsCompletion ?? defaultProgress.stepsCompletion ); useEffect(() => { @@ -49,32 +53,26 @@ export const Demo = ({ children }: IDemoProps): JSX.Element => { welcomeOpen, expanded, topic, - steps, + step, + stepsCompletion, }); - }, [welcomeOpen, expanded, topic, steps]); + }, [welcomeOpen, expanded, topic, step, stepsCompletion]); const onStart = () => { setTopic(0); - setSteps([0]); + setStep(0); + setStepsCompletion(Array(TOPICS.length).fill(0)); setExpanded(true); }; const onFinish = () => { - const completedSteps = steps.reduce( - (acc, step) => acc + (step || 0), - 1 - ); - const totalSteps = TOPICS.flatMap(({ steps }) => steps).length; + setFinishOpen(true); - if (completedSteps === totalSteps) { - setFinishOpen(true); - - trackEvent('demo', { - props: { - eventType: 'finish', - }, - }); - } + trackEvent('demo', { + props: { + eventType: 'finish', + }, + }); }; if (!uiConfig.flags.demo) return children; @@ -141,15 +139,11 @@ export const Demo = ({ children }: IDemoProps): JSX.Element => { { setTopic(topic); - setSteps(steps => { - const newSteps = [...steps]; - newSteps[topic] = 0; - return newSteps; - }); + setStep(0); trackEvent('demo', { props: { @@ -171,8 +165,10 @@ export const Demo = ({ children }: IDemoProps): JSX.Element => { /> ({ const StyledTooltip = styled('div')(({ theme }) => ({ '@keyframes pulse': { '0%': { - boxShadow: `0 0 0 0 ${alpha(theme.palette.primary.main, 0.7)}`, + boxShadow: `0 0 0 0 ${alpha(theme.palette.spotlight.pulse, 1)}`, }, '70%': { - boxShadow: `0 0 0 10px ${alpha(theme.palette.primary.main, 0)}`, + boxShadow: `0 0 0 16px ${alpha(theme.palette.spotlight.pulse, 0)}`, }, '100%': { - boxShadow: `0 0 0 0 ${alpha(theme.palette.primary.main, 0)}`, + boxShadow: `0 0 0 0 ${alpha(theme.palette.spotlight.pulse, 0)}`, }, }, position: 'relative', @@ -70,7 +70,7 @@ export interface IDemoStepTooltipProps extends TooltipRenderProps { step: ITutorialTopicStep; topic: number; topics: ITutorialTopic[]; - steps: number[]; + stepIndex: number; onClose: () => void; onBack: (step: ITutorialTopicStep) => void; onNext: (step: number) => void; @@ -81,7 +81,7 @@ export const DemoStepTooltip = ({ step, topic, topics, - steps, + stepIndex, onClose, onBack, onNext, @@ -95,6 +95,7 @@ export const DemoStepTooltip = ({ if (r !== 'backdropClick') onClose(); }} transitionDuration={0} + hideBackdrop > @@ -114,7 +115,7 @@ export const DemoStepTooltip = ({
0 || steps[topic] > 0} + condition={topic > 0 || stepIndex > 0} show={ diff --git a/frontend/src/component/demo/DemoSteps/DemoSteps.tsx b/frontend/src/component/demo/DemoSteps/DemoSteps.tsx index 86da1f0513..7443886363 100644 --- a/frontend/src/component/demo/DemoSteps/DemoSteps.tsx +++ b/frontend/src/component/demo/DemoSteps/DemoSteps.tsx @@ -12,8 +12,10 @@ import { usePlausibleTracker } from 'hooks/usePlausibleTracker'; interface IDemoStepsProps { setExpanded: React.Dispatch>; - steps: number[]; - setSteps: React.Dispatch>; + step: number; + setStep: React.Dispatch>; + stepsCompletion: number[]; + setStepsCompletion: React.Dispatch>; topic: number; setTopic: React.Dispatch>; topics: ITutorialTopic[]; @@ -22,8 +24,10 @@ interface IDemoStepsProps { export const DemoSteps = ({ setExpanded, - steps, - setSteps, + step, + setStep, + stepsCompletion, + setStepsCompletion, topic, setTopic, topics, @@ -40,14 +44,17 @@ export const DemoSteps = ({ const setTopicStep = (topic: number, step?: number) => { setRun(false); - setTopic(topic); if (step !== undefined) { - setSteps(steps => { - const newSteps = [...steps]; - newSteps[topic] = step; - return newSteps; - }); + if (stepsCompletion[topic] < step) { + setStepsCompletion(steps => { + const newSteps = [...steps]; + newSteps[topic] = step; + return newSteps; + }); + } + setStep(step); } + setTopic(topic); }; const close = () => { @@ -58,33 +65,41 @@ export const DemoSteps = ({ props: { eventType: 'close', topic: topics[topic].title, - step: steps[topic] + 1, + step: step + 1, }, }); }; const back = () => { setFlow('back'); - if (steps[topic] === 0) { + if (step === 0) { const newTopic = topic - 1; setTopicStep(newTopic, topics[newTopic].steps.length - 1); } else { - setTopicStep(topic, steps[topic] - 1); + setTopicStep(topic, step - 1); } }; const nextTopic = () => { - if (topic === topics.length - 1) { + const currentTopic = topic; + + const nextUnfinishedTopic = + topics.findIndex( + (topic, index) => + index !== currentTopic && + stepsCompletion[index] < topic.steps.length + ) ?? -1; + + if (nextUnfinishedTopic === -1) { setTopicStep(-1); setExpanded(false); onFinish(); } else { - const newTopic = topic + 1; - setTopicStep(newTopic, 0); + setTopicStep(nextUnfinishedTopic, 0); } }; - const next = (index = steps[topic]) => { + const next = (index = step) => { setFlow('next'); setTopicStep(topic, index + 1); if (index === topics[topic].steps.length - 1) { @@ -137,7 +152,12 @@ export const DemoSteps = ({ 'click', e => { const targetEl = e.target as HTMLElement; - if (!targetEl.closest('.__floater')) + if ( + !targetEl.closest('.__floater') && + !targetEl.className.includes( + 'react-joyride__overlay' + ) + ) clickHandler(e); }, { @@ -189,18 +209,18 @@ export const DemoSteps = ({ useEffect(() => { if (topic === -1) return; const currentTopic = topics[topic]; - const currentStepIndex = steps[topic]; - const currentStep = currentTopic.steps[currentStepIndex]; + const currentStep = currentTopic.steps[step]; if (!currentStep) return; if ( currentStep.href && - location.pathname !== currentStep.href.split('?')[0] + !location.pathname.endsWith(currentStep.href.split('?')[0]) ) { navigate(currentStep.href); } + waitForLoad(currentStep); - }, [topic, steps]); + }, [topic, step]); useEffect(() => { if (topic > -1) topics[topic].setup?.(); @@ -216,7 +236,7 @@ export const DemoSteps = ({ return ( ({ interface IDemoTopicsProps { expanded: boolean; setExpanded: React.Dispatch>; - steps: number[]; + stepsCompletion: number[]; currentTopic: number; setCurrentTopic: (topic: number) => void; topics: ITutorialTopic[]; @@ -154,13 +154,16 @@ interface IDemoTopicsProps { export const DemoTopics = ({ expanded, setExpanded, - steps, + stepsCompletion, currentTopic, setCurrentTopic, topics, onWelcome, }: IDemoTopicsProps) => { - const completedSteps = steps.reduce((acc, step) => acc + (step || 0), 0); + const completedSteps = stepsCompletion.reduce( + (acc, step) => acc + (step || 0), + 0 + ); const totalSteps = topics.flatMap(({ steps }) => steps).length; const percentage = (completedSteps / totalSteps) * 100; @@ -194,7 +197,8 @@ export const DemoTopics = ({ {topics.map((topic, index) => { const selected = currentTopic === index; - const completed = steps[index] === topic.steps.length; + const completed = + stepsCompletion[index] === topic.steps.length; return ( Save your strategy., + backCloseModal: true, + }, + { + target: 'button[data-testid="DIALOGUE_CONFIRM_ID"]', + content: Confirm your changes., + optional: true, + backCloseModal: true, + }, + { + href: `/projects/${PROJECT}?sort=name`, + target: `div[data-testid="TOGGLE-demoApp.step2-${ENVIRONMENT}"]`, content: ( <> - Save your strategy to apply it. + Finally, toggle{' '} + demoApp.step2 } > - Look at the demo page after saving! + Look at the demo page to see your changes! ), - backCloseModal: true, - }, - { - target: 'button[data-testid="DIALOGUE_CONFIRM_ID"]', - content: ( - <> - Confirm your changes. - } - > - Look at the demo page after saving! - - - ), - optional: true, - backCloseModal: true, + nextButton: true, }, ], }, @@ -332,35 +329,32 @@ export const TOPICS: ITutorialTopic[] = [ }, { target: 'button[data-testid="STRATEGY_FORM_SUBMIT_ID"]', + content: Save your strategy., + }, + { + target: 'button[data-testid="DIALOGUE_CONFIRM_ID"]', + content: Confirm your changes., + optional: true, + backCloseModal: true, + }, + { + href: `/projects/${PROJECT}?sort=name`, + target: `div[data-testid="TOGGLE-demoApp.step3-${ENVIRONMENT}"]`, content: ( <> - Save your strategy to apply it. + Finally, toggle{' '} + demoApp.step3 } > - Look at the demo page after saving! + Look at the demo page to see your changes! ), - }, - { - target: 'button[data-testid="DIALOGUE_CONFIRM_ID"]', - content: ( - <> - Confirm your changes. - } - > - Look at the demo page after saving! - - - ), - optional: true, - backCloseModal: true, + nextButton: true, }, ], }, @@ -507,19 +501,26 @@ export const TOPICS: ITutorialTopic[] = [ }, { target: 'button[data-testid="DIALOGUE_CONFIRM_ID"]', + content: Save your variants., + }, + { + href: `/projects/${PROJECT}?sort=name`, + target: `div[data-testid="TOGGLE-demoApp.step4-${ENVIRONMENT}"]`, content: ( <> - Save your variants to apply them. + Finally, toggle{' '} + demoApp.step4 } > - Look at the demo page after saving! + Look at the demo page to see your changes! ), + nextButton: true, }, ], }, diff --git a/frontend/src/themes/dark-theme.ts b/frontend/src/themes/dark-theme.ts index b3ad3cef50..28f9297667 100644 --- a/frontend/src/themes/dark-theme.ts +++ b/frontend/src/themes/dark-theme.ts @@ -193,6 +193,15 @@ const theme = { */ highlight: 'rgba(255, 234, 204, 0.7)', + /** + * Used for the interactive guide spotlight + */ + spotlight: { + border: '#8c89bf', + outline: '#bcb9f3', + pulse: '#bcb9f3', + }, + /** * Background color used for the API command in the sidebar */ @@ -268,6 +277,10 @@ export default createTheme({ // Skeleton MuiCssBaseline: { styleOverrides: { + '#react-joyride-portal ~ .MuiDialog-root': { + zIndex: 1500, + }, + '.skeleton': { '&::before': { backgroundColor: theme.palette.background.elevation1, diff --git a/frontend/src/themes/theme.ts b/frontend/src/themes/theme.ts index a0a7b3c790..0278125050 100644 --- a/frontend/src/themes/theme.ts +++ b/frontend/src/themes/theme.ts @@ -179,6 +179,15 @@ const theme = { */ highlight: colors.orange[200], + /** + * Used for the interactive guide spotlight + */ + spotlight: { + border: '#463cfb', + outline: '#6058f5', + pulse: '#463cfb', + }, + /** * Background color used for the API command in the sidebar */ @@ -254,6 +263,10 @@ export default createTheme({ // Skeleton MuiCssBaseline: { styleOverrides: { + '#react-joyride-portal ~ .MuiDialog-root': { + zIndex: 1500, + }, + '.skeleton': { '&::before': { backgroundColor: theme.palette.background.elevation1, diff --git a/frontend/src/themes/themeTypes.ts b/frontend/src/themes/themeTypes.ts index 20bb770315..5bde8f34f0 100644 --- a/frontend/src/themes/themeTypes.ts +++ b/frontend/src/themes/themeTypes.ts @@ -91,6 +91,15 @@ declare module '@mui/material/styles' { */ highlight: string; + /** + * Used for the interactive guide spotlight + */ + spotlight: { + border: string; + outline: string; + pulse: string; + }; + /** * For Links */