feat: Add navigation and basic pages structure
All checks were successful
Deploy Development / deploy (push) Successful in 34s

This commit is contained in:
Lars 2026-04-22 06:48:18 +02:00
parent 3b2c3605fd
commit c4b1b54f61
6 changed files with 255 additions and 40 deletions

View File

@ -1,8 +1,12 @@
import React from 'react'
import { BrowserRouter as Router, Routes, Route, Navigate } from 'react-router-dom'
import { AuthProvider, useAuth } from './context/AuthContext'
import Navigation from './components/Navigation'
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'
// Protected Route Component
function ProtectedRoute({ children }) {
@ -22,7 +26,12 @@ function ProtectedRoute({ children }) {
)
}
return isAuthenticated ? children : <Navigate to="/login" replace />
return isAuthenticated ? (
<>
<Navigation />
{children}
</>
) : <Navigate to="/login" replace />
}
// Public Route Component (redirect to dashboard if already logged in)
@ -68,6 +77,30 @@ function AppRoutes() {
</ProtectedRoute>
}
/>
<Route
path="/profile"
element={
<ProtectedRoute>
<ProfilePage />
</ProtectedRoute>
}
/>
<Route
path="/exercises"
element={
<ProtectedRoute>
<ExercisesPage />
</ProtectedRoute>
}
/>
<Route
path="/clubs"
element={
<ProtectedRoute>
<ClubsPage />
</ProtectedRoute>
}
/>
{/* Catch all - redirect to dashboard or login */}
<Route path="*" element={<Navigate to="/" replace />} />

View File

@ -0,0 +1,119 @@
import { Link, useLocation, useNavigate } from 'react-router-dom'
import { useAuth } from '../context/AuthContext'
function Navigation() {
const location = useLocation()
const navigate = useNavigate()
const { user, logout } = useAuth()
const handleLogout = async () => {
await logout()
navigate('/login')
}
const isActive = (path) => location.pathname === path
return (
<nav style={{
background: 'var(--surface)',
borderBottom: '1px solid var(--border)',
padding: '0 1rem',
position: 'sticky',
top: 0,
zIndex: 1000
}}>
<div style={{
maxWidth: '1200px',
margin: '0 auto',
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
height: '60px'
}}>
{/* Logo/Title */}
<Link to="/" style={{
fontSize: '1.25rem',
fontWeight: 600,
color: 'var(--text1)',
textDecoration: 'none'
}}>
🥋 Shinkan
</Link>
{/* Main Navigation */}
<div style={{ display: 'flex', gap: '1rem', alignItems: 'center' }}>
<Link
to="/"
style={{
color: isActive('/') ? 'var(--accent)' : 'var(--text2)',
textDecoration: 'none',
fontWeight: isActive('/') ? 600 : 400
}}
>
Dashboard
</Link>
<Link
to="/exercises"
style={{
color: isActive('/exercises') ? 'var(--accent)' : 'var(--text2)',
textDecoration: 'none',
fontWeight: isActive('/exercises') ? 600 : 400
}}
>
Übungen
</Link>
<Link
to="/clubs"
style={{
color: isActive('/clubs') ? 'var(--accent)' : 'var(--text2)',
textDecoration: 'none',
fontWeight: isActive('/clubs') ? 600 : 400
}}
>
Vereine
</Link>
<Link
to="/profile"
style={{
color: isActive('/profile') ? 'var(--accent)' : 'var(--text2)',
textDecoration: 'none',
fontWeight: isActive('/profile') ? 600 : 400
}}
>
Profil
</Link>
{/* User Menu */}
<div style={{
borderLeft: '1px solid var(--border)',
paddingLeft: '1rem',
marginLeft: '0.5rem',
display: 'flex',
alignItems: 'center',
gap: '0.75rem'
}}>
<span style={{ color: 'var(--text2)', fontSize: '0.875rem' }}>
{user?.name || user?.email}
</span>
<button
onClick={handleLogout}
style={{
padding: '0.5rem 1rem',
background: 'transparent',
border: '1px solid var(--border)',
borderRadius: '6px',
color: 'var(--text2)',
cursor: 'pointer',
fontSize: '0.875rem'
}}
>
Abmelden
</button>
</div>
</div>
</div>
</nav>
)
}
export default Navigation

View File

@ -0,0 +1,17 @@
function ClubsPage() {
return (
<div style={{ padding: '2rem' }}>
<div style={{ maxWidth: '1200px', margin: '0 auto' }}>
<h1>Vereinsverwaltung</h1>
<div className="card" style={{ marginTop: '1.5rem' }}>
<p style={{ color: 'var(--text2)' }}>
Vereinsverwaltung folgt in Kürze
</p>
</div>
</div>
</div>
)
}
export default ClubsPage

View File

@ -1,5 +1,4 @@
import React, { useState, useEffect } from 'react'
import { useNavigate } from 'react-router-dom'
import { useAuth } from '../context/AuthContext'
import api from '../utils/api'
@ -7,8 +6,7 @@ function Dashboard() {
const [version, setVersion] = useState(null)
const [profile, setProfile] = useState(null)
const [loading, setLoading] = useState(true)
const { logout } = useAuth()
const navigate = useNavigate()
const { user } = useAuth()
useEffect(() => {
loadData()
@ -29,18 +27,6 @@ function Dashboard() {
}
}
const handleLogout = async () => {
try {
await api.logout()
} catch (err) {
console.error('Logout error:', err)
} finally {
localStorage.removeItem('authToken')
logout()
navigate('/login')
}
}
if (loading) {
return (
<div style={{ padding: '2rem', textAlign: 'center' }}>
@ -51,32 +37,15 @@ function Dashboard() {
}
return (
<div style={{ minHeight: '100vh', background: 'var(--bg)', padding: '1rem' }}>
{/* Header */}
<div style={{
maxWidth: '1200px',
margin: '0 auto',
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
marginBottom: '2rem'
}}>
<div>
<h1 style={{ margin: 0 }}>🥋 Shinkan Jinkendo</h1>
<p style={{ color: 'var(--text2)', margin: '0.25rem 0 0 0' }}>
Willkommen, {profile?.name || profile?.email}
</p>
</div>
<button className="btn btn-secondary" onClick={handleLogout}>
Abmelden
</button>
</div>
{/* Main Content */}
<div style={{ minHeight: '100vh', background: 'var(--bg)', padding: '2rem' }}>
<div style={{ maxWidth: '1200px', margin: '0 auto' }}>
<h1>Dashboard</h1>
<p style={{ color: 'var(--text2)', marginTop: '0.5rem' }}>
Willkommen, {user?.name || user?.email}!
</p>
{/* Welcome Card */}
<div className="card" style={{ marginBottom: '1.5rem' }}>
<h2>Dashboard</h2>
<div className="card" style={{ marginTop: '1.5rem', marginBottom: '1.5rem' }}>
<h2>Willkommen bei Shinkan Jinkendo</h2>
<p style={{ color: 'var(--text2)' }}>
Trainer- und Vereinsplattform für Kampfsport-Trainingsplanung
</p>

View File

@ -0,0 +1,17 @@
function ExercisesPage() {
return (
<div style={{ padding: '2rem' }}>
<div style={{ maxWidth: '1200px', margin: '0 auto' }}>
<h1>Übungsverwaltung</h1>
<div className="card" style={{ marginTop: '1.5rem' }}>
<p style={{ color: 'var(--text2)' }}>
Übungsverwaltung wird als nächstes implementiert
</p>
</div>
</div>
</div>
)
}
export default ExercisesPage

View File

@ -0,0 +1,60 @@
import { useAuth } from '../context/AuthContext'
function ProfilePage() {
const { user } = useAuth()
return (
<div style={{ padding: '2rem' }}>
<div style={{ maxWidth: '800px', margin: '0 auto' }}>
<h1>Profil</h1>
<div className="card" style={{ marginTop: '1.5rem' }}>
<h2>Persönliche Daten</h2>
<div style={{ marginTop: '1rem' }}>
<div style={{ display: 'grid', gridTemplateColumns: '150px 1fr', gap: '1rem', marginBottom: '1rem' }}>
<strong>Name:</strong>
<span>{user?.name || '-'}</span>
<strong>E-Mail:</strong>
<span>{user?.email}</span>
<strong>Rolle:</strong>
<span style={{
padding: '0.25rem 0.5rem',
background: user?.role === 'admin' ? 'var(--accent)' : 'var(--surface2)',
color: user?.role === 'admin' ? 'white' : 'var(--text1)',
borderRadius: '4px',
display: 'inline-block',
fontSize: '0.875rem'
}}>
{user?.role || 'user'}
</span>
<strong>Tier:</strong>
<span style={{
padding: '0.25rem 0.5rem',
background: user?.tier === 'premium' ? 'var(--accent)' : 'var(--surface2)',
color: user?.tier === 'premium' ? 'white' : 'var(--text1)',
borderRadius: '4px',
display: 'inline-block',
fontSize: '0.875rem'
}}>
{user?.tier || 'free'}
</span>
</div>
</div>
</div>
<div className="card" style={{ marginTop: '1.5rem' }}>
<h2>Einstellungen</h2>
<p style={{ color: 'var(--text2)', marginTop: '0.5rem' }}>
Bearbeitung folgt in Kürze
</p>
</div>
</div>
</div>
)
}
export default ProfilePage