Files
breakpilot-lehrer/studio-v2/app/learn/[unitId]/quiz/page.tsx
Benjamin Admin 9ba420fa91
Some checks failed
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-school (push) Successful in 42s
CI / test-go-edu-search (push) Successful in 34s
CI / test-python-klausur (push) Failing after 2m51s
CI / test-python-agent-core (push) Successful in 21s
CI / test-nodejs-website (push) Successful in 29s
Fix: Remove broken getKlausurApiUrl and clean up empty lines
sed replacement left orphaned hostname references in story page
and empty lines in getApiBase functions.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-24 16:02:04 +02:00

158 lines
6.1 KiB
TypeScript

'use client'
import React, { useState, useEffect, useCallback } from 'react'
import { useParams, useRouter } from 'next/navigation'
import { useTheme } from '@/lib/ThemeContext'
import { QuizQuestion } from '@/components/learn/QuizQuestion'
interface MCQuestion {
id: string
question: string
options: { id: string; text: string }[]
correct_answer: string
explanation?: string
}
function getApiBase() {
return '' // Same-origin proxy
}
export default function QuizPage() {
const { unitId } = useParams<{ unitId: string }>()
const router = useRouter()
const { isDark } = useTheme()
const [questions, setQuestions] = useState<MCQuestion[]>([])
const [currentIndex, setCurrentIndex] = useState(0)
const [isLoading, setIsLoading] = useState(true)
const [error, setError] = useState<string | null>(null)
const [stats, setStats] = useState({ correct: 0, incorrect: 0 })
const [isComplete, setIsComplete] = useState(false)
const glassCard = isDark
? 'bg-white/10 backdrop-blur-xl border border-white/10'
: 'bg-white/80 backdrop-blur-xl border border-black/5'
useEffect(() => {
loadMC()
}, [unitId])
const loadMC = async () => {
setIsLoading(true)
try {
const resp = await fetch(`${getApiBase()}/api/learning-units/${unitId}/mc`)
if (!resp.ok) throw new Error(`HTTP ${resp.status}`)
const data = await resp.json()
setQuestions(data.questions || [])
} catch (err: any) {
setError(err.message)
} finally {
setIsLoading(false)
}
}
const handleAnswer = useCallback((correct: boolean) => {
setStats((prev) => ({
correct: prev.correct + (correct ? 1 : 0),
incorrect: prev.incorrect + (correct ? 0 : 1),
}))
if (currentIndex + 1 >= questions.length) {
setIsComplete(true)
} else {
setCurrentIndex((i) => i + 1)
}
}, [currentIndex, questions.length])
if (isLoading) {
return (
<div className={`min-h-screen flex items-center justify-center ${isDark ? 'bg-gradient-to-br from-indigo-900 via-purple-900 to-pink-800' : 'bg-gradient-to-br from-slate-100 via-blue-50 to-cyan-100'}`}>
<div className={`w-8 h-8 border-4 ${isDark ? 'border-purple-400' : 'border-purple-600'} border-t-transparent rounded-full animate-spin`} />
</div>
)
}
return (
<div className={`min-h-screen flex flex-col ${isDark ? 'bg-gradient-to-br from-indigo-900 via-purple-900 to-pink-800' : 'bg-gradient-to-br from-slate-100 via-blue-50 to-cyan-100'}`}>
{/* Header */}
<div className={`${glassCard} border-0 border-b`}>
<div className="max-w-2xl mx-auto px-6 py-4 flex items-center justify-between">
<button
onClick={() => router.push('/learn')}
className={`flex items-center gap-2 text-sm ${isDark ? 'text-white/60 hover:text-white' : 'text-slate-500 hover:text-slate-900'}`}
>
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 19l-7-7 7-7" />
</svg>
Zurueck
</button>
<h1 className={`text-lg font-bold ${isDark ? 'text-white' : 'text-slate-900'}`}>
Quiz
</h1>
<span className={`text-sm ${isDark ? 'text-white/50' : 'text-slate-500'}`}>
{questions.length} Fragen
</span>
</div>
</div>
{/* Content */}
<div className="flex-1 flex items-center justify-center px-6 py-8">
{error ? (
<div className={`${glassCard} rounded-2xl p-8 text-center max-w-md`}>
<p className={isDark ? 'text-red-300' : 'text-red-600'}>{error}</p>
<button onClick={() => router.push('/learn')} className="mt-4 px-4 py-2 rounded-xl bg-purple-500 text-white text-sm">
Zurueck
</button>
</div>
) : isComplete ? (
<div className={`${glassCard} rounded-3xl p-10 text-center max-w-md w-full`}>
<div className="text-5xl mb-4">
{stats.correct === questions.length ? '🏆' : stats.correct > stats.incorrect ? '🎉' : '💪'}
</div>
<h2 className={`text-2xl font-bold mb-2 ${isDark ? 'text-white' : 'text-slate-900'}`}>
{stats.correct === questions.length ? 'Perfekt!' : 'Geschafft!'}
</h2>
<p className={`text-lg mb-4 ${isDark ? 'text-white/70' : 'text-slate-600'}`}>
{stats.correct} von {questions.length} richtig
({Math.round((stats.correct / questions.length) * 100)}%)
</p>
<div className="w-full h-3 rounded-full bg-white/10 overflow-hidden mb-6">
<div
className="h-full rounded-full bg-gradient-to-r from-purple-500 to-pink-500"
style={{ width: `${(stats.correct / questions.length) * 100}%` }}
/>
</div>
<div className="flex gap-3">
<button
onClick={() => { setCurrentIndex(0); setStats({ correct: 0, incorrect: 0 }); setIsComplete(false); loadMC() }}
className="flex-1 py-3 rounded-xl bg-gradient-to-r from-purple-500 to-pink-500 text-white font-medium"
>
Nochmal
</button>
<button
onClick={() => router.push('/learn')}
className={`flex-1 py-3 rounded-xl border font-medium ${isDark ? 'border-white/20 text-white/80' : 'border-slate-300 text-slate-700'}`}
>
Zurueck
</button>
</div>
</div>
) : questions[currentIndex] ? (
<QuizQuestion
question={questions[currentIndex].question}
options={questions[currentIndex].options}
correctAnswer={questions[currentIndex].correct_answer}
explanation={questions[currentIndex].explanation}
questionNumber={currentIndex + 1}
totalQuestions={questions.length}
onAnswer={handleAnswer}
isDark={isDark}
/>
) : (
<p className={isDark ? 'text-white/60' : 'text-slate-500'}>Keine Quiz-Fragen verfuegbar.</p>
)}
</div>
</div>
)
}