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 React from 'react'
|
||||||
import { BrowserRouter as Router, Routes, Route, Navigate } from 'react-router-dom'
|
import { BrowserRouter as Router, Routes, Route, Navigate } from 'react-router-dom'
|
||||||
import { AuthProvider, useAuth } from './context/AuthContext'
|
import { AuthProvider, useAuth } from './context/AuthContext'
|
||||||
|
import Navigation from './components/Navigation'
|
||||||
import LoginPage from './pages/LoginPage'
|
import LoginPage from './pages/LoginPage'
|
||||||
import Dashboard from './pages/Dashboard'
|
import Dashboard from './pages/Dashboard'
|
||||||
|
import ProfilePage from './pages/ProfilePage'
|
||||||
|
import ExercisesPage from './pages/ExercisesPage'
|
||||||
|
import ClubsPage from './pages/ClubsPage'
|
||||||
|
|
||||||
// Protected Route Component
|
// Protected Route Component
|
||||||
function ProtectedRoute({ children }) {
|
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)
|
// Public Route Component (redirect to dashboard if already logged in)
|
||||||
|
|
@ -68,6 +77,30 @@ function AppRoutes() {
|
||||||
</ProtectedRoute>
|
</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 */}
|
{/* Catch all - redirect to dashboard or login */}
|
||||||
<Route path="*" element={<Navigate to="/" replace />} />
|
<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 React, { useState, useEffect } from 'react'
|
||||||
import { useNavigate } from 'react-router-dom'
|
|
||||||
import { useAuth } from '../context/AuthContext'
|
import { useAuth } from '../context/AuthContext'
|
||||||
import api from '../utils/api'
|
import api from '../utils/api'
|
||||||
|
|
||||||
|
|
@ -7,8 +6,7 @@ function Dashboard() {
|
||||||
const [version, setVersion] = useState(null)
|
const [version, setVersion] = useState(null)
|
||||||
const [profile, setProfile] = useState(null)
|
const [profile, setProfile] = useState(null)
|
||||||
const [loading, setLoading] = useState(true)
|
const [loading, setLoading] = useState(true)
|
||||||
const { logout } = useAuth()
|
const { user } = useAuth()
|
||||||
const navigate = useNavigate()
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
loadData()
|
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) {
|
if (loading) {
|
||||||
return (
|
return (
|
||||||
<div style={{ padding: '2rem', textAlign: 'center' }}>
|
<div style={{ padding: '2rem', textAlign: 'center' }}>
|
||||||
|
|
@ -51,32 +37,15 @@ function Dashboard() {
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={{ minHeight: '100vh', background: 'var(--bg)', padding: '1rem' }}>
|
<div style={{ minHeight: '100vh', background: 'var(--bg)', padding: '2rem' }}>
|
||||||
{/* 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={{ maxWidth: '1200px', margin: '0 auto' }}>
|
<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 */}
|
{/* Welcome Card */}
|
||||||
<div className="card" style={{ marginBottom: '1.5rem' }}>
|
<div className="card" style={{ marginTop: '1.5rem', marginBottom: '1.5rem' }}>
|
||||||
<h2>Dashboard</h2>
|
<h2>Willkommen bei Shinkan Jinkendo</h2>
|
||||||
<p style={{ color: 'var(--text2)' }}>
|
<p style={{ color: 'var(--text2)' }}>
|
||||||
Trainer- und Vereinsplattform für Kampfsport-Trainingsplanung
|
Trainer- und Vereinsplattform für Kampfsport-Trainingsplanung
|
||||||
</p>
|
</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