dramaling-vocab-learning/frontend/app/learn/page.tsx

976 lines
38 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

'use client'
import { useState, useEffect } from 'react'
import { useRouter } from 'next/navigation'
import { Navigation } from '@/components/Navigation'
import AudioPlayer from '@/components/AudioPlayer'
import VoiceRecorder from '@/components/VoiceRecorder'
import LearningComplete from '@/components/LearningComplete'
export default function LearnPage() {
const router = useRouter()
const [mounted, setMounted] = useState(false)
const [currentCardIndex, setCurrentCardIndex] = useState(0)
const [isFlipped, setIsFlipped] = useState(false)
const [mode, setMode] = useState<'flip' | 'quiz' | 'fill' | 'listening' | 'speaking'>('flip')
const [score, setScore] = useState({ correct: 0, total: 0 })
const [selectedAnswer, setSelectedAnswer] = useState<string | null>(null)
const [showResult, setShowResult] = useState(false)
const [fillAnswer, setFillAnswer] = useState('')
const [showHint, setShowHint] = useState(false)
const [modalImage, setModalImage] = useState<string | null>(null)
const [showReportModal, setShowReportModal] = useState(false)
const [reportReason, setReportReason] = useState('')
const [reportingCard, setReportingCard] = useState<any>(null)
const [showComplete, setShowComplete] = useState(false)
const [quizOptions, setQuizOptions] = useState<string[]>(['brought', 'determine', 'achieve', 'consider'])
// Mock data with real example images
const cards = [
{
id: 1,
word: 'brought',
partOfSpeech: 'verb',
pronunciation: '/brɔːt/',
translation: '提出、帶來',
definition: 'Past tense of bring; to mention or introduce a topic in conversation',
example: 'He brought this thing up during our meeting and no one agreed.',
exampleTranslation: '他在我們的會議中提出了這件事,但沒有人同意。',
exampleImage: '/images/examples/bring_up.png',
synonyms: ['mentioned', 'raised', 'introduced'],
difficulty: 'B1'
},
{
id: 2,
word: 'instincts',
partOfSpeech: 'noun',
pronunciation: '/ˈɪnstɪŋkts/',
translation: '本能、直覺',
definition: 'Natural abilities that help living things survive without learning',
example: 'Animals use their instincts to find food and stay safe.',
exampleTranslation: '動物利用本能來尋找食物並保持安全。',
exampleImage: '/images/examples/instinct.png',
synonyms: ['intuition', 'impulse', 'tendency'],
difficulty: 'B2'
},
{
id: 3,
word: 'warrants',
partOfSpeech: 'noun',
pronunciation: '/ˈːrənts/',
translation: '搜查令、授權令',
definition: 'Official documents that give police permission to do something',
example: 'The police obtained warrants to search the building.',
exampleTranslation: '警方取得了搜查令來搜查這棟建築物。',
exampleImage: '/images/examples/warrant.png',
synonyms: ['authorization', 'permit', 'license'],
difficulty: 'C1'
}
]
const currentCard = cards[currentCardIndex]
// Client-side mounting and quiz options generation
useEffect(() => {
setMounted(true)
const currentWord = cards[currentCardIndex].word;
// Fixed options based on current card index
const optionSets = [
[currentWord, 'determine', 'achieve', 'consider'], // for index 0
[currentWord, 'brought', 'achieve', 'negotiate'], // for index 1
[currentWord, 'brought', 'instincts', 'determine'] // for index 2
];
const options = optionSets[currentCardIndex] || [currentWord, 'determine', 'achieve', 'consider'];
setQuizOptions(options);
// Reset quiz state when card changes
setSelectedAnswer(null);
setShowResult(false);
}, [currentCardIndex])
const handleFlip = () => {
setIsFlipped(!isFlipped)
}
const handleNext = () => {
if (currentCardIndex < cards.length - 1) {
setCurrentCardIndex(currentCardIndex + 1)
setIsFlipped(false)
setSelectedAnswer(null)
setShowResult(false)
setFillAnswer('')
setShowHint(false)
} else {
setShowComplete(true)
}
}
const handlePrevious = () => {
if (currentCardIndex > 0) {
setCurrentCardIndex(currentCardIndex - 1)
setIsFlipped(false)
setSelectedAnswer(null)
setShowResult(false)
setFillAnswer('')
setShowHint(false)
}
}
const handleQuizAnswer = (answer: string) => {
if (showResult) return
setSelectedAnswer(answer)
setShowResult(true)
const isCorrect = answer === currentCard.translation
setScore(prev => ({
correct: isCorrect ? prev.correct + 1 : prev.correct,
total: prev.total + 1
}))
}
const handleFillAnswer = () => {
if (showResult) return
setShowResult(true)
const isCorrect = fillAnswer.toLowerCase().trim() === currentCard.word.toLowerCase()
setScore(prev => ({
correct: isCorrect ? prev.correct + 1 : prev.correct,
total: prev.total + 1
}))
}
const handleListeningAnswer = (answer: string) => {
if (showResult) return
setSelectedAnswer(answer)
setShowResult(true)
const isCorrect = answer === currentCard.word
setScore(prev => ({
correct: isCorrect ? prev.correct + 1 : prev.correct,
total: prev.total + 1
}))
}
const handleSpeakingAnswer = (transcript: string) => {
setShowResult(true)
const isCorrect = transcript.toLowerCase().includes(currentCard.word.toLowerCase())
setScore(prev => ({
correct: isCorrect ? prev.correct + 1 : prev.correct,
total: prev.total + 1
}))
}
const handleReportSubmit = () => {
console.log('Report submitted:', {
card: reportingCard,
reason: reportReason
})
setShowReportModal(false)
setReportReason('')
setReportingCard(null)
}
const handleRestart = () => {
setCurrentCardIndex(0)
setIsFlipped(false)
setSelectedAnswer(null)
setShowResult(false)
setFillAnswer('')
setShowHint(false)
setScore({ correct: 0, total: 0 })
setShowComplete(false)
}
// Show loading screen until mounted
if (!mounted) {
return (
<div className="min-h-screen bg-gradient-to-br from-blue-50 to-indigo-100 flex items-center justify-center">
<div className="text-gray-500 text-lg">...</div>
</div>
)
}
return (
<div className="min-h-screen bg-gradient-to-br from-blue-50 to-indigo-100">
{/* Navigation */}
<Navigation
showExitLearning={true}
onExitLearning={() => router.push('/dashboard')}
/>
<div className="max-w-4xl mx-auto px-4 py-8">
{/* Progress Bar */}
<div className="mb-8">
<div className="flex justify-between items-center mb-2">
<span className="text-sm text-gray-600"></span>
<div className="flex items-center gap-4">
<span className="text-sm text-gray-600">
{currentCardIndex + 1} / {cards.length}
</span>
<div className="text-sm">
<span className="text-green-600 font-semibold">{score.correct}</span>
<span className="text-gray-500">/</span>
<span className="text-gray-600">{score.total}</span>
{score.total > 0 && (
<span className="text-blue-600 ml-2">
({Math.round((score.correct / score.total) * 100)}%)
</span>
)}
</div>
</div>
</div>
<div className="w-full bg-gray-200 rounded-full h-2">
<div
className="bg-primary h-2 rounded-full transition-all"
style={{ width: `${((currentCardIndex + 1) / cards.length) * 100}%` }}
></div>
</div>
</div>
{/* Mode Toggle */}
<div className="flex justify-center mb-6">
<div className="bg-white rounded-lg shadow-sm p-1 inline-flex flex-wrap">
<button
onClick={() => setMode('flip')}
className={`px-3 py-2 rounded-md transition-colors ${
mode === 'flip'
? 'bg-primary text-white'
: 'text-gray-600 hover:text-gray-900'
}`}
>
</button>
<button
onClick={() => setMode('quiz')}
className={`px-3 py-2 rounded-md transition-colors ${
mode === 'quiz'
? 'bg-primary text-white'
: 'text-gray-600 hover:text-gray-900'
}`}
>
</button>
<button
onClick={() => setMode('listening')}
className={`px-3 py-2 rounded-md transition-colors ${
mode === 'listening'
? 'bg-primary text-white'
: 'text-gray-600 hover:text-gray-900'
}`}
>
</button>
<button
onClick={() => setMode('fill')}
className={`px-3 py-2 rounded-md transition-colors ${
mode === 'fill'
? 'bg-primary text-white'
: 'text-gray-600 hover:text-gray-900'
}`}
>
</button>
<button
onClick={() => setMode('speaking')}
className={`px-3 py-2 rounded-md transition-colors ${
mode === 'speaking'
? 'bg-primary text-white'
: 'text-gray-600 hover:text-gray-900'
}`}
>
</button>
</div>
</div>
{mode === 'flip' ? (
/* Flip Card Mode */
<div className="relative">
{/* Error Report Button for Flip Mode */}
<div className="flex justify-end mb-2">
<button
onClick={() => {
setReportingCard(currentCard)
setShowReportModal(true)
}}
className="px-3 py-2 rounded-md transition-colors text-gray-600 hover:text-gray-900"
>
🚩
</button>
</div>
<div className="card-container" onClick={handleFlip}>
<div className={`card ${isFlipped ? 'flipped' : ''}`}>
{/* Front */}
<div className="card-front">
<div className="bg-white rounded-xl shadow-lg p-8 text-center cursor-pointer hover:shadow-xl transition-shadow">
<div className="mb-4">
<span className="inline-block bg-blue-100 text-blue-800 text-xs px-2 py-1 rounded-full">
{currentCard.difficulty}
</span>
</div>
<h2 className="text-4xl font-bold text-gray-900 mb-4">
{currentCard.word}
</h2>
<p className="text-gray-600 mb-4">
{currentCard.partOfSpeech} {currentCard.pronunciation}
</p>
<AudioPlayer text={currentCard.word} />
<p className="text-sm text-gray-500 mt-4">
</p>
</div>
</div>
{/* Back */}
<div className="card-back">
<div className="bg-white rounded-xl shadow-lg p-8 cursor-pointer hover:shadow-xl transition-shadow">
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
{/* Left Column - Text Content */}
<div>
<div className="mb-4">
<span className="inline-block bg-blue-100 text-blue-800 text-xs px-2 py-1 rounded-full">
{currentCard.difficulty}
</span>
</div>
<h2 className="text-3xl font-bold text-gray-900 mb-2">
{currentCard.word}
</h2>
<p className="text-lg text-blue-600 font-semibold mb-4">
{currentCard.translation}
</p>
<p className="text-gray-600 mb-4">
{currentCard.partOfSpeech} {currentCard.pronunciation}
</p>
<div className="mb-4">
<AudioPlayer text={currentCard.word} />
</div>
<div className="mb-4">
<h3 className="font-semibold text-gray-900 mb-2"></h3>
<p className="text-gray-700">{currentCard.definition}</p>
</div>
<div className="mb-4">
<h3 className="font-semibold text-gray-900 mb-2"></h3>
<p className="text-gray-700 italic mb-1">"{currentCard.example}"</p>
<p className="text-gray-600 text-sm">"{currentCard.exampleTranslation}"</p>
</div>
<div>
<h3 className="font-semibold text-gray-900 mb-2"></h3>
<div className="flex flex-wrap gap-2">
{currentCard.synonyms.map((synonym, index) => (
<span
key={index}
className="bg-gray-100 text-gray-700 px-2 py-1 rounded text-sm"
>
{synonym}
</span>
))}
</div>
</div>
</div>
{/* Right Column - Image */}
<div className="flex items-center justify-center">
<div className="relative">
<img
src={currentCard.exampleImage}
alt={`Example for ${currentCard.word}`}
className="rounded-lg shadow-md max-w-full h-auto cursor-pointer hover:shadow-lg transition-shadow"
onClick={(e) => {
e.stopPropagation()
setModalImage(currentCard.exampleImage)
}}
/>
<div className="absolute top-2 right-2 bg-black bg-opacity-50 text-white text-xs px-2 py-1 rounded">
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
{/* Navigation */}
<div className="flex justify-between mt-6">
<button
onClick={handlePrevious}
disabled={currentCardIndex === 0}
className="px-4 py-2 bg-gray-500 text-white rounded-lg disabled:opacity-50 disabled:cursor-not-allowed hover:bg-gray-600 transition-colors"
>
</button>
<button
onClick={handleNext}
className="px-4 py-2 bg-primary text-white rounded-lg hover:bg-primary-dark transition-colors"
>
{currentCardIndex === cards.length - 1 ? '完成' : '下一張'}
</button>
</div>
</div>
) : mode === 'quiz' ? (
/* Quiz Mode - 選擇題:英文定義選中文翻譯 */
<div className="relative">
{/* Error Report Button for Quiz Mode */}
<div className="flex justify-end mb-2">
<button
onClick={() => {
setReportingCard(currentCard)
setShowReportModal(true)
}}
className="px-3 py-2 rounded-md transition-colors text-gray-600 hover:text-gray-900"
>
🚩
</button>
</div>
<div className="bg-white rounded-xl shadow-lg p-8">
<div className="text-center mb-8">
<div className="mb-4">
<span className="inline-block bg-blue-100 text-blue-800 text-xs px-2 py-1 rounded-full">
{currentCard.difficulty}
</span>
</div>
<h2 className="text-3xl font-bold text-gray-900 mb-4">
{currentCard.word}
</h2>
<p className="text-gray-600 mb-4">
{currentCard.partOfSpeech} {currentCard.pronunciation}
</p>
<div className="mb-6">
<AudioPlayer text={currentCard.word} />
</div>
<div className="bg-gray-50 rounded-lg p-4 mb-6">
<p className="text-gray-700 text-lg">
{currentCard.definition}
</p>
</div>
<p className="text-lg text-gray-700 mb-2">
</p>
</div>
<div className="space-y-3">
{quizOptions.map((option, idx) => (
<button
key={idx}
onClick={() => !showResult && handleQuizAnswer(option)}
disabled={showResult}
className={`w-full p-4 text-left rounded-lg border-2 transition-all ${
showResult
? option === currentCard.translation
? 'border-green-500 bg-green-50 text-green-700'
: option === selectedAnswer
? 'border-red-500 bg-red-50 text-red-700'
: 'border-gray-200 bg-gray-50 text-gray-500'
: 'border-gray-200 hover:border-blue-300 hover:bg-blue-50'
}`}
>
{option}
</button>
))}
</div>
{showResult && (
<div className={`mt-6 p-4 rounded-lg ${
selectedAnswer === currentCard.translation
? 'bg-green-50 border border-green-200'
: 'bg-red-50 border border-red-200'
}`}>
<p className={`font-semibold ${
selectedAnswer === currentCard.translation
? 'text-green-700'
: 'text-red-700'
}`}>
{selectedAnswer === currentCard.translation ? '正確!' : '錯誤!'}
</p>
{selectedAnswer !== currentCard.translation && (
<p className="text-gray-700 mt-2">
{currentCard.translation}
</p>
)}
</div>
)}
{/* Navigation */}
<div className="flex justify-between mt-8">
<button
onClick={handlePrevious}
disabled={currentCardIndex === 0}
className="px-4 py-2 bg-gray-500 text-white rounded-lg disabled:opacity-50 disabled:cursor-not-allowed hover:bg-gray-600 transition-colors"
>
</button>
<button
onClick={handleNext}
className="px-4 py-2 bg-primary text-white rounded-lg hover:bg-primary-dark transition-colors"
>
{currentCardIndex === cards.length - 1 ? '完成' : '下一張'}
</button>
</div>
</div>
</div>
) : mode === 'fill' ? (
/* Fill in the Blank Mode - 填空題 */
<div className="relative">
{/* Error Report Button for Fill Mode */}
<div className="flex justify-end mb-2">
<button
onClick={() => {
setReportingCard(currentCard)
setShowReportModal(true)
}}
className="px-3 py-2 rounded-md transition-colors text-gray-600 hover:text-gray-900"
>
🚩
</button>
</div>
<div className="bg-white rounded-xl shadow-lg p-8">
<div className="text-center mb-8">
<div className="mb-4">
<span className="inline-block bg-blue-100 text-blue-800 text-xs px-2 py-1 rounded-full">
{currentCard.difficulty}
</span>
</div>
<h2 className="text-2xl font-bold text-gray-900 mb-4">
</h2>
<div className="bg-gray-50 rounded-lg p-6 mb-6">
<p className="text-lg text-gray-700 mb-4">
<strong></strong>{currentCard.definition}
</p>
<p className="text-lg text-gray-700">
<strong></strong>{currentCard.translation}
</p>
</div>
<p className="text-lg text-gray-700 mb-4">
</p>
</div>
<div className="max-w-md mx-auto">
<input
type="text"
value={fillAnswer}
onChange={(e) => setFillAnswer(e.target.value)}
onKeyDown={(e) => {
if (e.key === 'Enter' && !showResult && fillAnswer.trim()) {
handleFillAnswer()
}
}}
placeholder="輸入英文單字..."
disabled={showResult}
className="w-full p-4 text-center text-xl border-2 border-gray-200 rounded-lg focus:border-blue-500 focus:outline-none disabled:bg-gray-100"
/>
<div className="flex gap-2 mt-4">
{!showResult && fillAnswer.trim() && (
<button
onClick={handleFillAnswer}
className="flex-1 px-4 py-2 bg-primary text-white rounded-lg hover:bg-primary-dark transition-colors"
>
</button>
)}
<button
onClick={() => setShowHint(!showHint)}
className="px-4 py-2 bg-gray-500 text-white rounded-lg hover:bg-gray-600 transition-colors"
>
{showHint ? '隱藏提示' : '顯示提示'}
</button>
</div>
{showHint && (
<div className="mt-4 p-4 bg-yellow-50 border border-yellow-200 rounded-lg">
<p className="text-yellow-800">
<strong></strong> {currentCard.word.length}
"{currentCard.word[0].toUpperCase()}"
</p>
</div>
)}
</div>
{showResult && (
<div className={`mt-6 p-4 rounded-lg max-w-md mx-auto ${
fillAnswer.toLowerCase().trim() === currentCard.word.toLowerCase()
? 'bg-green-50 border border-green-200'
: 'bg-red-50 border border-red-200'
}`}>
<p className={`font-semibold text-center ${
fillAnswer.toLowerCase().trim() === currentCard.word.toLowerCase()
? 'text-green-700'
: 'text-red-700'
}`}>
{fillAnswer.toLowerCase().trim() === currentCard.word.toLowerCase() ? '正確!' : '錯誤!'}
</p>
{fillAnswer.toLowerCase().trim() !== currentCard.word.toLowerCase() && (
<p className="text-gray-700 mt-2 text-center">
<strong>{currentCard.word}</strong>
</p>
)}
<div className="mt-4 text-center">
<p className="text-gray-600 mb-2">{currentCard.pronunciation}</p>
<AudioPlayer text={currentCard.word} />
</div>
</div>
)}
{/* Navigation */}
<div className="flex justify-between mt-8">
<button
onClick={handlePrevious}
disabled={currentCardIndex === 0}
className="px-4 py-2 bg-gray-500 text-white rounded-lg disabled:opacity-50 disabled:cursor-not-allowed hover:bg-gray-600 transition-colors"
>
</button>
<button
onClick={handleNext}
className="px-4 py-2 bg-primary text-white rounded-lg hover:bg-primary-dark transition-colors"
>
{currentCardIndex === cards.length - 1 ? '完成' : '下一張'}
</button>
</div>
</div>
</div>
) : mode === 'listening' ? (
/* Listening Test Mode - 聽力測試 */
<div className="relative">
{/* Error Report Button for Listening Mode */}
<div className="flex justify-end mb-2">
<button
onClick={() => {
setReportingCard(currentCard)
setShowReportModal(true)
}}
className="px-3 py-2 rounded-md transition-colors text-gray-600 hover:text-gray-900"
>
🚩
</button>
</div>
<div className="bg-white rounded-xl shadow-lg p-8">
<div className="text-center mb-8">
<div className="mb-4">
<span className="inline-block bg-blue-100 text-blue-800 text-xs px-2 py-1 rounded-full">
{currentCard.difficulty}
</span>
</div>
<h2 className="text-2xl font-bold text-gray-900 mb-6">
</h2>
<div className="bg-gray-50 rounded-lg p-6 mb-6">
<p className="text-lg text-gray-700 mb-4">
<strong></strong>{currentCard.translation}
</p>
<p className="text-lg text-gray-700">
<strong></strong>{currentCard.definition}
</p>
</div>
<p className="text-lg text-gray-700 mb-6">
</p>
<div className="mb-8">
<AudioPlayer
text={currentCard.word}
/>
</div>
</div>
{/* Word Options */}
<div className="grid grid-cols-2 gap-3">
{[currentCard.word, 'determine', 'achieve', 'consider'].map((word) => (
<button
key={word}
onClick={() => !showResult && handleListeningAnswer(word)}
disabled={showResult}
className={`p-4 text-center rounded-lg border-2 transition-all ${
showResult
? word === currentCard.word
? 'border-green-500 bg-green-50 text-green-700'
: word === selectedAnswer
? 'border-red-500 bg-red-50 text-red-700'
: 'border-gray-200 bg-gray-50 text-gray-500'
: 'border-gray-200 hover:border-blue-300 hover:bg-blue-50'
}`}
>
{word}
</button>
))}
</div>
{showResult && (
<div className={`mt-6 p-4 rounded-lg ${
selectedAnswer === currentCard.word
? 'bg-green-50 border border-green-200'
: 'bg-red-50 border border-red-200'
}`}>
<p className={`font-semibold ${
selectedAnswer === currentCard.word
? 'text-green-700'
: 'text-red-700'
}`}>
{selectedAnswer === currentCard.word ? '正確!' : '錯誤!'}
</p>
{selectedAnswer !== currentCard.word && (
<div className="mt-2">
<p className="text-gray-700">
<strong>{currentCard.word}</strong>
</p>
<p className="text-gray-600 mt-1">
{currentCard.pronunciation}
</p>
</div>
)}
</div>
)}
{/* Navigation */}
<div className="flex justify-between mt-8">
<button
onClick={handlePrevious}
disabled={currentCardIndex === 0}
className="px-4 py-2 bg-gray-500 text-white rounded-lg disabled:opacity-50 disabled:cursor-not-allowed hover:bg-gray-600 transition-colors"
>
</button>
<button
onClick={handleNext}
className="px-4 py-2 bg-primary text-white rounded-lg hover:bg-primary-dark transition-colors"
>
{currentCardIndex === cards.length - 1 ? '完成' : '下一張'}
</button>
</div>
</div>
</div>
) : mode === 'speaking' ? (
/* Speaking Test Mode - 口說測試 */
<div className="relative">
{/* Error Report Button for Speaking Mode */}
<div className="flex justify-end mb-2">
<button
onClick={() => {
setReportingCard(currentCard)
setShowReportModal(true)
}}
className="px-3 py-2 rounded-md transition-colors text-gray-600 hover:text-gray-900"
>
🚩
</button>
</div>
<div className="bg-white rounded-xl shadow-lg p-8">
<div className="text-center mb-8">
<div className="mb-4">
<span className="inline-block bg-blue-100 text-blue-800 text-xs px-2 py-1 rounded-full">
{currentCard.difficulty}
</span>
</div>
<h2 className="text-2xl font-bold text-gray-900 mb-6">
</h2>
<div className="bg-gray-50 rounded-lg p-6 mb-6">
<p className="text-lg text-gray-700 mb-4">
<strong></strong>{currentCard.translation}
</p>
<p className="text-lg text-gray-700 mb-4">
<strong></strong>{currentCard.definition}
</p>
<p className="text-gray-600">
<strong></strong>{currentCard.pronunciation}
</p>
</div>
<p className="text-lg text-gray-700 mb-6">
</p>
<div className="mb-6">
<AudioPlayer text={currentCard.word} />
<p className="text-sm text-gray-500 mt-2">
</p>
</div>
</div>
<div className="max-w-md mx-auto">
<VoiceRecorder
targetText={currentCard.word}
onRecordingComplete={() => {
// 簡化處理:直接顯示結果
handleSpeakingAnswer(currentCard.word)
}}
/>
</div>
{showResult && (
<div className="mt-6 p-4 rounded-lg bg-blue-50 border border-blue-200 max-w-md mx-auto">
<p className="text-blue-700 text-center">
...
</p>
<div className="mt-4 text-center">
<p className="text-gray-700">
<strong>{currentCard.word}</strong>
</p>
<p className="text-gray-600 mt-1">
{currentCard.pronunciation}
</p>
<div className="mt-2">
<AudioPlayer text={currentCard.word} />
</div>
</div>
</div>
)}
{/* Navigation */}
<div className="flex justify-between mt-8">
<button
onClick={handlePrevious}
disabled={currentCardIndex === 0}
className="px-4 py-2 bg-gray-500 text-white rounded-lg disabled:opacity-50 disabled:cursor-not-allowed hover:bg-gray-600 transition-colors"
>
</button>
<button
onClick={handleNext}
className="px-4 py-2 bg-primary text-white rounded-lg hover:bg-primary-dark transition-colors"
>
{currentCardIndex === cards.length - 1 ? '完成' : '下一張'}
</button>
</div>
</div>
</div>
) : null}
{/* Report Modal */}
{showReportModal && (
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
<div className="bg-white rounded-lg p-6 max-w-md w-full mx-4">
<h3 className="text-lg font-semibold mb-4"></h3>
<div className="mb-4">
<p className="text-sm text-gray-600 mb-2">
{reportingCard?.word}
</p>
</div>
<div className="mb-4">
<label className="block text-sm font-medium text-gray-700 mb-2">
</label>
<select
value={reportReason}
onChange={(e) => setReportReason(e.target.value)}
className="w-full p-2 border border-gray-300 rounded-md focus:ring-blue-500 focus:border-blue-500"
>
<option value=""></option>
<option value="translation"></option>
<option value="definition"></option>
<option value="pronunciation"></option>
<option value="example"></option>
<option value="image"></option>
<option value="other"></option>
</select>
</div>
<div className="flex gap-2">
<button
onClick={() => setShowReportModal(false)}
className="flex-1 px-4 py-2 border border-gray-300 text-gray-700 rounded-md hover:bg-gray-50"
>
</button>
<button
onClick={handleReportSubmit}
disabled={!reportReason}
className="flex-1 px-4 py-2 bg-red-600 text-white rounded-md hover:bg-red-700 disabled:opacity-50 disabled:cursor-not-allowed"
>
</button>
</div>
</div>
</div>
)}
{/* Image Modal */}
{modalImage && (
<div
className="fixed inset-0 bg-black bg-opacity-75 flex items-center justify-center z-50"
onClick={() => setModalImage(null)}
>
<div className="relative max-w-4xl max-h-[90vh] mx-4">
<img
src={modalImage}
alt="放大圖片"
className="max-w-full max-h-full rounded-lg"
/>
<button
onClick={() => setModalImage(null)}
className="absolute top-4 right-4 bg-black bg-opacity-50 text-white p-2 rounded-full hover:bg-opacity-75"
>
</button>
</div>
</div>
)}
{/* Complete Modal */}
{showComplete && (
<LearningComplete
score={score}
mode={mode}
onRestart={handleRestart}
onBackToDashboard={() => router.push('/dashboard')}
/>
)}
</div>
<style jsx>{`
.card-container {
perspective: 1000px;
height: 400px;
}
.card {
position: relative;
width: 100%;
height: 100%;
text-align: center;
transition: transform 0.6s;
transform-style: preserve-3d;
}
.card.flipped {
transform: rotateY(180deg);
}
.card-front, .card-back {
position: absolute;
width: 100%;
height: 100%;
backface-visibility: hidden;
display: flex;
align-items: center;
justify-content: center;
}
.card-back {
transform: rotateY(180deg);
}
.card-front > div, .card-back > div {
width: 100%;
height: 100%;
overflow-y: auto;
}
`}</style>
</div>
)
}