From 46a90ae910a8e3b794c6dc0baf89b674683369c3 Mon Sep 17 00:00:00 2001 From: Lars Date: Wed, 22 Apr 2026 16:25:36 +0200 Subject: [PATCH] feat: Complete design foundation with responsive navigation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Design System: - app.css already exists with full design tokens, dark mode, responsive breakpoints - CSS variables for colors, spacing, typography - Mobile-first layout with safe-area support (iOS notch) Navigation System: - config/appNav.js: Single source of truth for navigation items - Bottom-Nav for mobile (<1024px) with horizontal scrolling - DesktopSidebar for desktop (≥1024px) with fixed left sidebar - Role-based navigation (isAdmin adds Admin link) App.jsx Restructure: - Responsive layout: Bottom-Nav (mobile) + DesktopSidebar (desktop) - Protected/Public routes with loading states - Logout handler with confirmation PWA Setup: - viewport-fit=cover for notch support - apple-mobile-web-app meta tags - Icon links prepared (icons pending) Architecture: - Follows Mitai design patterns - Responsive breakpoint at 1024px - Single source of truth for navigation config Next: Icons, Exercise CRUD, Clubs/Groups Management --- frontend/index.html | 15 +++- frontend/src/App.jsx | 71 ++++++++++++++++-- frontend/src/components/DesktopSidebar.jsx | 86 ++++++++++++++++++++++ frontend/src/config/appNav.js | 48 ++++++++++++ 4 files changed, 211 insertions(+), 9 deletions(-) create mode 100644 frontend/src/components/DesktopSidebar.jsx create mode 100644 frontend/src/config/appNav.js 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} + +
+
+
+
🥋 Shinkan
+
+
{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 +}