ADD: added tournament creation without backend support
This commit is contained in:
@@ -53,6 +53,9 @@ func main() {
|
|||||||
api.GET("/players", func(c *gin.Context) {
|
api.GET("/players", func(c *gin.Context) {
|
||||||
player.GetPlayers(c, db.GetDB())
|
player.GetPlayers(c, db.GetDB())
|
||||||
})
|
})
|
||||||
|
api.GET("/players/:id", func(c *gin.Context) {
|
||||||
|
player.GetPlayer(c, db.GetDB(), c.Param("id"))
|
||||||
|
})
|
||||||
api.POST("/players", func(c *gin.Context) {
|
api.POST("/players", func(c *gin.Context) {
|
||||||
player.CreatePlayer(c, db.GetDB())
|
player.CreatePlayer(c, db.GetDB())
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ func CreatePlayer(c *gin.Context, db *sql.DB) {
|
|||||||
common.RespondError(c, http.StatusInternalServerError, "Failed to create player")
|
common.RespondError(c, http.StatusInternalServerError, "Failed to create player")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
AddRoleToPlayer(db, newPlayer.ID, "player")
|
err = AddRoleToPlayer(db, newPlayer.ID, "player")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Error adding role to player: %v", err)
|
log.Printf("Error adding role to player: %v", err)
|
||||||
common.RespondError(c, http.StatusInternalServerError, "Failed to assign role to player")
|
common.RespondError(c, http.StatusInternalServerError, "Failed to assign role to player")
|
||||||
@@ -65,7 +65,8 @@ func CreatePlayer(c *gin.Context, db *sql.DB) {
|
|||||||
common.RespondCreated(c, newPlayer)
|
common.RespondCreated(c, newPlayer)
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetPlayer(c *gin.Context, db *sql.DB) {
|
func GetPlayer(c *gin.Context, db *sql.DB, id string) {
|
||||||
|
log.Println("GetPlayer called with ID:", id)
|
||||||
playerID := c.Param("id")
|
playerID := c.Param("id")
|
||||||
if playerID == "" {
|
if playerID == "" {
|
||||||
common.RespondError(c, http.StatusBadRequest, "Player ID is required")
|
common.RespondError(c, http.StatusBadRequest, "Player ID is required")
|
||||||
@@ -84,10 +85,16 @@ func GetPlayer(c *gin.Context, db *sql.DB) {
|
|||||||
common.RespondError(c, http.StatusNotFound, "Player not found")
|
common.RespondError(c, http.StatusNotFound, "Player not found")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
role, err := GetPlayerRole(db, player.ID)
|
||||||
log.Printf("User %s (%s) requested player: %s", c.GetString("userId"), c.GetString("email"), player.Name)
|
if err != nil {
|
||||||
|
log.Printf("Error retrieving role for player ID %s: %v", player.ID, err)
|
||||||
|
common.RespondError(c, http.StatusInternalServerError, "Failed to retrieve player role")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
player.Role = role
|
||||||
|
log.Printf(player.ID, player.Name, player.Email, player.Role)
|
||||||
c.JSON(http.StatusOK, player)
|
c.JSON(http.StatusOK, player)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func UpdatePlayer(c *gin.Context, db *sql.DB) {
|
func UpdatePlayer(c *gin.Context, db *sql.DB) {
|
||||||
|
|||||||
@@ -134,9 +134,9 @@ func savePlayer(db *sql.DB, player Player) error {
|
|||||||
|
|
||||||
log.Printf("Saving player: ID=%v, Name=%v, Email=%v", player.ID, player.Name, player.Email)
|
log.Printf("Saving player: ID=%v, Name=%v, Email=%v", player.ID, player.Name, player.Email)
|
||||||
|
|
||||||
stmt := "INSERT INTO public.players (id, name, email) VALUES ($1, $2, $3)"
|
stmt := "INSERT INTO public.players (id, name, email,password_hash) VALUES ($1, $2, $3,$4)"
|
||||||
log.Printf("Generated SQL statement: %s", stmt)
|
log.Printf("Generated SQL statement: %s", stmt)
|
||||||
_, err := db.Exec(stmt, player.ID, player.Name, player.Email)
|
_, err := db.Exec(stmt, player.ID, player.Name, player.Email, player.Password)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Error saving player to database: %v", err)
|
log.Printf("Error saving player to database: %v", err)
|
||||||
@@ -202,7 +202,7 @@ func GetAllPlayers(db *sql.DB) ([]Player, error) {
|
|||||||
|
|
||||||
func GetPlayerByID(db *sql.DB, id string) (Player, error) {
|
func GetPlayerByID(db *sql.DB, id string) (Player, error) {
|
||||||
var player Player
|
var player Player
|
||||||
err := db.QueryRow("SELECT id, name, email FROM players WHERE id = $1", id).Scan(&player.ID, &player.Name, &player.Email, &player.Role)
|
err := db.QueryRow("SELECT id, name, email FROM players WHERE id = $1", id).Scan(&player.ID, &player.Name, &player.Email)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Error retrieving player by ID %s: %v", id, err)
|
log.Printf("Error retrieving player by ID %s: %v", id, err)
|
||||||
return Player{}, err
|
return Player{}, err
|
||||||
|
|||||||
BIN
backend/main
Executable file
BIN
backend/main
Executable file
Binary file not shown.
@@ -7,6 +7,9 @@ import TournamentDetails from './pages/TournamentDetails';
|
|||||||
import Players from './pages/Players';
|
import Players from './pages/Players';
|
||||||
import Navigation from './pages/Navigation';
|
import Navigation from './pages/Navigation';
|
||||||
import TeamManagement from './pages/Teams';
|
import TeamManagement from './pages/Teams';
|
||||||
|
import ViewEditPlayer from './pages/ViewEditPlayer';
|
||||||
|
import Tournaments from './pages/Tournaments';
|
||||||
|
import NewTournament from './pages/NewTournament';
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
|
|
||||||
@@ -17,12 +20,16 @@ function App() {
|
|||||||
<Routes>
|
<Routes>
|
||||||
<Route path="/" element={<Dashboard />} /> {/* Öffentlich */}
|
<Route path="/" element={<Dashboard />} /> {/* Öffentlich */}
|
||||||
<Route path="/login" element={<LoginPage />} />
|
<Route path="/login" element={<LoginPage />} />
|
||||||
|
<Route path="tournaments" element={<Tournaments />} />
|
||||||
{/* Geschützte Routen */}
|
{/* Geschützte Routen */}
|
||||||
<Route path="/players" element={<ProtectedRoute><Players /></ProtectedRoute>} />
|
<Route path="/players" element={<ProtectedRoute><Players /></ProtectedRoute>} />
|
||||||
|
<Route path="/players/:id" element={<ViewEditPlayer />} />
|
||||||
|
|
||||||
<Route path="/teams" element={<ProtectedRoute><TeamManagement /></ProtectedRoute>} />
|
<Route path="/teams" element={<ProtectedRoute><TeamManagement /></ProtectedRoute>} />
|
||||||
|
|
||||||
<Route path="/tournaments/:id" element={<ProtectedRoute><TournamentDetails /></ProtectedRoute>} />
|
<Route path="/tournament/:id" element={<ProtectedRoute><TournamentDetails /></ProtectedRoute>} />
|
||||||
|
<Route path="/tournament/new" element={<ProtectedRoute><NewTournament /></ProtectedRoute>} />
|
||||||
|
|
||||||
</Routes>
|
</Routes>
|
||||||
</Router>
|
</Router>
|
||||||
</AuthProvider>
|
</AuthProvider>
|
||||||
|
|||||||
@@ -22,12 +22,13 @@ export default function Dashboard() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="p-6 max-w-5xl mx-auto">
|
<div className="p-6 max-w-5xl mx-auto">
|
||||||
|
|
||||||
<h1 className="text-3xl font-bold mb-6">Turniere</h1>
|
<h1 className="text-3xl font-bold mb-6">Turniere</h1>
|
||||||
{error && <p className="text-red-600">{error}</p>}
|
{error && <p className="text-red-600">{error}</p>}
|
||||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
|
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
|
||||||
{tournaments.map(t => (
|
{tournaments.map(t => (
|
||||||
<div key={t.id} className="border rounded p-4 shadow hover:shadow-lg transition cursor-pointer">
|
<div key={t.id} className="border rounded p-4 shadow hover:shadow-lg transition cursor-pointer">
|
||||||
<Link to={`/tournaments/${t.id}`}>
|
<Link to={`/tournament/${t.id}`}>
|
||||||
<h2 className="text-xl font-semibold">{t.name}</h2>
|
<h2 className="text-xl font-semibold">{t.name}</h2>
|
||||||
<p>{t.teams.length} / {t.maxParticipants} Teilnehmer</p>
|
<p>{t.teams.length} / {t.maxParticipants} Teilnehmer</p>
|
||||||
<p>Ort: {t.location}</p>
|
<p>Ort: {t.location}</p>
|
||||||
|
|||||||
@@ -8,6 +8,8 @@ export default function Navigation() {
|
|||||||
<nav className="bg-blue-600 text-white p-4 flex justify-between">
|
<nav className="bg-blue-600 text-white p-4 flex justify-between">
|
||||||
<div className="space-x-4">
|
<div className="space-x-4">
|
||||||
<Link to="/">Dashboard</Link>
|
<Link to="/">Dashboard</Link>
|
||||||
|
<Link to="/tournaments">Turniere</Link>
|
||||||
|
|
||||||
{token && <Link to="/players">Spieler</Link>}
|
{token && <Link to="/players">Spieler</Link>}
|
||||||
{token && <Link to="/teams">Teams</Link>}
|
{token && <Link to="/teams">Teams</Link>}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
93
frontend/src/pages/NewTournament.tsx
Normal file
93
frontend/src/pages/NewTournament.tsx
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
import React, { useState } from 'react';
|
||||||
|
import { useNavigate } from 'react-router-dom';
|
||||||
|
import { createTournament } from './api';
|
||||||
|
|
||||||
|
const NewTournament = () => {
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const token = localStorage.getItem('token')|| '';
|
||||||
|
|
||||||
|
const [name, setName] = useState('');
|
||||||
|
const [location, setLocation] = useState('');
|
||||||
|
const [date, setDate] = useState('');
|
||||||
|
const [maxTeams, setMaxTeams] = useState(8);
|
||||||
|
const [message, setMessage] = useState('');
|
||||||
|
|
||||||
|
const handleSubmit = async (e: React.FormEvent) => {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
const data = JSON.stringify({ name, location, date, maxTeams });
|
||||||
|
|
||||||
|
const res = await createTournament(data, token);
|
||||||
|
// const data = await res.json();
|
||||||
|
|
||||||
|
if (res.ok) {
|
||||||
|
setMessage('Turnier erfolgreich erstellt.');
|
||||||
|
setTimeout(() => navigate('/tournaments'), 1500);
|
||||||
|
} else {
|
||||||
|
setMessage('Fehler beim Erstellen.');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="max-w-xl mx-auto mt-8 p-6 bg-white shadow-md rounded-md">
|
||||||
|
<h2 className="text-2xl font-semibold mb-6">Neues Turnier erstellen</h2>
|
||||||
|
<form className="space-y-4" onSubmit={handleSubmit}>
|
||||||
|
<div>
|
||||||
|
<label className="block font-medium mb-1">Turniername</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
className="w-full border border-gray-300 p-2 rounded"
|
||||||
|
value={name}
|
||||||
|
onChange={(e) => setName(e.target.value)}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label className="block font-medium mb-1">Ort</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
className="w-full border border-gray-300 p-2 rounded"
|
||||||
|
value={location}
|
||||||
|
onChange={(e) => setLocation(e.target.value)}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label className="block font-medium mb-1">Datum</label>
|
||||||
|
<input
|
||||||
|
type="date"
|
||||||
|
className="w-full border border-gray-300 p-2 rounded"
|
||||||
|
value={date}
|
||||||
|
onChange={(e) => setDate(e.target.value)}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label className="block font-medium mb-1">Maximale Teams</label>
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
className="w-full border border-gray-300 p-2 rounded"
|
||||||
|
value={maxTeams}
|
||||||
|
onChange={(e) => setMaxTeams(Number(e.target.value))}
|
||||||
|
min={2}
|
||||||
|
max={64}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
className="bg-blue-600 text-white px-4 py-2 rounded hover:bg-blue-700"
|
||||||
|
>
|
||||||
|
Erstellen
|
||||||
|
</button>
|
||||||
|
|
||||||
|
{message && <p className="text-green-600 font-medium mt-2">{message}</p>}
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default NewTournament;
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import { fetchPlayers,createPlayer,deletePlayer,updatePlayer } from './api';
|
import { fetchPlayers,createPlayer,deletePlayer,updatePlayer } from './api';
|
||||||
|
import { Navigate, useNavigate } from 'react-router-dom';
|
||||||
|
|
||||||
|
|
||||||
interface Player {
|
interface Player {
|
||||||
@@ -15,6 +16,7 @@ export default function PlayerManagement() {
|
|||||||
const [password, setPassword] = useState("");
|
const [password, setPassword] = useState("");
|
||||||
const [editingId, setEditingId] = useState<string | null>(null);
|
const [editingId, setEditingId] = useState<string | null>(null);
|
||||||
const token = localStorage.getItem('token');
|
const token = localStorage.getItem('token');
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (token) loadPlayers(token);
|
if (token) loadPlayers(token);
|
||||||
@@ -60,10 +62,9 @@ export default function PlayerManagement() {
|
|||||||
setEmail("");
|
setEmail("");
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleEdit = (player: Player) => {
|
const handleViewEdit = (playerID: string) => () => {
|
||||||
setName(player.name);
|
navigate(`/players/${playerID}`);
|
||||||
setEmail(player.email);
|
|
||||||
setEditingId(player.id);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleDelete = (id: string) => {
|
const handleDelete = (id: string) => {
|
||||||
@@ -128,8 +129,9 @@ export default function PlayerManagement() {
|
|||||||
<td className="border px-4 py-2">{player.email}</td>
|
<td className="border px-4 py-2">{player.email}</td>
|
||||||
<td className="border px-4 py-2 space-x-2">
|
<td className="border px-4 py-2 space-x-2">
|
||||||
<button
|
<button
|
||||||
onClick={() => handleEdit(player)}
|
key={player.id}
|
||||||
className="bg-yellow-400 text-white px-2 py-1 rounded"
|
onClick={handleViewEdit(player.id)}
|
||||||
|
className="bg-blue-600 text-white px-4 py-2 rounded hover:bg-blue-700"
|
||||||
>
|
>
|
||||||
Bearbeiten
|
Bearbeiten
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -3,19 +3,7 @@ import { useEffect, useState } from 'react';
|
|||||||
import { fetchTournament, updateTournament, registerTeam } from './api';
|
import { fetchTournament, updateTournament, registerTeam } from './api';
|
||||||
import { useAuth } from './AuthContext';
|
import { useAuth } from './AuthContext';
|
||||||
|
|
||||||
interface Team {
|
import { Tournament } from './types';
|
||||||
id: string;
|
|
||||||
name: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface Tournament {
|
|
||||||
id: string;
|
|
||||||
name: string;
|
|
||||||
location: string;
|
|
||||||
maxParticipants: number;
|
|
||||||
organizerId: string;
|
|
||||||
teams: Team[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function TournamentDetails() {
|
export default function TournamentDetails() {
|
||||||
const { id } = useParams<{ id: string }>();
|
const { id } = useParams<{ id: string }>();
|
||||||
|
|||||||
@@ -1,20 +1,61 @@
|
|||||||
import { useLocation } from 'react-router-dom';
|
import { Link, useLocation } from 'react-router-dom';
|
||||||
|
import { use, useEffect, useState } from 'react';
|
||||||
|
import { fetchTournaments, fetchTournament } from './api'; // Importiere die API-Funktion
|
||||||
|
import type { Tournament } from './types';
|
||||||
|
|
||||||
function useQuery() {
|
function useQuery() {
|
||||||
return new URLSearchParams(useLocation().search);
|
return new URLSearchParams(useLocation().search);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function Tournaments() {
|
export default function Tournament() {
|
||||||
const query = useQuery();
|
const query = useQuery();
|
||||||
const type = query.get("type");
|
const type = query.get("type");
|
||||||
const location = query.get("location");
|
const location = query.get("location");
|
||||||
|
const [tournaments, setTournaments] = useState<Tournament[]>([]);
|
||||||
|
const userId = localStorage.getItem('userId') || '';
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
async function loadTournaments() {
|
||||||
|
try {
|
||||||
|
const tournaments = await fetchTournaments();
|
||||||
|
setTournaments(tournaments);
|
||||||
|
console.log("Geladene Turniere:", tournaments);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Fehler beim Laden der Turniere:", error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
loadTournaments();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
// Replace this with however you get the current user's id
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="p-6">
|
<div className="p-6">
|
||||||
|
<h1 className="text-3xl font-bold mb-6">Meine Tourniere</h1>
|
||||||
|
<div className="max-h-64 overflow-y-auto mb-6 border rounded p-4 bg-white shadow">
|
||||||
|
<div className="flex justify-between items-center mb-4">
|
||||||
|
<span className="font-semibold text-lg">Meine Turniere</span>
|
||||||
|
<Link to="/tournament/new" className="px-3 py-1 border rounded bg-green-500 text-white">Neues Tournier erstellen</Link>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
{tournaments.filter(t => t.organizerId === userId).length === 0 ? (
|
||||||
|
<p className="text-gray-500">Du bist aktuell kein Organisator eines Turniers.</p>
|
||||||
|
) : (
|
||||||
|
tournaments
|
||||||
|
.filter(t => t.organizerId === userId)
|
||||||
|
.map(t => (
|
||||||
|
<div key={t.id} className="mb-4 last:mb-0 border-b pb-2">
|
||||||
|
<div className="font-semibold">{t.name}</div>
|
||||||
|
<div className="text-sm text-gray-600">{t.date instanceof Date ? t.date.toLocaleDateString() : t.date} – {t.location}</div>
|
||||||
|
</div>
|
||||||
|
))
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
<h2 className="text-2xl font-bold mb-4">Turniere</h2>
|
<h2 className="text-2xl font-bold mb-4">Turniere</h2>
|
||||||
{type && <p>Gefiltert nach: <strong>{type}</strong></p>}
|
{type && <p>Gefiltert nach: <strong>{type}</strong></p>}
|
||||||
{location && <p>Standort: <strong>{location}</strong></p>}
|
{location && <p>Standort: <strong>{location}</strong></p>}
|
||||||
{/* TODO: Backend-Daten hier anzeigen */}
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
131
frontend/src/pages/ViewEditPlayer.tsx
Normal file
131
frontend/src/pages/ViewEditPlayer.tsx
Normal file
@@ -0,0 +1,131 @@
|
|||||||
|
import React, { useEffect, useState } from 'react';
|
||||||
|
import { useParams } from 'react-router-dom';
|
||||||
|
import { getUserFromToken } from '../components/utils/jwt'; // Importiere die Funktion zum Decodieren des Tokens
|
||||||
|
import {fetchPlayer} from './api'; // Importiere die Funktion zum Abrufen des Spielers
|
||||||
|
|
||||||
|
interface Player {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
email: string;
|
||||||
|
role: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ViewEditPlayer = () => {
|
||||||
|
const { id } = useParams<{ id: string }>();
|
||||||
|
const token = localStorage.getItem('token');
|
||||||
|
const currentUser = token ? getUserFromToken(token) : null;
|
||||||
|
const isAdmin = currentUser?.role === 'admin';
|
||||||
|
|
||||||
|
const [player, setPlayer] = useState<Player | null>(null);
|
||||||
|
const [name, setName] = useState('');
|
||||||
|
const [email, setEmail] = useState('');
|
||||||
|
const [role, setRole] = useState('');
|
||||||
|
const [message, setMessage] = useState('');
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const fetchData = async () => {
|
||||||
|
if (!token || !id) {
|
||||||
|
setMessage('Token oder Spieler-ID fehlt.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const data = await fetchPlayer(token, id);
|
||||||
|
console.log("Geladener Spieler:", data);
|
||||||
|
setPlayer(data);
|
||||||
|
setName(data.name);
|
||||||
|
setEmail(data.email);
|
||||||
|
setRole(data.role);
|
||||||
|
} catch (error) {
|
||||||
|
setMessage('Spieler konnte nicht geladen werden.');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
fetchData();
|
||||||
|
}, [id, token]);
|
||||||
|
|
||||||
|
const handleSave = async () => {
|
||||||
|
const res = await fetch(`/api/players/${id}`, {
|
||||||
|
method: 'PUT',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
Authorization: `Bearer ${token}`,
|
||||||
|
},
|
||||||
|
body: JSON.stringify({ name, email, role }),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (res.ok) {
|
||||||
|
setMessage('Spieler wurde aktualisiert.');
|
||||||
|
} else {
|
||||||
|
setMessage('Fehler beim Speichern.');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!player) return <p className="text-center mt-10">Lade Spieler...</p>;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="max-w-xl mx-auto mt-8 p-6 bg-white shadow-md rounded-md">
|
||||||
|
<h2 className="text-2xl font-semibold mb-4">Spielerprofil</h2>
|
||||||
|
|
||||||
|
<div className="space-y-4">
|
||||||
|
<div>
|
||||||
|
<label className="block font-medium mb-1">Name</label>
|
||||||
|
{isAdmin ? (
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
className="w-full border border-gray-300 p-2 rounded-md"
|
||||||
|
value={name}
|
||||||
|
onChange={(e) => setName(e.target.value)}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<p>{player.name}</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label className="block font-medium mb-1">E-Mail</label>
|
||||||
|
{isAdmin ? (
|
||||||
|
<input
|
||||||
|
type="email"
|
||||||
|
className="w-full border border-gray-300 p-2 rounded-md"
|
||||||
|
value={email}
|
||||||
|
onChange={(e) => setEmail(e.target.value)}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<p>{player.email}</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label className="block font-medium mb-1">Rolle</label>
|
||||||
|
{isAdmin ? (
|
||||||
|
<select
|
||||||
|
className="w-full border border-gray-300 p-2 rounded-md"
|
||||||
|
value={role}
|
||||||
|
onChange={(e) => setRole(e.target.value)}
|
||||||
|
>
|
||||||
|
<option value="user">User</option>
|
||||||
|
<option value="admin">Admin</option>
|
||||||
|
</select>
|
||||||
|
) : (
|
||||||
|
<p>{player.role}</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{isAdmin && (
|
||||||
|
<button
|
||||||
|
className="bg-blue-600 text-white px-4 py-2 rounded-md hover:bg-blue-700"
|
||||||
|
onClick={handleSave}
|
||||||
|
>
|
||||||
|
Speichern
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{message && (
|
||||||
|
<p className="text-green-600 font-medium mt-2">{message}</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ViewEditPlayer;
|
||||||
@@ -53,6 +53,15 @@ export async function deleteTournament(id: string, token: string) {
|
|||||||
return res.json();
|
return res.json();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function fetchPlayer(token: string, id: string) {
|
||||||
|
const res = await fetch(`${API_URL}/players/${id}`, {
|
||||||
|
headers: { Authorization: `Bearer ${token}` },
|
||||||
|
});
|
||||||
|
if (!res.ok) throw new Error('Fehler beim Laden des Spielers');
|
||||||
|
return res.json();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
export async function fetchPlayers(token: string) {
|
export async function fetchPlayers(token: string) {
|
||||||
const res = await fetch(`${API_URL}/players`, {
|
const res = await fetch(`${API_URL}/players`, {
|
||||||
headers: { Authorization: `Bearer ${token}` },
|
headers: { Authorization: `Bearer ${token}` },
|
||||||
|
|||||||
14
frontend/src/pages/types.tsx
Normal file
14
frontend/src/pages/types.tsx
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
export interface Team {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Tournament {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
location: string;
|
||||||
|
maxParticipants: number;
|
||||||
|
organizerId: string;
|
||||||
|
date: Date;
|
||||||
|
teams: Team[];
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user