feat(App): implement code-splitting for improved performance and user experience
All checks were successful
Deploy Development / deploy (push) Successful in 39s
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
All checks were successful
Deploy Development / deploy (push) Successful in 39s
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
- Refactored the App component to utilize React's lazy loading for page components, enhancing load times and performance. - Introduced a fallback UI with a spinner during component loading, improving user feedback during navigation. - Updated the AuthContext to use useCallback and useMemo for optimized performance in login and logout functions, reducing unnecessary re-renders. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
85163ad440
commit
1c268555f6
|
|
@ -1,4 +1,4 @@
|
||||||
import React from 'react'
|
import React, { Suspense, lazy } from 'react'
|
||||||
import {
|
import {
|
||||||
RouterProvider,
|
RouterProvider,
|
||||||
createBrowserRouter,
|
createBrowserRouter,
|
||||||
|
|
@ -12,45 +12,66 @@ import { ToastProvider } from './context/ToastContext'
|
||||||
import { OrgInboxProvider, useOrgInbox } from './context/OrgInboxContext'
|
import { OrgInboxProvider, useOrgInbox } from './context/OrgInboxContext'
|
||||||
import DesktopSidebar from './components/DesktopSidebar'
|
import DesktopSidebar from './components/DesktopSidebar'
|
||||||
import { getMainNavItems } from './config/appNav'
|
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 InboxPage from './pages/InboxPage'
|
|
||||||
import SkillsPage from './pages/SkillsPage'
|
|
||||||
import TrainingPlanningPage from './pages/TrainingPlanningPage'
|
|
||||||
import TrainingFrameworkProgramsListPage from './pages/TrainingFrameworkProgramsListPage'
|
|
||||||
import TrainingFrameworkProgramEditPage from './pages/TrainingFrameworkProgramEditPage'
|
|
||||||
import TrainingModulesListPage from './pages/TrainingModulesListPage'
|
|
||||||
import TrainingModuleEditPage from './pages/TrainingModuleEditPage'
|
|
||||||
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 AdminHomeRedirect from './components/AdminHomeRedirect'
|
import AdminHomeRedirect from './components/AdminHomeRedirect'
|
||||||
import PlatformAdminRoute from './components/PlatformAdminRoute'
|
import PlatformAdminRoute from './components/PlatformAdminRoute'
|
||||||
import MediaLibraryPage from './pages/MediaLibraryPage'
|
|
||||||
import LegalPage from './pages/LegalPage'
|
|
||||||
import AdminLegalDocumentsPage from './pages/AdminLegalDocumentsPage'
|
|
||||||
import SettingsLegalPage from './pages/SettingsLegalPage'
|
|
||||||
import ActiveClubSwitcher from './components/ActiveClubSwitcher'
|
import ActiveClubSwitcher from './components/ActiveClubSwitcher'
|
||||||
import InactiveMembershipBanner from './components/InactiveMembershipBanner'
|
import InactiveMembershipBanner from './components/InactiveMembershipBanner'
|
||||||
import './app.css'
|
import './app.css'
|
||||||
|
|
||||||
|
const LoginPage = lazy(() => import('./pages/LoginPage'))
|
||||||
|
const VerifyPage = lazy(() => import('./pages/VerifyPage'))
|
||||||
|
const Dashboard = lazy(() => import('./pages/Dashboard'))
|
||||||
|
const AccountSettingsPage = lazy(() => import('./pages/AccountSettingsPage'))
|
||||||
|
const SettingsSystemInfoPage = lazy(() => import('./pages/SettingsSystemInfoPage'))
|
||||||
|
const ExercisesListPage = lazy(() => import('./pages/ExercisesListPage'))
|
||||||
|
const ExerciseDetailPage = lazy(() => import('./pages/ExerciseDetailPage'))
|
||||||
|
const ExerciseFormPage = lazy(() => import('./pages/ExerciseFormPage'))
|
||||||
|
const ClubsPage = lazy(() => import('./pages/ClubsPage'))
|
||||||
|
const InboxPage = lazy(() => import('./pages/InboxPage'))
|
||||||
|
const SkillsPage = lazy(() => import('./pages/SkillsPage'))
|
||||||
|
const TrainingPlanningPage = lazy(() => import('./pages/TrainingPlanningPage'))
|
||||||
|
const TrainingFrameworkProgramsListPage = lazy(() =>
|
||||||
|
import('./pages/TrainingFrameworkProgramsListPage'),
|
||||||
|
)
|
||||||
|
const TrainingFrameworkProgramEditPage = lazy(() =>
|
||||||
|
import('./pages/TrainingFrameworkProgramEditPage'),
|
||||||
|
)
|
||||||
|
const TrainingModulesListPage = lazy(() => import('./pages/TrainingModulesListPage'))
|
||||||
|
const TrainingModuleEditPage = lazy(() => import('./pages/TrainingModuleEditPage'))
|
||||||
|
const TrainingUnitRunPage = lazy(() => import('./pages/TrainingUnitRunPage'))
|
||||||
|
const TrainingCoachPage = lazy(() => import('./pages/TrainingCoachPage'))
|
||||||
|
const AdminCatalogsPage = lazy(() => import('./pages/AdminCatalogsPage'))
|
||||||
|
const AdminHierarchyPage = lazy(() => import('./pages/AdminHierarchyPage'))
|
||||||
|
const AdminMaturityModelsPage = lazy(() => import('./pages/AdminMaturityModelsPage'))
|
||||||
|
const TrainerContextsPage = lazy(() => import('./pages/TrainerContextsPage'))
|
||||||
|
const MediaWikiImportPage = lazy(() => import('./pages/MediaWikiImportPage'))
|
||||||
|
const AdminUsersPage = lazy(() => import('./pages/AdminUsersPage'))
|
||||||
|
const MediaLibraryPage = lazy(() => import('./pages/MediaLibraryPage'))
|
||||||
|
const LegalPage = lazy(() => import('./pages/LegalPage'))
|
||||||
|
const AdminLegalDocumentsPage = lazy(() => import('./pages/AdminLegalDocumentsPage'))
|
||||||
|
const SettingsLegalPage = lazy(() => import('./pages/SettingsLegalPage'))
|
||||||
|
|
||||||
/** Shield „Admin“: nur Super-Admin (global). Vereinsorga: Vereine → Mitglieder. */
|
/** Shield „Admin“: nur Super-Admin (global). Vereinsorga: Vereine → Mitglieder. */
|
||||||
function computeShowAdminNav(currentUser) {
|
function computeShowAdminNav(currentUser) {
|
||||||
return currentUser?.role === 'superadmin'
|
return currentUser?.role === 'superadmin'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function AppRouteFallback() {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
minHeight: '100vh',
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
background: 'var(--bg)',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div className="spinner"></div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
// Bottom Navigation (Mobile)
|
// Bottom Navigation (Mobile)
|
||||||
function Nav({ showAdminNav }) {
|
function Nav({ showAdminNav }) {
|
||||||
const { canAccessOrgInbox, inboxCount } = useOrgInbox()
|
const { canAccessOrgInbox, inboxCount } = useOrgInbox()
|
||||||
|
|
@ -270,7 +291,9 @@ function App() {
|
||||||
return (
|
return (
|
||||||
<AuthProvider>
|
<AuthProvider>
|
||||||
<ToastProvider>
|
<ToastProvider>
|
||||||
<RouterProvider router={appRouter} />
|
<Suspense fallback={<AppRouteFallback />}>
|
||||||
|
<RouterProvider router={appRouter} />
|
||||||
|
</Suspense>
|
||||||
</ToastProvider>
|
</ToastProvider>
|
||||||
</AuthProvider>
|
</AuthProvider>
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,12 @@
|
||||||
import { createContext, useContext, useState, useEffect, useCallback, useRef } from 'react'
|
import {
|
||||||
|
createContext,
|
||||||
|
useContext,
|
||||||
|
useState,
|
||||||
|
useEffect,
|
||||||
|
useCallback,
|
||||||
|
useMemo,
|
||||||
|
useRef,
|
||||||
|
} from 'react'
|
||||||
import api, { ACTIVE_CLUB_STORAGE_KEY } from '../utils/api'
|
import api, { ACTIVE_CLUB_STORAGE_KEY } from '../utils/api'
|
||||||
import { activeClubMemberships } from '../utils/activeClub'
|
import { activeClubMemberships } from '../utils/activeClub'
|
||||||
|
|
||||||
|
|
@ -94,7 +102,7 @@ export function AuthProvider({ children }) {
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
/** Fallback, falls ohne checkAuth gesetzt wird (Legacy / Token-Injektion) */
|
/** Fallback, falls ohne checkAuth gesetzt wird (Legacy / Token-Injektion) */
|
||||||
const login = (payload) => {
|
const login = useCallback((payload) => {
|
||||||
if (payload?.profile != null) {
|
if (payload?.profile != null) {
|
||||||
syncStoredActiveClub(payload.profile)
|
syncStoredActiveClub(payload.profile)
|
||||||
setUser(payload.profile)
|
setUser(payload.profile)
|
||||||
|
|
@ -112,9 +120,9 @@ export function AuthProvider({ children }) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
setUser(payload)
|
setUser(payload)
|
||||||
}
|
}, [])
|
||||||
|
|
||||||
const logout = () => {
|
const logout = useCallback(() => {
|
||||||
setUser(null)
|
setUser(null)
|
||||||
localStorage.removeItem('authToken')
|
localStorage.removeItem('authToken')
|
||||||
localStorage.removeItem(ACTIVE_CLUB_STORAGE_KEY)
|
localStorage.removeItem(ACTIVE_CLUB_STORAGE_KEY)
|
||||||
|
|
@ -123,17 +131,20 @@ export function AuthProvider({ children }) {
|
||||||
sessionStorage.removeItem(key)
|
sessionStorage.removeItem(key)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}, [])
|
||||||
|
|
||||||
const value = {
|
const value = useMemo(
|
||||||
user,
|
() => ({
|
||||||
isAuthenticated: !!user,
|
user,
|
||||||
loading,
|
isAuthenticated: !!user,
|
||||||
login,
|
loading,
|
||||||
logout,
|
login,
|
||||||
checkAuth,
|
logout,
|
||||||
setActiveClub,
|
checkAuth,
|
||||||
}
|
setActiveClub,
|
||||||
|
}),
|
||||||
|
[user, loading, login, logout, checkAuth, setActiveClub],
|
||||||
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AuthContext.Provider value={value}>
|
<AuthContext.Provider value={value}>
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,33 @@ export default defineConfig({
|
||||||
},
|
},
|
||||||
build: {
|
build: {
|
||||||
outDir: 'dist',
|
outDir: 'dist',
|
||||||
sourcemap: false
|
sourcemap: false,
|
||||||
}
|
rollupOptions: {
|
||||||
|
output: {
|
||||||
|
manualChunks(id) {
|
||||||
|
if (!id.includes('node_modules')) return
|
||||||
|
if (id.includes('jspdf')) return 'vendor-pdf'
|
||||||
|
if (id.includes('lucide-react')) return 'vendor-icons'
|
||||||
|
if (
|
||||||
|
id.includes('react-markdown') ||
|
||||||
|
id.includes('/marked/') ||
|
||||||
|
id.includes('remark-') ||
|
||||||
|
id.includes('mdast') ||
|
||||||
|
id.includes('micromark') ||
|
||||||
|
id.includes('unist')
|
||||||
|
) {
|
||||||
|
return 'vendor-markdown'
|
||||||
|
}
|
||||||
|
if (id.includes('react-router')) return 'vendor-router'
|
||||||
|
if (
|
||||||
|
/[/\\]node_modules[/\\]react-dom[/\\]/.test(id) ||
|
||||||
|
/[/\\]node_modules[/\\]react[/\\]/.test(id) ||
|
||||||
|
/[/\\]node_modules[/\\]scheduler[/\\]/.test(id)
|
||||||
|
) {
|
||||||
|
return 'vendor-react'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
})
|
})
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user