Add migration learning platform: Onboarding, Translation, Parent Portal
Phase 1.1 — user_language_api.py: Stores native language preference per user (TR/AR/UK/RU/PL/DE/EN). Onboarding page with flag-based language selection for students and parents. Phase 1.2 — translation_service.py: Batch-translates vocabulary words into target languages via Ollama LLM. Stores in translations JSONB. New endpoint POST /vocabulary/translate triggers translation. Phase 2.1 — Parent Portal (/parent): Simplified UI in parent's native language showing child's learning progress. Daily tips translated. Phase 2.2 — Parent Quiz (/parent/quiz/[unitId]): Parents can quiz their child on vocabulary WITHOUT speaking DE or EN. Shows word in child's learning language + parent's native language as hint. Answer hidden by default, revealed on tap. All UI text translated into 7 languages (DE/EN/TR/AR/UK/RU/PL). Arabic gets RTL layout support. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
125
studio-v2/app/parent/page.tsx
Normal file
125
studio-v2/app/parent/page.tsx
Normal file
@@ -0,0 +1,125 @@
|
||||
'use client'
|
||||
|
||||
import React, { useState, useEffect } from 'react'
|
||||
import Link from 'next/link'
|
||||
import { useTheme } from '@/lib/ThemeContext'
|
||||
import { useLanguage } from '@/lib/LanguageContext'
|
||||
|
||||
interface LearningUnit {
|
||||
id: string
|
||||
label: string
|
||||
meta: string
|
||||
status: string
|
||||
created_at: string
|
||||
}
|
||||
|
||||
// Parent-specific translations (expanded from i18n)
|
||||
const parentT: Record<string, Record<string, string>> = {
|
||||
title: { de: 'Eltern-Portal', tr: 'Ebeveyn Portal\u0131', ar: '\u0628\u0648\u0627\u0628\u0629 \u0627\u0644\u0648\u0627\u0644\u062f\u064a\u0646', uk: '\u041f\u043e\u0440\u0442\u0430\u043b \u0431\u0430\u0442\u044c\u043a\u0456\u0432', ru: '\u041f\u043e\u0440\u0442\u0430\u043b \u0440\u043e\u0434\u0438\u0442\u0435\u043b\u0435\u0439', pl: 'Portal rodzica', en: 'Parent Portal' },
|
||||
greeting: { de: 'Willkommen!', tr: 'Hos geldiniz!', ar: '\u0645\u0631\u062d\u0628\u0627!', uk: '\u041b\u0430\u0441\u043a\u0430\u0432\u043e \u043f\u0440\u043e\u0441\u0438\u043c\u043e!', ru: '\u0414\u043e\u0431\u0440\u043e \u043f\u043e\u0436\u0430\u043b\u043e\u0432\u0430\u0442\u044c!', pl: 'Witamy!', en: 'Welcome!' },
|
||||
child_progress: { de: 'Lernfortschritt', tr: '\u00d6\u011frenme ilerlemesi', ar: '\u062a\u0642\u062f\u0645 \u0627\u0644\u062a\u0639\u0644\u0645', uk: '\u041f\u0440\u043e\u0433\u0440\u0435\u0441 \u043d\u0430\u0432\u0447\u0430\u043d\u043d\u044f', ru: '\u041f\u0440\u043e\u0433\u0440\u0435\u0441\u0441 \u043e\u0431\u0443\u0447\u0435\u043d\u0438\u044f', pl: 'Post\u0119py w nauce', en: 'Learning Progress' },
|
||||
quiz_child: { de: 'Vokabeln abfragen', tr: 'Kelime sorgula', ar: '\u0627\u062e\u062a\u0628\u0627\u0631 \u0627\u0644\u0645\u0641\u0631\u062f\u0627\u062a', uk: '\u041e\u043f\u0438\u0442\u0430\u0442\u0438 \u0441\u043b\u043e\u0432\u0430', ru: '\u041e\u043f\u0440\u043e\u0441\u0438\u0442\u044c \u0441\u043b\u043e\u0432\u0430', pl: 'Odpyta\u0107 s\u0142\u00f3wka', en: 'Quiz your child' },
|
||||
no_units: { de: 'Noch keine Lerneinheiten vorhanden.', tr: 'Hen\u00fcz \u00f6\u011frenme birimi yok.', ar: '\u0644\u0627 \u062a\u0648\u062c\u062f \u0648\u062d\u062f\u0627\u062a \u062a\u0639\u0644\u064a\u0645\u064a\u0629 \u0628\u0639\u062f.', uk: '\u041d\u0430\u0432\u0447\u0430\u043b\u044c\u043d\u0438\u0445 \u043c\u043e\u0434\u0443\u043b\u0456\u0432 \u043f\u043e\u043a\u0438 \u043d\u0435\u043c\u0430\u0454.', ru: '\u0423\u0447\u0435\u0431\u043d\u044b\u0445 \u043c\u043e\u0434\u0443\u043b\u0435\u0439 \u043f\u043e\u043a\u0430 \u043d\u0435\u0442.', pl: 'Brak jednostek do nauki.', en: 'No learning units yet.' },
|
||||
words: { de: 'Woerter', tr: 'kelime', ar: '\u0643\u0644\u0645\u0627\u062a', uk: '\u0441\u043b\u0456\u0432', ru: '\u0441\u043b\u043e\u0432', pl: 's\u0142\u00f3w', en: 'words' },
|
||||
tip: { de: 'Tipp fuer heute', tr: 'Bug\u00fcn\u00fcn ipucu', ar: '\u0646\u0635\u064a\u062d\u0629 \u0627\u0644\u064a\u0648\u0645', uk: '\u041f\u043e\u0440\u0430\u0434\u0430 \u043d\u0430 \u0441\u044c\u043e\u0433\u043e\u0434\u043d\u0456', ru: '\u0421\u043e\u0432\u0435\u0442 \u043d\u0430 \u0441\u0435\u0433\u043e\u0434\u043d\u044f', pl: 'Wskaz\u00f3wka na dzi\u015b', en: 'Tip for today' },
|
||||
}
|
||||
|
||||
export default function ParentPage() {
|
||||
const { isDark } = useTheme()
|
||||
const { language } = useLanguage()
|
||||
const [units, setUnits] = useState<LearningUnit[]>([])
|
||||
const [isLoading, setIsLoading] = useState(true)
|
||||
|
||||
const t = (key: string) => parentT[key]?.[language] || parentT[key]?.['de'] || key
|
||||
|
||||
const glassCard = isDark
|
||||
? 'bg-white/10 backdrop-blur-xl border border-white/10'
|
||||
: 'bg-white/80 backdrop-blur-xl border border-black/5'
|
||||
|
||||
useEffect(() => {
|
||||
fetch('/api/learning-units/')
|
||||
.then(r => r.ok ? r.json() : [])
|
||||
.then(setUnits)
|
||||
.catch(() => {})
|
||||
.finally(() => setIsLoading(false))
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<div className={`min-h-screen ${isDark ? 'bg-gradient-to-br from-slate-900 via-blue-900 to-indigo-900' : 'bg-gradient-to-br from-blue-50 via-indigo-50 to-purple-50'}`}
|
||||
dir={language === 'ar' ? 'rtl' : 'ltr'}>
|
||||
|
||||
{/* Header */}
|
||||
<div className={`${glassCard} border-0 border-b`}>
|
||||
<div className="max-w-lg mx-auto px-6 py-5">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<h1 className={`text-2xl font-bold ${isDark ? 'text-white' : 'text-slate-900'}`}>
|
||||
{t('title')}
|
||||
</h1>
|
||||
<p className={`text-lg ${isDark ? 'text-white/60' : 'text-slate-500'}`}>
|
||||
{t('greeting')}
|
||||
</p>
|
||||
</div>
|
||||
<Link href="/onboarding" className={`text-sm px-3 py-1.5 rounded-lg ${isDark ? 'bg-white/10 text-white/60' : 'bg-slate-100 text-slate-500'}`}>
|
||||
{language.toUpperCase()}
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Content */}
|
||||
<div className="max-w-lg mx-auto px-6 py-6 space-y-4">
|
||||
<h2 className={`text-lg font-semibold ${isDark ? 'text-white/80' : 'text-slate-700'}`}>
|
||||
{t('child_progress')}
|
||||
</h2>
|
||||
|
||||
{isLoading && (
|
||||
<div className="flex justify-center py-12">
|
||||
<div className="w-8 h-8 border-4 border-blue-400 border-t-transparent rounded-full animate-spin" />
|
||||
</div>
|
||||
)}
|
||||
|
||||
{!isLoading && units.length === 0 && (
|
||||
<div className={`${glassCard} rounded-2xl p-8 text-center`}>
|
||||
<p className={isDark ? 'text-white/50' : 'text-slate-500'}>{t('no_units')}</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{units.map(unit => (
|
||||
<div key={unit.id} className={`${glassCard} rounded-2xl p-5`}>
|
||||
<h3 className={`text-lg font-semibold mb-1 ${isDark ? 'text-white' : 'text-slate-900'}`}>
|
||||
{unit.label}
|
||||
</h3>
|
||||
<p className={`text-sm mb-4 ${isDark ? 'text-white/50' : 'text-slate-500'}`}>
|
||||
{unit.meta}
|
||||
</p>
|
||||
|
||||
<Link
|
||||
href={`/parent/quiz/${unit.id}`}
|
||||
className="w-full flex items-center justify-center gap-2 py-3 rounded-xl bg-gradient-to-r from-blue-500 to-cyan-500 text-white font-medium hover:shadow-lg transition-all"
|
||||
>
|
||||
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
|
||||
</svg>
|
||||
{t('quiz_child')}
|
||||
</Link>
|
||||
</div>
|
||||
))}
|
||||
|
||||
{/* Daily Tip */}
|
||||
<div className={`${glassCard} rounded-2xl p-5`}>
|
||||
<h3 className={`text-sm font-medium mb-2 ${isDark ? 'text-yellow-300/80' : 'text-yellow-600'}`}>
|
||||
{t('tip')}
|
||||
</h3>
|
||||
<p className={`text-sm ${isDark ? 'text-white/70' : 'text-slate-600'}`}>
|
||||
{language === 'tr' && '\u00c7ocugunuza her g\u00fcn 5 dakika kelime \u00e7al\u0131s\u0131n. K\u0131sa ama d\u00fczenli \u00e7al\u0131sma en etkili y\u00f6ntemdir.'}
|
||||
{language === 'ar' && '\u0627\u0637\u0644\u0628 \u0645\u0646 \u0637\u0641\u0644\u0643 \u0623\u0646 \u064a\u062a\u062f\u0631\u0628 \u0639\u0644\u0649 \u0627\u0644\u0645\u0641\u0631\u062f\u0627\u062a 5 \u062f\u0642\u0627\u0626\u0642 \u0643\u0644 \u064a\u0648\u0645. \u0627\u0644\u062a\u062f\u0631\u064a\u0628 \u0627\u0644\u0642\u0635\u064a\u0631 \u0648\u0627\u0644\u0645\u0646\u062a\u0638\u0645 \u0647\u0648 \u0627\u0644\u0623\u0643\u062b\u0631 \u0641\u0639\u0627\u0644\u064a\u0629.'}
|
||||
{language === 'uk' && '\u041f\u043e\u043f\u0440\u043e\u0441\u0456\u0442\u044c \u0434\u0438\u0442\u0438\u043d\u0443 \u0432\u0447\u0438\u0442\u0438 \u0441\u043b\u043e\u0432\u0430 5 \u0445\u0432\u0438\u043b\u0438\u043d \u043a\u043e\u0436\u043d\u043e\u0433\u043e \u0434\u043d\u044f. \u041a\u043e\u0440\u043e\u0442\u043a\u0456 \u0430\u043b\u0435 \u0440\u0435\u0433\u0443\u043b\u044f\u0440\u043d\u0456 \u0437\u0430\u043d\u044f\u0442\u0442\u044f \u043d\u0430\u0439\u0435\u0444\u0435\u043a\u0442\u0438\u0432\u043d\u0456\u0448\u0456.'}
|
||||
{language === 'ru' && '\u041f\u043e\u043f\u0440\u043e\u0441\u0438\u0442\u0435 \u0440\u0435\u0431\u0435\u043d\u043a\u0430 \u0443\u0447\u0438\u0442\u044c \u0441\u043b\u043e\u0432\u0430 5 \u043c\u0438\u043d\u0443\u0442 \u043a\u0430\u0436\u0434\u044b\u0439 \u0434\u0435\u043d\u044c. \u041a\u043e\u0440\u043e\u0442\u043a\u0438\u0435 \u0440\u0435\u0433\u0443\u043b\u044f\u0440\u043d\u044b\u0435 \u0437\u0430\u043d\u044f\u0442\u0438\u044f \u0441\u0430\u043c\u044b\u0435 \u044d\u0444\u0444\u0435\u043a\u0442\u0438\u0432\u043d\u044b\u0435.'}
|
||||
{(language === 'de' || !['tr','ar','uk','ru'].includes(language)) && 'Bitten Sie Ihr Kind jeden Tag 5 Minuten Vokabeln zu ueben. Kurze regelmaessige Uebungen sind am effektivsten.'}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
175
studio-v2/app/parent/quiz/[unitId]/page.tsx
Normal file
175
studio-v2/app/parent/quiz/[unitId]/page.tsx
Normal file
@@ -0,0 +1,175 @@
|
||||
'use client'
|
||||
|
||||
import React, { useState, useEffect, useCallback } from 'react'
|
||||
import { useParams, useRouter } from 'next/navigation'
|
||||
import { useTheme } from '@/lib/ThemeContext'
|
||||
import { useLanguage } from '@/lib/LanguageContext'
|
||||
import { AudioButton } from '@/components/learn/AudioButton'
|
||||
|
||||
interface QAItem {
|
||||
id: string; question: string; answer: string
|
||||
translations?: Record<string, { text?: string }>
|
||||
}
|
||||
|
||||
const pt: Record<string, Record<string, string>> = {
|
||||
ask_child: { de: 'Fragen Sie Ihr Kind:', tr: '\u00c7ocu\u011funuza sorun:', ar: '\u0627\u0633\u0623\u0644 \u0637\u0641\u0644\u0643:', uk: '\u0417\u0430\u043f\u0438\u0442\u0430\u0439\u0442\u0435 \u0434\u0438\u0442\u0438\u043d\u0443:', ru: '\u0421\u043f\u0440\u043e\u0441\u0438\u0442\u0435 \u0440\u0435\u0431\u0435\u043d\u043a\u0430:', en: 'Ask your child:' },
|
||||
correct_answer: { de: 'Richtige Antwort:', tr: 'Do\u011fru cevap:', ar: '\u0627\u0644\u0625\u062c\u0627\u0628\u0629 \u0627\u0644\u0635\u062d\u064a\u062d\u0629:', uk: '\u041f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u0430 \u0432\u0456\u0434\u043f\u043e\u0432\u0456\u0434\u044c:', ru: '\u041f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u044b\u0439 \u043e\u0442\u0432\u0435\u0442:', en: 'Correct answer:' },
|
||||
show_answer: { de: 'Antwort zeigen', tr: 'Cevab\u0131 g\u00f6ster', ar: '\u0625\u0638\u0647\u0627\u0631 \u0627\u0644\u0625\u062c\u0627\u0628\u0629', uk: '\u041f\u043e\u043a\u0430\u0437\u0430\u0442\u0438 \u0432\u0456\u0434\u043f\u043e\u0432\u0456\u0434\u044c', ru: '\u041f\u043e\u043a\u0430\u0437\u0430\u0442\u044c \u043e\u0442\u0432\u0435\u0442', en: 'Show answer' },
|
||||
hide_answer: { de: 'Antwort verbergen', tr: 'Cevab\u0131 gizle', ar: '\u0625\u062e\u0641\u0627\u0621 \u0627\u0644\u0625\u062c\u0627\u0628\u0629', uk: '\u0421\u0445\u043e\u0432\u0430\u0442\u0438 \u0432\u0456\u0434\u043f\u043e\u0432\u0456\u0434\u044c', ru: '\u0421\u043a\u0440\u044b\u0442\u044c \u043e\u0442\u0432\u0435\u0442', en: 'Hide answer' },
|
||||
wrong: { de: 'Falsch', tr: 'Yanl\u0131\u015f', ar: '\u062e\u0637\u0623', uk: '\u041d\u0435\u043f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u043e', ru: '\u041d\u0435\u043f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u043e', en: 'Wrong' },
|
||||
correct: { de: 'Richtig', tr: 'Do\u011fru', ar: '\u0635\u062d\u064a\u062d', uk: '\u041f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u043e', ru: '\u041f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u043e', en: 'Correct' },
|
||||
done: { de: 'Fertig!', tr: 'Bitti!', ar: '\u0627\u0646\u062a\u0647\u0649!', uk: '\u0413\u043e\u0442\u043e\u0432\u043e!', ru: '\u0413\u043e\u0442\u043e\u0432\u043e!', en: 'Done!' },
|
||||
again: { de: 'Nochmal', tr: 'Tekrar', ar: '\u0645\u0631\u0629 \u0623\u062e\u0631\u0649', uk: '\u0429\u0435 \u0440\u0430\u0437', ru: '\u0415\u0449\u0435 \u0440\u0430\u0437', en: 'Again' },
|
||||
back: { de: 'Zurueck', tr: 'Geri', ar: '\u0631\u062c\u0648\u0639', uk: '\u041d\u0430\u0437\u0430\u0434', ru: '\u041d\u0430\u0437\u0430\u0434', en: 'Back' },
|
||||
question: { de: 'Frage', tr: 'Soru', ar: '\u0633\u0624\u0627\u0644', uk: '\u041f\u0438\u0442\u0430\u043d\u043d\u044f', ru: '\u0412\u043e\u043f\u0440\u043e\u0441', en: 'Question' },
|
||||
}
|
||||
|
||||
export default function ParentQuizPage() {
|
||||
const { unitId } = useParams<{ unitId: string }>()
|
||||
const router = useRouter()
|
||||
const { isDark } = useTheme()
|
||||
const { language } = useLanguage()
|
||||
|
||||
const [items, setItems] = useState<QAItem[]>([])
|
||||
const [currentIndex, setCurrentIndex] = useState(0)
|
||||
const [showAnswer, setShowAnswer] = useState(false)
|
||||
const [isLoading, setIsLoading] = useState(true)
|
||||
const [stats, setStats] = useState({ correct: 0, incorrect: 0 })
|
||||
const [isComplete, setIsComplete] = useState(false)
|
||||
|
||||
const t = (key: string) => pt[key]?.[language] || pt[key]?.['de'] || key
|
||||
|
||||
const glassCard = isDark
|
||||
? 'bg-white/10 backdrop-blur-xl border border-white/10'
|
||||
: 'bg-white/80 backdrop-blur-xl border border-black/5'
|
||||
|
||||
useEffect(() => {
|
||||
fetch(`/api/learning-units/${unitId}/qa`)
|
||||
.then(r => r.ok ? r.json() : { qa_items: [] })
|
||||
.then(d => setItems(d.qa_items || []))
|
||||
.catch(() => {})
|
||||
.finally(() => setIsLoading(false))
|
||||
}, [unitId])
|
||||
|
||||
const handleResult = useCallback((correct: boolean) => {
|
||||
setStats(prev => ({
|
||||
correct: prev.correct + (correct ? 1 : 0),
|
||||
incorrect: prev.incorrect + (correct ? 0 : 1),
|
||||
}))
|
||||
setShowAnswer(false)
|
||||
if (currentIndex + 1 >= items.length) { setIsComplete(true) }
|
||||
else { setCurrentIndex(i => i + 1) }
|
||||
}, [currentIndex, items.length])
|
||||
|
||||
const currentItem = items[currentIndex]
|
||||
const nativeTranslation = currentItem?.translations?.[language]?.text || ''
|
||||
|
||||
if (isLoading) {
|
||||
return <div className={`min-h-screen flex items-center justify-center ${isDark ? 'bg-gradient-to-br from-slate-900 via-blue-900 to-indigo-900' : 'bg-gradient-to-br from-blue-50 via-indigo-50 to-purple-50'}`}>
|
||||
<div className="w-8 h-8 border-4 border-blue-400 border-t-transparent rounded-full animate-spin" />
|
||||
</div>
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={`min-h-screen flex flex-col ${isDark ? 'bg-gradient-to-br from-slate-900 via-blue-900 to-indigo-900' : 'bg-gradient-to-br from-blue-50 via-indigo-50 to-purple-50'}`}
|
||||
dir={language === 'ar' ? 'rtl' : 'ltr'}>
|
||||
|
||||
{/* Header */}
|
||||
<div className={`${glassCard} border-0 border-b`}>
|
||||
<div className="max-w-lg mx-auto px-6 py-4 flex items-center justify-between">
|
||||
<button onClick={() => router.push('/parent')} className={`text-sm ${isDark ? 'text-white/60' : 'text-slate-500'}`}>
|
||||
{t('back')}
|
||||
</button>
|
||||
<span className={`font-bold ${isDark ? 'text-white' : 'text-slate-900'}`}>
|
||||
{t('question')} {currentIndex + 1}/{items.length}
|
||||
</span>
|
||||
<span />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Progress */}
|
||||
<div className="w-full h-1.5 bg-white/10">
|
||||
<div className="h-full bg-gradient-to-r from-blue-500 to-cyan-500 transition-all" style={{ width: `${(currentIndex / Math.max(items.length, 1)) * 100}%` }} />
|
||||
</div>
|
||||
|
||||
<div className="flex-1 flex items-center justify-center px-6 py-8">
|
||||
{isComplete ? (
|
||||
<div className={`${glassCard} rounded-3xl p-10 text-center max-w-md w-full`}>
|
||||
<div className="text-5xl mb-4">{stats.correct > stats.incorrect ? '\uD83C\uDF89' : '\uD83D\uDCAA'}</div>
|
||||
<h2 className={`text-2xl font-bold mb-2 ${isDark ? 'text-white' : 'text-slate-900'}`}>{t('done')}</h2>
|
||||
<p className={`text-lg mb-6 ${isDark ? 'text-white/70' : 'text-slate-600'}`}>
|
||||
{stats.correct}/{items.length} {t('correct').toLowerCase()}
|
||||
</p>
|
||||
<div className="flex gap-3">
|
||||
<button onClick={() => { setCurrentIndex(0); setStats({ correct: 0, incorrect: 0 }); setIsComplete(false) }}
|
||||
className="flex-1 py-3 rounded-xl bg-gradient-to-r from-blue-500 to-cyan-500 text-white font-medium">{t('again')}</button>
|
||||
<button onClick={() => router.push('/parent')}
|
||||
className={`flex-1 py-3 rounded-xl border font-medium ${isDark ? 'border-white/20 text-white/80' : 'border-slate-300 text-slate-700'}`}>{t('back')}</button>
|
||||
</div>
|
||||
</div>
|
||||
) : currentItem ? (
|
||||
<div className="w-full max-w-md space-y-6">
|
||||
{/* Instruction for parent */}
|
||||
<p className={`text-center text-sm ${isDark ? 'text-white/50' : 'text-slate-500'}`}>
|
||||
{t('ask_child')}
|
||||
</p>
|
||||
|
||||
{/* Word to ask */}
|
||||
<div className={`${glassCard} rounded-3xl p-8 text-center`}>
|
||||
<span className={`text-3xl font-bold ${isDark ? 'text-white' : 'text-slate-900'}`}>
|
||||
{currentItem.question}
|
||||
</span>
|
||||
{nativeTranslation && (
|
||||
<p className={`text-lg mt-3 ${isDark ? 'text-blue-300/70' : 'text-blue-600'}`}>
|
||||
({nativeTranslation})
|
||||
</p>
|
||||
)}
|
||||
<div className="flex justify-center gap-3 mt-4">
|
||||
<AudioButton text={currentItem.question} lang="en" isDark={isDark} size="md" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Show/Hide Answer */}
|
||||
<button
|
||||
onClick={() => setShowAnswer(!showAnswer)}
|
||||
className={`w-full py-3 rounded-xl border text-sm font-medium transition-all ${
|
||||
isDark ? 'border-white/20 text-white/70 hover:bg-white/5' : 'border-slate-200 text-slate-600 hover:bg-slate-50'
|
||||
}`}
|
||||
>
|
||||
{showAnswer ? t('hide_answer') : t('show_answer')}
|
||||
</button>
|
||||
|
||||
{showAnswer && (
|
||||
<div className={`${glassCard} rounded-2xl p-6 text-center`}>
|
||||
<p className={`text-xs mb-2 ${isDark ? 'text-white/40' : 'text-slate-400'}`}>{t('correct_answer')}</p>
|
||||
<span className={`text-2xl font-bold ${isDark ? 'text-green-300' : 'text-green-700'}`}>
|
||||
{currentItem.answer}
|
||||
</span>
|
||||
{nativeTranslation && (
|
||||
<p className={`text-sm mt-1 ${isDark ? 'text-white/50' : 'text-slate-500'}`}>
|
||||
({nativeTranslation})
|
||||
</p>
|
||||
)}
|
||||
<div className="flex justify-center mt-3">
|
||||
<AudioButton text={currentItem.answer} lang="de" isDark={isDark} size="md" />
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Right/Wrong Buttons */}
|
||||
<div className="flex gap-4">
|
||||
<button onClick={() => handleResult(false)}
|
||||
className="flex-1 py-4 rounded-2xl font-semibold bg-gradient-to-r from-red-500 to-rose-500 text-white hover:shadow-lg transition-all">
|
||||
{t('wrong')}
|
||||
</button>
|
||||
<button onClick={() => handleResult(true)}
|
||||
className="flex-1 py-4 rounded-2xl font-semibold bg-gradient-to-r from-green-500 to-emerald-500 text-white hover:shadow-lg transition-all">
|
||||
{t('correct')}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user