// Member-Profil: Rang & XP, Discord-Rollen, Alpha-Key, Playtester-Status // + Rollen-Selfservice (Rollen-Menüs direkt im Browser togglen). import { useEffect, useState } from 'react'; import { Link } from 'react-router-dom'; import { apiGet, apiPost, apiDelete } from '../api.js'; import { IconCheck, IconMagic } from '../icons.jsx'; function fmtDate(iso) { if (!iso) return '—'; return new Intl.DateTimeFormat('de-DE', { day: '2-digit', month: 'long', year: 'numeric' }) .format(new Date(iso)); } export default function Profil({ me }) { const [profile, setProfile] = useState(null); const [menus, setMenus] = useState([]); const [error, setError] = useState(null); const [busy, setBusy] = useState(''); const [copied, setCopied] = useState(false); const [wipeMsg, setWipeMsg] = useState(''); useEffect(() => { apiGet('/api/profile').then(setProfile).catch((e) => setError(e?.message ?? 'Fehler')); apiGet('/api/myroles').then((d) => setMenus(d.menus)).catch(() => {}); window.scrollTo(0, 0); }, []); async function toggleRole(menuId, roleId) { setBusy(`${menuId}:${roleId}`); try { const res = await apiPost('/api/myroles/toggle', { menu_id: menuId, role_id: roleId }); setMenus((old) => old.map((m) => m.id !== menuId ? m : { ...m, entries: m.entries.map((e) => { if (e.role_id === roleId) return { ...e, has: res.has }; // Exklusiv: die anderen sind jetzt sicher weg return m.exclusive && res.has ? { ...e, has: false } : e; }), })); // Rollen-Chips oben mitziehen apiGet('/api/profile').then(setProfile).catch(() => {}); } catch { /* Server meldet schon */ } setBusy(''); } async function wipeData() { if (!window.confirm( 'Wirklich alle gespeicherten Daten löschen?\n\n' + 'Betrifft: XP/Level, Geburtstag, Playtester-Eintrag, Abstimmungen, ' + 'Erinnerungen, Rollen-Sicherung und deinen Alpha-Key.\n\n' + 'Das lässt sich nicht rückgängig machen.' )) return; try { const res = await apiDelete('/api/profile'); const total = Object.values(res.deleted).reduce((a, b) => a + b, 0); setWipeMsg(`✓ ${total} Einträge gelöscht.`); apiGet('/api/profile').then(setProfile).catch(() => {}); } catch { setWipeMsg('✗ Löschen fehlgeschlagen — melde dich bitte beim Team.'); } } async function copyKey() { try { await navigator.clipboard.writeText(profile.alphaKey); setCopied(true); setTimeout(() => setCopied(false), 1500); } catch { /* Browser ohne Clipboard-Rechte */ } } if (!me.loading && !me.user) { return (
Profil

Nicht eingeloggt

Mit Discord einloggen, um dein Profil zu sehen.

); } const pct = profile?.level ? Math.floor((profile.level.progress / profile.level.needed) * 100) : 0; return ( <>
Member

Profil

Dein Stand auf dem Server — Rang, Rollen und Keys.

{error &&

✗ {error}

} {!profile && !error &&

Lade …

} {profile && ( <> {/* Kopf-Karte */}
{profile.user.avatar && }

{profile.user.displayName}

Dabei seit {fmtDate(profile.user.joinedAt)} {profile.playtester && ( Playtester )}

{profile.roles.length > 0 && (
{profile.roles.map((r) => ( {r.name} ))}
)}
{/* Level */} {profile.level && (

// Level {profile.level.level} {profile.level.rank ? ` — Platz #${profile.level.rank}` : ''}

{profile.level.progress.toLocaleString('de-DE')} / {profile.level.needed.toLocaleString('de-DE')} XP bis Level {profile.level.level + 1} ({pct}%) · gesamt {profile.level.xp.toLocaleString('de-DE')} XP

)} {/* Alpha-Key */} {profile.alphaKey && (

Dein Alpha-Key

{profile.alphaKey}
)} {/* Rollen-Selfservice */} {menus.length > 0 && (

Rollen wählen

{menus.map((m) => (

{m.title}

{m.description &&

{m.description}

} {m.exclusive &&

Nur eine Rolle aus diesem Menü gleichzeitig.

}
{m.entries.map((e) => ( ))}
))}
)} {/* DSGVO Art. 17: eigene Daten löschen */}

Meine Daten

Du kannst jederzeit alle Daten löschen, die hier zu dir gespeichert sind: XP/Level, Geburtstag, Playtester-Eintrag, Abstimmungen, Erinnerungen, Rollen-Sicherung und einen zugeteilten Alpha-Key. Was du im Discord gepostet hast, löschst du bitte dort. Details im{' '} Datenschutz.

{wipeMsg &&

{wipeMsg}

}
)}
); }