diff --git a/application/frontend/src/hooks/index.ts b/application/frontend/src/hooks/index.ts index 637f9d319..74aac2712 100644 --- a/application/frontend/src/hooks/index.ts +++ b/application/frontend/src/hooks/index.ts @@ -1,2 +1,3 @@ export { useEnvironment } from './useEnvironment'; export { useLocationFromOutsideRoute } from './useLocationFromOutsideRoute'; +export { useCapabilities } from './useCapabilities'; diff --git a/application/frontend/src/hooks/useCapabilities.ts b/application/frontend/src/hooks/useCapabilities.ts new file mode 100644 index 000000000..b3aceecad --- /dev/null +++ b/application/frontend/src/hooks/useCapabilities.ts @@ -0,0 +1,25 @@ +import { useEffect, useState } from 'react'; + +import { useEnvironment } from './useEnvironment'; + +export type Capabilities = { + myopencre: boolean; +}; + +export const useCapabilities = () => { + const { apiUrl } = useEnvironment(); + const [capabilities, setCapabilities] = useState(null); + const [loading, setLoading] = useState(true); + + useEffect(() => { + const baseUrl = apiUrl.replace('/rest/v1', ''); + + fetch(`${baseUrl}/api/capabilities`) + .then((res) => res.json()) + .then(setCapabilities) + .catch(() => setCapabilities({ myopencre: false })) + .finally(() => setLoading(false)); + }, [apiUrl]); + + return { capabilities, loading }; +}; diff --git a/application/frontend/src/hooks/useLocationFromOutsideRoute.tsx b/application/frontend/src/hooks/useLocationFromOutsideRoute.tsx index 290fdc1ee..b21404b87 100644 --- a/application/frontend/src/hooks/useLocationFromOutsideRoute.tsx +++ b/application/frontend/src/hooks/useLocationFromOutsideRoute.tsx @@ -1,7 +1,7 @@ import { matchPath } from 'react-router'; import { useLocation } from 'react-router-dom'; -import { ROUTES } from '../routes'; +import { IRoute } from '../routes'; interface UseLocationFromOutsideRouteReturn { params: Record; @@ -9,14 +9,28 @@ interface UseLocationFromOutsideRouteReturn { showFilter: boolean; } -export const useLocationFromOutsideRoute = (): UseLocationFromOutsideRouteReturn => { - // The current URL +/** + * Determines route metadata (params, url, showFilter) + * based on the currently active route. + * + * NOTE: + * - This hook no longer imports ROUTES directly + * - Routes must be passed in (already capability-resolved) + */ +export const useLocationFromOutsideRoute = (routes: IRoute[]): UseLocationFromOutsideRouteReturn => { const { pathname } = useLocation(); - // The current ROUTE, from our URL - const currentRoute = ROUTES.map(({ path, showFilter }) => ({ - ...matchPath(pathname, path), - showFilter, - })).find((matchedPath) => matchedPath?.isExact); + + const currentRoute = routes + .map(({ path, showFilter }) => ({ + ...matchPath(pathname, { + path, + exact: true, + strict: false, + }), + showFilter, + })) + .find((matchedPath) => matchedPath?.isExact); + return { params: currentRoute?.params || {}, url: currentRoute?.url || '', diff --git a/application/frontend/src/routes.tsx b/application/frontend/src/routes.tsx index 5d18ba09a..05edecbbd 100644 --- a/application/frontend/src/routes.tsx +++ b/application/frontend/src/routes.tsx @@ -30,13 +30,19 @@ export interface IRoute { component: ReactNode | ReactNode[]; showFilter: boolean; } - -export const ROUTES: IRoute[] = [ - { - path: '/myopencre', - component: MyOpenCRE, - showFilter: false, - }, +export interface Capabilities { + myopencre: boolean; +} +export const ROUTES = (capabilities: Capabilities): IRoute[] => [ + ...(capabilities.myopencre + ? [ + { + path: '/myopencre', + component: MyOpenCRE, + showFilter: false, + }, + ] + : []), { path: INDEX, diff --git a/application/frontend/src/scaffolding/Header/Header.tsx b/application/frontend/src/scaffolding/Header/Header.tsx index a44a97d9b..9f2cd117b 100644 --- a/application/frontend/src/scaffolding/Header/Header.tsx +++ b/application/frontend/src/scaffolding/Header/Header.tsx @@ -7,18 +7,24 @@ import { NavLink } from 'react-router-dom'; import { Button } from 'semantic-ui-react'; import { ClearFilterButton } from '../../components/FilterButton/FilterButton'; +import { Capabilities } from '../../hooks/useCapabilities'; import { useLocationFromOutsideRoute } from '../../hooks/useLocationFromOutsideRoute'; -import { MyOpenCRE } from '../../pages/MyOpenCRE/MyOpenCRE'; import { SearchBar } from '../../pages/Search/components/SearchBar'; +import { ROUTES } from '../../routes'; + +interface HeaderProps { + capabilities: Capabilities; +} +export const Header = ({ capabilities }: HeaderProps) => { + const routes = ROUTES(capabilities); -export const Header = () => { let currentUrlParams = new URLSearchParams(window.location.search); const history = useHistory(); const HandleDoFilter = () => { currentUrlParams.set('applyFilters', 'true'); history.push(window.location.pathname + '?' + currentUrlParams.toString()); }; - const { showFilter } = useLocationFromOutsideRoute(); + const { showFilter } = useLocationFromOutsideRoute(routes); const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false); useEffect(() => { @@ -69,9 +75,11 @@ export const Header = () => { Explorer - - MyOpenCRE - + {capabilities.myopencre && ( + + MyOpenCRE + + )}
@@ -190,14 +198,16 @@ export const Header = () => { > Explorer - - MyOpenCRE - + {capabilities.myopencre && ( + + MyOpenCRE + + )}
diff --git a/application/frontend/src/scaffolding/MainContentArea/MainContentArea.tsx b/application/frontend/src/scaffolding/MainContentArea/MainContentArea.tsx index 51a9b06e1..e1f201e02 100644 --- a/application/frontend/src/scaffolding/MainContentArea/MainContentArea.tsx +++ b/application/frontend/src/scaffolding/MainContentArea/MainContentArea.tsx @@ -1,12 +1,19 @@ import React from 'react'; +import { useCapabilities } from '../../hooks/useCapabilities'; import { Header, Router } from '../index'; export const MainContentArea = () => { + const { capabilities, loading } = useCapabilities(); + + if (loading || !capabilities) { + return null; // or spinner + } + return ( <> -
- +
+ ); }; diff --git a/application/frontend/src/scaffolding/Router/Router.tsx b/application/frontend/src/scaffolding/Router/Router.tsx index 5ba2c9b2f..61bfa1d96 100644 --- a/application/frontend/src/scaffolding/Router/Router.tsx +++ b/application/frontend/src/scaffolding/Router/Router.tsx @@ -1,19 +1,27 @@ import React from 'react'; import { Route, Switch } from 'react-router-dom'; +import { Capabilities } from '../../hooks/useCapabilities'; import { ROUTES } from '../../routes'; import { NoRoute } from '../index'; -export const Router = () => ( - - {ROUTES.map(({ path, component: Component }) => { - if (!Component) { - return null; - } - const TypedComponent = Component as React.ElementType; +interface RouterProps { + capabilities: Capabilities; +} - return } />; - })} - - -); +export const Router = ({ capabilities }: RouterProps) => { + const routes = ROUTES(capabilities); + + return ( + + {routes.map(({ path, component: Component }) => { + if (!Component) return null; + + const TypedComponent = Component as React.ElementType; + + return } />; + })} + + + ); +};