diff --git a/jest.config.ts b/jest.config.ts index 1665132..feaeab0 100644 --- a/jest.config.ts +++ b/jest.config.ts @@ -10,7 +10,7 @@ import type { Config } from 'jest'; const config: Config = { testEnvironment: 'jsdom', moduleNameMapper: { - '^.+\\.svg\\?react$': 'jest-svg-transformer', + '^.+\\.svg\\?react$': '/src/_mocks_/svg.tsx', '^.+\\.(css|less|scss)$': 'identity-obj-proxy', }, // see https://github.com/react-dnd/react-dnd/issues/3443 diff --git a/src/routes/index.ts b/src/_mocks_/fetch.ts similarity index 56% rename from src/routes/index.ts rename to src/_mocks_/fetch.ts index 30825e5..a470a00 100644 --- a/src/routes/index.ts +++ b/src/_mocks_/fetch.ts @@ -1,8 +1,13 @@ -/* +/** * Copyright (c) 2024, RTE (http://www.rte-france.com) * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -export * from './router'; +export default function fetchMock() { + return Promise.resolve({ + ok: true, + json: () => ({ appsMetadataServerUrl: '' }), // just to remove the error logs when fetching env + }); +} diff --git a/src/components/App/index.ts b/src/_mocks_/svg.tsx similarity index 57% rename from src/components/App/index.ts rename to src/_mocks_/svg.tsx index 8353c88..d95be4c 100644 --- a/src/components/App/index.ts +++ b/src/_mocks_/svg.tsx @@ -1,11 +1,12 @@ -/* +/** * Copyright (c) 2024, RTE (http://www.rte-france.com) * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -import AppComponent from './app'; -export type App = typeof AppComponent; +import { forwardRef, SVGProps } from 'react'; -export { AppWrapper } from './app-wrapper'; +const SvgrMock = forwardRef>((props, ref) => ); + +export default SvgrMock; diff --git a/src/components/App/App.tsx b/src/components/App/App.tsx new file mode 100644 index 0000000..7ca28f2 --- /dev/null +++ b/src/components/App/App.tsx @@ -0,0 +1,205 @@ +/* + * Copyright (c) 2020, RTE (http://www.rte-france.com) + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +import { useCallback, useEffect, useState } from 'react'; +import { useDispatch, useSelector } from 'react-redux'; +import { Grid } from '@mui/material'; +import { + AnnouncementNotification, + AuthenticationRouter, + CardErrorBoundary, + fetchConfigParameter, + fetchConfigParameters, + getComputedLanguage, + getPreLoginPath, + initializeAuthenticationProd, + NotificationsUrlKeys, + useNotificationsListener, + UserManagerState, + useSnackMessage, +} from '@gridsuite/commons-ui'; +import { selectComputedLanguage, selectLanguage, selectTheme } from '../../redux/actions'; +import { AppState } from '../../redux/reducer'; +import { AppsMetadataSrv, ConfigParameters } from '../../services'; +import { APP_NAME, COMMON_APP_NAME, PARAM_LANGUAGE, PARAM_THEME } from '../../utils/config-params'; +import AppTopBar from './AppTopBar'; +import { useDebugRender } from '../../utils/hooks'; +import { AppDispatch } from '../../redux/store'; +import { Navigate, Route, Routes, useLocation, useMatch, useNavigate } from 'react-router'; +import PageNotFound from './PageNotFound'; +import { FormattedMessage } from 'react-intl'; +import { MainPaths } from './utils'; +import { Announcements, Groups, Profiles, Users } from '../../pages'; +import HomePage from './HomePage'; + +export default function App() { + useDebugRender('app'); + const { snackError } = useSnackMessage(); + const dispatch = useDispatch(); + const user = useSelector((state: AppState) => state.user); + + const updateParams = useCallback( + (params: ConfigParameters) => { + console.groupCollapsed('received UI parameters'); + console.table(params); + console.groupEnd(); + params.forEach((param) => { + switch (param.name) { + case PARAM_THEME: + dispatch(selectTheme(param.value)); + break; + case PARAM_LANGUAGE: + dispatch(selectLanguage(param.value)); + dispatch(selectComputedLanguage(getComputedLanguage(param.value))); + break; + default: + break; + } + }); + }, + [dispatch] + ); + + const updateConfig = useCallback( + (event: MessageEvent) => { + const eventData = JSON.parse(event.data); + if (eventData?.headers?.parameterName) { + fetchConfigParameter(APP_NAME, eventData.headers.parameterName) + .then((param) => updateParams([param])) + .catch((error) => snackError({ messageTxt: error.message, headerId: 'paramsRetrievingError' })); + } + }, + [updateParams, snackError] + ); + + useNotificationsListener(NotificationsUrlKeys.CONFIG, { listenerCallbackMessage: updateConfig }); + + useEffect(() => { + if (user !== null) { + fetchConfigParameters(COMMON_APP_NAME) + .then((params) => updateParams(params)) + .catch((error) => + snackError({ + messageTxt: error.message, + headerId: 'paramsRetrievingError', + }) + ); + + fetchConfigParameters(APP_NAME) + .then((params) => updateParams(params)) + .catch((error) => + snackError({ + messageTxt: error.message, + headerId: 'paramsRetrievingError', + }) + ); + } + }, [user, dispatch, updateParams, snackError]); + + const signInCallbackError = useSelector((state: AppState) => state.signInCallbackError); + const authenticationRouterError = useSelector((state: AppState) => state.authenticationRouterError); + const showAuthenticationRouterLogin = useSelector((state: AppState) => state.showAuthenticationRouterLogin); + const [userManager, setUserManager] = useState({ + instance: null, + error: null, + }); + const navigate = useNavigate(); + const location = useLocation(); + + // Can't use lazy initializer because useRouteMatch is a hook + const [initialMatchSilentRenewCallbackUrl] = useState( + useMatch({ + path: '/silent-renew-callback', + }) + ); + + const [initialMatchSigninCallbackUrl] = useState( + useMatch({ + path: '/sign-in-callback', + }) + ); + + useEffect(() => { + // need subfunction when async as suggested by rule react-hooks/exhaustive-deps + (async function initializeAuthentication() { + try { + setUserManager({ + instance: await initializeAuthenticationProd( + dispatch, + initialMatchSilentRenewCallbackUrl != null, + AppsMetadataSrv.fetchIdpSettings, + initialMatchSigninCallbackUrl != null + ), + error: null, + }); + } catch (error: any) { + setUserManager({ instance: null, error: error.message }); + } + })(); + // Note: initialMatchSilentRenewCallbackUrl and dispatch don't change + }, [initialMatchSilentRenewCallbackUrl, dispatch, initialMatchSigninCallbackUrl]); + + return ( + + + + + + + + + +
+ {user !== null ? ( + + } /> + } /> + } /> + } /> + } /> + } + /> + Error: logout failed; you are still logged in.} + /> + } />} + /> + + ) : ( + + )} +
+
+
+
+ ); +} diff --git a/src/components/App/app-top-bar.tsx b/src/components/App/AppTopBar.tsx similarity index 83% rename from src/components/App/app-top-bar.tsx rename to src/components/App/AppTopBar.tsx index 0293ec7..52dcf4a 100644 --- a/src/components/App/app-top-bar.tsx +++ b/src/components/App/AppTopBar.tsx @@ -5,21 +5,13 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -import { - type AnchorHTMLAttributes, - forwardRef, - type FunctionComponent, - type ReactElement, - useEffect, - useMemo, - useState, -} from 'react'; -import { capitalize, Tab, Tabs, useTheme } from '@mui/material'; +import { type AnchorHTMLAttributes, forwardRef, type ReactElement, SyntheticEvent, useEffect, useState } from 'react'; +import { capitalize, Tab, Tabs, TabsProps, useTheme } from '@mui/material'; import { Groups, ManageAccounts, NotificationImportant, PeopleAlt } from '@mui/icons-material'; -import { fetchAppsMetadata, logout, Metadata, TopBar } from '@gridsuite/commons-ui'; +import { fetchAppsMetadata, logout, Metadata, TopBar, UserManagerState } from '@gridsuite/commons-ui'; import { useParameterState } from '../parameters'; import { APP_NAME, PARAM_LANGUAGE, PARAM_THEME } from '../../utils/config-params'; -import { NavLink, type To, useMatches, useNavigate } from 'react-router'; +import { NavLink, type To, useNavigate } from 'react-router'; import { useDispatch, useSelector } from 'react-redux'; import { FormattedMessage } from 'react-intl'; import { AppsMetadataSrv, StudySrv } from '../../services'; @@ -28,7 +20,7 @@ import GridAdminLogoDark from '../../images/GridAdmin_logo_dark.svg?react'; import AppPackage from '../../../package.json'; import { AppState } from '../../redux/reducer'; import { AppDispatch } from '../../redux/store'; -import { MainPaths } from '../../routes/utils'; +import { MainPaths } from './utils'; const tabs = new Map([ [ @@ -89,27 +81,22 @@ const tabs = new Map([ ], ]); -const AppTopBar: FunctionComponent = () => { +type AppTopBarProps = { + userManagerInstance: UserManagerState['instance']; +}; + +export default function AppTopBar({ userManagerInstance }: Readonly) { const theme = useTheme(); const dispatch = useDispatch(); const user = useSelector((state: AppState) => state.user); - const userManagerInstance = useSelector((state: AppState) => state.userManager?.instance); const navigate = useNavigate(); - const matches = useMatches(); - const selectedTabValue = useMemo(() => { - const handle: any = matches - .map((match) => match.handle) - .filter((handle: any) => !!handle?.appBar_tab) - .shift(); - const tabValue: MainPaths = handle?.appBar_tab; - return tabValue && tabs.has(tabValue) ? tabValue : false; - }, [matches]); const [themeLocal, handleChangeTheme] = useParameterState(PARAM_THEME); const [languageLocal, handleChangeLanguage] = useParameterState(PARAM_LANGUAGE); const [appsAndUrls, setAppsAndUrls] = useState([]); + const [tabValue, setTabValue] = useState(false); useEffect(() => { if (user !== null) { @@ -119,6 +106,10 @@ const AppTopBar: FunctionComponent = () => { } }, [user]); + const handleChange = (_: SyntheticEvent, newValue: number) => { + setTabValue(newValue); + }; + return ( { visibility: !user ? 'hidden' : undefined, flexGrow: 1, }} - value={selectedTabValue} + value={tabValue} + onChange={handleChange} > {[...tabs.values()]} ); -}; -export default AppTopBar; +} diff --git a/src/components/App/app-wrapper.tsx b/src/components/App/AppWrapper.tsx similarity index 71% rename from src/components/App/app-wrapper.tsx rename to src/components/App/AppWrapper.tsx index 779ed7a..656af88 100644 --- a/src/components/App/app-wrapper.tsx +++ b/src/components/App/AppWrapper.tsx @@ -5,8 +5,8 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -import App from './app'; -import { FunctionComponent, type PropsWithChildren, useMemo } from 'react'; +import App from './App'; +import { useMemo } from 'react'; import { CssBaseline, responsiveFontSizes, ThemeOptions } from '@mui/material'; import { createTheme, StyledEngineProvider, Theme, ThemeProvider } from '@mui/material/styles'; import { enUS as MuiCoreEnUS, frFR as MuiCoreFrFR } from '@mui/material/locale'; @@ -39,9 +39,9 @@ import messages_fr from '../../translations/fr.json'; import { store } from '../../redux/store'; import { PARAM_THEME } from '../../utils/config-params'; import { AppState } from '../../redux/reducer'; -import { AppWithAuthRouter } from '../../routes'; import { useNotificationsUrlGenerator } from '../../utils/notifications-provider'; import { AllCommunityModule, ModuleRegistry, provideGlobalGridOptions } from 'ag-grid-community'; +import { BrowserRouter } from 'react-router'; // Register all community features (migration to V33) ModuleRegistry.registerModules([AllCommunityModule]); @@ -145,51 +145,42 @@ function intlToDateFnsLocale(lng: GsLangUser) { } } -/** - * Layer injecting Theme, Internationalization (i18n) and other tools (snackbar, error boundary, ...) - */ -const AppWrapperRouterLayout: typeof App = (props: Readonly>) => { +const AppWrapperWithRedux = () => { const computedLanguage = useSelector((state: AppState) => state.computedLanguage); const theme = useSelector((state: AppState) => state[PARAM_THEME]); const themeCompiled = useMemo(() => getMuiTheme(theme, computedLanguage), [computedLanguage, theme]); const urlMapper = useNotificationsUrlGenerator(); + return ( - - - - - - - - {props.children} - - - - - - + + + + + + + + + + + + + + + + ); }; -/** - * Layer managing router depending on user authentication state - */ -const AppWrapperWithRedux: FunctionComponent = () => ( - -); - -/** - * Layer injecting Redux store in context - */ -export const AppWrapper: FunctionComponent = () => ( - - - -); -export default AppWrapper; +export default function AppWrapper() { + return ( + + + + ); +} diff --git a/src/routes/HomePage.tsx b/src/components/App/HomePage.tsx similarity index 100% rename from src/routes/HomePage.tsx rename to src/components/App/HomePage.tsx diff --git a/src/components/App/PageNotFound.tsx b/src/components/App/PageNotFound.tsx new file mode 100644 index 0000000..0b981e2 --- /dev/null +++ b/src/components/App/PageNotFound.tsx @@ -0,0 +1,33 @@ +/** + * Copyright (c) 2020, RTE (http://www.rte-france.com) + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +import Container from '@mui/material/Container'; +import ErrorOutlineIcon from '@mui/icons-material/ErrorOutline'; +import { ReactNode } from 'react'; + +const styles = { + error: { + fontSize: '64px', + width: '20%', + marginLeft: '40%', + }, + container: { + marginTop: '70px', + }, +}; + +const PageNotFound = ({ message }: { message: ReactNode }) => { + return ( + +
+ +

{message}

+
+ ); +}; + +export default PageNotFound; diff --git a/src/components/App/app.test.tsx b/src/components/App/app.test.tsx index 68fd693..34ed2d2 100644 --- a/src/components/App/app.test.tsx +++ b/src/components/App/app.test.tsx @@ -6,21 +6,17 @@ */ // app.test.tsx -import React, { FunctionComponent, PropsWithChildren } from 'react'; import { createRoot } from 'react-dom/client'; import { act } from 'react-dom/test-utils'; import { IntlProvider } from 'react-intl'; import { Provider } from 'react-redux'; -import { createMemoryRouter, Outlet, RouterProvider } from 'react-router'; -import App from './app'; -import { store } from '../../redux/store'; -import { createTheme, StyledEngineProvider, ThemeProvider } from '@mui/material/styles'; +import { BrowserRouter } from 'react-router'; +import { createTheme, CssBaseline, StyledEngineProvider, ThemeProvider } from '@mui/material'; import { SnackbarProvider } from '@gridsuite/commons-ui'; -import { CssBaseline } from '@mui/material'; -import { appRoutes } from '../../routes/utils'; - -let container: HTMLElement | null = null; +import App from './App'; +import { store } from '../../redux/store'; +let container: HTMLDivElement | null = null; beforeEach(() => { // setup a DOM element as a render target container = document.createElement('div'); @@ -33,48 +29,28 @@ afterEach(() => { container = null; }); -//broken test -it.skip('renders', async () => { - if (container === null) { - throw new Error('No container was defined'); - } - const root = createRoot(container); - const AppWrapperRouterLayout: FunctionComponent> = () => ( - - - - - - - - - - - - - ); - const router = createMemoryRouter( - [ - { - element: ( - - - - ), - children: appRoutes(), - }, - ] - //{ basename: props.basename } - ); +it('renders', async () => { + const root = createRoot(container!); await act(async () => root.render( - - - + + + + + + + + + + + + + + ) ); - expect(container.textContent).toContain('GridAdmin'); + expect(container?.textContent).toContain('GridAdmin'); act(() => { root.unmount(); }); diff --git a/src/components/App/app.tsx b/src/components/App/app.tsx deleted file mode 100644 index 3c1b2ec..0000000 --- a/src/components/App/app.tsx +++ /dev/null @@ -1,111 +0,0 @@ -/* - * Copyright (c) 2020, RTE (http://www.rte-france.com) - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. - */ - -import { type PropsWithChildren, useCallback, useEffect } from 'react'; -import { useDispatch, useSelector } from 'react-redux'; -import { Grid } from '@mui/material'; -import { - AnnouncementNotification, - CardErrorBoundary, - fetchConfigParameter, - fetchConfigParameters, - getComputedLanguage, - NotificationsUrlKeys, - useNotificationsListener, - useSnackMessage, -} from '@gridsuite/commons-ui'; -import { selectComputedLanguage, selectLanguage, selectTheme } from '../../redux/actions'; -import { AppState } from '../../redux/reducer'; -import { ConfigParameters } from '../../services'; -import { APP_NAME, COMMON_APP_NAME, PARAM_LANGUAGE, PARAM_THEME } from '../../utils/config-params'; -import AppTopBar from './app-top-bar'; -import { useDebugRender } from '../../utils/hooks'; -import { AppDispatch } from '../../redux/store'; - -export default function App({ children }: Readonly>) { - useDebugRender('app'); - const { snackError } = useSnackMessage(); - const dispatch = useDispatch(); - const user = useSelector((state: AppState) => state.user); - - const updateParams = useCallback( - (params: ConfigParameters) => { - console.groupCollapsed('received UI parameters'); - console.table(params); - console.groupEnd(); - params.forEach((param) => { - switch (param.name) { - case PARAM_THEME: - dispatch(selectTheme(param.value)); - break; - case PARAM_LANGUAGE: - dispatch(selectLanguage(param.value)); - dispatch(selectComputedLanguage(getComputedLanguage(param.value))); - break; - default: - break; - } - }); - }, - [dispatch] - ); - - const updateConfig = useCallback( - (event: MessageEvent) => { - const eventData = JSON.parse(event.data); - if (eventData?.headers?.parameterName) { - fetchConfigParameter(APP_NAME, eventData.headers.parameterName) - .then((param) => updateParams([param])) - .catch((error) => snackError({ messageTxt: error.message, headerId: 'paramsRetrievingError' })); - } - }, - [updateParams, snackError] - ); - - useNotificationsListener(NotificationsUrlKeys.CONFIG, { listenerCallbackMessage: updateConfig }); - - useEffect(() => { - if (user !== null) { - fetchConfigParameters(COMMON_APP_NAME) - .then((params) => updateParams(params)) - .catch((error) => - snackError({ - messageTxt: error.message, - headerId: 'paramsRetrievingError', - }) - ); - - fetchConfigParameters(APP_NAME) - .then((params) => updateParams(params)) - .catch((error) => - snackError({ - messageTxt: error.message, - headerId: 'paramsRetrievingError', - }) - ); - } - }, [user, dispatch, updateParams, snackError]); - - return ( - - - - - - - {/*Router outlet ->*/ children} - - - ); -} diff --git a/src/components/App/utils.ts b/src/components/App/utils.ts new file mode 100644 index 0000000..74834d2 --- /dev/null +++ b/src/components/App/utils.ts @@ -0,0 +1,13 @@ +/* + * Copyright (c) 2025, RTE (http://www.rte-france.com) + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +export enum MainPaths { + users = 'users', + profiles = 'profiles', + groups = 'groups', + announcements = 'announcements', +} diff --git a/src/index.tsx b/src/index.tsx index 698e4ad..c8ce492 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -10,7 +10,7 @@ import 'typeface-roboto'; import React from 'react'; import { createRoot } from 'react-dom/client'; import './index.css'; -import { AppWrapper } from './components/App'; +import AppWrapper from './components/App/AppWrapper'; const container = document.getElementById('root'); if (container) { diff --git a/src/pages/announcements/add-announcement-form.tsx b/src/pages/announcements/add-announcement-form.tsx index 5f48303..078b7d9 100644 --- a/src/pages/announcements/add-announcement-form.tsx +++ b/src/pages/announcements/add-announcement-form.tsx @@ -137,7 +137,7 @@ export default function AddAnnouncementForm({ onAnnouncementCreated }: Readonly< FormProps={{ style: { height: '100%' } }} > - + diff --git a/src/pages/announcements/announcements-page.tsx b/src/pages/announcements/announcements-page.tsx index d554fa4..2122f19 100644 --- a/src/pages/announcements/announcements-page.tsx +++ b/src/pages/announcements/announcements-page.tsx @@ -122,7 +122,7 @@ export default function AnnouncementsPage() { // Note: using for the columns didn't work return ( - + diff --git a/src/pages/groups/groups-page.tsx b/src/pages/groups/groups-page.tsx index 57234b8..99beabc 100644 --- a/src/pages/groups/groups-page.tsx +++ b/src/pages/groups/groups-page.tsx @@ -41,7 +41,7 @@ const GroupsPage: FunctionComponent = () => { return ( <> - + { return ( <> - + { return ( <> - + { - console.error(error); - }, [error]); - return ( - - Oops! - - Sorry, an unexpected error has occurred. - - {isRouteErrorResponse(error) && ( - <> - - {error.status} - - - {error.statusText} - - - )} -

- {error.message || error?.data?.message || error.statusText} -

- {isRouteErrorResponse(error) && error.data.error && ( -
-                    
-                        {(function () {
-                            try {
-                                return JSON.stringify(error.data.error, undefined, 2);
-                            } catch (e) {
-                                return null;
-                            }
-                        })() ?? `${error.data.error}`}
-                    
-                
- )} -
- ); -} diff --git a/src/routes/router.tsx b/src/routes/router.tsx deleted file mode 100644 index 0a2b2c9..0000000 --- a/src/routes/router.tsx +++ /dev/null @@ -1,140 +0,0 @@ -/* - * Copyright (c) 2024, RTE (http://www.rte-france.com) - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. - */ - -import { FunctionComponent, PropsWithChildren, useEffect, useMemo, useState } from 'react'; -import { AuthenticationRouter, initializeAuthenticationProd } from '@gridsuite/commons-ui'; -import { - createBrowserRouter, - Outlet, - RouteObject, - RouterProvider, - useLocation, - useMatch, - useNavigate, -} from 'react-router'; -import { useDispatch, useSelector } from 'react-redux'; -import { AppState } from '../redux/reducer'; -import { AppsMetadataSrv } from '../services'; -import type { App } from '../components/App'; -import { updateUserManagerDestructured } from '../redux/actions'; -import { getErrorMessage } from '../utils/error'; -import { AppDispatch } from '../redux/store'; -import { appRoutes } from './utils'; - -const AuthRouter: FunctionComponent<{ - userManager: Parameters[0]['userManager']; -}> = (props, context) => { - const signInCallbackError = useSelector((state: AppState) => state.signInCallbackError); - const authenticationRouterError = useSelector((state: AppState) => state.authenticationRouterError); - const showAuthenticationRouterLogin = useSelector((state: AppState) => state.showAuthenticationRouterLogin); - const dispatch = useDispatch(); - const navigate = useNavigate(); - const location = useLocation(); - - return ( - - ); -}; - -/** - * Manage authentication state. - *
Sub-component because `useMatch` must be under router context. - */ -const AppAuthStateWithRouterLayer: FunctionComponent> = (props, context) => { - const AppRouterLayout = props.layout; - const dispatch = useDispatch(); - - // Can't use lazy initializer because useMatch is a hook - const [initialMatchSilentRenewCallbackUrl] = useState( - useMatch({ - path: '/silent-renew-callback', - }) - ); - const [initialMatchSignInCallbackUrl] = useState( - useMatch({ - path: '/sign-in-callback', - }) - ); - - useEffect(() => { - // need subfunction when async as suggested by rule react-hooks/exhaustive-deps - (async function initializeAuthentication() { - try { - dispatch( - updateUserManagerDestructured( - (await initializeAuthenticationProd( - dispatch, - initialMatchSilentRenewCallbackUrl != null, - AppsMetadataSrv.fetchIdpSettings, - initialMatchSignInCallbackUrl != null - )) ?? null, - null - ) - ); - } catch (error: unknown) { - dispatch(updateUserManagerDestructured(null, getErrorMessage(error))); - } - })(); - // Note: initialize and initialMatchSilentRenewCallbackUrl & initialMatchSignInCallbackUrl won't change - }, [dispatch, initialMatchSilentRenewCallbackUrl, initialMatchSignInCallbackUrl]); - - return {props.children}; -}; - -/** - * Manage authentication and assure cohabitation of legacy router and new data router api - */ -export const AppWithAuthRouter: FunctionComponent<{ - basename: string; - layout: App; -}> = (props, context) => { - const user = useSelector((state: AppState) => state.user); - const router = useMemo( - () => - createBrowserRouter( - user - ? [ - /*new react-router v6 api*/ - { - element: ( - - - - ), - children: appRoutes(), - }, - ] - : ([ - /*legacy component router*/ - { - path: '*', - Component: () => , - }, - ] as RouteObject[]), - { basename: props.basename } - ), - [props.basename, props.layout, user] - ); - return ; -}; - -const LegacyAuthRouter: FunctionComponent<{ layout: App }> = (props) => { - const userManager = useSelector((state: AppState) => state.userManager); - return ( - - - - ); -}; diff --git a/src/routes/utils.tsx b/src/routes/utils.tsx deleted file mode 100644 index 427ee52..0000000 --- a/src/routes/utils.tsx +++ /dev/null @@ -1,77 +0,0 @@ -/* - * Copyright (c) 2025, RTE (http://www.rte-france.com) - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. - */ - -import { Navigate, RouteObject } from 'react-router'; -import { Profiles, Users, Groups } from '../pages'; -import ErrorPage from './ErrorPage'; -import HomePage from './HomePage'; -import { getPreLoginPath } from '@gridsuite/commons-ui'; -import { FormattedMessage } from 'react-intl'; -import { Announcements } from '../pages/announcements'; - -export enum MainPaths { - users = 'users', - profiles = 'profiles', - groups = 'groups', - announcements = 'announcements', -} - -export function appRoutes(): RouteObject[] { - return [ - { - path: '/', - errorElement: , - children: [ - { - index: true, - element: , - }, - { - path: `/${MainPaths.users}`, - element: , - handle: { - appBar_tab: MainPaths.users, - }, - }, - { - path: `/${MainPaths.profiles}`, - element: , - handle: { - appBar_tab: MainPaths.profiles, - }, - }, - { - path: `/${MainPaths.groups}`, - element: , - handle: { - appBar_tab: MainPaths.groups, - }, - }, - { - path: `/${MainPaths.announcements}`, - element: , - handle: { - appBar_tab: MainPaths.announcements, - }, - }, - ], - }, - { - path: '/sign-in-callback', - element: , - }, - { - path: '/logout-callback', - element: , - }, - { - path: '*', - element: , - errorElement: , - }, - ]; -}