Skip to content

Commit

Permalink
Update react router to data router (#182)
Browse files Browse the repository at this point in the history
* Turn Layout into pathless Route

* Migrate to router provider

* Define data routes for data router

* Lift layout route

* Add top level error boundary

* Lift redirect root route

* Lift all the remaining routes

* Reorganize configPromise

* Turn layout into root route

* Rename Layout and PulpMenu
  • Loading branch information
mdellweg authored Jan 21, 2025
1 parent f608302 commit c88372f
Show file tree
Hide file tree
Showing 8 changed files with 113 additions and 52 deletions.
67 changes: 44 additions & 23 deletions src/app-routes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ import { Trans } from '@lingui/react/macro';
import { Banner, Flex, FlexItem } from '@patternfly/react-core';
import WrenchIcon from '@patternfly/react-icons/dist/esm/icons/wrench-icon';
import { type ElementType } from 'react';
import { Navigate, Route, Routes, useLocation } from 'react-router';
import { ExternalLink, NotFound } from 'src/components';
import { Navigate, redirect, useLocation } from 'react-router';
import { ErrorBoundary, ExternalLink, NotFound } from 'src/components';
import {
AboutProject,
AnsibleRemoteDetail,
Expand Down Expand Up @@ -365,26 +365,47 @@ const AuthHandler = ({
);
};

export const AppRoutes = () => (
<Routes>
{routes.map(({ beta, component, noAuth, path }, index) => (
<Route
element={
<AuthHandler
beta={beta}
component={component}
noAuth={noAuth}
path={path}
/>
}
key={index}
const appRoutes = () =>
routes.map(({ beta, component, noAuth, path, ...rest }) => ({
element: (
<AuthHandler
beta={beta}
component={component}
noAuth={noAuth}
path={path}
/>
))}
<Route
path={'/'}
element={<Navigate to={formatPath(Paths.core.status)} />}
/>
<Route path='*' element={<NotFound />} />
</Routes>
);
),
path: path,
...rest,
}));

const convert = (m) => {
const {
default: Component,
clientLoader: loader,
clientAction: action,
...rest
} = m;
return { ...rest, loader, action, Component };
};

export const dataRoutes = [
{
id: 'root',
lazy: () => import('src/routes/root').then((m) => convert(m)),
children: [
{
errorElement: <ErrorBoundary />,
children: [
{
index: true,
loader: () => redirect(formatPath(Paths.core.status)),
},
...appRoutes(),
// "No matching route" is not handled by the error boundary.
{ path: '*', element: <NotFound /> },
],
},
],
},
];
27 changes: 27 additions & 0 deletions src/components/error-boundary.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { isRouteErrorResponse, useRouteError } from 'react-router';

export const ErrorBoundary = () => {
const error = useRouteError();

if (isRouteErrorResponse(error)) {
return (
<>
<h1>
{error.status} {error.statusText}
</h1>
<p>{error.data.toString()}</p>
</>
);
} else if (error instanceof Error) {
return (
<div>
<h1>Error</h1>
<p>{error.message}</p>
<p>The stack trace is:</p>
<pre>{error.stack}</pre>
</div>
);
console.error(error);
return <div>Something went horribly wrong!</div>;
}
};
1 change: 1 addition & 0 deletions src/components/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ export { EmptyStateNoData } from './empty-state-no-data';
export { EmptyStateUnauthorized } from './empty-state-unauthorized';
export { EmptyStateNotImplemented } from './empty-state-under-construction';
export { EmptyStateXs } from './empty-state-xs';
export { ErrorBoundary } from './error-boundary';
export { ExecutionEnvironmentHeader } from './execution-environment-header';
export { ExternalLink } from './external-link';
export { FormFieldHelper } from './form-field-helper';
Expand Down
26 changes: 8 additions & 18 deletions src/entrypoint.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,12 @@ import '@patternfly/patternfly/patternfly.scss';
import { Button } from '@patternfly/react-core';
import { StrictMode, useEffect, useState } from 'react';
import { createRoot } from 'react-dom/client';
import { BrowserRouter } from 'react-router';
import { Alert, LoadingSpinner, UIVersion } from 'src/components';
import { AppContextProvider } from './app-context';
import { AppRoutes } from './app-routes';
import { RouterProvider, createBrowserRouter } from 'react-router';
import { Alert, LoadingSpinner } from 'src/components';
import { dataRoutes } from './app-routes';
import './darkmode';
import './l10n';
import { StandaloneLayout } from './layout';
import { configFallback, configPromise } from './ui-config';
import { UserContextProvider } from './user-context';

// App entrypoint

Expand Down Expand Up @@ -94,16 +91,9 @@ function LoadConfig(_props) {
);
}

return (
<BrowserRouter basename={config.UI_BASE_PATH}>
<UserContextProvider>
<AppContextProvider>
<StandaloneLayout>
<AppRoutes />
</StandaloneLayout>
<UIVersion />
</AppContextProvider>
</UserContextProvider>
</BrowserRouter>
);
const router = createBrowserRouter(dataRoutes, {
basename: config.UI_BASE_PATH,
});

return <RouterProvider router={router} />;
}
6 changes: 3 additions & 3 deletions src/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ import {
SmallLogo,
StatefulDropdown,
} from 'src/components';
import { StandaloneMenu } from './menu';
import { PulpMenu } from './menu';
import { Paths, formatPath } from './paths';
import { useUserContext } from './user-context';

Expand Down Expand Up @@ -84,7 +84,7 @@ const UserDropdown = ({
/>
);

export const StandaloneLayout = ({ children }: { children: ReactNode }) => {
export const Layout = ({ children }: { children: ReactNode }) => {
const [aboutModalVisible, setAboutModalVisible] = useState<boolean>(false);
const { credentials, clearCredentials } = useUserContext();

Expand Down Expand Up @@ -127,7 +127,7 @@ export const StandaloneLayout = ({ children }: { children: ReactNode }) => {
const Sidebar = (
<PageSidebar>
<PageSidebarBody>
<StandaloneMenu />
<PulpMenu />
</PageSidebarBody>
</PageSidebar>
);
Expand Down
2 changes: 1 addition & 1 deletion src/menu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -286,7 +286,7 @@ function usePlugins() {
return plugins;
}

export const StandaloneMenu = () => {
export const PulpMenu = () => {
const [expandedSections, setExpandedSections] = useState([]);

const location = useLocation();
Expand Down
22 changes: 22 additions & 0 deletions src/routes/root.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { Outlet, useNavigation } from 'react-router';
import { AppContextProvider } from 'src/app-context';
import { LoadingSpinner, UIVersion } from 'src/components';
import { Layout } from 'src/layout';
import { UserContextProvider } from 'src/user-context';

export default function Root() {
const navigation = useNavigation();
const isNavigating = Boolean(navigation.location);

return (
<UserContextProvider>
<AppContextProvider>
<Layout>
{isNavigating && <LoadingSpinner />}
<Outlet />
</Layout>
<UIVersion />
</AppContextProvider>
</UserContextProvider>
);
}
14 changes: 7 additions & 7 deletions src/ui-config.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import defaults from '../pulp-ui-config.json';

export const configPromise = fetch('/pulp-ui-config.json').then((data) =>
data.status > 0 && data.status < 300
? data.json()
: Promise.reject(`${data.status}: ${data.statusText}`),
);
export const configPromise = fetch('/pulp-ui-config.json')
.then((data) =>
data.status > 0 && data.status < 300
? data.json()
: Promise.reject(`${data.status}: ${data.statusText}`),
)
.then((data) => (config = data));

export let config = null;

export const configFallback = () => (config = defaults);

configPromise.then((data) => (config = data));

0 comments on commit c88372f

Please sign in to comment.