- Playtester: /playtester-setup postet Bewerbungs-Embed mit 🧪-Button; Toggle vergibt Rolle + DB-Eintrag; Liste + Rollen-Auswahl auf der Setup-Seite - Starboard: ab konfigurierbarem ⭐-Schwellwert Repost als Embed in den Best-of-Kanal (Dedupe, kein Selbst-Boarding); neue Intents/Partials für Reaktionen - Galerie: Bilder aus dem Screenshot-Kanal lokal archiviert (live + /galerie-backfill, Delete-Sync, Bot reagiert mit ⭐) → öffentliche /galerie-Seite mit Hover-Grid - Setup-Seite: Sektion // Community (Rolle, Starboard-Kanal + Schwellwert, Screenshot-Kanal) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
120 lines
5.1 KiB
React
120 lines
5.1 KiB
React
import { useEffect, useMemo, useState } from 'react';
|
|
import { Routes, Route, NavLink, Navigate } from 'react-router-dom';
|
|
import { apiGet } from './api.js';
|
|
import Devlogs from './pages/Devlogs.jsx';
|
|
import Changelog from './pages/Changelog.jsx';
|
|
import Roadmap from './pages/Roadmap.jsx';
|
|
import Galerie from './pages/Galerie.jsx';
|
|
import Commits from './pages/Commits.jsx';
|
|
import Settings from './pages/Settings.jsx';
|
|
|
|
function DiscordMark() {
|
|
return (
|
|
<svg className="discord-mark" viewBox="0 0 24 24" aria-hidden="true">
|
|
<path
|
|
fill="currentColor"
|
|
d="M20.32 4.37a19.8 19.8 0 0 0-4.93-1.51 13.78 13.78 0 0 0-.64 1.28 18.27 18.27 0 0 0-5.5 0 12.64 12.64 0 0 0-.64-1.28c-1.71.29-3.37.8-4.93 1.51A20.26 20.26 0 0 0 .1 18.06a19.9 19.9 0 0 0 6.07 3.03c.49-.66.93-1.37 1.3-2.1a12.9 12.9 0 0 1-2.05-.98c.17-.12.34-.25.5-.38a14.2 14.2 0 0 0 12.16 0c.16.13.33.26.5.38-.65.39-1.34.71-2.05.98.37.73.81 1.44 1.3 2.1a19.84 19.84 0 0 0 6.07-3.03 20.2 20.2 0 0 0-3.58-13.69ZM8.02 15.33c-1.18 0-2.16-1.08-2.16-2.42 0-1.33.95-2.42 2.16-2.42 1.21 0 2.18 1.09 2.16 2.42 0 1.34-.95 2.42-2.16 2.42Zm7.96 0c-1.18 0-2.15-1.08-2.15-2.42 0-1.33.95-2.42 2.15-2.42 1.22 0 2.18 1.09 2.16 2.42 0 1.34-.94 2.42-2.16 2.42Z"
|
|
/>
|
|
</svg>
|
|
);
|
|
}
|
|
|
|
/* Aufsteigende Neon-Partikel wie auf der Platform-Seite */
|
|
function Particles() {
|
|
const particles = useMemo(
|
|
() =>
|
|
Array.from({ length: 24 }, (_, i) => ({
|
|
id: i,
|
|
left: `${Math.random() * 100}%`,
|
|
dur: `${6 + Math.random() * 8}s`,
|
|
delay: `${Math.random() * 8}s`,
|
|
})),
|
|
[]
|
|
);
|
|
return (
|
|
<div className="particles-wrap" aria-hidden="true">
|
|
{particles.map((p) => (
|
|
<span
|
|
key={p.id}
|
|
className="particle"
|
|
style={{ left: p.left, '--dur': p.dur, '--delay': p.delay, bottom: 0 }}
|
|
/>
|
|
))}
|
|
</div>
|
|
);
|
|
}
|
|
|
|
export default function App() {
|
|
// Login-Status einmal beim Laden holen
|
|
const [me, setMe] = useState({ user: null, admin: false, loading: true });
|
|
const [scrolled, setScrolled] = useState(false);
|
|
|
|
useEffect(() => {
|
|
apiGet('/api/me')
|
|
.then((data) => setMe({ ...data, loading: false }))
|
|
.catch(() => setMe({ user: null, admin: false, loading: false }));
|
|
}, []);
|
|
|
|
useEffect(() => {
|
|
const onScroll = () => setScrolled(window.scrollY > 24);
|
|
window.addEventListener('scroll', onScroll, { passive: true });
|
|
return () => window.removeEventListener('scroll', onScroll);
|
|
}, []);
|
|
|
|
return (
|
|
<div className="app">
|
|
<Particles />
|
|
|
|
<header className={`nav ${scrolled ? 'scrolled' : ''}`}>
|
|
<NavLink to="/" className="nav-logo">
|
|
D4RKST<span>3R</span>
|
|
</NavLink>
|
|
|
|
<nav className="nav-links">
|
|
<NavLink to="/devlogs">Devlog</NavLink>
|
|
<NavLink to="/roadmap">Roadmap</NavLink>
|
|
<NavLink to="/galerie">Galerie</NavLink>
|
|
<NavLink to="/changelog">Changelog</NavLink>
|
|
{me.admin && <NavLink to="/commits">Commits</NavLink>}
|
|
{me.admin && <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>
|
|
) : (
|
|
<a className="btn-login" href="/auth/login">
|
|
<DiscordMark /> Login
|
|
</a>
|
|
)}
|
|
</div>
|
|
</header>
|
|
|
|
<main className="main">
|
|
<Routes>
|
|
<Route path="/" element={<Navigate to="/devlogs" replace />} />
|
|
<Route path="/devlogs" element={<Devlogs me={me} />} />
|
|
<Route path="/roadmap" element={<Roadmap />} />
|
|
<Route path="/galerie" element={<Galerie />} />
|
|
<Route path="/changelog" element={<Changelog />} />
|
|
<Route path="/commits" element={<Commits me={me} />} />
|
|
<Route path="/settings" element={<Settings me={me} />} />
|
|
<Route path="*" element={<Navigate to="/devlogs" replace />} />
|
|
</Routes>
|
|
</main>
|
|
|
|
<footer className="footer">
|
|
<span className="footer-logo">D4RKST<span>3R</span></span>
|
|
<span className="footer-sep">//</span>
|
|
<a href="https://git.d4rkst3r.de/D4rkst3r" target="_blank" rel="noreferrer">
|
|
git.d4rkst3r.de
|
|
</a>
|
|
</footer>
|
|
</div>
|
|
);
|
|
}
|