From a32c267ddaca45b870bbd68e03480154a3b6b40e Mon Sep 17 00:00:00 2001 From: D4rkst3r Date: Fri, 31 Jul 2026 16:17:50 +0200 Subject: [PATCH] Feature-Seite: alle Funktionen aus dem Modul-Register MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit /features zeigte bisher dieselbe Landing wie /, obwohl die Navigation und ein Knopf auf der Startseite eine echte Funktionsliste versprachen. Die Seite listet jetzt alle 35 Bot-Funktionen, nach denselben Gruppen sortiert wie im Panel, dazu sechs Punkte zum Drumherum (Webinterface, Texte, API/SSO, eigene Seiten, Team-Rechte, Betrieb). Die Einträge kommen über das neue öffentliche /api/features aus src/modules.js — derselben Quelle, aus der die Modul-Schalter kommen. Eine neue Funktion erscheint damit automatisch auf der Produktseite, statt dass jemand daran denken muss. Der Endpunkt gibt bewusst nur Name, Beschreibung und Gruppe heraus, nicht welche Module hier gerade laufen. Auch die Zahl auf der Startseite kommt von dort, damit nicht zwei Stellen dieselbe Zahl pflegen. Co-Authored-By: Claude Fable 5 --- docs/konzept-zwei-seiten.md | 2 +- frontend/src/BotApp.jsx | 5 +- frontend/src/pages/BotFeatures.jsx | 147 +++++++++++++++++++++++++++++ frontend/src/pages/BotLanding.jsx | 9 ++ frontend/src/style.css | 36 +++++++ src/web/api.js | 10 +- 6 files changed, 205 insertions(+), 4 deletions(-) create mode 100644 frontend/src/pages/BotFeatures.jsx diff --git a/docs/konzept-zwei-seiten.md b/docs/konzept-zwei-seiten.md index 89e2001..7d31643 100644 --- a/docs/konzept-zwei-seiten.md +++ b/docs/konzept-zwei-seiten.md @@ -40,7 +40,7 @@ Konfigurieren. | Route | Inhalt | |---|---| | `/` | Landing: „Das kann der Bot" — Hero, Feature-Kacheln, Einladen-Button | -| `/features` | Alle Funktionen ausführlich, gruppiert (Community, Moderation, Feeds, Server) | +| `/features` | Alle Funktionen ausführlich, gruppiert — die Liste kommt aus `/api/features` und damit aus dem Modul-Register, kann also nicht veralten | | `/commands` | Slash-Command-Referenz, aus dem Code erzeugt | | `/docs` | Kurzanleitungen: API v1, SSO, Webhooks, Design-System | | `/dashboard` | die heutige Config-Seite (Login + Berechtigung nötig) | diff --git a/frontend/src/BotApp.jsx b/frontend/src/BotApp.jsx index a55b613..ac255b1 100644 --- a/frontend/src/BotApp.jsx +++ b/frontend/src/BotApp.jsx @@ -1,9 +1,10 @@ // Rahmen der Bot-Produktseite — eigene Navigation, orange Leitfarbe. -// Das Dashboard ist dieselbe Setup-Seite wie bisher, nur unter /dashboard. +// 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'; @@ -82,7 +83,7 @@ export default function BotApp() {
} /> - } /> + } /> } /> } /> } /> diff --git a/frontend/src/pages/BotFeatures.jsx b/frontend/src/pages/BotFeatures.jsx new file mode 100644 index 0000000..a85adc8 --- /dev/null +++ b/frontend/src/pages/BotFeatures.jsx @@ -0,0 +1,147 @@ +// Funktionsliste der Produktseite. Die Einträge kommen aus /api/features und +// damit aus demselben Register wie die Module im Panel — die Seite kann also +// nicht veralten, wenn eine Funktion dazukommt. +import { useEffect, useState } from 'react'; +import { Link } from 'react-router-dom'; +import { apiGet } from '../api.js'; +import { + IconDevlog, IconCommits, IconChangelog, IconActivity, IconMessage, IconPlay, + IconLevel, IconStar, IconGallery, IconThumbsUp, IconGiveaway, IconBirthday, + IconEvents, IconGame, IconKey, IconRoles, IconApplications, IconShield, + IconArchive, IconMail, IconSupport, IconMagic, IconTeam, IconRefresh, + IconWarning, IconServer, IconMonitor, IconVoice, IconZap, IconClock, + IconSend, IconTag, IconDatabase, IconPackage, IconLock, + IconApi, IconPages, IconBrand, IconBot, IconArrowRight, IconIdea, +} from '../icons.jsx'; + +// Ein Symbol je Funktion — bewusst hier und nicht im Register, damit der +// Bot-Code nichts über das Frontend wissen muss. +const FEATURE_ICONS = { + devlogs: IconDevlog, commit_feed: IconCommits, releases: IconChangelog, + weekly_recap: IconActivity, devlog_threads: IconMessage, social: IconPlay, + levels: IconLevel, starboard: IconStar, gallery: IconGallery, + voting: IconThumbsUp, giveaways: IconGiveaway, birthdays: IconBirthday, + events: IconEvents, playtester: IconGame, alpha_keys: IconKey, + role_menus: IconRoles, applications: IconApplications, + moderation: IconShield, modlog: IconArchive, modmail: IconMail, + tickets: IconSupport, welcome: IconMagic, autorole: IconTeam, + sticky_roles: IconRefresh, bug_reports: IconWarning, + server_monitor: IconServer, watchdog: IconMonitor, temp_voice: IconVoice, + triggers: IconZap, reminders: IconClock, scheduled_posts: IconSend, + tags: IconTag, backups: IconDatabase, repo_backups: IconPackage, + member_gate: IconLock, +}; + +// Was kein Discord-Modul ist, sondern zum Drumherum gehört +const EXTRAS = [ + [IconBrand, 'Webinterface im eigenen Look', + 'Jede Funktion wird im Browser eingestellt — keine Konfigurationsdatei, kein Neustart. ' + + 'Farben, Bot-Avatar und Banner gehören dazu, und andere Dienste können denselben Look mitbenutzen.'], + [IconMessage, 'Texte selbst schreiben', + 'Begrüßung, Level-Ansage, Ticket-Texte — alles, was der Bot nach außen schreibt, ' + + 'lässt sich im Panel ändern, mit Platzhaltern für Name, Zahl und Link.'], + [IconApi, 'API & Single Sign-On', + 'Eigene Skripte posten über die API v1 mit Bearer-Schlüsseln und Bereichs-Rechten. ' + + 'Andere Dienste nutzen den Discord-Login mit und bekommen die Rollen gleich mitgeliefert.'], + [IconPages, 'Eigene Seiten', + 'Regeln, Über uns, FAQ — in Markdown geschrieben, mit einem Klick veröffentlicht ' + + 'und automatisch im Menü.'], + [IconTeam, 'Rechte fürs Team', + 'Team-Mitglieder bekommen gezielten Zugriff auf einzelne Bereiche. ' + + 'Wer was geändert hat, steht im Protokoll.'], + [IconServer, 'Läuft überall', + 'Node und SQLite in einem Container, ein Volume für die Daten. Nächtliche Sicherungen ' + + 'inklusive — von der Datenbank und allen Repositories.'], +]; + +export default function BotFeatures() { + const [data, setData] = useState(null); + + useEffect(() => { + apiGet('/api/features').then(setData).catch(() => setData({ groups: [], features: [] })); + window.scrollTo(0, 0); + }, []); + + const total = data ? data.features.length + EXTRAS.length : null; + + return ( + <> +
+
FEATURES
+
+ +
+ {!data &&

Lade …

} + + {data?.groups.map((group) => { + const items = data.features.filter((f) => f.group === group.id); + if (items.length === 0) return null; + return ( +
+

// {group.label}

+
+ {items.map((f) => { + const Icon = FEATURE_ICONS[f.id] ?? IconIdea; + return ( +
+ +
+ {f.name} +

{f.desc}

+
+
+ ); + })} +
+
+ ); + })} + + {data && ( +
+

// Drumherum

+
+ {EXTRAS.map(([Icon, name, desc]) => ( +
+ +
+ {name} +

{desc}

+
+
+ ))} +
+
+ )} + + {data && ( +
+

+ Gebaut, weil drei Bots mit je eigenem Abo für eine Community + weder übersichtlich noch günstig sind. Der Quelltext liegt offen, + der Betrieb kostet einen Container. +

+
+ + Befehle ansehen + + + Zur Übersicht + +
+
+ )} +
+ + ); +} diff --git a/frontend/src/pages/BotLanding.jsx b/frontend/src/pages/BotLanding.jsx index c578eaa..226b597 100644 --- a/frontend/src/pages/BotLanding.jsx +++ b/frontend/src/pages/BotLanding.jsx @@ -23,11 +23,14 @@ const HIGHLIGHTS = [ export default function BotLanding() { const [status, setStatus] = useState(null); const [hub, setHub] = useState(null); + const [featureCount, setFeatureCount] = useState(null); useEffect(() => { // Nur verlinken, wenn der Hub wirklich woanders liegt apiGet('/api/legal').then((d) => setHub(d.split ? d.hubUrl : null)).catch(() => {}); apiGet('/api/serverstats').then(setStatus).catch(() => {}); + // Zahl kommt aus dem Modul-Register, damit sie nicht veraltet + apiGet('/api/features').then((d) => setFeatureCount(d.features.length)).catch(() => {}); window.scrollTo(0, 0); }, []); @@ -59,6 +62,12 @@ export default function BotLanding() {
+ {featureCount && ( +
+ {featureCount} + Funktionen, einzeln schaltbar +
+ )}
16 Slash-Befehle diff --git a/frontend/src/style.css b/frontend/src/style.css index eec5cc8..0c1cc0b 100644 --- a/frontend/src/style.css +++ b/frontend/src/style.css @@ -1642,6 +1642,42 @@ button.with-icon, a.with-icon { justify-content: center; } .site-bot .particle { background: #ff4d00; } .bot-hero { min-height: 460px; } +/* Funktionsliste der Produktseite */ +.feature-group { margin-bottom: 2.6rem; } +.feature-grid { + display: grid; gap: .8rem; + grid-template-columns: repeat(auto-fill, minmax(320px, 1fr)); + margin-top: 1.1rem; +} +.feature-card { + display: flex; gap: .9rem; align-items: flex-start; + padding: 1.1rem 1.2rem; + transition: border-color .2s, transform .2s; +} +.feature-card:hover { border-color: var(--neon); transform: translateY(-2px); } +.feature-icon { + flex: none; display: inline-flex; + color: var(--neon); margin-top: .15rem; +} +.feature-name { + display: block; + font-family: var(--display); font-size: 1.1rem; + letter-spacing: .04em; color: var(--text); + margin-bottom: .3rem; +} +.feature-desc { + color: var(--muted); font-weight: 300; + font-size: .92rem; line-height: 1.5; margin: 0; +} +.feature-outro { + border-top: 1px solid var(--border); + padding-top: 1.8rem; margin-top: .5rem; +} +.feature-outro p { + color: var(--muted); font-weight: 300; + line-height: 1.7; max-width: 46rem; margin: 0 0 1.4rem; +} + /* Befehlsliste */ .cmd-list { display: flex; flex-direction: column; gap: .5rem; } .cmd-row { diff --git a/src/web/api.js b/src/web/api.js index 3110378..c836bb5 100644 --- a/src/web/api.js +++ b/src/web/api.js @@ -20,7 +20,7 @@ import { savePage, getPage, listPages, listPublishedPages, deletePage, } from '../db.js'; import crypto from 'node:crypto'; -import { moduleStates, setModuleEnabled, MODULE_GROUPS } from '../modules.js'; +import { moduleStates, setModuleEnabled, MODULE_GROUPS, MODULES } from '../modules.js'; import { templateStates, setTemplate, TEMPLATE_GROUPS } from '../templates.js'; import { lastResults, GAME_ICONS } from '../bot/server-monitor.js'; import { publishAppForm, unpublishAppForm, MAX_QUESTIONS } from '../bot/app-forms.js'; @@ -185,6 +185,14 @@ export function registerApiRoutes(app, client) { return { ok: true, modules: moduleStates() }; }); + // Funktionsliste für die Produktseite — öffentlich und ohne Zustand: + // was der Bot kann, nicht was hier gerade eingeschaltet ist. Quelle ist + // dasselbe Register wie die Module, damit die Seite nicht veraltet. + app.get('/api/features', async () => ({ + groups: MODULE_GROUPS, + features: MODULES.map((m) => ({ id: m.id, name: m.name, desc: m.desc, group: m.group })), + })); + // --- Text-Vorlagen: was der Bot nach außen schreibt --- // (nicht /api/templates — das sind die gespeicherten Composer-Nachrichten)