mirror of
https://github.com/ceratic/project_vollidioten_website.git
synced 2026-05-14 00:16:47 +02:00
feat: Add DatabaseManager and LinkPlayer components, implement authentication and linking logic
- 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.
This commit is contained in:
201
pages/LinkPlayer.tsx
Normal file
201
pages/LinkPlayer.tsx
Normal file
@@ -0,0 +1,201 @@
|
||||
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;
|
||||
Reference in New Issue
Block a user