diff --git a/frontend/index.html b/frontend/index.html
index c818fb6..de010d4 100644
--- a/frontend/index.html
+++ b/frontend/index.html
@@ -2,9 +2,20 @@
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
Shinkan Jinkendo
diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx
index 2575da2..e3fb0c3 100644
--- a/frontend/src/App.jsx
+++ b/frontend/src/App.jsx
@@ -1,16 +1,55 @@
import React from 'react'
-import { BrowserRouter as Router, Routes, Route, Navigate } from 'react-router-dom'
+import { BrowserRouter as Router, Routes, Route, Navigate, NavLink, useLocation } from 'react-router-dom'
import { AuthProvider, useAuth } from './context/AuthContext'
-import Navigation from './components/Navigation'
+import DesktopSidebar from './components/DesktopSidebar'
+import { getMainNavItems } from './config/appNav'
import LoginPage from './pages/LoginPage'
import Dashboard from './pages/Dashboard'
import ProfilePage from './pages/ProfilePage'
import ExercisesPage from './pages/ExercisesPage'
import ClubsPage from './pages/ClubsPage'
+import './app.css'
+
+// 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 (
+
+ )
+}
// Protected Route Component
function ProtectedRoute({ children }) {
- const { isAuthenticated, loading } = useAuth()
+ const { isAuthenticated, loading, user, logout } = useAuth()
+
+ const handleLogout = () => {
+ if (confirm('Wirklich abmelden?')) {
+ logout()
+ window.location.href = '/'
+ }
+ }
if (loading) {
return (
@@ -26,12 +65,30 @@ function ProtectedRoute({ children }) {
)
}
- return isAuthenticated ? (
+ if (!isAuthenticated) {
+ return
+ }
+
+ const isAdmin = user?.role === 'admin' || user?.role === 'superadmin'
+
+ return (
<>
-
- {children}
+
+
>
- ) :
+ )
}
// Public Route Component (redirect to dashboard if already logged in)
diff --git a/frontend/src/components/DesktopSidebar.jsx b/frontend/src/components/DesktopSidebar.jsx
new file mode 100644
index 0000000..4642c43
--- /dev/null
+++ b/frontend/src/components/DesktopSidebar.jsx
@@ -0,0 +1,86 @@
+import { NavLink, useLocation } from 'react-router-dom'
+import { LogOut } from 'lucide-react'
+import { getMainNavItems } from '../config/appNav'
+
+function sidebarLinkActive(pathname, item, routerIsActive) {
+ if (item.to.startsWith('/admin')) return pathname.startsWith('/admin')
+ return routerIsActive
+}
+
+/**
+ * Desktop-Sidebar (≥1024px) — Sichtbarkeit via CSS (.desktop-sidebar).
+ */
+export default function DesktopSidebar({
+ isAdmin,
+ user,
+ onLogout
+}) {
+ const loc = useLocation()
+ const items = getMainNavItems(isAdmin)
+ const tier = user?.tier || ''
+
+ return (
+
+ )
+}
diff --git a/frontend/src/config/appNav.js b/frontend/src/config/appNav.js
new file mode 100644
index 0000000..0aa93a7
--- /dev/null
+++ b/frontend/src/config/appNav.js
@@ -0,0 +1,48 @@
+import {
+ LayoutDashboard,
+ BookOpen,
+ Calendar,
+ Building2,
+ Settings,
+ Shield
+} from 'lucide-react'
+
+/**
+ * Shinkan Navigation Configuration
+ * Single source of truth für Bottom-Nav (Mobile) + Desktop-Sidebar
+ *
+ * @typedef {{ to: string, label: string, shortLabel?: string, end?: boolean, Icon: import('react').ForwardRefExoticComponent }} AppNavItem
+ */
+
+/** @returns {Omit[]} */
+function baseItems() {
+ return [
+ { to: '/', label: 'Übersicht', end: true },
+ { to: '/exercises', label: 'Übungen', shortLabel: 'Übungen' },
+ { to: '/planning', label: 'Planung' },
+ { to: '/clubs', label: 'Vereine' },
+ { to: '/settings', label: 'Einstellungen', shortLabel: 'Einst.' }
+ ]
+}
+
+/** @param {boolean} isAdmin */
+export function getMainNavItems(isAdmin) {
+ const icons = [
+ LayoutDashboard,
+ BookOpen,
+ Calendar,
+ Building2,
+ Settings
+ ]
+
+ const raw = baseItems().map((item, i) => ({
+ ...item,
+ Icon: icons[i]
+ }))
+
+ if (isAdmin) {
+ raw.push({ to: '/admin', label: 'Admin', end: false, Icon: Shield })
+ }
+
+ return raw
+}