feat: Add navigation and basic pages structure
All checks were successful
Deploy Development / deploy (push) Successful in 34s
All checks were successful
Deploy Development / deploy (push) Successful in 34s
This commit is contained in:
parent
3b2c3605fd
commit
c4b1b54f61
|
|
@ -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 />} />
|
||||
|
|
|
|||
119
frontend/src/components/Navigation.jsx
Normal file
119
frontend/src/components/Navigation.jsx
Normal 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
|
||||
17
frontend/src/pages/ClubsPage.jsx
Normal file
17
frontend/src/pages/ClubsPage.jsx
Normal 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
|
||||
|
|
@ -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>
|
||||
|
|
|
|||
17
frontend/src/pages/ExercisesPage.jsx
Normal file
17
frontend/src/pages/ExercisesPage.jsx
Normal 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
|
||||
60
frontend/src/pages/ProfilePage.jsx
Normal file
60
frontend/src/pages/ProfilePage.jsx
Normal 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
|
||||
Loading…
Reference in New Issue
Block a user