mirror of
https://github.com/ceratic/project_vollidioten_website.git
synced 2026-05-14 00:16:47 +02:00
197 lines
9.3 KiB
TypeScript
197 lines
9.3 KiB
TypeScript
import React, { useState, useEffect } from 'react';
|
|
import { Link, useLocation } from 'react-router-dom';
|
|
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,
|
|
to
|
|
}: {
|
|
active: boolean;
|
|
label: string;
|
|
to: string;
|
|
}) => (
|
|
<Link
|
|
to={to}
|
|
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}
|
|
</Link>
|
|
);
|
|
|
|
const Layout: React.FC<LayoutProps> = ({ children, activeTab, onNavigate }) => {
|
|
const [mobileMenuOpen, setMobileMenuOpen] = useState(false);
|
|
const [user, setUser] = useState<DiscordUser | null>(null);
|
|
const [rememberMe, setRememberMe] = useState(() => {
|
|
// Load remember me preference from localStorage
|
|
return localStorage.getItem('rememberMe') === 'true';
|
|
});
|
|
|
|
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" to="/" />
|
|
<NavItem active={activeTab === 'cities'} label="Städte" to="/cities" />
|
|
<NavItem active={activeTab === 'players'} label="Bürger" to="/players" />
|
|
{/* <NavItem active={activeTab === 'organizations'} label="Organisationen" to="/organizations" />*/}
|
|
<NavItem active={activeTab === 'projects'} label="Unternehmen" to="/projects" />
|
|
{user?.isAdmin && (
|
|
<NavItem active={activeTab === 'admin'} label="Admin" to="/admin" />
|
|
)}
|
|
</nav>
|
|
</div>
|
|
|
|
<div className="flex items-center gap-4">
|
|
{user?.isAdmin && (
|
|
<>
|
|
<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">
|
|
<Link to="/" onClick={() => setMobileMenuOpen(false)} className="block py-2 text-textMuted hover:text-textMain">Übersicht</Link>
|
|
<Link to="/cities" onClick={() => setMobileMenuOpen(false)} className="block py-2 text-textMuted hover:text-textMain">Städte</Link>
|
|
<Link to="/players" onClick={() => setMobileMenuOpen(false)} className="block py-2 text-textMuted hover:text-textMain">Bürger</Link>
|
|
<Link to="/organizations" onClick={() => setMobileMenuOpen(false)} className="block py-2 text-textMuted hover:text-textMain">Organisationen</Link>
|
|
<Link to="/projects" onClick={() => setMobileMenuOpen(false)} className="block py-2 text-textMuted hover:text-textMain">Unternehmen</Link>
|
|
{user?.isAdmin && (
|
|
<Link to="/admin" onClick={() => setMobileMenuOpen(false)} className="block py-2 text-red-400 hover:text-red-300">Admin</Link>
|
|
)}
|
|
<Link to="/datapack" onClick={() => setMobileMenuOpen(false)} className="block py-2 text-textMain">Datapack holen</Link>
|
|
<Link to="/setup" onClick={() => setMobileMenuOpen(false)} className="block py-2 text-accentInfo font-mono text-sm border-t border-white/5 pt-4">{"Admin Setup >_"}</Link>
|
|
</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>
|
|
) : (
|
|
<div className="flex items-center gap-2">
|
|
<button
|
|
onClick={() => authService.login(rememberMe)}
|
|
className="flex items-center gap-2 text-textMain hover:text-accentInfo transition-colors font-medium text-sm"
|
|
>
|
|
<Icons.Users className="w-4 h-4" />
|
|
<span>Discord Login</span>
|
|
</button>
|
|
<label className="flex items-center gap-1 text-xs text-textMuted cursor-pointer">
|
|
<input
|
|
type="checkbox"
|
|
checked={rememberMe}
|
|
onChange={(e) => {
|
|
setRememberMe(e.target.checked);
|
|
// Store preference for next login
|
|
localStorage.setItem('rememberMe', e.target.checked.toString());
|
|
}}
|
|
className="w-3 h-3 text-accentInfo bg-surface border-border rounded focus:ring-accentInfo"
|
|
/>
|
|
<span>Remember me (30 days)</span>
|
|
</label>
|
|
</div>
|
|
)}
|
|
</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;
|