feat: Premium-Redesign + Fortschritts-UI
- Zentrales Design-Token-System in index.css (Farben/Spacing/Radien/Schatten/Fonts) - Alle Live-Screens auf Tokens: Karten, BottomNav (aktiver Pill), Feed, Auth - Auth: DM Sans entfernt, Akzent vereinheitlicht (Braun), Tokens gescoped - Profil neu: Tagesziel-Ring, Streak-Heatmap, Wochen-Graph, echter Skills-Radar, Eckdaten - Feed-EP-Badge mit Tagesziel-Ring (ProgressRing-Komponente) - Game/Pro als gestaltetes 'Bald verfügbar' (ComingSoon) - Konsumiert neue API: getStats/setDailyGoal, degradiert sauber bei 404 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,7 +1,8 @@
|
||||
import { useEffect, useState } from 'react'
|
||||
import './Feed.css'
|
||||
import { useAuth } from '../context/AuthContext'
|
||||
import { getFeedPairs, saveProgress, getUserProgress } from '../api/directus'
|
||||
import { getFeedPairs, saveProgress, getUserProgress, getStats } from '../api/directus'
|
||||
import ProgressRing from '../components/ProgressRing'
|
||||
import PairSentenceCard from '../components/PairSentenceCard'
|
||||
import PairYesNoCard from '../components/PairYesNoCard'
|
||||
import PairWordCard from '../components/PairWordCard'
|
||||
@@ -24,6 +25,7 @@ export default function Feed() {
|
||||
const [loading, setLoading] = useState(true)
|
||||
const [empty, setEmpty] = useState(false)
|
||||
const [totalEp, setTotalEp] = useState(null)
|
||||
const [daily, setDaily] = useState(null) // { ep, daily_goal_ep } – wenn /auth/stats verfügbar
|
||||
|
||||
// Target language from user profile, fall back to 'de'
|
||||
const lang = user?.language_target_short || 'de'
|
||||
@@ -43,37 +45,42 @@ export default function Feed() {
|
||||
getUserProgress(token)
|
||||
.then(p => setTotalEp(p.total_ep))
|
||||
.catch(() => {})
|
||||
// Tagesziel-Fortschritt – degradiert lautlos, falls /auth/stats noch nicht deployed ist
|
||||
getStats(token)
|
||||
.then(s => { if (s?.today) setDaily(s.today) })
|
||||
.catch(() => {})
|
||||
}, [token])
|
||||
|
||||
function handleComplete(item, result) {
|
||||
setDone(prev => new Set([...prev, item.meta.pairId]))
|
||||
const correct = result === 'correct'
|
||||
const earned = correct ? item.meta.points : 0
|
||||
saveProgress({
|
||||
pairId: item.meta.pairId,
|
||||
correct,
|
||||
points: correct ? item.meta.points : 0,
|
||||
points: earned,
|
||||
userToken: token,
|
||||
})
|
||||
.then(res => { if (res?.total_ep != null) setTotalEp(res.total_ep) })
|
||||
.catch(err => console.error('saveProgress error', err))
|
||||
// Tagesziel optimistisch hochzählen
|
||||
if (earned > 0) setDaily(d => d ? { ...d, ep: (d.ep || 0) + earned } : d)
|
||||
}
|
||||
|
||||
const visible = cards.filter(c => !done.has(c.meta.pairId))
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<div className="feed">
|
||||
<div style={{ padding: '60px 24px', textAlign: 'center', color: '#9A8F85', fontFamily: 'DM Sans, sans-serif' }}>
|
||||
Lade Karten…
|
||||
</div>
|
||||
<div className="feed page-enter">
|
||||
<div className="feed-empty">Lade Karten…</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
if (empty || visible.length === 0) {
|
||||
return (
|
||||
<div className="feed">
|
||||
<div style={{ padding: '60px 24px', textAlign: 'center', color: '#9A8F85', fontFamily: 'DM Sans, sans-serif' }}>
|
||||
<div className="feed page-enter">
|
||||
<div className="feed-empty">
|
||||
{cards.length === 0
|
||||
? 'Noch keine Inhalte verfügbar.'
|
||||
: 'Super! Alle Karten abgeschlossen. 🎉'}
|
||||
@@ -82,16 +89,22 @@ export default function Feed() {
|
||||
)
|
||||
}
|
||||
|
||||
const goalPct = daily && daily.daily_goal_ep ? (daily.ep || 0) / daily.daily_goal_ep : 0
|
||||
|
||||
return (
|
||||
<div className="feed">
|
||||
<div className="feed page-enter">
|
||||
{totalEp != null && (
|
||||
<div style={{
|
||||
position: 'sticky', top: 8, zIndex: 5, alignSelf: 'center',
|
||||
background: '#fff', border: '1px solid #EFE7DE', borderRadius: 999,
|
||||
padding: '6px 14px', margin: '4px auto 8px', fontFamily: 'DM Sans, sans-serif',
|
||||
fontWeight: 600, color: '#7A6A58', boxShadow: '0 2px 8px rgba(0,0,0,0.05)',
|
||||
}}>
|
||||
⭐ {totalEp} EP
|
||||
<div className="ep-badge">
|
||||
<ProgressRing
|
||||
value={daily ? goalPct : 1}
|
||||
size={26} stroke={4}
|
||||
color={goalPct >= 1 ? 'var(--success)' : 'var(--gold)'}
|
||||
>
|
||||
<span style={{ fontSize: 11 }}>{goalPct >= 1 ? '✓' : '⭐'}</span>
|
||||
</ProgressRing>
|
||||
<span className="ep-value">
|
||||
{totalEp}<small>EP{daily ? ` · ${daily.ep || 0}/${daily.daily_goal_ep} heute` : ''}</small>
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
{visible.map((item) => {
|
||||
|
||||
Reference in New Issue
Block a user