Hub und Bot-Produktseite lassen sich jetzt auf Englisch umstellen. Die Wahl haengt am Browser (Deutsch nur, wenn der Browser es will) und bleibt im localStorage stehen. - src/i18n.jsx: Context mit t(), tOr(), Datums- und Zahlenformat der Sprache. Keine Bibliothek, die Woerterbuecher werden mitgebaut — kein Nachlade-Blitzer. - Platzhalter im %s-Stil, dieselbe Schreibweise wie in den Bot-Texten. - Umschalter in der Navigation, zusaetzlich im Schubladen-Menue: .auth ist auf schmalen Schirmen ausgeblendet, sonst waere der Wechsel dort unerreichbar. - Datums- und Zahlenformat folgen mit (31.07.2026 / 31/07/2026, 45.231 / 45,231). - Die Funktionsliste kommt weiter deutsch vom Server; englische Modulnamen liegen unter feat.<id> im Woerterbuch. Fehlt einer, bleibt der Servertext stehen — ein neues Modul verschwindet nie von der Seite. Das Config-Panel und die Discord-Texte bleiben deutsch. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
116 lines
5.3 KiB
React
116 lines
5.3 KiB
React
// Rahmen der Bot-Produktseite — eigene Navigation, orange Leitfarbe.
|
|
// Das Dashboard ist dieselbe Config-Seite wie bisher, nur unter /dashboard.
|
|
import { useEffect, useState } from 'react';
|
|
import { Routes, Route, NavLink, Navigate, useLocation } from 'react-router-dom';
|
|
import { apiGet } from './api.js';
|
|
import BotLanding from './pages/BotLanding.jsx';
|
|
import BotFeatures from './pages/BotFeatures.jsx';
|
|
import BotCommands from './pages/BotCommands.jsx';
|
|
import Settings from './pages/Settings.jsx';
|
|
import Impressum from './pages/Impressum.jsx';
|
|
import Datenschutz from './pages/Datenschutz.jsx';
|
|
import { IconMenu, IconX } from './icons.jsx';
|
|
import { useT, LangSwitch } from './i18n.jsx';
|
|
|
|
export default function BotApp() {
|
|
const [me, setMe] = useState({ user: null, admin: false, scopes: [], loading: true });
|
|
const [scrolled, setScrolled] = useState(false);
|
|
const [drawer, setDrawer] = useState(false);
|
|
const [hub, setHub] = useState(null);
|
|
const location = useLocation();
|
|
const { t } = useT();
|
|
|
|
useEffect(() => { setDrawer(false); }, [location.pathname]);
|
|
|
|
useEffect(() => {
|
|
apiGet('/api/me')
|
|
.then((data) => setMe({ scopes: [], ...data, loading: false }))
|
|
.catch(() => setMe({ user: null, admin: false, scopes: [], loading: false }));
|
|
apiGet('/api/legal').then((d) => setHub(d.split ? d.hubUrl : null)).catch(() => {});
|
|
}, []);
|
|
|
|
useEffect(() => {
|
|
const onScroll = () => setScrolled(window.scrollY > 24);
|
|
window.addEventListener('scroll', onScroll, { passive: true });
|
|
return () => window.removeEventListener('scroll', onScroll);
|
|
}, []);
|
|
|
|
const canDashboard = me.admin || me.scopes?.length > 0;
|
|
|
|
return (
|
|
<div className="app site-bot">
|
|
<header className={`nav ${scrolled ? 'scrolled' : ''}`}>
|
|
<NavLink to="/" className="nav-logo">
|
|
D4RKBOT<span>.</span>
|
|
</NavLink>
|
|
|
|
<nav className="nav-links">
|
|
<NavLink to="/features">{t('nav.features')}</NavLink>
|
|
<NavLink to="/commands">{t('nav.commands')}</NavLink>
|
|
{canDashboard && <NavLink to="/dashboard">{t('nav.dashboard')}</NavLink>}
|
|
{hub && <a href={hub}>{t('nav.community')}</a>}
|
|
</nav>
|
|
|
|
<div className="auth">
|
|
<LangSwitch />
|
|
{me.loading ? null : me.user ? (
|
|
<span 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={t('nav.signOut')}>
|
|
<IconX size={13} />
|
|
</a>
|
|
</span>
|
|
) : (
|
|
<a className="btn-login" href="/auth/login">{t('nav.signIn')}</a>
|
|
)}
|
|
</div>
|
|
|
|
<button className="burger" onClick={() => setDrawer((d) => !d)} aria-label={t('nav.menu')}>
|
|
{drawer ? <IconX size={20} /> : <IconMenu size={20} />}
|
|
</button>
|
|
</header>
|
|
|
|
{drawer && (
|
|
<nav className="drawer" onClick={() => setDrawer(false)}>
|
|
<NavLink to="/features">{t('nav.features')}</NavLink>
|
|
<NavLink to="/commands">{t('nav.commands')}</NavLink>
|
|
{canDashboard && <NavLink to="/dashboard">{t('nav.dashboard')}</NavLink>}
|
|
{hub && <a href={hub}>{t('nav.community')}</a>}
|
|
{me.user
|
|
? <a href="/auth/logout">{t('nav.signOut')}</a>
|
|
: <a href="/auth/login">{t('nav.signIn')}</a>}
|
|
{/* Auf schmalen Schirmen ist .auth ausgeblendet — der
|
|
Sprachwechsel muss trotzdem erreichbar bleiben. */}
|
|
<div className="drawer-lang" onClick={(e) => e.stopPropagation()}>
|
|
<LangSwitch />
|
|
</div>
|
|
</nav>
|
|
)}
|
|
|
|
<main className="main">
|
|
<Routes>
|
|
<Route path="/" element={<BotLanding />} />
|
|
<Route path="/features" element={<BotFeatures />} />
|
|
<Route path="/commands" element={<BotCommands />} />
|
|
<Route path="/dashboard" element={<Settings me={me} />} />
|
|
<Route path="/impressum" element={<Impressum />} />
|
|
<Route path="/datenschutz" element={<Datenschutz />} />
|
|
<Route path="*" element={<Navigate to="/" replace />} />
|
|
</Routes>
|
|
</main>
|
|
|
|
<footer className="footer">
|
|
<span className="footer-logo">D4RKBOT<span>.</span></span>
|
|
<span className="footer-sep">//</span>
|
|
<a href="https://git.d4rkst3r.de/D4rkst3r/d4rkbot" target="_blank" rel="noreferrer">
|
|
{t('footer.source')}
|
|
</a>
|
|
<span className="footer-sep">//</span>
|
|
<NavLink to="/impressum">{t('footer.imprint')}</NavLink>
|
|
<NavLink to="/datenschutz">{t('footer.privacy')}</NavLink>
|
|
</footer>
|
|
</div>
|
|
);
|
|
}
|