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

358 lines
14 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 } from 'react'
import Link from 'next/link'
export default function LearnPage() {
const [currentCardIndex, setCurrentCardIndex] = useState(0)
const [isFlipped, setIsFlipped] = useState(false)
const [mode, setMode] = useState<'flip' | 'quiz'>('flip')
const [score, setScore] = useState({ correct: 0, total: 0 })
const [selectedAnswer, setSelectedAnswer] = useState<string | null>(null)
const [showResult, setShowResult] = useState(false)
// Mock data
const cards = [
{
id: 1,
word: 'negotiate',
partOfSpeech: 'verb',
pronunciation: '/nɪˈɡoʊʃieɪt/',
translation: '協商、談判',
definition: 'To discuss something with someone in order to reach an agreement',
example: 'We need to negotiate a better deal with our suppliers.',
exampleTranslation: '我們需要與供應商協商更好的交易。',
synonyms: ['bargain', 'discuss', 'mediate'],
difficulty: 'intermediate'
},
{
id: 2,
word: 'perspective',
partOfSpeech: 'noun',
pronunciation: '/pərˈspektɪv/',
translation: '觀點、看法',
definition: 'A particular way of considering something',
example: 'From my perspective, this is the best solution.',
exampleTranslation: '從我的角度來看,這是最好的解決方案。',
synonyms: ['viewpoint', 'outlook', 'stance'],
difficulty: 'intermediate'
},
{
id: 3,
word: 'accomplish',
partOfSpeech: 'verb',
pronunciation: '/əˈkɒmplɪʃ/',
translation: '完成、達成',
definition: 'To finish something successfully or to achieve something',
example: 'She accomplished her goal of running a marathon.',
exampleTranslation: '她完成了跑馬拉松的目標。',
synonyms: ['achieve', 'complete', 'fulfill'],
difficulty: 'intermediate'
}
]
const currentCard = cards[currentCardIndex]
// Quiz mode options
const quizOptions = [
'協商、談判',
'觀點、看法',
'完成、達成',
'建議、提議'
]
const handleFlip = () => {
setIsFlipped(!isFlipped)
}
const handleNext = () => {
if (currentCardIndex < cards.length - 1) {
setCurrentCardIndex(currentCardIndex + 1)
setIsFlipped(false)
setSelectedAnswer(null)
setShowResult(false)
}
}
const handlePrevious = () => {
if (currentCardIndex > 0) {
setCurrentCardIndex(currentCardIndex - 1)
setIsFlipped(false)
setSelectedAnswer(null)
setShowResult(false)
}
}
const handleDifficultyRate = (rating: number) => {
// Mock rating logic
console.log(`Rated ${rating} for ${currentCard.word}`)
handleNext()
}
const handleQuizAnswer = (answer: string) => {
setSelectedAnswer(answer)
setShowResult(true)
if (answer === currentCard.translation) {
setScore({ ...score, correct: score.correct + 1, total: score.total + 1 })
} else {
setScore({ ...score, total: score.total + 1 })
}
}
return (
<div className="min-h-screen bg-gradient-to-br from-blue-50 to-indigo-100">
{/* Navigation */}
<nav className="bg-white shadow-sm border-b">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div className="flex justify-between h-16">
<div className="flex items-center space-x-8">
<Link href="/dashboard" className="text-2xl font-bold text-primary">DramaLing</Link>
<div className="hidden md:flex space-x-6">
<Link href="/dashboard" className="text-gray-600 hover:text-gray-900"></Link>
<Link href="/flashcards" className="text-gray-600 hover:text-gray-900"></Link>
<Link href="/learn" className="text-gray-900 font-medium"></Link>
<Link href="/generate" className="text-gray-600 hover:text-gray-900">AI </Link>
</div>
</div>
<button
onClick={() => window.location.href = '/dashboard'}
className="text-gray-600 hover:text-gray-900"
>
×
</button>
</div>
</div>
</nav>
<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>
<span className="text-sm text-gray-600">
{currentCardIndex + 1} / {cards.length}
</span>
</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">
<button
onClick={() => setMode('flip')}
className={`px-4 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-4 py-2 rounded-md transition-colors ${
mode === 'quiz'
? 'bg-primary text-white'
: 'text-gray-600 hover:text-gray-900'
}`}
>
</button>
</div>
</div>
{mode === 'flip' ? (
/* Flip Card Mode */
<div className="relative">
<div
className="relative w-full h-96 cursor-pointer"
onClick={handleFlip}
style={{ perspective: '1000px' }}
>
<div
className={`absolute w-full h-full transition-transform duration-600 ${
isFlipped ? 'rotate-y-180' : ''
}`}
style={{
transformStyle: 'preserve-3d',
transform: isFlipped ? 'rotateY(180deg)' : 'rotateY(0deg)'
}}
>
{/* Front of card */}
<div
className="absolute w-full h-full bg-white rounded-2xl shadow-xl p-8 flex flex-col items-center justify-center"
style={{ backfaceVisibility: 'hidden' }}
>
<div className="text-4xl font-bold text-gray-900 mb-4">
{currentCard.word}
</div>
<div className="text-lg text-gray-600 mb-2">
{currentCard.partOfSpeech}
</div>
<div className="text-lg text-gray-500">
{currentCard.pronunciation}
</div>
<div className="mt-8 text-sm text-gray-400">
</div>
</div>
{/* Back of card */}
<div
className="absolute w-full h-full bg-white rounded-2xl shadow-xl p-8 overflow-y-auto"
style={{
backfaceVisibility: 'hidden',
transform: 'rotateY(180deg)'
}}
>
<div className="space-y-4">
<div>
<div className="text-sm font-semibold text-gray-700 mb-1"></div>
<div className="text-2xl font-bold text-gray-900">{currentCard.translation}</div>
</div>
<div>
<div className="text-sm font-semibold text-gray-700 mb-1"></div>
<div className="text-gray-600">{currentCard.definition}</div>
</div>
<div>
<div className="text-sm font-semibold text-gray-700 mb-1"></div>
<div className="text-gray-600">{currentCard.example}</div>
<div className="text-gray-500 text-sm mt-1">{currentCard.exampleTranslation}</div>
</div>
<div>
<div className="text-sm font-semibold text-gray-700 mb-1"></div>
<div className="flex flex-wrap gap-2">
{currentCard.synonyms.map((syn, idx) => (
<span key={idx} className="px-3 py-1 bg-gray-100 rounded-full text-sm">
{syn}
</span>
))}
</div>
</div>
</div>
</div>
</div>
</div>
{/* Difficulty Rating */}
{isFlipped && (
<div className="mt-8">
<div className="text-center mb-4">
<span className="text-gray-600"></span>
</div>
<div className="flex justify-center space-x-3">
<button
onClick={() => handleDifficultyRate(1)}
className="px-4 py-2 bg-red-100 text-red-700 rounded-lg hover:bg-red-200 transition-colors"
>
😔
</button>
<button
onClick={() => handleDifficultyRate(3)}
className="px-4 py-2 bg-yellow-100 text-yellow-700 rounded-lg hover:bg-yellow-200 transition-colors"
>
😐
</button>
<button
onClick={() => handleDifficultyRate(5)}
className="px-4 py-2 bg-green-100 text-green-700 rounded-lg hover:bg-green-200 transition-colors"
>
😊
</button>
</div>
</div>
)}
</div>
) : (
/* Quiz Mode */
<div className="bg-white rounded-2xl shadow-xl p-8">
<div className="mb-6">
<div className="text-sm text-gray-600 mb-2"></div>
<div className="text-3xl font-bold text-gray-900">{currentCard.word}</div>
<div className="text-lg text-gray-500 mt-1">{currentCard.pronunciation}</div>
</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'
: showResult && option === selectedAnswer && option !== currentCard.translation
? 'border-red-500 bg-red-50'
: selectedAnswer === option
? 'border-primary bg-primary-light'
: 'border-gray-200 hover:border-gray-300'
}`}
>
<div className="flex items-center justify-between">
<span className="font-medium">{option}</span>
{showResult && option === currentCard.translation && (
<svg className="w-5 h-5 text-green-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M5 13l4 4L19 7" />
</svg>
)}
{showResult && option === selectedAnswer && option !== currentCard.translation && (
<svg className="w-5 h-5 text-red-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
</svg>
)}
</div>
</button>
))}
</div>
{showResult && (
<div className="mt-6 p-4 bg-gray-50 rounded-lg">
<div className="text-sm font-semibold text-gray-700 mb-2"></div>
<div className="text-gray-600">{currentCard.example}</div>
<div className="text-gray-500 text-sm mt-1">{currentCard.exampleTranslation}</div>
</div>
)}
<div className="mt-6 text-center">
<div className="text-sm text-gray-600">
{score.total > 0 ? Math.round((score.correct / score.total) * 100) : 0}%
({score.correct}/{score.total})
</div>
</div>
</div>
)}
{/* Navigation Buttons */}
<div className="flex justify-between mt-8">
<button
onClick={handlePrevious}
disabled={currentCardIndex === 0}
className="flex items-center space-x-2 px-6 py-3 bg-white rounded-lg shadow-sm hover:shadow-md transition-shadow disabled:opacity-50 disabled:cursor-not-allowed"
>
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 19l-7-7 7-7" />
</svg>
<span></span>
</button>
<button
onClick={handleNext}
disabled={currentCardIndex === cards.length - 1}
className="flex items-center space-x-2 px-6 py-3 bg-primary text-white rounded-lg shadow-sm hover:bg-primary-hover transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
>
<span></span>
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5l7 7-7 7" />
</svg>
</button>
</div>
</div>
</div>
)
}