feat: persönlichere Profilseite + iOS-App-Setup

Profil: Begrüßung in Zielsprache, Kategorie-Punkte-Übersicht,
ruhigerer Header (kein rotierender Avatar/Online-Dot), Notch-Fix
und kompaktere Aktivitäts-Heatmap. Außerdem Capacitor-iOS-Projekt
und diverse Auth/Feed/Audio-Verbesserungen aus dem Premium-Redesign.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-15 12:55:13 +02:00
parent 712f9a243c
commit e7b4ec571e
49 changed files with 3221 additions and 210 deletions

View File

@@ -4,6 +4,9 @@ import { useAuth } from '../context/AuthContext'
import { getProfilData, getStats, getLanguageOptions, langById } from '../api/directus'
import ProgressRing from '../components/ProgressRing'
// Erdige Palette für die Kategorie-Dots/Balken (harmoniert mit dem Profil-Theme)
const CAT_COLORS = ['#C4A85A', '#7A5C3A', '#3D7055', '#B5732E', '#5B7DB1', '#9C5A8A']
function LogoutButton() {
const { logout } = useAuth()
return (
@@ -142,6 +145,7 @@ export default function Profil() {
const displayName = profil?.username || user?.username || '…'
const initials = displayName.slice(0, 2).toUpperCase()
const greeting = profil?.language_target_greeting || 'Hallo'
const points = profil?.total_ep ?? user?.total_ep ?? 0
const level = profil?.level ?? Math.floor(points / 500)
const epIntoLevel = points - level * 500
@@ -160,6 +164,8 @@ export default function Profil() {
const skills = stats?.skills || []
const hasSkillData = skills.some(s => s.seen > 0)
const accuracyPct = totals ? Math.round((totals.accuracy || 0) * 100) : null
const categories = stats?.categories || []
const maxCatPoints = Math.max(1, ...categories.map(c => c.points))
return (
<div className="profil page-enter">
@@ -171,7 +177,6 @@ export default function Profil() {
<div className="avatar-ring">
<div className="avatar-inner"><div className="avatar">{initials}</div></div>
</div>
<span className="online-dot" />
<div className="avatar-level-badge">
<svg viewBox="0 0 48 54" width="28" height="32">
<defs>
@@ -185,8 +190,8 @@ export default function Profil() {
</div>
</div>
<div className="profil-info">
<h2 className="profil-name">{displayName}</h2>
<p className="profil-handle">@{displayName.toLowerCase()}</p>
<h2 className="profil-name">{greeting}, {displayName}</h2>
<p className="profil-learning">lernt {langLabel}</p>
{streak > 0 && (
<p className="profil-streak">🔥 {streak} Tag{streak !== 1 ? 'e' : ''} Streak</p>
)}
@@ -230,6 +235,35 @@ export default function Profil() {
</div>
)}
{/* ── Kategorien (Punkte je Thema) ── */}
{stats && (
<div className="card">
<p className="card-title">KATEGORIEN</p>
{categories.length ? (
<div className="cat-list">
{categories.map((c, i) => {
const color = CAT_COLORS[i % CAT_COLORS.length]
return (
<div key={c.id} className="cat-row">
<div className="cat-head">
<span className="cat-dot" style={{ background: color }} />
<span className="cat-label">{c.label || 'Allgemein'}</span>
<span className="cat-points">{c.points} P</span>
</div>
<div className="cat-bar">
<div className="cat-bar-fill"
style={{ width: `${Math.round((c.points / maxCatPoints) * 100)}%`, background: color }} />
</div>
</div>
)
})}
</div>
) : (
<p className="skills-empty">Sammle Punkte deine Themen erscheinen hier.</p>
)}
</div>
)}
{/* ── Streak-Kalender ── */}
{stats && (
<div className="card">