Feature-Seite: alle Funktionen aus dem Modul-Register
Deploy / check (push) Has been cancelled
Deploy / deploy (push) Has been cancelled

/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 <noreply@anthropic.com>
This commit is contained in:
2026-07-31 16:17:50 +02:00
co-authored by Claude Fable 5
parent 2ce274707a
commit a32c267dda
6 changed files with 205 additions and 4 deletions
+147
View File
@@ -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 (
<>
<section className="page-header">
<div className="page-bg-text">FEATURES</div>
<div className="page-header-grid" aria-hidden="true" />
<div className="page-header-content">
<div className="page-tag">// Alles drin, nichts hinter einer Paywall</div>
<h1 className="page-title">Funktionen</h1>
<p className="page-subtitle">
{total
? `${total} Funktionen — jede einzeln ein- und ausschaltbar, jede im Browser einstellbar.`
: 'Jede Funktion einzeln ein- und ausschaltbar, jede im Browser einstellbar.'}
</p>
</div>
</section>
<section className="content">
{!data && <p className="notice">Lade </p>}
{data?.groups.map((group) => {
const items = data.features.filter((f) => f.group === group.id);
if (items.length === 0) return null;
return (
<div className="feature-group" key={group.id}>
<h2 className="settings-title">// {group.label}</h2>
<div className="feature-grid">
{items.map((f) => {
const Icon = FEATURE_ICONS[f.id] ?? IconIdea;
return (
<div className="card feature-card" key={f.id}>
<span className="feature-icon"><Icon size={20} /></span>
<div>
<span className="feature-name">{f.name}</span>
<p className="feature-desc">{f.desc}</p>
</div>
</div>
);
})}
</div>
</div>
);
})}
{data && (
<div className="feature-group">
<h2 className="settings-title">// Drumherum</h2>
<div className="feature-grid">
{EXTRAS.map(([Icon, name, desc]) => (
<div className="card feature-card" key={name}>
<span className="feature-icon"><Icon size={20} /></span>
<div>
<span className="feature-name">{name}</span>
<p className="feature-desc">{desc}</p>
</div>
</div>
))}
</div>
</div>
)}
{data && (
<div className="feature-outro">
<p>
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.
</p>
<div className="home-cta">
<Link className="btn btn-save with-icon" to="/commands">
<IconBot /> Befehle ansehen
</Link>
<Link className="btn btn-ghost with-icon" to="/">
Zur Übersicht <IconArrowRight size={15} />
</Link>
</div>
</div>
)}
</section>
</>
);
}