diff --git a/src/App.jsx b/src/App.jsx
index 1159d82..48297e7 100644
--- a/src/App.jsx
+++ b/src/App.jsx
@@ -22,7 +22,7 @@ function AppContent() {
)
}
- if (!user || !user.username || !user.language_native || !user.language_target) {
+ if (!user || !user.username || !user.language_native_id || !user.language_target_id) {
return
}
diff --git a/src/api/directus.js b/src/api/directus.js
index 086625f..a05e5f6 100644
--- a/src/api/directus.js
+++ b/src/api/directus.js
@@ -1,65 +1,57 @@
+// snakkimo-API client (Modulname bleibt directus.js aus historischen Gründen)
const BASE = import.meta.env.VITE_API_URL
-const json = { 'Content-Type': 'application/json' }
-const auth = (token) => ({ 'Content-Type': 'application/json', 'Authorization': `Bearer ${token}` })
+const json = { 'Content-Type': 'application/json' }
+const auth = (token) => ({ 'Content-Type': 'application/json', 'Authorization': `Bearer ${token}` })
// ── Auth ──────────────────────────────────────────────────────────────────────
export async function login(email, password) {
- const res = await fetch(`${BASE}/languparent/auth/login`, {
+ const res = await fetch(`${BASE}/auth/login`, {
method: 'POST', headers: json,
body: JSON.stringify({ email, password }),
})
const data = await res.json()
- // Kein Profil vorhanden → UI zum Profil-Setup leiten
- if (res.status === 403 && data.needsProfile) {
- return { access_token: null, needsProfile: true, userId: data.userId }
- }
if (!res.ok) throw new Error(data.error || 'Login fehlgeschlagen.')
- return { access_token: data.token }
+ return { access_token: data.token, needsProfile: !!data.needsProfile, userId: data.user?.id }
}
-export async function getMe(userToken) {
- const res = await fetch(`${BASE}/languparent/auth/me`, {
- headers: auth(userToken),
- })
- const data = await res.json()
- if (!res.ok) throw new Error('Profil konnte nicht geladen werden.')
- return data // bereits vom API-Server entpackt
-}
-
-// Registriert den Directus-User; gibt { registrationToken } zurück (10 Min gültig)
export async function registerUser(email, password) {
- const res = await fetch(`${BASE}/languparent/auth/register`, {
+ const res = await fetch(`${BASE}/auth/register`, {
method: 'POST', headers: json,
body: JSON.stringify({ email, password }),
})
const data = await res.json()
if (!res.ok) throw new Error(data.error || 'Registrierung fehlgeschlagen.')
- return data // { registrationToken }
+ return { token: data.token, userId: data.user?.id, needsProfile: !!data.needsProfile }
}
-export async function checkUsername(username /*, _userToken — nicht mehr nötig */) {
+export async function getMe(userToken) {
+ const res = await fetch(`${BASE}/auth/me`, { headers: auth(userToken) })
+ const data = await res.json()
+ if (!res.ok) throw new Error(data.error || 'Profil konnte nicht geladen werden.')
+ return data
+}
+
+export async function checkUsername(username, userToken) {
const res = await fetch(
- `${BASE}/languparent/auth/check-username?username=${encodeURIComponent(username)}`,
+ `${BASE}/auth/check-username?username=${encodeURIComponent(username)}`,
+ { headers: auth(userToken) },
)
const data = await res.json()
if (!res.ok) throw new Error(data.error || 'Username-Check fehlgeschlagen.')
return data.available
}
-// Erstellt Profil über API-Server (Admin-Token bleibt server-seitig)
-// userToken ist das kurzlebige Registration-Token aus registerUser()
-// Gibt { token, expiresIn } zurück — muss via saveToken gespeichert werden
+// Legt Profil an (user_names + users_public). userToken = JWT aus register/login.
export async function createProfile({ username, nativeLang, targetLang, userToken }) {
- const res = await fetch(`${BASE}/languparent/auth/profile`, {
- method: 'POST',
- headers: { ...json, 'X-Registration-Token': userToken },
+ const res = await fetch(`${BASE}/auth/profile`, {
+ method: 'POST', headers: auth(userToken),
body: JSON.stringify({ username, nativeLang, targetLang }),
})
const data = await res.json()
if (!res.ok) throw new Error(data.error || 'Profilerstellung fehlgeschlagen.')
- return data // { token, expiresIn }
+ return data
}
// ── Sprachen ──────────────────────────────────────────────────────────────────
@@ -67,18 +59,18 @@ export async function createProfile({ username, nativeLang, targetLang, userToke
const LANG_META = {
de: { flag: '🇩🇪', speech: 'de-DE' },
en: { flag: '🇬🇧', speech: 'en-US' },
- se: { flag: '🇸🇪', speech: 'sv-SE' },
+ sv: { flag: '🇸🇪', speech: 'sv-SE' },
}
export async function getLanguageOptions() {
- const res = await fetch(`${BASE}/languparent/languages`)
+ const res = await fetch(`${BASE}/auth/languages`)
const data = await res.json()
if (!res.ok) throw new Error('Sprachen konnten nicht geladen werden.')
return (data || []).map(l => ({
id: l.id,
- label: l.title_de,
- suffix: l.short,
- ...(LANG_META[l.short] || { flag: '🌐', speech: l.short }),
+ label: l.titel_de,
+ suffix: l.short_en,
+ ...(LANG_META[l.short_en] || { flag: '🌐', speech: l.short_en }),
}))
}
@@ -86,81 +78,27 @@ export function langById(id, options) {
return (options || []).find(l => l.id === id) || null
}
-// ── Learning Pair ─────────────────────────────────────────────────────────────
+// ── Feed ──────────────────────────────────────────────────────────────────────
-export async function getActiveLearningPair(_profileId, userToken) {
- // profileId steckt im JWT — der API-Server liest ihn von dort
- const res = await fetch(`${BASE}/languparent/pair`, { headers: auth(userToken) })
- const data = await res.json()
- if (!res.ok) return null
- return data
-}
-
-export async function addPointsToPair(pairId, newPoints, userToken) {
- const res = await fetch(`${BASE}/languparent/pair/${pairId}/points`, {
- method: 'PATCH', headers: auth(userToken),
- body: JSON.stringify({ points: newPoints }),
- })
- return res.ok
-}
-
-// ── Content ───────────────────────────────────────────────────────────────────
-
-export async function getWords(userToken) {
- const res = await fetch(`${BASE}/languparent/words`, { headers: auth(userToken) })
- const data = await res.json()
- return data || []
-}
-
-export async function getQuestions(userToken) {
- const res = await fetch(`${BASE}/languparent/questions`, { headers: auth(userToken) })
- const data = await res.json()
- return data || []
-}
-
-export async function getQAPairsAtLevel(level, userToken, langSuffix = 'de') {
+export async function getFeedPairs(userToken, lang = 'de', limit = 20) {
const res = await fetch(
- `${BASE}/languparent/qa-pairs?level=${level}&lang=${langSuffix}`,
- { headers: auth(userToken) },
+ `${BASE}/auth/feed?lang=${encodeURIComponent(lang)}&limit=${limit}`,
+ { headers: auth(userToken) }
)
const data = await res.json()
- return data || []
-}
-
-// ── Fortschritt ───────────────────────────────────────────────────────────────
-
-export async function getUserProgress(_profileId, userToken, toLangId = null) {
- // profileId steckt im JWT — der API-Server filtert danach
- let url = `${BASE}/languparent/progress`
- if (toLangId) url += `?lang=${toLangId}`
- const res = await fetch(url, { headers: auth(userToken) })
- const data = await res.json()
- return data || []
-}
-
-export async function saveProgress(payload, userToken) {
- const res = await fetch(`${BASE}/languparent/progress`, {
- method: 'POST', headers: auth(userToken),
- body: JSON.stringify(payload),
- })
- const data = await res.json()
- if (!res.ok) return null
+ if (!res.ok) throw new Error(data.error || 'Feed konnte nicht geladen werden.')
return data
}
-// ── Assets ────────────────────────────────────────────────────────────────────
+// ── Stubs (content-Endpunkte kommen später) ───────────────────────────────────
-// Bilder via API-Server proxied — kein Directus-Token im Browser nötig
-export function assetUrl(fileId /*, _userToken — nicht mehr nötig */) {
- if (!fileId) return null
- return `${BASE}/languparent/assets/${fileId}`
-}
-
-// ── Profil ────────────────────────────────────────────────────────────────────
-
-export async function getProfilData(userToken) {
- return getMe(userToken)
-}
-
-// Stub — Punkte laufen über addPointsToPair
+export async function getActiveLearningPair() { return null }
+export async function addPointsToPair() { return false }
+export async function getWords() { return [] }
+export async function getQuestions() { return [] }
+export async function getQAPairsAtLevel() { return [] }
+export async function getUserProgress() { return [] }
+export async function saveProgress() { return null }
+export function assetUrl(fileId) { return fileId || null }
+export async function getProfilData(userToken) { return getMe(userToken) }
export async function addPointsToUser() { return true }
diff --git a/src/components/auth/LoginForm.jsx b/src/components/auth/LoginForm.jsx
index 53f4077..bb2685f 100644
--- a/src/components/auth/LoginForm.jsx
+++ b/src/components/auth/LoginForm.jsx
@@ -17,7 +17,8 @@ export default function LoginForm({ onNeedsProfile }) {
try {
const result = await login(email, pw)
if (result.needsProfile) {
- onNeedsProfile(result.userId, null)
+ // JWT mitgeben — Profil-Step braucht es als Auth
+ onNeedsProfile(result.userId, result.access_token)
return
}
saveToken(result.access_token)
diff --git a/src/components/auth/RegisterStep1.jsx b/src/components/auth/RegisterStep1.jsx
index 31594fc..26c39b8 100644
--- a/src/components/auth/RegisterStep1.jsx
+++ b/src/components/auth/RegisterStep1.jsx
@@ -14,8 +14,8 @@ export default function RegisterStep1({ onSuccess }) {
if (pw.length < 8) { setError('Passwort muss mindestens 8 Zeichen haben.'); return }
setError(''); setLoading(true)
try {
- const { registrationToken } = await registerUser(email, pw)
- onSuccess(null, registrationToken) // Token statt userId — AuthScreen speichert es als pendingToken
+ const { token, userId } = await registerUser(email, pw)
+ onSuccess(userId, token) // JWT direkt aus Register
} catch (err) {
setError(err.message)
} finally {
diff --git a/src/components/auth/RegisterStep2.jsx b/src/components/auth/RegisterStep2.jsx
index 7aaad29..4c8ba4a 100644
--- a/src/components/auth/RegisterStep2.jsx
+++ b/src/components/auth/RegisterStep2.jsx
@@ -1,5 +1,5 @@
import { useState, useEffect } from 'react'
-import { checkUsername, createProfile, getLanguageOptions } from '../../api/directus'
+import { checkUsername, createProfile, getLanguageOptions, getMe } from '../../api/directus'
import { useAuth } from '../../context/AuthContext'
import { FormGroup, Input, Select, Button, Alert, StepDots } from './ui'
@@ -35,10 +35,11 @@ export default function RegisterStep2({ userId, userToken, onSuccess }) {
if (!available) {
setError('Dieser Username ist bereits vergeben.'); setLoading(false); return
}
- // userToken ist das kurzlebige Registration-Token aus Schritt 1
- const { token } = await createProfile({ username, nativeLang, targetLang, userToken })
- saveToken(token)
- setUser({ id: userId, username, language_native: nativeLang, language_target: targetLang })
+ // userToken = JWT aus register/login. Profil anlegen, dann Token persistieren.
+ await createProfile({ username, nativeLang, targetLang, userToken })
+ saveToken(userToken)
+ const me = await getMe(userToken)
+ setUser(me)
onSuccess(username)
} catch (err) {
setError(err.message)
diff --git a/src/pages/Feed.jsx b/src/pages/Feed.jsx
index 3e38053..de12d06 100644
--- a/src/pages/Feed.jsx
+++ b/src/pages/Feed.jsx
@@ -1,289 +1,49 @@
-import { useEffect, useRef, useState } from 'react'
+import { useEffect, useState } from 'react'
import './Feed.css'
import { useAuth } from '../context/AuthContext'
-import {
- getActiveLearningPair, getWords, getQuestions, getUserProgress,
- getLanguageOptions, langById,
- saveProgress, addPointsToPair,
- getQAPairsAtLevel, assetUrl,
-} from '../api/directus'
-import NewWordTextCard from '../components/NewWordTextCard'
-import NewWordVoiceCard from '../components/NewWordVoiceCard'
-import LetterOrderCard from '../components/LetterOrderCard'
-import SentenceFillCard from '../components/SentenceFillCard'
-import LanguageParentCard from '../components/LanguageParentCard'
+import { getFeedPairs } from '../api/directus'
+import PairSentenceCard from '../components/PairSentenceCard'
+import PairYesNoCard from '../components/PairYesNoCard'
+import PairWordCard from '../components/PairWordCard'
-// Ein Wort gilt als gemeistert, wenn es in der aktiven Sprachrichtung
-// mindestens MASTERY_THRESHOLD korrekt beantwortete Kacheln gesammelt hat.
-const MASTERY_THRESHOLD = 3
-// Wie viele verschiedene Wörter gleichzeitig im Feed erscheinen
-const FEED_WORD_BUDGET = 6
+// Points per answer_type
+const POINTS = { text: 2, yes_no: 2, word: 3, question: 3 }
-// Punkteformel: selbes/niedrigeres Level = 1 Punkt, jeder Level höher = +1 Punkt
-function computePoints(cardLevel, userLevel) {
- return Math.max(1, 1 + ((cardLevel || 1) - (userLevel || 1)))
-}
-
-function shuffle(arr) {
- const a = [...arr]
- for (let i = a.length - 1; i > 0; i--) {
- const j = Math.floor(Math.random() * (i + 1))
- ;[a[i], a[j]] = [a[j], a[i]]
+function buildCard(pair) {
+ return {
+ type: pair.answer_type,
+ meta: { pairId: pair.id, points: pair.difficulty_level || POINTS[pair.answer_type] || 2, cardType: pair.answer_type },
+ card: pair,
}
- return a
-}
-
-const pickN = (arr, n) => shuffle(arr).slice(0, Math.max(0, n))
-
-function pickWordsToLearn(unmastered, userLevel, budget) {
- if (unmastered.length === 0) return []
- const same = unmastered.filter(w => (w.level || 1) === userLevel)
- const higher = unmastered.filter(w => (w.level || 1) > userLevel && (w.level || 1) <= userLevel + 2)
- const farHigher = unmastered.filter(w => (w.level || 1) > userLevel + 2)
- const lower = unmastered.filter(w => (w.level || 1) < userLevel)
-
- if (same.length > 0) {
- const sameN = Math.ceil(budget * 0.8)
- const higherN = budget - sameN
- let result = [
- ...pickN(same, Math.min(sameN, same.length)),
- ...pickN(higher, Math.min(higherN, higher.length)),
- ]
- if (result.length < budget) {
- const used = new Set(result.map(w => w.id))
- const rest = unmastered.filter(w => !used.has(w.id))
- result = [...result, ...pickN(rest, budget - result.length)]
- }
- return shuffle(result)
- }
- if (higher.length > 0) return pickN(higher, budget)
- if (farHigher.length > 0) return pickN(farHigher, budget)
- return pickN(lower, budget)
-}
-
-function buildWordCards(words, userLevel, fromLang, toLang) {
- const cards = []
- words.forEach(w => {
- const word = w[`title_${toLang.suffix}`]
- const translation = w[`title_${fromLang.suffix}`]
- if (!word || !translation) return
- const level = w.level || 1
- const points = computePoints(level, userLevel)
-
- const base = { language: toLang.label, points, word, translation, level }
-
- cards.push({
- type: 'text',
- meta: { wordId: w.id, cardType: 'write', points, level },
- card: { ...base, baseForm: word, prompt: `Schreib das Wort auf ${toLang.label}` },
- })
- cards.push({
- type: 'voice',
- meta: { wordId: w.id, cardType: 'speak', points, level },
- card: { ...base, baseForm: word, prompt: `Sprich das Wort auf ${toLang.label}`, speechLang: toLang.speech },
- })
- if (word.length >= 4) {
- cards.push({
- type: 'letter',
- meta: { wordId: w.id, cardType: 'write', points, level },
- card: { ...base, prompt: 'Tippe die Buchstaben in der richtigen Reihenfolge' },
- })
- }
- })
- return cards
-}
-
-function buildLanguageParentCards(qaPairs, userLevel, toLang, token) {
- return qaPairs.map(qp => {
- const points = computePoints(qp.level, userLevel)
- return {
- type: 'languparent',
- meta: { pairId: qp.pairId, cardType: 'speak', points, level: qp.level },
- card: {
- language: toLang.label,
- points,
- level: qp.level,
- statement: qp.statement,
- imageUrl: assetUrl(qp.pictureFileId, token),
- primaryWord: qp.primaryWord,
- speechLang: toLang.speech,
- selections: qp.selections,
- },
- }
- })
-}
-
-function buildQuestionCardsFor(questions, masteredWordIds, userLevel, toLang) {
- const cards = []
- questions.forEach(q => {
- const wordIds = (q.related_words || []).map(rw => rw.words_id).filter(Boolean)
- if (wordIds.length === 0) return
- if (!wordIds.every(id => masteredWordIds.has(id))) return
-
- const qText = q[`question_${toLang.suffix}`]
- const answer = q[`answer_${toLang.suffix}`]
- if (!qText || !answer) return
-
- const level = q.level || 1
- const points = computePoints(level, userLevel)
-
- cards.push({
- type: 'sentence',
- meta: { questionId: q.id, wordIds, cardType: 'sentence_fill', points, level },
- card: {
- language: toLang.label, points, level,
- word: answer,
- translation: qText,
- prompt: 'Antworte auf die Frage',
- },
- })
- })
- return cards
}
export default function Feed() {
const { user, token } = useAuth()
- const [cards, setCards] = useState([])
- const [loading, setLoading] = useState(true)
- const [ctx, setCtx] = useState(null)
- const [runningPoints, setRunningPoints] = useState(0)
- const [empty, setEmpty] = useState(false)
+ const [cards, setCards] = useState([])
+ const [done, setDone] = useState(new Set())
+ const [loading, setLoading] = useState(true)
+ const [empty, setEmpty] = useState(false)
- // Laufende Mastery-Verwaltung für diese Session
- const correctsRef = useRef({}) // wordId -> Anzahl korrekter Antworten (persistiert + session)
- const masteredRef = useRef(new Set())
- const questionsRef = useRef([])
- const appendedQRef = useRef(new Set()) // bereits angehängte questionIds
- const userLevelRef = useRef(1)
- const toLangRef = useRef(null)
- const pointsQueueRef = useRef(Promise.resolve()) // serialisiert Punkte-Updates
+ // Target language from user profile, fall back to 'de'
+ const lang = user?.language_target_short || 'de'
useEffect(() => {
- async function load() {
- try {
- const [pair, langs] = await Promise.all([
- getActiveLearningPair(user.username, token),
- getLanguageOptions(),
- ])
- if (!pair) { setEmpty(true); setLoading(false); return }
-
- const fromLang = langById(pair.language_from, langs)
- const toLang = langById(pair.language_to, langs)
- if (!fromLang || !toLang) { setEmpty(true); setLoading(false); return }
-
- const userLevel = pair.current_level || 1
-
- const [words, questions, progress, qaPairs] = await Promise.all([
- getWords(token),
- getQuestions(token),
- getUserProgress(user.username, token, pair.language_to),
- getQAPairsAtLevel(userLevel, token, toLang.suffix),
- ])
-
- const correctsByWord = {}
- progress.forEach(p => {
- if (p.result === 'correct' && p.word) {
- correctsByWord[p.word] = (correctsByWord[p.word] || 0) + 1
- }
- })
- const mastered = new Set(
- Object.entries(correctsByWord)
- .filter(([, c]) => c >= MASTERY_THRESHOLD)
- .map(([id]) => id)
- )
-
- correctsRef.current = correctsByWord
- masteredRef.current = mastered
- questionsRef.current = questions
- userLevelRef.current = userLevel
- toLangRef.current = toLang
-
- const unmastered = words.filter(w => !mastered.has(w.id))
- const chosen = pickWordsToLearn(unmastered, userLevel, FEED_WORD_BUDGET)
-
- const wordCards = buildWordCards(chosen, userLevel, fromLang, toLang)
- const questionCards = buildQuestionCardsFor(questions, mastered, userLevel, toLang)
- const lpCards = buildLanguageParentCards(qaPairs, userLevel, toLang, token)
-
- // bereits angehängte Frage-IDs merken, damit wir sie nicht doppelt einstreuen
- questionCards.forEach(c => appendedQRef.current.add(c.meta.questionId))
-
- const allCards = [...lpCards, ...wordCards, ...questionCards]
- setCards(allCards)
- setEmpty(allCards.length === 0)
- setCtx({
- pair,
- fromLangId: pair.language_from,
- toLangId: pair.language_to,
- profileId: user.username,
- })
- setRunningPoints(pair.points || 0)
- } catch (err) {
- console.error('Feed load error', err)
- setEmpty(true)
- } finally {
- setLoading(false)
- }
- }
- load()
- }, [user.username, token])
-
- async function handleComplete(item, result) {
- if (!ctx) return
- const earned = result === 'correct' ? (item.meta.points || 1) : 0
-
- saveProgress({
- user: ctx.profileId,
- word: item.meta.wordId || null,
- question: item.meta.questionId || null,
- card_type: item.meta.cardType,
- result,
- points_earned: earned,
- language_from: ctx.fromLangId,
- language_to: ctx.toLangId,
- }, token).catch(() => {})
-
- // Punkte serialisiert patchen, damit parallele Karten nicht denselben Basiswert überschreiben
- if (earned > 0) {
- setRunningPoints(p => p + earned)
- pointsQueueRef.current = pointsQueueRef.current.then(async () => {
- ctx.pair.points = (ctx.pair.points || 0) + earned
- try { await addPointsToPair(ctx.pair.id, ctx.pair.points, token) } catch {}
+ getFeedPairs(token, lang, 20)
+ .then(pairs => {
+ const built = pairs.map(buildCard)
+ setCards(built)
+ setEmpty(built.length === 0)
})
- }
+ .catch(err => { console.error('Feed load error', err); setEmpty(true) })
+ .finally(() => setLoading(false))
+ }, [token, lang])
- // In-Session-Mastery: korrekte Wort-Antwort erhöht Zähler; neu gemasterte
- // Wörter können Frage-Kacheln freischalten.
- if (result === 'correct' && item.meta.wordId) {
- const wid = item.meta.wordId
- const newCount = (correctsRef.current[wid] || 0) + 1
- correctsRef.current[wid] = newCount
-
- if (!masteredRef.current.has(wid) && newCount >= MASTERY_THRESHOLD) {
- masteredRef.current.add(wid)
-
- const newQuestions = questionsRef.current.filter(q => {
- if (appendedQRef.current.has(q.id)) return false
- const wordIds = (q.related_words || []).map(rw => rw.words_id).filter(Boolean)
- if (wordIds.length === 0) return false
- return wordIds.every(id => masteredRef.current.has(id))
- })
-
- if (newQuestions.length > 0) {
- const extra = buildQuestionCardsFor(
- newQuestions,
- masteredRef.current,
- userLevelRef.current,
- toLangRef.current,
- )
- extra.forEach(c => appendedQRef.current.add(c.meta.questionId))
- setCards(prev => [...prev, ...extra])
- setEmpty(false)
- }
- }
- }
+ function handleComplete(item) {
+ setDone(prev => new Set([...prev, item.meta.pairId]))
}
+ const visible = cards.filter(c => !done.has(c.meta.pairId))
+
if (loading) {
return (
@@ -294,11 +54,13 @@ export default function Feed() {
)
}
- if (empty) {
+ if (empty || visible.length === 0) {
return (
- Super! Du hast alle Wörter deines Levels gemeistert. Neue Wörter kommen bald.
+ {cards.length === 0
+ ? 'Noch keine Inhalte verfügbar.'
+ : 'Super! Alle Karten abgeschlossen. 🎉'}
)
@@ -306,16 +68,15 @@ export default function Feed() {
return (
- {cards.map((item, i) => {
- const enrichedCard = { ...item.card, totalPoints: runningPoints }
- const handler = (r) => handleComplete(item, r)
+ {visible.map((item) => {
+ const cardWithMeta = { ...item.card, meta: item.meta }
+ const handler = () => handleComplete(item)
return (
-
- {item.type === 'text' &&
}
- {item.type === 'voice' &&
}
- {item.type === 'letter' &&
}
- {item.type === 'sentence' &&
}
- {item.type === 'languparent' &&
}
+
+ {item.type === 'text' &&
}
+ {item.type === 'yes_no' &&
}
+ {item.type === 'word' &&
}
+ {item.type === 'question'&&
}
)
})}
diff --git a/src/pages/Profil.jsx b/src/pages/Profil.jsx
index 800a650..9bccd4e 100644
--- a/src/pages/Profil.jsx
+++ b/src/pages/Profil.jsx
@@ -3,6 +3,28 @@ import './Profil.css'
import { useAuth } from '../context/AuthContext'
import { getProfilData, getActiveLearningPair, getLanguageOptions, langById } from '../api/directus'
+function LogoutButton() {
+ const { logout } = useAuth()
+ return (
+
{ e.currentTarget.style.color = '#C0544A'; e.currentTarget.style.background = '#FBF0EF' }}
+ onMouseLeave={e => { e.currentTarget.style.color = '#9A8878'; e.currentTarget.style.background = 'none' }}
+ >
+
+
+
+
+
+
+ )
+}
+
const SKILLS = [
{ label: 'Vokabular', value: 0.78 },
{ label: 'Grammatik', value: 0.65 },
@@ -124,7 +146,8 @@ export default function Profil() {
const streak = profil?.streak_days ?? 0
return (
-