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:
Lars Behrends
2025-12-28 16:46:04 +01:00
parent 6abdffe22a
commit d3d7ec46e6
40 changed files with 5967 additions and 102 deletions

85
App.tsx
View File

@@ -1,4 +1,4 @@
import React, { useState } from 'react';
import React, { useState, useEffect } from 'react';
import Layout from './components/Layout';
import Dashboard from './pages/Dashboard';
import PlayerProfile from './pages/PlayerProfile';
@@ -9,7 +9,12 @@ import Cities from './pages/Cities';
import CityProfile from './pages/CityProfile';
import ProjectProfile from './pages/ProjectProfile';
import DatapackGenerator from './pages/DatapackGenerator';
import { MOCK_PLAYERS, MOCK_ORGS, MOCK_PROJECTS } from './constants';
import DatabaseManager from './pages/DatabaseManager'; // Ensure this file exists or remove import if not
import LinkPlayer from './pages/LinkPlayer';
import AdminPage from './pages/Admin';
import { dbService } from './services/DatabaseService';
import { authService } from './services/AuthService';
import { DiscordUser } from './types';
import { Icons } from './components/IconSet';
function App() {
@@ -19,6 +24,44 @@ function App() {
const [selectedProjectId, setSelectedProjectId] = useState<string | null>(null);
const [selectedOrgId, setSelectedOrgId] = useState<string | null>(null);
// Auth state
const [user, setUser] = useState<DiscordUser | null>(null);
const [authChecked, setAuthChecked] = useState(false);
// State for data from DB (Async)
const [players, setPlayers] = useState(dbService.getPlayers());
const [orgs, setOrgs] = useState(dbService.getOrgs());
const [projects, setProjects] = useState(dbService.getProjects());
// Subscribe to DB updates (when fetch completes or edits happen)
useEffect(() => {
const unsub = dbService.subscribe(() => {
setPlayers([...dbService.getPlayers()]);
setOrgs([...dbService.getOrgs()]);
setProjects([...dbService.getProjects()]);
});
return unsub;
}, []);
// Subscribe to auth updates
useEffect(() => {
const unsub = authService.subscribe((currentUser) => {
setUser(currentUser);
setAuthChecked(true);
// If user is logged in but not linked, redirect to link page
if (currentUser && !currentUser.linkedPlayerUuid) {
setActiveTab('link-player');
} else if (currentUser && currentUser.linkedPlayerUuid) {
// User is fully authenticated, redirect to dashboard if on link page
if (activeTab === 'link-player') {
setActiveTab('dashboard');
}
}
});
return unsub;
}, [activeTab]);
const handleNavigate = (tab: string) => {
setActiveTab(tab);
if (tab !== 'players') setSelectedPlayerId(null);
@@ -27,21 +70,18 @@ function App() {
if (tab !== 'organizations') setSelectedOrgId(null);
};
// Helper to jump to a player from another view
const navigateToPlayer = (id: string) => {
setSelectedPlayerId(id);
setActiveTab('players');
};
// Helper to jump to a project from another view
const navigateToProject = (id: string) => {
setSelectedProjectId(id);
setActiveTab('projects');
};
// Helper to jump to an org/city
const navigateToOrg = (id: string) => {
const org = MOCK_ORGS.find(o => o.id === id);
const org = orgs.find(o => o.id === id);
if (org?.type === 'City') {
setSelectedCityId(id);
setActiveTab('cities');
@@ -52,11 +92,16 @@ function App() {
};
const renderContent = () => {
// Show link player page if user is logged in but not linked
if (activeTab === 'link-player') {
return <LinkPlayer />;
}
if (activeTab === 'dashboard') return <Dashboard />;
if (activeTab === 'projects') {
if (selectedProjectId) {
const project = MOCK_PROJECTS.find(p => p.id === selectedProjectId);
const project = projects.find(p => p.id === selectedProjectId);
if (project) return (
<ProjectProfile
project={project}
@@ -66,12 +111,12 @@ function App() {
/>
);
}
return <Projects onSelectProject={setSelectedProjectId} />;
return <Projects onSelectProject={setSelectedProjectId} />;
}
if (activeTab === 'organizations') {
if (selectedOrgId) {
const org = MOCK_ORGS.find(o => o.id === selectedOrgId);
const org = orgs.find(o => o.id === selectedOrgId);
if (org) return (
<CityProfile
city={org}
@@ -88,10 +133,10 @@ function App() {
if (activeTab === 'setup') return <SetupGuide />;
if (activeTab === 'datapack') return <DatapackGenerator />;
if (activeTab === 'cities') {
if (selectedCityId) {
const city = MOCK_ORGS.find(o => o.id === selectedCityId);
const city = orgs.find(o => o.id === selectedCityId);
if (city) return (
<CityProfile
city={city}
@@ -107,7 +152,7 @@ function App() {
if (activeTab === 'players') {
if (selectedPlayerId) {
const player = MOCK_PLAYERS.find(p => p.uuid === selectedPlayerId);
const player = players.find(p => p.uuid === selectedPlayerId);
if (player) return <PlayerProfile player={player} onBack={() => setSelectedPlayerId(null)} />;
}
@@ -126,7 +171,7 @@ function App() {
</div>
<div className="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-4">
{MOCK_PLAYERS.map(player => (
{players.map(player => (
<div
key={player.uuid}
onClick={() => setSelectedPlayerId(player.uuid)}
@@ -149,6 +194,16 @@ function App() {
</div>
);
}
// Admin panel (only for admins)
if (activeTab === 'admin') {
return <AdminPage onBack={() => setActiveTab('dashboard')} />;
}
// Placeholder for database manager if user navigates there (needs explicit page or component)
if (activeTab === 'database') {
return <div className="p-10 text-center text-textMuted">Datenbank wird jetzt über das Backend (SQLite) verwaltet.</div>
}
return (
<div className="flex flex-col items-center justify-center h-[50vh] text-textMuted">
@@ -165,4 +220,4 @@ function App() {
);
}
export default App;
export default App;