refactor(web): migrate to data router (createBrowserRouter) to enable useBlocker (#46)

Convert app.tsx route tree verbatim to a module-level data router via
createRoutesFromElements + RouterProvider, and the test harness to
createMemoryRouter + RouterProvider. The search NavLink-click test now mounts
its routes as real data-router routes so RouterProvider intercepts the link
(descendant <Routes> under a catch-all let it fall through to a jsdom navigation).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-07 23:07:03 +02:00
parent f3881e8c7c
commit ed0c13907c
3 changed files with 83 additions and 49 deletions
+46 -44
View File
@@ -1,5 +1,5 @@
import { lazy, Suspense } from "react";
import { BrowserRouter, Navigate, Route, Routes } from "react-router-dom";
import { createBrowserRouter, createRoutesFromElements, Navigate, Route, RouterProvider } from "react-router-dom";
import { RequireAuth } from "./auth/require-auth";
import { LoginPage } from "./auth/login-page";
@@ -29,55 +29,57 @@ function FormFallback() {
return <div role="status" className="p-4 text-sm text-muted-foreground">Loading</div>;
}
export function App() {
return (
<BrowserRouter>
<Routes>
<Route path="/login" element={<LoginPage />} />
<Route element={<RequireAuth />}>
<Route element={<AppShell />}>
const router = createBrowserRouter(
createRoutesFromElements(
<>
<Route path="/login" element={<LoginPage />} />
<Route element={<RequireAuth />}>
<Route element={<AppShell />}>
<Route
path="/objects/new"
element={
<Suspense fallback={<FormFallback />}>
<ObjectNewPage />
</Suspense>
}
/>
<Route path="/objects" element={<ObjectsPage />}>
<Route path=":id" element={<ObjectDetail />} />
<Route
path="/objects/new"
path=":id/edit"
element={
<Suspense fallback={<FormFallback />}>
<ObjectNewPage />
<ObjectEditForm />
</Suspense>
}
/>
<Route path="/objects" element={<ObjectsPage />}>
<Route path=":id" element={<ObjectDetail />} />
<Route
path=":id/edit"
element={
<Suspense fallback={<FormFallback />}>
<ObjectEditForm />
</Suspense>
}
/>
</Route>
<Route path="/vocabularies" element={<VocabulariesPage />}>
<Route index element={<SelectVocabularyPrompt />} />
<Route path=":id" element={<VocabularyTerms />} />
</Route>
<Route path="/search" element={<SearchPage />}>
<Route index element={<SelectSearchPrompt />} />
<Route path=":id" element={<ObjectDetail />} />
</Route>
<Route path="/authorities" element={<Navigate to="/authorities/person" replace />} />
<Route path="/authorities/:kind" element={<AuthoritiesPage />} />
<Route
path="/fields"
element={
<Suspense fallback={<FormFallback />}>
<FieldsPage />
</Suspense>
}
/>
<Route path="/" element={<Navigate to="/objects" replace />} />
</Route>
<Route path="/vocabularies" element={<VocabulariesPage />}>
<Route index element={<SelectVocabularyPrompt />} />
<Route path=":id" element={<VocabularyTerms />} />
</Route>
<Route path="/search" element={<SearchPage />}>
<Route index element={<SelectSearchPrompt />} />
<Route path=":id" element={<ObjectDetail />} />
</Route>
<Route path="/authorities" element={<Navigate to="/authorities/person" replace />} />
<Route path="/authorities/:kind" element={<AuthoritiesPage />} />
<Route
path="/fields"
element={
<Suspense fallback={<FormFallback />}>
<FieldsPage />
</Suspense>
}
/>
<Route path="/" element={<Navigate to="/objects" replace />} />
</Route>
<Route path="*" element={<Navigate to="/objects" replace />} />
</Routes>
</BrowserRouter>
);
</Route>
<Route path="*" element={<Navigate to="/objects" replace />} />
</>,
),
);
export function App() {
return <RouterProvider router={router} />;
}