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

257 lines
12 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 Link from 'next/link'
import { useState } from 'react'
import { ProtectedRoute } from '@/components/shared/ProtectedRoute'
import { Navigation } from '@/components/shared/Navigation'
import { useAuth } from '@/contexts/AuthContext'
function DashboardContent() {
const [activeTab, setActiveTab] = useState('overview')
const { user, logout } = useAuth()
// Mock data
const stats = {
totalWords: 234,
wordsToday: 12,
streak: 7,
accuracy: 85,
todayReview: 23,
completedToday: 15
}
const recentWords = [
{ id: 1, word: 'negotiate', translation: '協商', status: 'learned' },
{ id: 2, word: 'accomplish', translation: '完成', status: 'learning' },
{ id: 3, word: 'perspective', translation: '觀點', status: 'new' },
{ id: 4, word: 'substantial', translation: '大量的', status: 'learned' },
]
const cardSets = [
{ id: 1, name: '美劇經典台詞', count: 45, progress: 60 },
{ id: 2, name: '商務英文必備', count: 30, progress: 30 },
{ id: 3, name: '日常對話', count: 25, progress: 80 },
]
return (
<div className="min-h-screen bg-gray-50">
{/* Navigation */}
<Navigation />
{/* Main Content */}
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
{/* Welcome Section */}
<div className="bg-white rounded-xl shadow-sm p-6 mb-6">
<h2 className="text-2xl font-bold text-gray-900 mb-2">{user?.displayName || user?.username}! 🌟</h2>
<p className="text-gray-600"> {stats.todayReview} </p>
<div className="mt-4 flex gap-3">
<Link
href="/review-simple"
className="bg-primary text-white px-6 py-2 rounded-lg font-medium hover:bg-primary-hover transition-colors"
>
</Link>
<Link
href="/generate"
className="border border-primary text-primary px-6 py-2 rounded-lg font-medium hover:bg-primary-light transition-colors"
>
AI
</Link>
</div>
</div>
{/* Stats Grid */}
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4 mb-8">
<div className="bg-white rounded-xl shadow-sm p-6">
<div className="flex items-center justify-between mb-2">
<span className="text-gray-600 text-sm"></span>
<svg className="w-5 h-5 text-blue-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 6.253v13m0-13C10.832 5.477 9.246 5 7.5 5S4.168 5.477 3 6.253v13C4.168 18.477 5.754 18 7.5 18s3.332.477 4.5 1.253m0-13C13.168 5.477 14.754 5 16.5 5c1.747 0 3.332.477 4.5 1.253v13C19.832 18.477 18.247 18 16.5 18c-1.746 0-3.332.477-4.5 1.253" />
</svg>
</div>
<div className="text-3xl font-bold text-gray-900">{stats.totalWords}</div>
<div className="text-sm text-green-600 mt-1">+{stats.wordsToday} </div>
</div>
<div className="bg-white rounded-xl shadow-sm p-6">
<div className="flex items-center justify-between mb-2">
<span className="text-gray-600 text-sm"></span>
<svg className="w-5 h-5 text-orange-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M17.657 18.657A8 8 0 016.343 7.343S7 9 9 10c0-2 .5-5 2.986-7C14 5 16.09 5.777 17.656 7.343A7.975 7.975 0 0120 13a7.975 7.975 0 01-2.343 5.657z" />
</svg>
</div>
<div className="text-3xl font-bold text-gray-900">{stats.streak} </div>
<div className="text-sm text-orange-600 mt-1">🔥 </div>
</div>
<div className="bg-white rounded-xl shadow-sm p-6">
<div className="flex items-center justify-between mb-2">
<span className="text-gray-600 text-sm"></span>
<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="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
</div>
<div className="text-3xl font-bold text-gray-900">{stats.accuracy}%</div>
<div className="text-sm text-gray-600 mt-1"> 82%</div>
</div>
<div className="bg-white rounded-xl shadow-sm p-6">
<div className="flex items-center justify-between mb-2">
<span className="text-gray-600 text-sm"></span>
<svg className="w-5 h-5 text-purple-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M13 7h8m0 0v8m0-8l-8 8-4-4-6 6" />
</svg>
</div>
<div className="text-3xl font-bold text-gray-900">{stats.completedToday}/{stats.todayReview}</div>
<div className="w-full bg-gray-200 rounded-full h-2 mt-2">
<div
className="bg-purple-500 h-2 rounded-full"
style={{ width: `${(stats.completedToday / stats.todayReview) * 100}%` }}
></div>
</div>
</div>
</div>
{/* Content Tabs */}
<div className="bg-white rounded-xl shadow-sm">
<div className="border-b border-gray-200">
<nav className="-mb-px flex space-x-8 px-6" aria-label="Tabs">
<button
onClick={() => setActiveTab('overview')}
className={`py-4 px-1 border-b-2 font-medium text-sm ${
activeTab === 'overview'
? 'border-primary text-primary'
: 'border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300'
}`}
>
</button>
<button
onClick={() => setActiveTab('sets')}
className={`py-4 px-1 border-b-2 font-medium text-sm ${
activeTab === 'sets'
? 'border-primary text-primary'
: 'border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300'
}`}
>
</button>
<button
onClick={() => setActiveTab('progress')}
className={`py-4 px-1 border-b-2 font-medium text-sm ${
activeTab === 'progress'
? 'border-primary text-primary'
: 'border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300'
}`}
>
</button>
</nav>
</div>
<div className="p-6">
{activeTab === 'overview' && (
<div>
<h3 className="text-lg font-semibold mb-4"></h3>
<div className="space-y-3">
{recentWords.map(word => (
<div key={word.id} className="flex items-center justify-between p-4 border rounded-lg hover:bg-gray-50">
<div className="flex items-center space-x-4">
<div className="w-2 h-2 rounded-full bg-green-500"></div>
<div>
<div className="font-semibold">{word.word}</div>
<div className="text-sm text-gray-600">{word.translation}</div>
</div>
</div>
<span className={`px-3 py-1 rounded-full text-xs font-medium ${
word.status === 'learned' ? 'bg-green-100 text-green-800' :
word.status === 'learning' ? 'bg-yellow-100 text-yellow-800' :
'bg-gray-100 text-gray-800'
}`}>
{word.status === 'learned' ? '已掌握' :
word.status === 'learning' ? '學習中' : '新詞'}
</span>
</div>
))}
</div>
</div>
)}
{activeTab === 'sets' && (
<div>
<h3 className="text-lg font-semibold mb-4"></h3>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
{cardSets.map(set => (
<div key={set.id} className="border rounded-lg p-4 hover:shadow-md transition-shadow">
<h4 className="font-semibold mb-2">{set.name}</h4>
<p className="text-sm text-gray-600 mb-3">{set.count} </p>
<div className="w-full bg-gray-200 rounded-full h-2 mb-3">
<div
className="bg-primary h-2 rounded-full"
style={{ width: `${set.progress}%` }}
></div>
</div>
<Link
href={`/flashcards/${set.id}`}
className="text-primary text-sm font-medium hover:text-primary-hover"
>
</Link>
</div>
))}
</div>
</div>
)}
{activeTab === 'progress' && (
<div>
<h3 className="text-lg font-semibold mb-4"></h3>
<div className="bg-gray-50 rounded-lg p-6">
<div className="grid grid-cols-2 md:grid-cols-4 gap-6">
<div>
<div className="text-sm text-gray-600"></div>
<div className="text-2xl font-bold mt-1">89 </div>
</div>
<div>
<div className="text-sm text-gray-600"></div>
<div className="text-2xl font-bold mt-1">312 </div>
</div>
<div>
<div className="text-sm text-gray-600"></div>
<div className="text-2xl font-bold mt-1">15 </div>
</div>
<div>
<div className="text-sm text-gray-600"></div>
<div className="text-2xl font-bold mt-1">32 </div>
</div>
</div>
<div className="mt-6 pt-6 border-t">
<div className="text-sm text-gray-600 mb-2">7</div>
<div className="flex items-end space-x-2 h-20">
{[15, 20, 18, 25, 22, 30, 12].map((value, index) => (
<div key={index} className="flex-1">
<div
className="bg-primary rounded-t"
style={{ height: `${(value / 30) * 100}%` }}
></div>
</div>
))}
</div>
</div>
</div>
</div>
)}
</div>
</div>
</div>
</div>
)
}
export default function DashboardPage() {
return (
<ProtectedRoute>
<DashboardContent />
</ProtectedRoute>
)
}