shinkan-jinkendo/frontend/src/App.jsx
Lars c1d1c2d7e0
Some checks failed
Deploy Development / deploy (push) Successful in 35s
Test Suite / pytest-backend (push) Successful in 25s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 7s
Test Suite / playwright-tests (push) Failing after 1m43s
feat: implement lazy loading for MediaLibraryPage and optimize video rendering
- Refactored MediaLibraryPage to use React's lazy loading and Suspense for improved performance during page load.
- Updated video rendering logic to utilize createElement for better flexibility and maintainability.
- Enhanced loading state with a spinner to improve user experience while media content is being fetched.
2026-05-08 09:02:19 +02:00

224 lines
7.0 KiB
JavaScript

import React, { lazy, Suspense } from 'react'
import {
BrowserRouter as Router,
Routes,
Route,
Navigate,
NavLink,
useLocation,
Outlet,
} from 'react-router-dom'
import { AuthProvider, useAuth } from './context/AuthContext'
import DesktopSidebar from './components/DesktopSidebar'
import { getMainNavItems } from './config/appNav'
import LoginPage from './pages/LoginPage'
import VerifyPage from './pages/VerifyPage'
import Dashboard from './pages/Dashboard'
import AccountSettingsPage from './pages/AccountSettingsPage'
import SettingsSystemInfoPage from './pages/SettingsSystemInfoPage'
import ExercisesListPage from './pages/ExercisesListPage'
import ExerciseDetailPage from './pages/ExerciseDetailPage'
import ExerciseFormPage from './pages/ExerciseFormPage'
import ClubsPage from './pages/ClubsPage'
import SkillsPage from './pages/SkillsPage'
import TrainingPlanningPage from './pages/TrainingPlanningPage'
import TrainingFrameworkProgramsListPage from './pages/TrainingFrameworkProgramsListPage'
import TrainingFrameworkProgramEditPage from './pages/TrainingFrameworkProgramEditPage'
import TrainingUnitRunPage from './pages/TrainingUnitRunPage'
import TrainingCoachPage from './pages/TrainingCoachPage'
import AdminCatalogsPage from './pages/AdminCatalogsPage'
import AdminHierarchyPage from './pages/AdminHierarchyPage'
import AdminMaturityModelsPage from './pages/AdminMaturityModelsPage'
import TrainerContextsPage from './pages/TrainerContextsPage'
import MediaWikiImportPage from './pages/MediaWikiImportPage'
import AdminUsersPage from './pages/AdminUsersPage'
import ActiveClubSwitcher from './components/ActiveClubSwitcher'
import './app.css'
const MediaLibraryPage = lazy(() => import('./pages/MediaLibraryPage'))
// Bottom Navigation (Mobile)
function Nav({ isAdmin }) {
const items = getMainNavItems(isAdmin)
const loc = useLocation()
const navItemActive = (pathname, item, routerIsActive) => {
if (item.to.startsWith('/admin')) return pathname.startsWith('/admin')
return routerIsActive
}
return (
<nav className="bottom-nav">
{items.map((item) => (
<NavLink
key={item.to}
to={item.to}
end={!!item.end}
className={({ isActive }) =>
'nav-item' + (navItemActive(loc.pathname, item, isActive) ? ' active' : '')
}
>
<item.Icon size={26} strokeWidth={2} />
<span>{item.shortLabel || item.label}</span>
</NavLink>
))}
</nav>
)
}
function ProtectedLayout() {
const { isAuthenticated, loading, user, logout } = useAuth()
const handleLogout = () => {
if (confirm('Wirklich abmelden?')) {
logout()
window.location.href = '/'
}
}
if (loading) {
return (
<div
style={{
minHeight: '100vh',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
background: 'var(--bg)',
}}
>
<div className="spinner"></div>
</div>
)
}
if (!isAuthenticated) {
return <Navigate to="/login" replace />
}
const isAdmin = user?.role === 'admin' || user?.role === 'superadmin'
return (
<>
<DesktopSidebar isAdmin={isAdmin} user={user} onLogout={handleLogout} />
<div className="app-shell">
<div className="app-shell__column">
<div className="app-header app-header--mobile app-header--mobile-stack">
<div className="app-header-mobile__top">
<div className="app-logo">🥋 Shinkan</div>
</div>
<ActiveClubSwitcher variant="mobile" />
</div>
<div className="app-main">
<Outlet />
</div>
<Nav isAdmin={isAdmin} />
</div>
</div>
</>
)
}
function PublicRoute({ children }) {
const { isAuthenticated, loading } = useAuth()
if (loading) {
return (
<div
style={{
minHeight: '100vh',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
background: 'var(--bg)',
}}
>
<div className="spinner"></div>
</div>
)
}
return !isAuthenticated ? children : <Navigate to="/" replace />
}
function AppRoutes() {
return (
<Routes>
<Route path="/verify" element={<VerifyPage />} />
<Route
path="/login"
element={
<PublicRoute>
<LoginPage />
</PublicRoute>
}
/>
<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="media"
element={
<Suspense
fallback={
<div
style={{
minHeight: '50vh',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
background: 'var(--bg)',
}}
>
<div className="spinner" />
</div>
}
>
<MediaLibraryPage />
</Suspense>
}
/>
<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="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/run/:unitId/coach" element={<TrainingCoachPage />} />
<Route path="planning/run/:unitId" element={<TrainingUnitRunPage />} />
<Route path="planning" element={<TrainingPlanningPage />} />
<Route path="admin" element={<Navigate to="/admin/hierarchy" replace />} />
<Route path="admin/users" element={<AdminUsersPage />} />
<Route path="admin/hierarchy" element={<AdminHierarchyPage />} />
<Route path="admin/maturity-models" element={<AdminMaturityModelsPage />} />
<Route path="admin/catalogs" element={<AdminCatalogsPage />} />
<Route path="admin/mediawiki-import" element={<MediaWikiImportPage />} />
<Route path="trainer-contexts" element={<TrainerContextsPage />} />
</Route>
<Route path="*" element={<Navigate to="/" replace />} />
</Routes>
)
}
function App() {
return (
<AuthProvider>
<Router>
<AppRoutes />
</Router>
</AuthProvider>
)
}
export default App