'use client' import { useState, useEffect, useRef, useLayoutEffect } 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(null) const [showResult, setShowResult] = useState(false) const [fillAnswer, setFillAnswer] = useState('') const [showHint, setShowHint] = useState(false) const [modalImage, setModalImage] = useState(null) const [showReportModal, setShowReportModal] = useState(false) const [reportReason, setReportReason] = useState('') const [reportingCard, setReportingCard] = useState(null) const [showComplete, setShowComplete] = useState(false) const [quizOptions, setQuizOptions] = useState(['brought', 'determine', 'achieve', 'consider']) const [cardHeight, setCardHeight] = useState(400) // Refs for measuring card content heights const cardFrontRef = useRef(null) const cardBackRef = useRef(null) const cardContainerRef = useRef(null) // 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: '/ˈwɔː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] // Calculate optimal card height based on content (only when card changes) const calculateCardHeight = () => { if (!cardFrontRef.current || !cardBackRef.current) return 400; // Get the scroll heights to measure actual content const frontHeight = cardFrontRef.current.scrollHeight; const backHeight = cardBackRef.current.scrollHeight; console.log('Heights calculated:', { frontHeight, backHeight }); // Debug log // Use the maximum height with padding const maxHeight = Math.max(frontHeight, backHeight); const paddedHeight = maxHeight + 40; // Add padding for visual spacing // Ensure minimum height for visual consistency return Math.max(paddedHeight, 450); }; // Update card height only when card content changes (not on flip) useLayoutEffect(() => { if (mounted && cardFrontRef.current && cardBackRef.current) { // Wait for DOM to be fully rendered const timer = setTimeout(() => { const newHeight = calculateCardHeight(); setCardHeight(newHeight); }, 50); return () => clearTimeout(timer); } }, [currentCardIndex, mounted]); // Client-side mounting and quiz options generation useEffect(() => { setMounted(true) const currentWord = cards[currentCardIndex].word; // Generate quiz options with current word and other words from the deck const otherWords = cards .filter((_, idx) => idx !== currentCardIndex) .map(card => card.word) .slice(0, 3); // Take 3 other words // Add the current word and shuffle const options = [currentWord, ...otherWords].sort(() => Math.random() - 0.5); 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) // Height will be recalculated in useLayoutEffect } else { setShowComplete(true) } } const handlePrevious = () => { if (currentCardIndex > 0) { setCurrentCardIndex(currentCardIndex - 1) setIsFlipped(false) setSelectedAnswer(null) setShowResult(false) setFillAnswer('') setShowHint(false) // Height will be recalculated in useLayoutEffect } } const handleQuizAnswer = (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 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 (
載入中...
) } return (
{/* Navigation */} router.push('/dashboard')} />
{/* Progress Bar */}
進度
{currentCardIndex + 1} / {cards.length}
{score.correct} / {score.total} {score.total > 0 && ( ({Math.round((score.correct / score.total) * 100)}%) )}
{/* Mode Toggle */}
{mode === 'flip' ? ( /* Flip Card Mode */
{/* Error Report Button for Flip Mode */}
{/* Front */}

{currentCard.word}

{currentCard.pronunciation}
{/* Back */}
{/* Content Sections */}
{/* Definition */}

定義

{currentCard.definition}

{/* Example */}

例句

"{currentCard.example}"

"{currentCard.exampleTranslation}"

{/* Synonyms */}

同義詞

{currentCard.synonyms.map((synonym, index) => ( {synonym} ))}
{/* Navigation */}
) : mode === 'quiz' ? ( /* Quiz Mode - 選擇題:英文定義選中文翻譯 */
{/* Error Report Button for Quiz Mode */}

定義

{currentCard.definition}

請選擇符合上述定義的英文詞彙:

{quizOptions.map((option, idx) => ( ))}
{showResult && (

{selectedAnswer === currentCard.word ? '正確!' : '錯誤!'}

{selectedAnswer !== currentCard.word && (

正確答案是:{currentCard.word} {currentCard.pronunciation}

)} {selectedAnswer === currentCard.word && (

{currentCard.word} {currentCard.pronunciation}

)}
)}
{/* Navigation */}
) : mode === 'fill' ? ( /* Fill in the Blank Mode - 填空題 */
{/* Error Report Button for Fill Mode */}
{currentCard.difficulty}

填空題

定義:{currentCard.definition}

中文翻譯:{currentCard.translation}

請輸入對應的英文單字:

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" />
{!showResult && fillAnswer.trim() && ( )}
{showHint && (

提示:這個詞有 {currentCard.word.length} 個字母, 開頭是 "{currentCard.word[0].toUpperCase()}"

)}
{showResult && (

{fillAnswer.toLowerCase().trim() === currentCard.word.toLowerCase() ? '正確!' : '錯誤!'}

{fillAnswer.toLowerCase().trim() !== currentCard.word.toLowerCase() && (

正確答案是:{currentCard.word}

)}

發音:{currentCard.pronunciation}

)}
{/* Navigation */}
) : mode === 'listening' ? ( /* Listening Test Mode - 聽力測試 */
{/* Error Report Button for Listening Mode */}
{currentCard.difficulty}

聽力測試

中文翻譯:{currentCard.translation}

定義:{currentCard.definition}

請聽發音並選擇正確的英文單字:

{/* Word Options */}
{[currentCard.word, 'determine', 'achieve', 'consider'].map((word) => ( ))}
{showResult && (

{selectedAnswer === currentCard.word ? '正確!' : '錯誤!'}

{selectedAnswer !== currentCard.word && (

正確答案是:{currentCard.word}

發音:{currentCard.pronunciation}

)}
)}
{/* Navigation */}
) : mode === 'speaking' ? ( /* Speaking Test Mode - 口說測試 */
{/* Error Report Button for Speaking Mode */}
{currentCard.difficulty}

口說測試

中文翻譯:{currentCard.translation}

定義:{currentCard.definition}

發音:{currentCard.pronunciation}

請說出這個英文單字:

點擊播放聽一遍正確發音

{ // 簡化處理:直接顯示結果 handleSpeakingAnswer(currentCard.word) }} />
{showResult && (

錄音完成!系統正在評估你的發音...

目標單字:{currentCard.word}

發音:{currentCard.pronunciation}

)}
{/* Navigation */}
) : null} {/* Report Modal */} {showReportModal && (

回報錯誤

單字:{reportingCard?.word}

)} {/* Image Modal */} {modalImage && (
setModalImage(null)} >
放大圖片
)} {/* Complete Modal */} {showComplete && ( router.push('/dashboard')} /> )}
) }