feat: EP-Fortschritt speichern + echtes Profil

- saveProgress/getUserProgress (POST /auth/progress, /auth/me)
- Feed: onComplete bucht EP, EP-Zähler oben
- Profil: echte total_ep/level/streak statt hartkodiert

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-02 21:30:11 +02:00
parent 6b31fddb27
commit e7dbb9d0a7
3 changed files with 59 additions and 14 deletions

View File

@@ -1,7 +1,7 @@
import { useEffect, useState } from 'react'
import './Feed.css'
import { useAuth } from '../context/AuthContext'
import { getFeedPairs } from '../api/directus'
import { getFeedPairs, saveProgress, getUserProgress } from '../api/directus'
import PairSentenceCard from '../components/PairSentenceCard'
import PairYesNoCard from '../components/PairYesNoCard'
import PairWordCard from '../components/PairWordCard'
@@ -23,6 +23,7 @@ export default function Feed() {
const [done, setDone] = useState(new Set())
const [loading, setLoading] = useState(true)
const [empty, setEmpty] = useState(false)
const [totalEp, setTotalEp] = useState(null)
// Target language from user profile, fall back to 'de'
const lang = user?.language_target_short || 'de'
@@ -38,8 +39,23 @@ export default function Feed() {
.finally(() => setLoading(false))
}, [token, lang])
function handleComplete(item) {
useEffect(() => {
getUserProgress(token)
.then(p => setTotalEp(p.total_ep))
.catch(() => {})
}, [token])
function handleComplete(item, result) {
setDone(prev => new Set([...prev, item.meta.pairId]))
const correct = result === 'correct'
saveProgress({
pairId: item.meta.pairId,
correct,
points: correct ? item.meta.points : 0,
userToken: token,
})
.then(res => { if (res?.total_ep != null) setTotalEp(res.total_ep) })
.catch(err => console.error('saveProgress error', err))
}
const visible = cards.filter(c => !done.has(c.meta.pairId))
@@ -68,9 +84,19 @@ export default function Feed() {
return (
<div className="feed">
{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>
)}
{visible.map((item) => {
const cardWithMeta = { ...item.card, meta: item.meta }
const handler = () => handleComplete(item)
const handler = (result) => handleComplete(item, result)
return (
<div key={item.meta.pairId} className="feed-slot">
{item.type === 'text' && <PairSentenceCard card={cardWithMeta} onComplete={handler} />}