mirror of
https://github.com/ceratic/project_vollidioten_website.git
synced 2026-05-14 00:16:47 +02:00
- Created DatabaseManager component for managing database access via phpMyAdmin. - Developed LinkPlayer component to link Discord accounts with game characters, including user authentication and error handling. - Added mock data files for players, organizations, and projects to handle backend unavailability. - Implemented AuthService for managing user authentication and session checks. - Created DatabaseService to fetch and manage player, organization, and project data with fallback to mock data. - Added HTML page for handling authentication unavailability. - Developed a test script for validating Docker setup and required files.
202 lines
6.9 KiB
TypeScript
202 lines
6.9 KiB
TypeScript
import React, { useState, useEffect } from 'react';
|
|
import { authService } from '../services/AuthService';
|
|
import { DiscordUser } from '../types';
|
|
|
|
interface Player {
|
|
uuid: string;
|
|
username: string;
|
|
tags: string[];
|
|
stats: {
|
|
playtimeHours: number;
|
|
level: number;
|
|
role: string;
|
|
};
|
|
}
|
|
|
|
const LinkPlayer: React.FC = () => {
|
|
const [user, setUser] = useState<DiscordUser | null>(null);
|
|
const [unlinkedPlayers, setUnlinkedPlayers] = useState<Player[]>([]);
|
|
const [loading, setLoading] = useState(true);
|
|
const [linking, setLinking] = useState(false);
|
|
const [error, setError] = useState<string | null>(null);
|
|
|
|
useEffect(() => {
|
|
// Get current user
|
|
const unsubscribe = authService.subscribe(setUser);
|
|
return unsubscribe;
|
|
}, []);
|
|
|
|
useEffect(() => {
|
|
// Fetch unlinked players
|
|
if (user) {
|
|
fetchUnlinkedPlayers();
|
|
}
|
|
}, [user]);
|
|
|
|
const fetchUnlinkedPlayers = async () => {
|
|
try {
|
|
setLoading(true);
|
|
const response = await fetch('https://vollidioten.ceraticsoft.de/api/unlinked-players');
|
|
if (response.ok) {
|
|
const players = await response.json();
|
|
setUnlinkedPlayers(players);
|
|
} else {
|
|
setError('Fehler beim Laden der Spielerliste');
|
|
}
|
|
} catch (err) {
|
|
console.error('Error fetching unlinked players:', err);
|
|
setError('Netzwerkfehler beim Laden der Spielerliste');
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
};
|
|
|
|
const linkPlayer = async (playerUuid: string) => {
|
|
if (!user) return;
|
|
|
|
try {
|
|
setLinking(true);
|
|
setError(null);
|
|
|
|
const response = await fetch('https://vollidioten.ceraticsoft.de/api/link-user', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
},
|
|
credentials: 'include',
|
|
body: JSON.stringify({ playerUuid }),
|
|
});
|
|
|
|
if (response.ok) {
|
|
// Update user data
|
|
const updatedUser = { ...user, linkedPlayerUuid: playerUuid };
|
|
setUser(updatedUser);
|
|
|
|
// Redirect to dashboard
|
|
window.location.href = '/';
|
|
} else {
|
|
setError('Fehler beim Verknüpfen des Accounts');
|
|
}
|
|
} catch (err) {
|
|
console.error('Error linking player:', err);
|
|
setError('Netzwerkfehler beim Verknüpfen');
|
|
} finally {
|
|
setLinking(false);
|
|
}
|
|
};
|
|
|
|
if (!user) {
|
|
return (
|
|
<div className="min-h-screen flex items-center justify-center">
|
|
<div className="text-center">
|
|
<h2 className="text-xl font-semibold mb-4">Nicht eingeloggt</h2>
|
|
<p>Bitte loggen Sie sich zuerst ein.</p>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
if (user.linkedPlayerUuid) {
|
|
// User is already linked, redirect to dashboard
|
|
window.location.href = '/';
|
|
return null;
|
|
}
|
|
|
|
return (
|
|
<div className="min-h-screen bg-background py-12 px-4">
|
|
<div className="max-w-4xl mx-auto">
|
|
<div className="text-center mb-8">
|
|
<div className="flex items-center justify-center gap-4 mb-6">
|
|
<img
|
|
src={user.avatarUrl}
|
|
alt={user.username}
|
|
className="w-16 h-16 rounded-full border-2 border-accentInfo"
|
|
/>
|
|
<div className="text-left">
|
|
<h1 className="text-3xl font-bold text-textMain">Willkommen, {user.username}!</h1>
|
|
<p className="text-textMuted">Verbinde deinen Discord-Account mit einem Bürger</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="bg-surface rounded-lg p-6 border border-border">
|
|
<h2 className="text-xl font-semibold mb-4 text-textMain">Wähle deinen Bürger</h2>
|
|
<p className="text-textMuted mb-6">
|
|
Wähle einen Bürger aus der Liste der verfügbaren Charaktere aus, um deinen Discord-Account zu verknüpfen.
|
|
</p>
|
|
|
|
{error && (
|
|
<div className="bg-red-500/10 border border-red-500/20 rounded-lg p-4 mb-6">
|
|
<p className="text-red-400">{error}</p>
|
|
</div>
|
|
)}
|
|
|
|
{loading ? (
|
|
<div className="flex justify-center py-8">
|
|
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-accentInfo"></div>
|
|
</div>
|
|
) : (
|
|
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-3">
|
|
{unlinkedPlayers.map((player) => (
|
|
<div
|
|
key={player.uuid}
|
|
className="bg-surfaceHighlight border border-border rounded-lg p-4 hover:border-accentInfo transition-colors"
|
|
>
|
|
<div className="flex items-center gap-3 mb-3">
|
|
<div className="w-10 h-10 bg-gradient-to-br from-accentInfo to-blue-900 rounded-full flex items-center justify-center">
|
|
<span className="font-bold text-white text-sm">
|
|
{player.username.charAt(0).toUpperCase()}
|
|
</span>
|
|
</div>
|
|
<div>
|
|
<h3 className="font-semibold text-textMain">{player.username}</h3>
|
|
<p className="text-sm text-textMuted">Level {player.stats.level}</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="mb-4">
|
|
<div className="flex flex-wrap gap-1">
|
|
{player.tags.map((tag, index) => (
|
|
<span
|
|
key={index}
|
|
className="text-xs bg-accentInfo/10 text-accentInfo px-2 py-1 rounded"
|
|
>
|
|
{tag}
|
|
</span>
|
|
))}
|
|
</div>
|
|
</div>
|
|
|
|
<div className="text-sm text-textMuted mb-4">
|
|
<p>Spielzeit: {player.stats.playtimeHours}h</p>
|
|
<p>Rolle: {player.stats.role}</p>
|
|
</div>
|
|
|
|
<button
|
|
onClick={() => linkPlayer(player.uuid)}
|
|
disabled={linking}
|
|
className="w-full bg-accentInfo hover:bg-accentInfo/80 disabled:opacity-50 disabled:cursor-not-allowed text-white font-medium py-2 px-4 rounded transition-colors"
|
|
>
|
|
{linking ? 'Verknüpfe...' : 'Diesen Bürger wählen'}
|
|
</button>
|
|
</div>
|
|
))}
|
|
</div>
|
|
)}
|
|
|
|
{unlinkedPlayers.length === 0 && !loading && (
|
|
<div className="text-center py-8">
|
|
<p className="text-textMuted">Keine verfügbaren Bürger gefunden.</p>
|
|
<p className="text-sm text-textMuted mt-2">
|
|
Alle Bürger sind bereits verknüpft oder es gibt einen Fehler beim Laden.
|
|
</p>
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default LinkPlayer;
|