UX-Paket: Navbar aufgeräumt, /server-Seite, Lightbox, Skeletons, Audit-Log, Reroll
- Navbar entschlackt: nur noch Devlog · Roadmap · Server · Community · Setup; Galerie/Level/Events/Changelog im Community-Dropdown, Profil/Commits/Logout im Avatar-Menü; Mobile-Burger mit Drawer - Öffentliche /server-Seite: Live-Status-Karten mit Spieler-Balken, Map, Connect-Button und 24h-Spielerzahl-Chart (SVG); Monitor sampelt jede Abfrage in player_history (7 Tage Retention), GET /api/servers liefert nur öffentliche Felder (keine Query-URLs/Hosts) - Lightbox: Galerie- und Devlog-Bilder öffnen im Overlay mit Pfeiltasten, Buttons, Wisch-Gesten und Zähler statt neuem Tab - Skeleton-Loader: schimmernde Platzhalter auf Devlogs, Roadmap, Galerie, Level, Changelog und Server statt "Lade …" - Team-Audit-Log: audit_log-Tabelle (max 500 Einträge), Logging bei Settings/Composer/Vorlagen/Rollen-Menüs/Gameservern/Team/API-Keys/ Branding, GET /api/auditlog (Owner) + Liste im Team-Tab - Giveaway: 🔁 Reroll-Button (Admin-only) + 👥 Teilnehmerliste am Gewinner-Post Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
+75
-13
@@ -1,5 +1,5 @@
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import { Routes, Route, NavLink, Navigate } from 'react-router-dom';
|
||||
import { useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { Routes, Route, NavLink, Navigate, useLocation } from 'react-router-dom';
|
||||
import { apiGet } from './api.js';
|
||||
import Devlogs from './pages/Devlogs.jsx';
|
||||
import Changelog from './pages/Changelog.jsx';
|
||||
@@ -10,6 +10,33 @@ import Events from './pages/Events.jsx';
|
||||
import Commits from './pages/Commits.jsx';
|
||||
import Settings from './pages/Settings.jsx';
|
||||
import Profil from './pages/Profil.jsx';
|
||||
import Server from './pages/Server.jsx';
|
||||
|
||||
const COMMUNITY_LINKS = [
|
||||
['/galerie', 'Galerie'],
|
||||
['/level', 'Level'],
|
||||
['/events', 'Events'],
|
||||
['/changelog', 'Changelog'],
|
||||
];
|
||||
|
||||
/** Dropdown in der Navbar (schließt bei Klick daneben und bei Navigation) */
|
||||
function NavDropdown({ label, active, children }) {
|
||||
const [open, setOpen] = useState(false);
|
||||
const ref = useRef(null);
|
||||
useEffect(() => {
|
||||
const onDoc = (e) => { if (ref.current && !ref.current.contains(e.target)) setOpen(false); };
|
||||
document.addEventListener('mousedown', onDoc);
|
||||
return () => document.removeEventListener('mousedown', onDoc);
|
||||
}, []);
|
||||
return (
|
||||
<div className={`nav-drop ${open ? 'open' : ''}`} ref={ref}>
|
||||
<button className={`nav-drop-btn ${active ? 'active' : ''}`} onClick={() => setOpen((o) => !o)}>
|
||||
{label} <span className="nav-caret">▾</span>
|
||||
</button>
|
||||
{open && <div className="nav-drop-menu" onClick={() => setOpen(false)}>{children}</div>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function DiscordMark() {
|
||||
return (
|
||||
@@ -51,6 +78,12 @@ export default function App() {
|
||||
// Login-Status einmal beim Laden holen
|
||||
const [me, setMe] = useState({ user: null, admin: false, scopes: [], loading: true });
|
||||
const [scrolled, setScrolled] = useState(false);
|
||||
const [drawer, setDrawer] = useState(false);
|
||||
const location = useLocation();
|
||||
const communityActive = COMMUNITY_LINKS.some(([to]) => location.pathname.startsWith(to));
|
||||
|
||||
// Drawer bei Seitenwechsel schließen
|
||||
useEffect(() => { setDrawer(false); }, [location.pathname]);
|
||||
// Member-Gate: OAuth war okay, aber User ist nicht auf dem Discord (?gate=1)
|
||||
const [gated, setGated] = useState(() => new URLSearchParams(window.location.search).has('gate'));
|
||||
const [invite, setInvite] = useState(null);
|
||||
@@ -83,30 +116,58 @@ export default function App() {
|
||||
<nav className="nav-links">
|
||||
<NavLink to="/devlogs">Devlog</NavLink>
|
||||
<NavLink to="/roadmap">Roadmap</NavLink>
|
||||
<NavLink to="/galerie">Galerie</NavLink>
|
||||
<NavLink to="/level">Level</NavLink>
|
||||
<NavLink to="/events">Events</NavLink>
|
||||
<NavLink to="/changelog">Changelog</NavLink>
|
||||
{me.user && <NavLink to="/profil">Profil</NavLink>}
|
||||
{me.admin && <NavLink to="/commits">Commits</NavLink>}
|
||||
<NavLink to="/server">Server</NavLink>
|
||||
<NavDropdown label="Community" active={communityActive}>
|
||||
{COMMUNITY_LINKS.map(([to, label]) => (
|
||||
<NavLink to={to} key={to}>{label}</NavLink>
|
||||
))}
|
||||
</NavDropdown>
|
||||
{(me.admin || me.scopes?.length > 0) && <NavLink to="/settings">Setup</NavLink>}
|
||||
</nav>
|
||||
|
||||
<div className="auth">
|
||||
{me.loading ? null : me.user ? (
|
||||
<div className="user-chip">
|
||||
{me.user.avatar && <img className="avatar" src={me.user.avatar} alt="" />}
|
||||
<span className="username">{me.user.username}</span>
|
||||
<a className="logout" href="/auth/logout" title="Logout">✕</a>
|
||||
</div>
|
||||
<NavDropdown
|
||||
label={
|
||||
<span className="user-chip-inner">
|
||||
{me.user.avatar && <img className="avatar" src={me.user.avatar} alt="" />}
|
||||
<span className="username">{me.user.username}</span>
|
||||
</span>
|
||||
}
|
||||
>
|
||||
<NavLink to="/profil">Profil</NavLink>
|
||||
{me.admin && <NavLink to="/commits">Commits</NavLink>}
|
||||
<a href="/auth/logout">Logout</a>
|
||||
</NavDropdown>
|
||||
) : (
|
||||
<a className="btn-login" href="/auth/login">
|
||||
<DiscordMark /> Login
|
||||
</a>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<button className="burger" onClick={() => setDrawer((d) => !d)} aria-label="Menü">
|
||||
{drawer ? '✕' : '☰'}
|
||||
</button>
|
||||
</header>
|
||||
|
||||
{drawer && (
|
||||
<nav className="drawer" onClick={() => setDrawer(false)}>
|
||||
<NavLink to="/devlogs">Devlog</NavLink>
|
||||
<NavLink to="/roadmap">Roadmap</NavLink>
|
||||
<NavLink to="/server">Server</NavLink>
|
||||
{COMMUNITY_LINKS.map(([to, label]) => (
|
||||
<NavLink to={to} key={to}>{label}</NavLink>
|
||||
))}
|
||||
{me.user && <NavLink to="/profil">Profil</NavLink>}
|
||||
{me.admin && <NavLink to="/commits">Commits</NavLink>}
|
||||
{(me.admin || me.scopes?.length > 0) && <NavLink to="/settings">Setup</NavLink>}
|
||||
{me.user
|
||||
? <a href="/auth/logout">Logout</a>
|
||||
: <a href="/auth/login">Login mit Discord</a>}
|
||||
</nav>
|
||||
)}
|
||||
|
||||
<main className="main">
|
||||
{gated && (
|
||||
<div className="gate-banner">
|
||||
@@ -126,6 +187,7 @@ export default function App() {
|
||||
<Route path="/" element={<Navigate to="/devlogs" replace />} />
|
||||
<Route path="/devlogs" element={<Devlogs me={me} />} />
|
||||
<Route path="/roadmap" element={<Roadmap />} />
|
||||
<Route path="/server" element={<Server />} />
|
||||
<Route path="/galerie" element={<Galerie />} />
|
||||
<Route path="/level" element={<Level />} />
|
||||
<Route path="/events" element={<Events />} />
|
||||
|
||||
Reference in New Issue
Block a user