ADD: added database connection for players data handling and started login funtion with database

This commit is contained in:
hwinkel
2025-05-30 15:02:23 +02:00
parent 4158b87576
commit 1a2eec44a9
19 changed files with 924 additions and 58 deletions

View File

@@ -6,6 +6,7 @@ import Dashboard from './pages/Dashboard';
import TournamentDetails from './pages/TournamentDetails';
import Players from './pages/Players';
import Navigation from './pages/Navigation';
import TeamManagement from './pages/Teams';
function App() {
@@ -19,6 +20,8 @@ function App() {
{/* Geschützte Routen */}
<Route path="/players" element={<ProtectedRoute><Players /></ProtectedRoute>} />
<Route path="/teams" element={<ProtectedRoute><TeamManagement /></ProtectedRoute>} />
<Route path="/tournaments/:id" element={<ProtectedRoute><TournamentDetails /></ProtectedRoute>} />
</Routes>
</Router>

View File

@@ -0,0 +1,23 @@
// utils/jwt.ts
import { jwtDecode } from 'jwt-decode';
export interface TokenPayload {
userId: string;
email: string;
role: string;
exp: number;
}
export function getUserFromToken(token: string): TokenPayload | null {
try {
const decoded = jwtDecode<TokenPayload>(token);
if (decoded.exp && decoded.exp < Date.now() / 1000) {
console.warn("Token ist abgelaufen");
return null;
}
return decoded;
} catch (error) {
console.error("Fehler beim Decodieren des Tokens:", error);
return null;
}
}

View File

@@ -1,4 +1,5 @@
import React, { createContext, useContext, useState, useEffect } from 'react';
import { getUserFromToken } from '../components/utils/jwt';
interface AuthContextType {
token: string | null;
@@ -17,10 +18,26 @@ export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ children
useEffect(() => {
const storedToken = localStorage.getItem('token');
const storedUserId = localStorage.getItem('userId');
if (!storedToken) {
return
}
if (storedToken && storedUserId) {
setToken(storedToken);
setUserId(storedUserId);
}
const user = getUserFromToken(storedToken);
if (!user) {
logout(); // z.B. localStorage.clear() + navigate("/login")
return;
}
// ⏳ Logout bei Ablauf
const timeout = setTimeout(() => {
logout();
}, user.exp * 1000 - Date.now());
return () => clearTimeout(timeout);
}, []);
const login = (token: string, userId: string, role: string) => {
@@ -39,6 +56,8 @@ export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ children
localStorage.removeItem('token');
localStorage.removeItem('userId');
localStorage.removeItem('role');
localStorage.clear(); // Optional: Entfernt alle gespeicherten Daten
// Hier könntest du auch eine Weiterleitung zur Login-Seite hinzufügen
};
return (

View File

@@ -1,9 +1,9 @@
import { useState } from 'react';
import { useAuth } from './AuthContext';
import { login as apiLogin } from './api';
import { jwtDecode } from 'jwt-decode';
// import { jwtDecode } from 'jwt-decode';
import { useNavigate } from 'react-router-dom';
import { getUserFromToken } from '../components/utils/jwt';
export default function LoginPage() {
const { login } = useAuth();
const [email, setEmail] = useState('');
@@ -18,12 +18,13 @@ export default function LoginPage() {
const data = await apiLogin(email, password);
// Token aus JWT extrahieren (hier: UserID im Token Payload)
// Für Demo: Einfach Dummy UserID setzen, oder später JWT decode implementieren
type MyJwtPayload = {
userId: string
email: string;
role: string;
} & object;
const decodedData = jwtDecode<MyJwtPayload>(data.token);
var decodedData = getUserFromToken(data.token);
if (!decodedData || !decodedData.userId || !decodedData.role) {
setError('Ungültiges Token');
return;
}
// Dummy UserID für Demo
login(data.token, decodedData.userId, decodedData.role);
// Nach dem Login zur Dashboard-Seite navigieren
setTimeout(() => {

View File

@@ -9,6 +9,7 @@ export default function Navigation() {
<div className="space-x-4">
<Link to="/">Dashboard</Link>
{token && <Link to="/players">Spieler</Link>}
{token && <Link to="/teams">Teams</Link>}
</div>
<div>
{token ? (
@@ -16,7 +17,11 @@ export default function Navigation() {
Logout
</button>
) : (
<Link to="/login" className="px-3 py-1 border rounded">Login</Link>
<>
<Link to="/register" className="px-3 py-1 border rounded">Register</Link>
<span className="mx-2"></span>
<Link to="/login" className="px-3 py-1 border rounded bg-green-500 text-white">Login</Link>
</>
)}
</div>
</nav>

View File

@@ -1,46 +1,81 @@
import React, { useState } from 'react';
import React, { useEffect, useState } from 'react';
import { fetchPlayers,createPlayer,deletePlayer,updatePlayer } from './api';
interface Player {
id: number;
id: string;
name: string;
position: string;
email: string;
}
export default function PlayerManagement() {
const [players, setPlayers] = useState<Player[]>([]);
const [name, setName] = useState("");
const [position, setPosition] = useState("");
const [editingId, setEditingId] = useState<number | null>(null);
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const [editingId, setEditingId] = useState<string | null>(null);
const token = localStorage.getItem('token');
useEffect(() => {
if (token) loadPlayers(token);
}, [token]);
const loadPlayers = async (token: string) => {
try {
const data = await fetchPlayers(token);
console.log("Geladene Spieler:", data);
setPlayers(data);
} catch (error) {
console.error("Fehler beim Laden der Spieler:", error);
}
};
const handleAddOrUpdate = () => {
if (!name || !position) return;
if (!name || !email) return;
if (editingId !== null) {
setPlayers(players.map(p =>
p.id === editingId ? { ...p, name, position } : p
p.id === editingId ? { ...p, name, email } : p
));
if (token) {
updatePlayer(editingId, { name, email }, token);
}
setEditingId(null);
} else {
const newPlayer: Player = {
id: Date.now(),
id: "",
name,
position,
email,
};
setPlayers([...players, newPlayer]);
if (token) {
createPlayer(newPlayer, token);
setPlayers([...players, newPlayer]);
}
}
setName("");
setPosition("");
setEmail("");
};
const handleEdit = (player: Player) => {
setName(player.name);
setPosition(player.position);
setEmail(player.email);
setEditingId(player.id);
};
const handleDelete = (id: number) => {
const handleDelete = (id: string) => {
setPlayers(players.filter(p => p.id !== id));
if (token) {
deletePlayer(id, token);
}
if (editingId === id) {
setEditingId(null);
setName("");
setEmail("");
}
};
return (
@@ -57,9 +92,16 @@ export default function PlayerManagement() {
/>
<input
type="text"
placeholder="Position (z.B. Zuspieler)"
value={position}
onChange={(e) => setPosition(e.target.value)}
placeholder="Email"
value={email}
onChange={(e) => setEmail(e.target.value)}
className="border p-2 rounded"
/>
<input
type="password"
placeholder="Password"
value={password}
onChange={(e) => setPassword(e.target.value)}
className="border p-2 rounded"
/>
</div>
@@ -75,7 +117,7 @@ export default function PlayerManagement() {
<thead>
<tr className="bg-gray-100 text-left">
<th className="border px-4 py-2">Name</th>
<th className="border px-4 py-2">Position</th>
<th className="border px-4 py-2">Email</th>
<th className="border px-4 py-2">Aktionen</th>
</tr>
</thead>
@@ -83,7 +125,7 @@ export default function PlayerManagement() {
{players.map(player => (
<tr key={player.id}>
<td className="border px-4 py-2">{player.name}</td>
<td className="border px-4 py-2">{player.position}</td>
<td className="border px-4 py-2">{player.email}</td>
<td className="border px-4 py-2 space-x-2">
<button
onClick={() => handleEdit(player)}

View File

@@ -0,0 +1,142 @@
import React, { useEffect, useState } from 'react';
import { useNavigate } from 'react-router-dom';
import { getUserFromToken } from '../components/utils/jwt'; // Importiere die Funktion zum Decodieren des Tokens
interface Team {
id: string;
name: string;
players: string[]; // Emails oder Namen
createdBy: string;
}
const TeamManagement = () => {
const [teams, setTeams] = useState<Team[]>([]);
const [name, setName] = useState('');
const [player2, setPlayer2] = useState('');
const [error, setError] = useState('');
const [editingTeam, setEditingTeam] = useState<Team | null>(null);
const token = localStorage.getItem('token');
const user = token ? getUserFromToken(token) : null;
const isAdmin = user?.role === 'admin';
const fetchTeams = async () => {
const res = await fetch('/api/teams', {
headers: { Authorization: `Bearer ${token}` }
});
const data = await res.json();
if (res.ok) setTeams(data.data || []);
else setError(data.error || 'Fehler beim Laden der Teams');
};
useEffect(() => {
if (token) fetchTeams();
}, [token]);
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
const body = {
name,
players: [user?.email, player2],
};
const method = editingTeam ? 'PUT' : 'POST';
const url = editingTeam ? `/api/teams/${editingTeam.id}` : '/api/teams';
const res = await fetch(url, {
method,
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${token}`,
},
body: JSON.stringify(body),
});
const data = await res.json();
if (res.ok) {
setName('');
setPlayer2('');
setEditingTeam(null);
fetchTeams();
} else {
setError(data.error || 'Fehler beim Speichern');
}
};
const handleDelete = async (id: string) => {
const res = await fetch(`/api/teams/${id}`, {
method: 'DELETE',
headers: { Authorization: `Bearer ${token}` },
});
if (res.ok) fetchTeams();
else setError('Löschen fehlgeschlagen');
};
const canEdit = (team: Team) => isAdmin;
const canDelete = (team: Team) =>
isAdmin || team.createdBy === user?.userId;
const canCreate = () => isAdmin || true; // Normale Nutzer dürfen eigene Teams erstellen
return (
<div className="max-w-2xl mx-auto p-6 bg-white rounded-xl shadow-md mt-6">
<h2 className="text-2xl font-bold mb-4">Team Management</h2>
{error && <p style={{ color: 'red' }}>{error}</p>}
{canCreate() && (
<form onSubmit={handleSubmit} className="grid grid-cols-1 sm:grid-cols-2 gap-4 mb-4">
<h3>{editingTeam ? 'Team bearbeiten' : 'Team erstellen'}</h3>
<input
className="border p-2 rounded"
type="text"
placeholder="Teamname"
value={name}
onChange={(e) => setName(e.target.value)}
required
/>
<input
className="border p-2 rounded"
type="email"
placeholder="E-Mail von Mitspieler"
value={player2}
onChange={(e) => setPlayer2(e.target.value)}
required={!isAdmin}
/>
<button type="submit"
className="bg-blue-600 text-white px-4 py-2 rounded hover:bg-blue-700"
>{editingTeam ? 'Aktualisieren' : 'Erstellen'}</button>
</form>
)}
<hr />
<h3>Alle Teams</h3>
<ul>
{teams.map((team) => (
<li key={team.id}>
<strong>{team.name}</strong> Spieler: {team.players.join(', ')}
<div style={{ marginTop: '4px' }}>
{canEdit(team) && (
<button onClick={() => {
setName(team.name);
setPlayer2(team.players[1] || '');
setEditingTeam(team);
}}>
Bearbeiten
</button>
)}
{canDelete(team) && (
<button onClick={() => handleDelete(team.id)}>Löschen</button>
)}
</div>
</li>
))}
</ul>
</div>
);
};
export default TeamManagement;

View File

@@ -34,6 +34,63 @@ export async function updateTournament(id: string, data: any, token: string) {
return res.json();
}
export async function createTournament(data: any, token: string) {
const res = await fetch(`${API_URL}/tournaments`, {
method: 'POST',
headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${token}` },
body: JSON.stringify(data),
});
if (!res.ok) throw new Error('Turnier-Erstellung fehlgeschlagen');
return res.json();
}
export async function deleteTournament(id: string, token: string) {
const res = await fetch(`${API_URL}/tournaments/${id}`, {
method: 'DELETE',
headers: { Authorization: `Bearer ${token}` },
});
if (!res.ok) throw new Error('Turnier-Löschung fehlgeschlagen');
return res.json();
}
export async function fetchPlayers(token: string) {
const res = await fetch(`${API_URL}/players`, {
headers: { Authorization: `Bearer ${token}` },
});
if (!res.ok) throw new Error('Fehler beim Laden der Spieler');
return res.json();
}
export async function createPlayer(player: { name: string, email: string }, token: string) {
const res = await fetch(`${API_URL}/players`, {
method: 'POST',
headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${token}` },
body: JSON.stringify(player),
});
if (!res.ok) throw new Error('Spieler-Erstellung fehlgeschlagen');
return res.json();
}
export async function updatePlayer(id: string, player: { name?: string, email?: string }, token: string) {
const res = await fetch(`${API_URL}/players/${id}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${token}` },
body: JSON.stringify(player),
});
if (!res.ok) throw new Error('Spieler-Aktualisierung fehlgeschlagen');
return res.json();
}
export async function deletePlayer(id: string, token: string) {
const res = await fetch(`${API_URL}/players/${id}`, {
method: 'DELETE',
headers: { Authorization: `Bearer ${token}` },
});
if (!res.ok) throw new Error('Spieler-Löschung fehlgeschlagen');
return res.json();
}
export async function registerTeam(id: string, team: { name: string }, token: string) {
const res = await fetch(`${API_URL}/tournaments/${id}/register`, {
method: 'POST',