Community-Paket: Playtester-Programm, Starboard, Screenshot-Galerie

- 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>
This commit is contained in:
2026-07-23 12:34:34 +02:00
co-authored by Claude Opus 4.8
parent 6d8f383aed
commit a3c47656c5
13 changed files with 563 additions and 12 deletions
+67
View File
@@ -0,0 +1,67 @@
import { useEffect, useState } from 'react';
import { apiGet } from '../api.js';
const dateFmt = new Intl.DateTimeFormat('de-DE', { day: '2-digit', month: '2-digit', year: 'numeric' });
export default function Galerie() {
const [data, setData] = useState(null);
const [page, setPage] = useState(1);
const [error, setError] = useState(false);
useEffect(() => {
setError(false);
apiGet(`/api/gallery?page=${page}`).then(setData).catch(() => setError(true));
window.scrollTo(0, 0);
}, [page]);
const pages = data ? Math.ceil(data.total / data.pageSize) : 0;
const shots = (data?.items ?? []).flatMap((item) =>
item.images.map((src) => ({ src, author: item.author_name, date: item.posted_at, key: src }))
);
return (
<>
<section className="page-header">
<div className="page-bg-text">GALERIE</div>
<div className="page-header-grid" aria-hidden="true" />
<div className="page-header-content">
<div className="page-tag">// Community</div>
<h1 className="page-title">Galerie</h1>
<p className="page-subtitle">Screenshots aus der Community direkt aus dem Discord.</p>
</div>
</section>
<section className="content">
{error && <p className="notice">Galerie konnte nicht geladen werden.</p>}
{!error && !data && <p className="notice">Lade </p>}
{data?.total === 0 && <p className="notice">Noch keine Screenshots poste welche im Discord!</p>}
<div className="gallery-grid">
{shots.map((s) => (
<a className="shot" href={s.src} target="_blank" rel="noreferrer" key={s.key}>
<img src={s.src} alt={`Screenshot von ${s.author}`} loading="lazy" />
<span className="shot-meta">
<span className="shot-author">{s.author}</span>
<span className="shot-date">{dateFmt.format(new Date(s.date))}</span>
</span>
</a>
))}
</div>
{pages > 1 && (
<div className="pager">
<button className="btn" disabled={page <= 1} onClick={() => setPage(page - 1)}>
Neuere
</button>
<span className="pager-info">
{String(page).padStart(2, '0')} / {String(pages).padStart(2, '0')}
</span>
<button className="btn" disabled={page >= pages} onClick={() => setPage(page + 1)}>
Ältere
</button>
</div>
)}
</section>
</>
);
}