Files
d4rkbot/frontend/src/pages/BotFeatures.jsx
T
D4rkst3randClaude Fable 5 c6977f9c4a
Deploy / check (push) Has been cancelled
Deploy / deploy (push) Has been cancelled
Modul-Tab im Plugin-Raster: Filter-Reiter und Symbole
Vorbild war das MEE6-Dashboard: Kategorie-Reiter über einem Raster aus
Karten, jede mit einem eigenen Symbol. Inhaltlich war unser Modul-Tab
schon dort — es fehlten genau diese zwei Dinge.

Die Gruppen standen als Überschriften untereinander, was bei 35 Karten
eine lange Rolle ergab. Jetzt filtern Reiter mit Anzahl: Alle, die vier
Gruppen, dazu „Noch einzurichten" und „Ausgeschaltet". Die beiden letzten
sind bewusst dabei — mit genau diesen Fragen kommt man am häufigsten her.
Die Gruppe steht klein unter dem Namen, damit sie im gefilterten Raster
nicht verlorengeht.

Jede Karte hat eine Symbol-Kachel, die bei laufendem Modul in der
Markenfarbe leuchtet und sonst grau bleibt — man erkennt eine Funktion
damit am Bild statt am Lesen. Die Zuordnung lag schon in der
Funktionsseite; sie liegt jetzt in module-icons.jsx, damit beide Seiten
dieselbe benutzen.

Farbige Kachelbilder wie bei MEE6 gibt es bewusst nicht: die Strichsymbole
passen zum Rest der Oberfläche, bunte Kacheln wären ein Fremdkörper.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-31 17:22:04 +02:00

126 lines
6.0 KiB
React

// 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 {
IconMessage, IconTeam, IconServer, IconApi, IconPages, IconBrand,
IconBot, IconArrowRight,
} from '../icons.jsx';
import { moduleIcon } from '../module-icons.jsx';
// 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 = moduleIcon(f.id);
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>
</>
);
}