Files
project_vollidioten_website/components/Layout.tsx
Lars Behrends d3d7ec46e6 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.
2025-12-28 16:46:04 +01:00

175 lines
8.4 KiB
TypeScript

import React, { useState, useEffect } from 'react';
import { Icons } from './IconSet';
import { authService } from '../services/AuthService';
import { DiscordUser } from '../types';
interface LayoutProps {
children: React.ReactNode;
activeTab: string;
onNavigate: (tab: string) => void;
}
const NavItem = ({
active,
label,
onClick
}: {
active: boolean;
label: string;
onClick: () => void;
}) => (
<button
onClick={onClick}
className={`text-sm font-medium transition-colors duration-200 px-1 py-4 border-b-2 ${
active
? 'text-textMain border-accentInfo'
: 'text-textMuted border-transparent hover:text-textMain hover:border-border'
}`}
>
{label}
</button>
);
const Layout: React.FC<LayoutProps> = ({ children, activeTab, onNavigate }) => {
const [mobileMenuOpen, setMobileMenuOpen] = useState(false);
const [user, setUser] = useState<DiscordUser | null>(null);
useEffect(() => {
// Subscribe to auth changes
const unsubscribe = authService.subscribe(setUser);
return unsubscribe;
}, []);
return (
<div className="min-h-screen flex flex-col font-sans">
{/* Top Header - Website Style */}
<header className="sticky top-0 z-40 bg-background/80 backdrop-blur-md border-b border-border">
<div className="max-w-7xl mx-auto px-6 h-16 flex items-center justify-between">
<div className="flex items-center gap-10">
{/* Logo */}
<div
className="flex items-center gap-3 cursor-pointer group"
onClick={() => onNavigate('dashboard')}
>
<div className="w-8 h-8 bg-gradient-to-br from-accentInfo to-blue-900 rounded flex items-center justify-center shadow-glow group-hover:shadow-lg transition-shadow">
<span className="font-bold text-white text-sm">P.V.</span>
</div>
<span className="font-bold text-lg tracking-tight text-textMain">Projekt: Vollidion</span>
</div>
{/* Desktop Nav */}
<nav className="hidden md:flex items-center gap-6 h-full">
<NavItem active={activeTab === 'dashboard'} label="Übersicht" onClick={() => onNavigate('dashboard')} />
<NavItem active={activeTab === 'cities'} label="Städte" onClick={() => onNavigate('cities')} />
<NavItem active={activeTab === 'players'} label="Bürger" onClick={() => onNavigate('players')} />
{/* <NavItem active={activeTab === 'organizations'} label="Organisationen" onClick={() => onNavigate('organizations')} />*/}
<NavItem active={activeTab === 'projects'} label="Unternehmen" onClick={() => onNavigate('projects')} />
{user?.isAdmin && (
<NavItem active={activeTab === 'admin'} label="Admin" onClick={() => onNavigate('admin')} />
)}
</nav>
</div>
<div className="flex items-center gap-4">
<button
onClick={() => onNavigate('datapack')}
className="hidden md:flex items-center gap-2 text-xs font-medium text-textMain hover:text-accentInfo transition-colors"
>
<Icons.Box className="w-3 h-3" />
<span>Datapack holen</span>
</button>
<button
onClick={() => onNavigate('setup')}
className="hidden md:flex items-center gap-2 text-xs font-medium text-textMuted hover:text-accentInfo transition-colors border border-border rounded-full px-4 py-1.5 hover:bg-surfaceHighlight"
>
<Icons.Terminal className="w-3 h-3" />
<span>Admin Setup</span>
</button>
{/* Mobile Menu Toggle */}
<button
className="md:hidden text-textMuted hover:text-textMain"
onClick={() => setMobileMenuOpen(!mobileMenuOpen)}
>
<Icons.Layers className="w-6 h-6" />
</button>
</div>
</div>
{/* Mobile Nav Dropdown */}
{mobileMenuOpen && (
<div className="md:hidden border-t border-border bg-surface px-6 py-4 space-y-4 shadow-xl">
<div onClick={() => { onNavigate('dashboard'); setMobileMenuOpen(false); }} className="block py-2 text-textMuted hover:text-textMain">Übersicht</div>
<div onClick={() => { onNavigate('cities'); setMobileMenuOpen(false); }} className="block py-2 text-textMuted hover:text-textMain">Städte</div>
<div onClick={() => { onNavigate('players'); setMobileMenuOpen(false); }} className="block py-2 text-textMuted hover:text-textMain">Bürger</div>
<div onClick={() => { onNavigate('organizations'); setMobileMenuOpen(false); }} className="block py-2 text-textMuted hover:text-textMain">Organisationen</div>
<div onClick={() => { onNavigate('projects'); setMobileMenuOpen(false); }} className="block py-2 text-textMuted hover:text-textMain">Unternehmen</div>
{user?.isAdmin && (
<div onClick={() => { onNavigate('admin'); setMobileMenuOpen(false); }} className="block py-2 text-red-400 hover:text-red-300">Admin</div>
)}
<div onClick={() => { onNavigate('datapack'); setMobileMenuOpen(false); }} className="block py-2 text-textMain">Datapack holen</div>
<div onClick={() => { onNavigate('setup'); setMobileMenuOpen(false); }} className="block py-2 text-accentInfo font-mono text-sm border-t border-white/5 pt-4">{"Admin Setup >_"}</div>
</div>
)}
</header>
{/* Main Content - Page Flow */}
<main className="flex-1 w-full max-w-7xl mx-auto px-6 py-12 md:py-16">
<div className="animate-in fade-in duration-700 slide-in-from-bottom-4">
{children}
</div>
</main>
{/* Footer - Adds to the "Website" feel */}
<footer className="border-t border-border mt-auto bg-surface/30">
<div className="max-w-7xl mx-auto px-6 py-10 flex flex-col md:flex-row justify-between items-center text-sm text-textMuted">
<div className="flex items-center gap-2 mb-4 md:mb-0 opacity-50">
<div className="w-4 h-4 bg-textMuted rounded-full"></div>
<p>© 2024 Obsidian Platform</p>
</div>
<div className="flex flex-col md:flex-row items-center gap-8">
{/* Auth Section */}
<div className="flex items-center gap-4 mb-4 md:mb-0">
{user ? (
<div className="flex items-center gap-3">
<div className="flex items-center gap-2">
<img
src={user.avatarUrl}
alt={user.username}
className="w-6 h-6 rounded-full"
/>
<span className="text-textMain font-medium">{user.username}</span>
</div>
<button
onClick={() => authService.logout()}
className="text-xs text-textMuted hover:text-accentInfo transition-colors"
>
Logout
</button>
</div>
) : (
<button
onClick={() => authService.login()}
className="flex items-center gap-2 text-textMain hover:text-accentInfo transition-colors font-medium"
>
<Icons.Users className="w-4 h-4" />
<span>Discord Login</span>
</button>
)}
</div>
{/* Links */}
<div className="flex gap-8">
<span className="cursor-pointer hover:text-textMain transition-colors">Dokumentation</span>
<span className="cursor-pointer hover:text-textMain transition-colors">Server Status</span>
<span className="cursor-pointer hover:text-textMain transition-colors">Datenschutz</span>
</div>
</div>
</div>
</footer>
</div>
);
};
export default Layout;