refactor(App): migrate to Data Router for improved routing and unsaved changes handling
All checks were successful
Deploy Development / deploy (push) Successful in 41s
Test Suite / pytest-backend (push) Successful in 36s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 12s
Test Suite / playwright-tests (push) Successful in 56s

- Replaced `BrowserRouter` and `Routes` with `createBrowserRouter` and `RouterProvider` to support unsaved changes blocking.
- Restructured route definitions for better organization and clarity, maintaining existing functionality.
- Added comments to clarify the necessity of the Data Router for handling unsaved changes.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Lars 2026-05-13 17:19:59 +02:00
parent 49adb395dd
commit d50bed428b

View File

@ -1,8 +1,7 @@
import React from 'react'
import {
BrowserRouter as Router,
Routes,
Route,
RouterProvider,
createBrowserRouter,
Navigate,
NavLink,
useLocation,
@ -163,115 +162,115 @@ function PublicRoute({ children }) {
return !isAuthenticated ? children : <Navigate to="/" replace />
}
function AppRoutes() {
return (
<Routes>
<Route path="/verify" element={<VerifyPage />} />
<Route
path="/login"
element={
<PublicRoute>
<LoginPage />
</PublicRoute>
}
/>
{/* P-01: Öffentliche Rechtstextseiten — kein Auth erforderlich */}
<Route path="/impressum" element={<LegalPage type="impressum" />} />
<Route path="/datenschutz" element={<LegalPage type="datenschutz" />} />
<Route path="/nutzungsbedingungen" element={<LegalPage type="nutzungsbedingungen" />} />
<Route path="/medienrichtlinie" element={<LegalPage type="medienrichtlinie" />} />
<Route element={<ProtectedLayout />}>
<Route index element={<Dashboard />} />
<Route path="profile" element={<Navigate to="/settings" replace />} />
<Route path="settings" element={<AccountSettingsPage />} />
<Route path="settings/system" element={<SettingsSystemInfoPage />} />
<Route path="settings/legal" element={<SettingsLegalPage />} />
<Route path="media" element={<MediaLibraryPage />} />
<Route path="exercises">
<Route index element={<ExercisesListPage />} />
<Route path="new" element={<ExerciseFormPage />} />
<Route path=":id/edit" element={<ExerciseFormPage />} />
<Route path=":id" element={<ExerciseDetailPage />} />
</Route>
<Route path="clubs" element={<ClubsPage />} />
<Route path="inbox" element={<InboxPage />} />
<Route path="skills" element={<SkillsPage />} />
<Route path="planning/framework-programs/new" element={<TrainingFrameworkProgramEditPage />} />
<Route path="planning/framework-programs/:id" element={<TrainingFrameworkProgramEditPage />} />
<Route path="planning/framework-programs" element={<TrainingFrameworkProgramsListPage />} />
<Route path="planning/training-modules/new" element={<TrainingModuleEditPage />} />
<Route path="planning/training-modules/:id" element={<TrainingModuleEditPage />} />
<Route path="planning/training-modules" element={<TrainingModulesListPage />} />
<Route path="planning/run/:unitId/coach" element={<TrainingCoachPage />} />
<Route path="planning/run/:unitId" element={<TrainingUnitRunPage />} />
<Route path="planning" element={<TrainingPlanningPage />} />
<Route path="admin" element={<AdminHomeRedirect />} />
<Route
path="admin/users"
element={
<PlatformAdminRoute>
<AdminUsersPage />
</PlatformAdminRoute>
}
/>
<Route
path="admin/hierarchy"
element={
<PlatformAdminRoute>
<AdminHierarchyPage />
</PlatformAdminRoute>
}
/>
<Route
path="admin/maturity-models"
element={
<PlatformAdminRoute>
<AdminMaturityModelsPage />
</PlatformAdminRoute>
}
/>
<Route
path="admin/catalogs"
element={
<PlatformAdminRoute>
<AdminCatalogsPage />
</PlatformAdminRoute>
}
/>
<Route
path="admin/mediawiki-import"
element={
<PlatformAdminRoute>
<MediaWikiImportPage />
</PlatformAdminRoute>
}
/>
<Route
path="admin/legal-documents"
element={
<PlatformAdminRoute>
<AdminLegalDocumentsPage />
</PlatformAdminRoute>
}
/>
<Route path="trainer-contexts" element={<TrainerContextsPage />} />
</Route>
<Route path="*" element={<Navigate to="/" replace />} />
</Routes>
)
}
/**
* Data Router erforderlich für `useBlocker` (ungespeicherte Änderungen).
* Klassisches `BrowserRouter` stellt keinen DataRouterContext bereit; ohne Migration
* werfen Seiten mit `useUnsavedChangesBlocker` beim Rendern eine Invariante.
*/
const appRouter = createBrowserRouter([
{ path: '/verify', element: <VerifyPage /> },
{
path: '/login',
element: (
<PublicRoute>
<LoginPage />
</PublicRoute>
),
},
{ path: '/impressum', element: <LegalPage type="impressum" /> },
{ path: '/datenschutz', element: <LegalPage type="datenschutz" /> },
{ path: '/nutzungsbedingungen', element: <LegalPage type="nutzungsbedingungen" /> },
{ path: '/medienrichtlinie', element: <LegalPage type="medienrichtlinie" /> },
{
element: <ProtectedLayout />,
children: [
{ index: true, element: <Dashboard /> },
{ path: 'profile', element: <Navigate to="/settings" replace /> },
{ path: 'settings', element: <AccountSettingsPage /> },
{ path: 'settings/system', element: <SettingsSystemInfoPage /> },
{ path: 'settings/legal', element: <SettingsLegalPage /> },
{ path: 'media', element: <MediaLibraryPage /> },
{
path: 'exercises',
children: [
{ index: true, element: <ExercisesListPage /> },
{ path: 'new', element: <ExerciseFormPage /> },
{ path: ':id/edit', element: <ExerciseFormPage /> },
{ path: ':id', element: <ExerciseDetailPage /> },
],
},
{ path: 'clubs', element: <ClubsPage /> },
{ path: 'inbox', element: <InboxPage /> },
{ path: 'skills', element: <SkillsPage /> },
{ path: 'planning/framework-programs/new', element: <TrainingFrameworkProgramEditPage /> },
{ path: 'planning/framework-programs/:id', element: <TrainingFrameworkProgramEditPage /> },
{ path: 'planning/framework-programs', element: <TrainingFrameworkProgramsListPage /> },
{ path: 'planning/training-modules/new', element: <TrainingModuleEditPage /> },
{ path: 'planning/training-modules/:id', element: <TrainingModuleEditPage /> },
{ path: 'planning/training-modules', element: <TrainingModulesListPage /> },
{ path: 'planning/run/:unitId/coach', element: <TrainingCoachPage /> },
{ path: 'planning/run/:unitId', element: <TrainingUnitRunPage /> },
{ path: 'planning', element: <TrainingPlanningPage /> },
{ path: 'admin', element: <AdminHomeRedirect /> },
{
path: 'admin/users',
element: (
<PlatformAdminRoute>
<AdminUsersPage />
</PlatformAdminRoute>
),
},
{
path: 'admin/hierarchy',
element: (
<PlatformAdminRoute>
<AdminHierarchyPage />
</PlatformAdminRoute>
),
},
{
path: 'admin/maturity-models',
element: (
<PlatformAdminRoute>
<AdminMaturityModelsPage />
</PlatformAdminRoute>
),
},
{
path: 'admin/catalogs',
element: (
<PlatformAdminRoute>
<AdminCatalogsPage />
</PlatformAdminRoute>
),
},
{
path: 'admin/mediawiki-import',
element: (
<PlatformAdminRoute>
<MediaWikiImportPage />
</PlatformAdminRoute>
),
},
{
path: 'admin/legal-documents',
element: (
<PlatformAdminRoute>
<AdminLegalDocumentsPage />
</PlatformAdminRoute>
),
},
{ path: 'trainer-contexts', element: <TrainerContextsPage /> },
],
},
{ path: '*', element: <Navigate to="/" replace /> },
])
function App() {
return (
<AuthProvider>
<ToastProvider>
<Router>
<AppRoutes />
</Router>
<RouterProvider router={appRouter} />
</ToastProvider>
</AuthProvider>
)