Community-Endgame: Alpha-Keys, Bewerbungen, Events, Stats, Triggers, /remind, Social, Temp-Voice + MEE6-Bot-Karte
- Alpha-Keys: Pool im Community-Tab, Ein-Klick-Verteilung per DM an alle Playtester (Key bleibt frei, wenn die DM geblockt wird) - Bewerbungs-Formulare: Builder im neuen Bewerbungen-Tab (bis 5 Fragen), Button → Discord-Modal → Review-Embed mit ✅/❌, Rolle + DM bei Entscheidung - Events: GuildScheduledEventCreate → Announce-Embed; öffentliche /events-Seite aus den Discord-Events (5-min-Cache) - Server-Stats: activity_daily (Nachrichten/Joins/Leaves) → Balken-Chart auf /level - Triggers (Auto-Antworten, 30s-Cooldown), /remind (DM-Scheduler), Twitch-Live (Helix, App-Creds write-only) + YouTube-RSS-Announcements, Temp-Voice (Join to Create, Cleanup bei Leerstand + Start) - Brand-Tab: MEE6-Style Bot-Identity-Karte (Avatar-Vorschau mit Status-Dot, Bot-Name via setUsername, Presence online/idle/dnd, Aktivität) - Neue Intents: GuildVoiceStates, GuildScheduledEvents; alles smoke-getestet Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -10,12 +10,41 @@ function xpForLevel(level) {
|
||||
|
||||
const MEDALS = ['🥇', '🥈', '🥉'];
|
||||
|
||||
/** Nachrichten/Tag der letzten 30 Tage als Balken */
|
||||
function ActivityChart({ stats }) {
|
||||
const byDay = new Map(stats.days.map((d) => [d.day, d]));
|
||||
const days = [];
|
||||
for (let i = 29; i >= 0; i--) {
|
||||
const key = new Date(Date.now() - i * 86400000).toISOString().slice(0, 10);
|
||||
days.push({ key, ...(byDay.get(key) ?? { messages: 0, joins: 0, leaves: 0 }) });
|
||||
}
|
||||
const max = Math.max(1, ...days.map((d) => d.messages));
|
||||
const total = days.reduce((s, d) => s + d.messages, 0);
|
||||
return (
|
||||
<div className="roadmap-group" style={{ marginTop: '2.5rem' }}>
|
||||
<h2 className="settings-title">// Server-Aktivität — {total.toLocaleString('de-DE')} Nachrichten in 30 Tagen · {stats.members} Member</h2>
|
||||
<div className="act-chart">
|
||||
{days.map((d) => (
|
||||
<span
|
||||
key={d.key}
|
||||
className="act-bar"
|
||||
style={{ height: `${Math.max(4, (d.messages / max) * 100)}%` }}
|
||||
title={`${d.key}: ${d.messages} Nachrichten · +${d.joins}/−${d.leaves} Member`}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default function Level() {
|
||||
const [rows, setRows] = useState(null);
|
||||
const [stats, setStats] = useState(null);
|
||||
const [error, setError] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
apiGet('/api/levels').then((d) => setRows(d.levels)).catch(() => setError(true));
|
||||
apiGet('/api/serverstats').then(setStats).catch(() => {});
|
||||
window.scrollTo(0, 0);
|
||||
}, []);
|
||||
|
||||
@@ -58,6 +87,8 @@ export default function Level() {
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{stats && <ActivityChart stats={stats} />}
|
||||
</section>
|
||||
</>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user