Skip to content

Commit

Permalink
🔧📝🚚✔️ Refactor back and forward buttons in welcome modal, add tests, …
Browse files Browse the repository at this point in the history
…improve comments, move illustrations to different folder
  • Loading branch information
emilijadunoska committed Sep 4, 2024
1 parent 7365c3d commit c127bec
Show file tree
Hide file tree
Showing 24 changed files with 157 additions and 104 deletions.
13 changes: 13 additions & 0 deletions frontend/.env.template
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,16 @@

# This is for development only. For production you need to replace this with the actual URL.
VITE_API_URL=http://localhost:8000

# IDP Settings

# IDP URL
VITE_OAUTH_API_URL=https://lokiam.de

# IDP Client ID
# Dev only, change me for staging or production
VITE_OAUTH_CLIENT_ID=loki-front-dev

# IDP Redirect URL
# Dev only, change me for staging or production
VITE_OAUTH_REDIRECT_URL=http://localhost:5443
File renamed without changes
File renamed without changes
File renamed without changes
File renamed without changes
File renamed without changes
2 changes: 1 addition & 1 deletion frontend/docs/changelog/changelog-de.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ SPDX-License-Identifier: CC-BY-4.0

- Die Daten von Köln werden nun mit einer Auflösung in Stadtteilen angezeigt
- Onboarding-System für ESID. Es besteht aus:
- Willkommensmodal, das dem Benutzer beim ersten Besuch einen Überblick über die ESID anhand von Folien und Illustrationen zeigt
- Willkommensmodal, das dem Benutzer beim ersten Besuch einen Überblick über ESID anhand von Folien und Illustrationen zeigt
- Onboarding-Touren, die mit der React Joyride-Bibliothek erstellt wurden und auf die man durch Klicken auf die verschiedenen Tour-Chips in der Informationsschaltfläche in der oberen Leiste oder von der letzten Folie des Willkommensmodals aus zugreifen kann

### Verbesserungen
Expand Down
2 changes: 0 additions & 2 deletions frontend/locales/de-onboarding.json5
Original file line number Diff line number Diff line change
Expand Up @@ -120,8 +120,6 @@
spotlightClicks: true,
showProgress: true,
placement: 'left-start',
hideCloseButton: true,
hideFooter: true,
spotlightPadding: 50,
},
step4: {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// SPDX-FileCopyrightText: 2024 German Aerospace Center (DLR)
// SPDX-License-Identifier: Apache-2.0

import React from 'react';
import {describe, test, expect, vi} from 'vitest';
import {render, screen} from '@testing-library/react';
import {ThemeProvider} from '@mui/system';
import Theme from 'util/Theme';
import {Provider} from 'react-redux';
import {Store} from '../../../../store';
import StepButton from 'components/OnboardingComponents/WelcomeDialog/StepButton';

const handleClick = vi.fn();

const StepButtonTest = () => (
<ThemeProvider theme={Theme}>
<Provider store={Store}>
<StepButton direction={'next'} onClick={handleClick} disabled={false} />
</Provider>
</ThemeProvider>
);

describe('Step Button Component', () => {
test('renders the button', () => {
render(<StepButtonTest />);
expect(screen.getByRole('button')).toBeInTheDocument();
});
});
2 changes: 1 addition & 1 deletion frontend/src/components/MainContentTabs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import {selectTab} from '../store/UserPreferenceSlice';
import {useTheme} from '@mui/material/styles';

/*
* Lazy loading the components to improve the performance
* Lazy loading the components to improve the performance.
*/
const SimulationChart = React.lazy(() => import('./LineChartContainer'));
const ParameterEditor = React.lazy(() => import('./ParameterEditor'));
Expand Down
4 changes: 2 additions & 2 deletions frontend/src/components/OnboardingComponents/InfoButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import {setShowTooltip, setShowPopover} from '../../store/UserOnboardingSlice';
import TopBarPopover from './TopBarPopover';

/**
* this component is an information button in the top bar that opens a popover, which contains the onboarding tours which the user can take
* This component is an information button in the top bar that opens a popover, which contains the onboarding tours which the user can take.
*/
export default function InfoButton() {
const [anchorEl, setAnchorEl] = useState<null | HTMLElement>(null);
Expand All @@ -25,7 +25,7 @@ export default function InfoButton() {
const tours = useAppSelector((state) => state.userOnboarding.tours);

/**
* this use memo is to calculate the total number of tours and the number of completed tours for the popover progress bar
* This use memo is to calculate the total number of tours and the number of completed tours for the popover progress bar.
**/
const [totalTours, completedTours] = useMemo(() => {
const total = Object.keys(tours).length;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,13 +32,13 @@ interface TopBarPopoverProps {
}

/**
* This component is a popover that contains the onboarding tours which the user can take, it is rendered when the user clicks the information button in the top bar
* This component is a popover that contains the onboarding tours which the user can take, it is rendered when the user clicks the information button in the top bar.
*/
export default function TopBarPopover(props: TopBarPopoverProps): JSX.Element {
const {t: tOnboarding} = useTranslation('onboarding');

/**
* this use memo is to calculate the completion percentage of the tours for the progress bar
* This use memo is to calculate the completion percentage of the tours for the progress bar.
*/
const completionPercentage = useMemo(() => {
return props.totalTours > 0 ? (props.completedTours / props.totalTours) * 100 : 0;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,24 +7,24 @@ import {useTheme} from '@mui/material/styles';
import {TourType} from '../../../types/tours';

interface TourChipProps {
/** icon that is displayed on the left side of the chip */
/** Icon that is displayed on the left side of the chip. */
icon: React.ReactElement;

/** type of the tour that the chip represents */
/** Type of the tour that the chip represents. */
tourType: TourType;

/** label of the chip */
/** Label of the chip. */
label: string;

/** whether the tour is completed or not */
/** Whether the tour is completed or not. */
isCompleted: boolean;

/** function that is called when the chip is clicked */
/** Function that is called when the chip is clicked. */
onClick: (tour: TourType) => void;
}

/**
* This component is a chip that represents a tour that the user can take
* This component is a chip that represents a tour that the user can take.
*/
export default function TourChip(props: TourChipProps): JSX.Element {
const theme = useTheme();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import TourChip from './TourChip';
const TourSteps = lazy(() => import('./TourSteps'));

interface TourChipsProps {
/** the alignment of the chips, which varies between the components where they are rendered (welcome dialog and top bar popover). */
/** The alignment of the chips, which varies between the components where they are rendered (welcome dialog and top bar popover). */
align?: 'center' | 'left';
}

Expand All @@ -31,7 +31,7 @@ export default function TourChips({align = 'left'}: TourChipsProps): JSX.Element
const tours = useAppSelector((state) => state.userOnboarding.tours);

/**
* this is the memoized data for the tours that the user can take
* This is the memoized data for the tours that the user can take.
*/
const tourData = useMemo(
() => [
Expand All @@ -45,7 +45,7 @@ export default function TourChips({align = 'left'}: TourChipsProps): JSX.Element
);

/**
* this function is called when a tour is clicked and sets the current active tour
* This function is called when a tour is clicked and sets the current active tour.
*/
const onTourClick = useCallback(
(tour: TourType) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,17 +14,17 @@ import {DataSelection, selectDate, selectScenario} from '../../../store/DataSele
* Interface for the state of the tour steps
*/
interface State {
/**a flag to run or stop the tour */
/** A flag to run or stop the tour. */
run: boolean;
/**array of Step objects for the tour */
/** Array of Step objects for the tour. */
steps: Step[];
/**the current step index of the tour, keeping state of this value is necessary for joyride controlled (interactive) tours */
/** The current step index of the tour, keeping state of this value is necessary for joyride controlled (interactive) tours. */
stepIndex: number;
}

/**
* This component manages the joyride onboarding tour steps.
* To see debug messages in the console, set the debug flag to true in the joyride component below
* To see debug messages in the console, set the debug flag to true in the joyride component below.
*/
export default function TourSteps(): JSX.Element {
const [state, setState] = useState<State>({
Expand All @@ -33,7 +33,7 @@ export default function TourSteps(): JSX.Element {
stepIndex: 0,
});
const {run, steps, stepIndex} = state;
const [arePreferencesSaved, setArePreferencesSaved] = useState(false); // flag to indicate that the preferences are saved
const [arePreferencesSaved, setArePreferencesSaved] = useState(false); // Flag to indicate that the preferences are saved

const dispatch = useAppDispatch();
const theme = useTheme();
Expand All @@ -48,11 +48,11 @@ export default function TourSteps(): JSX.Element {
const dataSelection = useAppSelector((state) => state.dataSelection);
const scenarioList = useAppSelector((state) => state.scenarioList.scenarios);

const previousSimulationStart = useRef(simulationStart); // to keep track of the previous simulation start date for the scenario controlled tour
const savedUserDataSelection = useRef<null | DataSelection>(null); // to keep track of the original data selection before starting the tour
const previousSimulationStart = useRef(simulationStart); // To keep track of the previous simulation start date for the scenario controlled tour.
const savedUserDataSelection = useRef<null | DataSelection>(null); // To keep track of the original data selection before starting the tour.

/**
* this useMemo gets the localized tour steps and returns them as an array of step objects to use in the Joyride component
* This useMemo gets the localized tour steps and returns them as an array of step objects to use in the Joyride component.
*/
const localizedTourSteps: Step[] = useMemo(() => {
if (activeTour) {
Expand All @@ -65,30 +65,30 @@ export default function TourSteps(): JSX.Element {
}, [activeTour, tOnboarding]);

/**
* this effect saves the current data selection before starting the tour
* sets the simulation start date, and starts the tour once preferences are saved
* This effect saves the current data selection before starting the tour,
* sets the simulation start date, and starts the tour once preferences are saved.
*/
useEffect(() => {
if (activeTour && !showPopover && !showWelcomeDialog && !run) {
if (!savedUserDataSelection.current) {
savedUserDataSelection.current = dataSelection; // save the current data selection of the user so we can override it with different values during the tour
savedUserDataSelection.current = dataSelection; // Save the current data selection of the user so we can override it with different values during the tour

const maxDate = dataSelection.maxDate || '2024-07-08'; // get the maximum date from the data selection
dispatch(selectDate(maxDate)); // set the simulation start date (purple line) to the maximum date
const maxDate = dataSelection.maxDate || '2024-07-08'; // Get the maximum date from the data selection
dispatch(selectDate(maxDate)); // Set the simulation start date (purple line) to the maximum date

const firstScenarioId = Number(Object.keys(scenarioList)[0]); // we get the first scenario id (base scenario) from scenarioList
dispatch(selectScenario(firstScenarioId)); // dispatch the selectScenario action with the base scenario
const firstScenarioId = Number(Object.keys(scenarioList)[0]); // We get the first scenario id (base scenario) from scenarioList
dispatch(selectScenario(firstScenarioId)); // Dispatch the selectScenario action with the base scenario

setArePreferencesSaved(true); // flag to indicate that the preferences are saved
setArePreferencesSaved(true); // Flag to indicate that the preferences are saved
} else if (arePreferencesSaved) {
// starting the tour only after preferences are saved
// Starting the tour only after preferences are saved
setState((prevState) => ({
...prevState,
run: true,
steps: localizedTourSteps,
stepIndex: 0,
}));
setArePreferencesSaved(false); // reset the flag after the tour starts to prevent re-triggering
setArePreferencesSaved(false); // Reset the flag after the tour starts to prevent re-triggering
}
}
}, [
Expand All @@ -104,7 +104,7 @@ export default function TourSteps(): JSX.Element {
]);

/**
* this effect ensures that the tour is no longer active if the user closes the filter dialog during the execution of the tour
* This effect ensures that the tour is no longer active if the user closes the filter dialog during the execution of the tour.
*/
useEffect(() => {
if (activeTour === 'filter' && !isFilterDialogOpen && run && stepIndex > 0) {
Expand All @@ -114,7 +114,7 @@ export default function TourSteps(): JSX.Element {
}, [isFilterDialogOpen, activeTour, run, stepIndex, dispatch]);

/**
* this effect ensures that the correct tab is selected when the user clicks on a tour
* This effect ensures that the correct tab is selected when the user clicks on a tour.
*/
useEffect(() => {
if (activeTour) {
Expand All @@ -127,8 +127,8 @@ export default function TourSteps(): JSX.Element {
}, [activeTour, selectedTab, dispatch]);

/**
* this effect manages the controlled tour of the filter dialog,
* for joyride controlled tours, manual control with step index is required to make sure the tour is executed correctly
* This effect manages the controlled tour of the filter dialog.
* For joyride controlled tours, manual control with step index is required to make sure the tour is executed correctly.
*/
useEffect(() => {
if (activeTour === 'filter' && isFilterDialogOpen && stepIndex === 0) {
Expand All @@ -140,8 +140,8 @@ export default function TourSteps(): JSX.Element {
}, [activeTour, isFilterDialogOpen, stepIndex]);

/**
* this effect manages the 3rd step of the controlled scenario tour, which is to set the simulation start date to a specific date
* when the simulation start date is chosen, the tour should proceed to the next step
* This effect manages the 3rd step of the controlled scenario tour, which is to set the simulation start date to a specific date.
* When the simulation start date is chosen, the tour should proceed to the next step.
*/
useEffect(() => {
if (activeTour === 'scenario' && stepIndex === 2) {
Expand All @@ -156,38 +156,38 @@ export default function TourSteps(): JSX.Element {
}, [simulationStart, activeTour, stepIndex]);

/**
* this function handles the callback events from the Joyride component
* This function handles the callback events from the Joyride component.
*/
const handleJoyrideCallback = (data: CallBackProps) => {
const {action, index, status, type} = data;

// if the tour is finished, skipped or closed
// If the tour is finished, skipped or closed
if (
([STATUS.FINISHED, STATUS.SKIPPED] as string[]).includes(status) ||
action === ACTIONS.CLOSE ||
action === ACTIONS.STOP
) {
dispatch(selectDate(savedUserDataSelection.current?.date || '2024-07-08')); // restore the original date in the data selection
dispatch(selectScenario(savedUserDataSelection.current?.scenario || 0)); // restore the original selected scenario in the data selection
savedUserDataSelection.current = null; // reset the saved preferences after the tour is completed
dispatch(selectDate(savedUserDataSelection.current?.date || '2024-07-08')); // Restore the original date in the data selection
dispatch(selectScenario(savedUserDataSelection.current?.scenario || 0)); // Restore the original selected scenario in the data selection
savedUserDataSelection.current = null; // Reset the saved preferences after the tour is completed

// if the tour was finished and not skipped, mark as completed
// If the tour was finished and not skipped, mark as completed
if (status === STATUS.FINISHED && activeTour) {
dispatch(setTourCompleted({tour: activeTour, completed: true}));
}

// we reset the tour state so we can restart the tour again if the user clicks again
// We reset the tour state so we can restart the tour again if the user clicks again
dispatch(setActiveTour(null));
setState({run: false, steps: [], stepIndex: 0});

// this is to close the popover after the filter tour is completed
// This is to close the popover after the filter tour is completed
if (activeTour !== 'filter') {
dispatch(setShowPopover(true));
}

return;
}
// when next or back button is clicked
// When next or back button is clicked
if (type === EVENTS.STEP_AFTER || type === EVENTS.TARGET_NOT_FOUND) {
const nextStepIndex = action === ACTIONS.PREV ? index - 1 : index + 1;
setState((prevState) => ({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,21 +10,21 @@ import {useTheme} from '@mui/material/styles';
import {Trans} from 'react-i18next/TransWithoutContext';

interface SlideProps {
/** the current step of the dialog */
/** The current step of the dialog */
step: number;

/** the title of the slide */
/** The title of the slide */
title: string;

/** the content of the slide */
/** The content of the slide */
content: string;

/** the image source for the slide */
/** The image source for the slide */
imageSrc: string;
}

/**
* This component represents a slide in the welcome modal
* This component represents a slide in the welcome modal.
*/
export default function Slide({step, title, content, imageSrc}: SlideProps): JSX.Element {
const theme = useTheme();
Expand Down
Loading

0 comments on commit c127bec

Please sign in to comment.