Skip to content

Commit 27aa1b5

Browse files
fix: use legacy router as it is done everywhere else (#132)
* fix: use legacy router as it is done everywhere else Signed-off-by: Joris Mancini <[email protected]>.com> Signed-off-by: Joris Mancini <[email protected]> * fix: test Signed-off-by: Joris Mancini <[email protected]>.com> Signed-off-by: Joris Mancini <[email protected]> --------- Signed-off-by: Joris Mancini <[email protected]>.com> Signed-off-by: Joris Mancini <[email protected]>
1 parent b7a19fa commit 27aa1b5

20 files changed

+342
-511
lines changed

jest.config.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import type { Config } from 'jest';
1010
const config: Config = {
1111
testEnvironment: 'jsdom',
1212
moduleNameMapper: {
13-
'^.+\\.svg\\?react$': 'jest-svg-transformer',
13+
'^.+\\.svg\\?react$': '<rootDir>/src/_mocks_/svg.tsx',
1414
'^.+\\.(css|less|scss)$': 'identity-obj-proxy',
1515
},
1616
// see https://github.com/react-dnd/react-dnd/issues/3443
Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,13 @@
1-
/*
1+
/**
22
* Copyright (c) 2024, RTE (http://www.rte-france.com)
33
* This Source Code Form is subject to the terms of the Mozilla Public
44
* License, v. 2.0. If a copy of the MPL was not distributed with this
55
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
66
*/
77

8-
export * from './router';
8+
export default function fetchMock() {
9+
return Promise.resolve({
10+
ok: true,
11+
json: () => ({ appsMetadataServerUrl: '' }), // just to remove the error logs when fetching env
12+
});
13+
}
Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
1-
/*
1+
/**
22
* Copyright (c) 2024, RTE (http://www.rte-france.com)
33
* This Source Code Form is subject to the terms of the Mozilla Public
44
* License, v. 2.0. If a copy of the MPL was not distributed with this
55
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
66
*/
77

8-
import AppComponent from './app';
9-
export type App = typeof AppComponent;
8+
import { forwardRef, SVGProps } from 'react';
109

11-
export { AppWrapper } from './app-wrapper';
10+
const SvgrMock = forwardRef<SVGSVGElement, SVGProps<SVGSVGElement>>((props, ref) => <svg ref={ref} {...props} />);
11+
12+
export default SvgrMock;

src/components/App/App.tsx

Lines changed: 205 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,205 @@
1+
/*
2+
* Copyright (c) 2020, RTE (http://www.rte-france.com)
3+
* This Source Code Form is subject to the terms of the Mozilla Public
4+
* License, v. 2.0. If a copy of the MPL was not distributed with this
5+
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
6+
*/
7+
8+
import { useCallback, useEffect, useState } from 'react';
9+
import { useDispatch, useSelector } from 'react-redux';
10+
import { Grid } from '@mui/material';
11+
import {
12+
AnnouncementNotification,
13+
AuthenticationRouter,
14+
CardErrorBoundary,
15+
fetchConfigParameter,
16+
fetchConfigParameters,
17+
getComputedLanguage,
18+
getPreLoginPath,
19+
initializeAuthenticationProd,
20+
NotificationsUrlKeys,
21+
useNotificationsListener,
22+
UserManagerState,
23+
useSnackMessage,
24+
} from '@gridsuite/commons-ui';
25+
import { selectComputedLanguage, selectLanguage, selectTheme } from '../../redux/actions';
26+
import { AppState } from '../../redux/reducer';
27+
import { AppsMetadataSrv, ConfigParameters } from '../../services';
28+
import { APP_NAME, COMMON_APP_NAME, PARAM_LANGUAGE, PARAM_THEME } from '../../utils/config-params';
29+
import AppTopBar from './AppTopBar';
30+
import { useDebugRender } from '../../utils/hooks';
31+
import { AppDispatch } from '../../redux/store';
32+
import { Navigate, Route, Routes, useLocation, useMatch, useNavigate } from 'react-router';
33+
import PageNotFound from './PageNotFound';
34+
import { FormattedMessage } from 'react-intl';
35+
import { MainPaths } from './utils';
36+
import { Announcements, Groups, Profiles, Users } from '../../pages';
37+
import HomePage from './HomePage';
38+
39+
export default function App() {
40+
useDebugRender('app');
41+
const { snackError } = useSnackMessage();
42+
const dispatch = useDispatch<AppDispatch>();
43+
const user = useSelector((state: AppState) => state.user);
44+
45+
const updateParams = useCallback(
46+
(params: ConfigParameters) => {
47+
console.groupCollapsed('received UI parameters');
48+
console.table(params);
49+
console.groupEnd();
50+
params.forEach((param) => {
51+
switch (param.name) {
52+
case PARAM_THEME:
53+
dispatch(selectTheme(param.value));
54+
break;
55+
case PARAM_LANGUAGE:
56+
dispatch(selectLanguage(param.value));
57+
dispatch(selectComputedLanguage(getComputedLanguage(param.value)));
58+
break;
59+
default:
60+
break;
61+
}
62+
});
63+
},
64+
[dispatch]
65+
);
66+
67+
const updateConfig = useCallback(
68+
(event: MessageEvent) => {
69+
const eventData = JSON.parse(event.data);
70+
if (eventData?.headers?.parameterName) {
71+
fetchConfigParameter(APP_NAME, eventData.headers.parameterName)
72+
.then((param) => updateParams([param]))
73+
.catch((error) => snackError({ messageTxt: error.message, headerId: 'paramsRetrievingError' }));
74+
}
75+
},
76+
[updateParams, snackError]
77+
);
78+
79+
useNotificationsListener(NotificationsUrlKeys.CONFIG, { listenerCallbackMessage: updateConfig });
80+
81+
useEffect(() => {
82+
if (user !== null) {
83+
fetchConfigParameters(COMMON_APP_NAME)
84+
.then((params) => updateParams(params))
85+
.catch((error) =>
86+
snackError({
87+
messageTxt: error.message,
88+
headerId: 'paramsRetrievingError',
89+
})
90+
);
91+
92+
fetchConfigParameters(APP_NAME)
93+
.then((params) => updateParams(params))
94+
.catch((error) =>
95+
snackError({
96+
messageTxt: error.message,
97+
headerId: 'paramsRetrievingError',
98+
})
99+
);
100+
}
101+
}, [user, dispatch, updateParams, snackError]);
102+
103+
const signInCallbackError = useSelector((state: AppState) => state.signInCallbackError);
104+
const authenticationRouterError = useSelector((state: AppState) => state.authenticationRouterError);
105+
const showAuthenticationRouterLogin = useSelector((state: AppState) => state.showAuthenticationRouterLogin);
106+
const [userManager, setUserManager] = useState<UserManagerState>({
107+
instance: null,
108+
error: null,
109+
});
110+
const navigate = useNavigate();
111+
const location = useLocation();
112+
113+
// Can't use lazy initializer because useRouteMatch is a hook
114+
const [initialMatchSilentRenewCallbackUrl] = useState(
115+
useMatch({
116+
path: '/silent-renew-callback',
117+
})
118+
);
119+
120+
const [initialMatchSigninCallbackUrl] = useState(
121+
useMatch({
122+
path: '/sign-in-callback',
123+
})
124+
);
125+
126+
useEffect(() => {
127+
// need subfunction when async as suggested by rule react-hooks/exhaustive-deps
128+
(async function initializeAuthentication() {
129+
try {
130+
setUserManager({
131+
instance: await initializeAuthenticationProd(
132+
dispatch,
133+
initialMatchSilentRenewCallbackUrl != null,
134+
AppsMetadataSrv.fetchIdpSettings,
135+
initialMatchSigninCallbackUrl != null
136+
),
137+
error: null,
138+
});
139+
} catch (error: any) {
140+
setUserManager({ instance: null, error: error.message });
141+
}
142+
})();
143+
// Note: initialMatchSilentRenewCallbackUrl and dispatch don't change
144+
}, [initialMatchSilentRenewCallbackUrl, dispatch, initialMatchSigninCallbackUrl]);
145+
146+
return (
147+
<Grid
148+
container
149+
direction="column"
150+
spacing={0}
151+
justifyContent="flex-start"
152+
alignItems="stretch"
153+
sx={{ height: '100vh', width: '100vw' }}
154+
>
155+
<Grid item xs="auto">
156+
<AppTopBar userManagerInstance={userManager.instance} />
157+
</Grid>
158+
<Grid item xs="auto">
159+
<AnnouncementNotification user={user} />
160+
</Grid>
161+
<Grid item container xs component="main" height={'100%'}>
162+
<CardErrorBoundary>
163+
<div
164+
className="singlestretch-parent"
165+
style={{
166+
flexGrow: 1,
167+
}}
168+
>
169+
{user !== null ? (
170+
<Routes>
171+
<Route path={'/'} element={<HomePage />} />
172+
<Route path={`/${MainPaths.users}`} element={<Users />} />
173+
<Route path={`/${MainPaths.profiles}`} element={<Profiles />} />
174+
<Route path={`/${MainPaths.groups}`} element={<Groups />} />
175+
<Route path={`/${MainPaths.announcements}`} element={<Announcements />} />
176+
<Route
177+
path="/sign-in-callback"
178+
element={<Navigate replace to={getPreLoginPath() || '/'} />}
179+
/>
180+
<Route
181+
path="/logout-callback"
182+
element={<h1>Error: logout failed; you are still logged in.</h1>}
183+
/>
184+
<Route
185+
path="*"
186+
element={<PageNotFound message={<FormattedMessage id="PageNotFound" />} />}
187+
/>
188+
</Routes>
189+
) : (
190+
<AuthenticationRouter
191+
userManager={userManager}
192+
signInCallbackError={signInCallbackError}
193+
authenticationRouterError={authenticationRouterError}
194+
showAuthenticationRouterLogin={showAuthenticationRouterLogin}
195+
dispatch={dispatch}
196+
navigate={navigate}
197+
location={location}
198+
/>
199+
)}
200+
</div>
201+
</CardErrorBoundary>
202+
</Grid>
203+
</Grid>
204+
);
205+
}

src/components/App/app-top-bar.tsx renamed to src/components/App/AppTopBar.tsx

Lines changed: 18 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -5,21 +5,13 @@
55
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
66
*/
77

8-
import {
9-
type AnchorHTMLAttributes,
10-
forwardRef,
11-
type FunctionComponent,
12-
type ReactElement,
13-
useEffect,
14-
useMemo,
15-
useState,
16-
} from 'react';
17-
import { capitalize, Tab, Tabs, useTheme } from '@mui/material';
8+
import { type AnchorHTMLAttributes, forwardRef, type ReactElement, SyntheticEvent, useEffect, useState } from 'react';
9+
import { capitalize, Tab, Tabs, TabsProps, useTheme } from '@mui/material';
1810
import { Groups, ManageAccounts, NotificationImportant, PeopleAlt } from '@mui/icons-material';
19-
import { fetchAppsMetadata, logout, Metadata, TopBar } from '@gridsuite/commons-ui';
11+
import { fetchAppsMetadata, logout, Metadata, TopBar, UserManagerState } from '@gridsuite/commons-ui';
2012
import { useParameterState } from '../parameters';
2113
import { APP_NAME, PARAM_LANGUAGE, PARAM_THEME } from '../../utils/config-params';
22-
import { NavLink, type To, useMatches, useNavigate } from 'react-router';
14+
import { NavLink, type To, useNavigate } from 'react-router';
2315
import { useDispatch, useSelector } from 'react-redux';
2416
import { FormattedMessage } from 'react-intl';
2517
import { AppsMetadataSrv, StudySrv } from '../../services';
@@ -28,7 +20,7 @@ import GridAdminLogoDark from '../../images/GridAdmin_logo_dark.svg?react';
2820
import AppPackage from '../../../package.json';
2921
import { AppState } from '../../redux/reducer';
3022
import { AppDispatch } from '../../redux/store';
31-
import { MainPaths } from '../../routes/utils';
23+
import { MainPaths } from './utils';
3224

3325
const tabs = new Map<MainPaths, ReactElement>([
3426
[
@@ -89,27 +81,22 @@ const tabs = new Map<MainPaths, ReactElement>([
8981
],
9082
]);
9183

92-
const AppTopBar: FunctionComponent = () => {
84+
type AppTopBarProps = {
85+
userManagerInstance: UserManagerState['instance'];
86+
};
87+
88+
export default function AppTopBar({ userManagerInstance }: Readonly<AppTopBarProps>) {
9389
const theme = useTheme();
9490
const dispatch = useDispatch<AppDispatch>();
9591
const user = useSelector((state: AppState) => state.user);
96-
const userManagerInstance = useSelector((state: AppState) => state.userManager?.instance);
9792

9893
const navigate = useNavigate();
99-
const matches = useMatches();
100-
const selectedTabValue = useMemo(() => {
101-
const handle: any = matches
102-
.map((match) => match.handle)
103-
.filter((handle: any) => !!handle?.appBar_tab)
104-
.shift();
105-
const tabValue: MainPaths = handle?.appBar_tab;
106-
return tabValue && tabs.has(tabValue) ? tabValue : false;
107-
}, [matches]);
10894

10995
const [themeLocal, handleChangeTheme] = useParameterState(PARAM_THEME);
11096
const [languageLocal, handleChangeLanguage] = useParameterState(PARAM_LANGUAGE);
11197

11298
const [appsAndUrls, setAppsAndUrls] = useState<Metadata[]>([]);
99+
const [tabValue, setTabValue] = useState<TabsProps['value']>(false);
113100

114101
useEffect(() => {
115102
if (user !== null) {
@@ -119,6 +106,10 @@ const AppTopBar: FunctionComponent = () => {
119106
}
120107
}, [user]);
121108

109+
const handleChange = (_: SyntheticEvent, newValue: number) => {
110+
setTabValue(newValue);
111+
};
112+
122113
return (
123114
<TopBar
124115
appName={capitalize(APP_NAME)}
@@ -147,11 +138,11 @@ const AppTopBar: FunctionComponent = () => {
147138
visibility: !user ? 'hidden' : undefined,
148139
flexGrow: 1,
149140
}}
150-
value={selectedTabValue}
141+
value={tabValue}
142+
onChange={handleChange}
151143
>
152144
{[...tabs.values()]}
153145
</Tabs>
154146
</TopBar>
155147
);
156-
};
157-
export default AppTopBar;
148+
}

0 commit comments

Comments
 (0)