qizz + panel

This commit is contained in:
5t4l1n
2025-07-28 00:15:37 +05:30
parent cbecb72cc9
commit efd6708e5a
43 changed files with 61721 additions and 6158 deletions
+328 -7
View File
@@ -1,11 +1,332 @@
import { QuizRunner } from "@/components/quiz-runner"
'use client'
import React, { useState, useEffect } from 'react'
import { useParams, useRouter } from 'next/navigation'
import { Brain, Clock, CheckCircle, XCircle, Sparkles, AlertCircle } from 'lucide-react'
interface QuizPageProps {
params: {
quizId: string
interface Question {
id: string
question_number: number
question_text: string
options: string[]
correct_answer: string
points: number
ai_prediction?: any
}
interface Quiz {
id: string
title: string
description: string
questions: Question[]
generated_by?: string
total_points: number
}
export default function QuizTaking() {
const params = useParams()
const router = useRouter()
const quizId = params.quizId as string
const [quiz, setQuiz] = useState<Quiz | null>(null)
const [currentQuestion, setCurrentQuestion] = useState(0)
const [answers, setAnswers] = useState<Record<string, string>>({})
const [loading, setLoading] = useState(true)
const [submitting, setSubmitting] = useState(false)
const [results, setResults] = useState<any>(null)
const [showAIHint, setShowAIHint] = useState(false)
const [aiPrediction, setAIPrediction] = useState<any>(null)
const [error, setError] = useState('')
useEffect(() => {
fetchQuiz()
}, [quizId])
const fetchQuiz = async () => {
try {
const response = await fetch(`http://127.0.0.1:5000/api/quizzes/${quizId}`)
const data = await response.json()
if (data.success) {
setQuiz(data.quiz)
} else {
setError(data.error || 'Quiz not found')
}
} catch (err) {
setError('Failed to load quiz')
} finally {
setLoading(false)
}
}
}
export default function QuizPage({ params }: QuizPageProps) {
return <QuizRunner quizId={params.quizId} />
const getAIHint = async () => {
if (!quiz || !quiz.questions[currentQuestion]) return
try {
setShowAIHint(true)
const response = await fetch('http://127.0.0.1:5000/api/quizzes/ai-predict', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
question_text: quiz.questions[currentQuestion].question_text
})
})
const data = await response.json()
if (data.success) {
setAIPrediction(data.prediction)
}
} catch (err) {
console.error('Failed to get AI hint:', err)
}
}
const handleAnswerSelect = (questionId: string, answer: string) => {
setAnswers(prev => ({ ...prev, [questionId]: answer }))
}
const submitQuiz = async () => {
if (!quiz) return
const unanswered = quiz.questions.filter(q => !answers[q.id])
if (unanswered.length > 0) {
if (!confirm(`You have ${unanswered.length} unanswered questions. Submit anyway?`)) {
return
}
}
setSubmitting(true)
try {
const response = await fetch(`http://127.0.0.1:5000/api/quizzes/${quizId}/submit`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
answers,
participant_name: 'User' // You can get this from auth context
})
})
const data = await response.json()
if (data.success) {
setResults(data.results)
} else {
setError(data.error || 'Failed to submit quiz')
}
} catch (err) {
setError('Failed to submit quiz')
} finally {
setSubmitting(false)
}
}
if (loading) {
return (
<div className="min-h-screen bg-gray-900 text-white flex items-center justify-center">
<div className="text-center">
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-purple-600 mx-auto mb-4"></div>
<p>Loading AI Quiz...</p>
</div>
</div>
)
}
if (error) {
return (
<div className="min-h-screen bg-gray-900 text-white flex items-center justify-center">
<div className="text-center">
<AlertCircle className="h-12 w-12 text-red-400 mx-auto mb-4" />
<p className="text-xl mb-4">{error}</p>
<button
onClick={() => router.push('/quizzes')}
className="bg-blue-600 hover:bg-blue-700 px-6 py-2 rounded"
>
Back to Quizzes
</button>
</div>
</div>
)
}
if (results) {
return (
<div className="min-h-screen bg-gray-900 text-white">
<div className="max-w-4xl mx-auto p-6">
<div className="text-center mb-8">
<div className="text-6xl mb-4">
{results.score >= 80 ? '🏆' : results.score >= 60 ? '🎉' : '📚'}
</div>
<h1 className="text-3xl font-bold mb-2">Quiz Complete!</h1>
<p className="text-xl text-gray-300">
You scored {results.score}% ({results.correct_answers}/{results.total_questions})
</p>
</div>
{/* AI Feedback */}
{results.ai_feedback && (
<div className="bg-gray-800 rounded-lg p-6 mb-6">
<h2 className="text-xl font-bold mb-4 flex items-center space-x-2">
<Brain className="h-5 w-5 text-purple-400" />
<span>🤖 AI Feedback</span>
</h2>
<div className="space-y-4">
{results.ai_feedback.map((feedback: any, index: number) => (
<div key={index} className="bg-gray-900 p-4 rounded border-l-4 border-purple-500">
<h3 className="font-semibold mb-2">Question {index + 1}</h3>
<p className="text-sm text-gray-300 mb-2">{feedback.question}</p>
<div className="flex items-center space-x-2 mb-2">
{feedback.is_correct ? (
<CheckCircle className="h-4 w-4 text-green-400" />
) : (
<XCircle className="h-4 w-4 text-red-400" />
)}
<span className="text-sm">
Your answer: {feedback.user_answer}
</span>
</div>
{feedback.ai_feedback && (
<p className="text-sm text-purple-300 bg-purple-900 bg-opacity-30 p-2 rounded">
🤖 {feedback.ai_feedback.feedback}
</p>
)}
</div>
))}
</div>
</div>
)}
<div className="text-center">
<button
onClick={() => router.push('/quizzes')}
className="bg-blue-600 hover:bg-blue-700 px-8 py-3 rounded-lg font-semibold"
>
Back to Quizzes
</button>
</div>
</div>
</div>
)
}
if (!quiz) return null
const question = quiz.questions[currentQuestion]
const progress = ((currentQuestion + 1) / quiz.questions.length) * 100
return (
<div className="min-h-screen bg-gray-900 text-white">
<div className="max-w-4xl mx-auto p-6">
{/* Header */}
<div className="mb-6">
<div className="flex items-center justify-between mb-4">
<h1 className="text-2xl font-bold flex items-center space-x-2">
{quiz.generated_by === 'AI' && <Brain className="h-6 w-6 text-purple-400" />}
<span>{quiz.title}</span>
</h1>
<div className="text-sm text-gray-400">
Question {currentQuestion + 1} of {quiz.questions.length}
</div>
</div>
{/* Progress Bar */}
<div className="w-full bg-gray-700 rounded-full h-2">
<div
className="bg-purple-600 h-2 rounded-full transition-all duration-300"
style={{ width: `${progress}%` }}
></div>
</div>
</div>
{/* Question */}
<div className="bg-gray-800 rounded-lg p-6 mb-6">
<div className="flex justify-between items-start mb-4">
<h2 className="text-xl font-semibold">
{question.question_text}
</h2>
{quiz.generated_by === 'AI' && (
<button
onClick={getAIHint}
className="bg-purple-600 hover:bg-purple-700 px-3 py-1 rounded text-sm flex items-center space-x-1"
>
<Sparkles className="h-4 w-4" />
<span>AI Hint</span>
</button>
)}
</div>
{/* AI Hint */}
{showAIHint && aiPrediction && (
<div className="bg-purple-900 bg-opacity-30 border border-purple-600 p-4 rounded mb-4">
<h3 className="font-semibold mb-2 flex items-center space-x-2">
<Brain className="h-4 w-4" />
<span>🤖 AI Suggestion</span>
</h3>
<p className="text-sm">
AI predicts: <strong>{aiPrediction.predicted_answer}</strong>
</p>
<p className="text-xs text-gray-400 mt-1">
Confidence: {(aiPrediction.confidence * 100).toFixed(1)}%
</p>
</div>
)}
{/* Options */}
<div className="space-y-3">
{question.options.map((option, index) => (
<button
key={index}
onClick={() => handleAnswerSelect(question.id, option)}
className={`w-full p-4 text-left rounded-lg border transition-colors ${
answers[question.id] === option
? 'bg-purple-900 border-purple-500 text-purple-100'
: 'bg-gray-700 border-gray-600 hover:bg-gray-600'
}`}
>
<div className="flex items-center space-x-3">
<span className="w-6 h-6 rounded-full border-2 border-gray-400 flex items-center justify-center text-sm">
{String.fromCharCode(65 + index)}
</span>
<span>{option}</span>
</div>
</button>
))}
</div>
</div>
{/* Navigation */}
<div className="flex justify-between items-center">
<button
onClick={() => setCurrentQuestion(prev => Math.max(0, prev - 1))}
disabled={currentQuestion === 0}
className="bg-gray-700 hover:bg-gray-600 disabled:bg-gray-800 px-6 py-2 rounded"
>
Previous
</button>
<div className="text-sm text-gray-400">
{Object.keys(answers).length} of {quiz.questions.length} answered
</div>
{currentQuestion === quiz.questions.length - 1 ? (
<button
onClick={submitQuiz}
disabled={submitting}
className="bg-green-600 hover:bg-green-700 disabled:bg-gray-600 px-6 py-2 rounded flex items-center space-x-2"
>
{submitting ? (
<div className="animate-spin rounded-full h-4 w-4 border-b-2 border-white"></div>
) : null}
<span>{submitting ? 'Submitting...' : 'Submit Quiz'}</span>
</button>
) : (
<button
onClick={() => setCurrentQuestion(prev => Math.min(quiz.questions.length - 1, prev + 1))}
className="bg-blue-600 hover:bg-blue-700 px-6 py-2 rounded"
>
Next
</button>
)}
</div>
</div>
</div>
)
}
+251
View File
@@ -0,0 +1,251 @@
'use client'
import React, { useState } from 'react'
import { useRouter } from 'next/navigation'
import { Plus, Trash2, Save, ArrowLeft } from 'lucide-react'
interface Question {
question_text: string
options: string[]
correct_answer: string
points: number
}
export default function CreateQuizPage() {
const router = useRouter()
const [quiz, setQuiz] = useState({
title: '',
description: '',
difficulty: 'medium'
})
const [questions, setQuestions] = useState<Question[]>([])
const [currentQuestion, setCurrentQuestion] = useState<Question>({
question_text: '',
options: ['', '', '', ''],
correct_answer: '',
points: 10
})
const [loading, setLoading] = useState(false)
const addQuestion = () => {
if (!currentQuestion.question_text || currentQuestion.options.some(opt => !opt.trim()) || !currentQuestion.correct_answer) {
alert('Please fill all question fields')
return
}
setQuestions([...questions, { ...currentQuestion }])
setCurrentQuestion({
question_text: '',
options: ['', '', '', ''],
correct_answer: '',
points: 10
})
}
const removeQuestion = (index: number) => {
setQuestions(questions.filter((_, i) => i !== index))
}
const createQuiz = async () => {
if (!quiz.title || questions.length === 0) {
alert('Please add a title and at least one question')
return
}
setLoading(true)
try {
const quizData = {
...quiz,
questions: questions.map((q, index) => ({
...q,
id: `q_${index}`,
question_number: index + 1
})),
total_points: questions.reduce((sum, q) => sum + q.points, 0),
created_at: new Date().toISOString(),
generated_by: 'manual'
}
const response = await fetch('http://127.0.0.1:5000/api/quizzes/', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(quizData)
})
const data = await response.json()
if (data.success) {
alert('✅ Quiz created successfully!')
router.push('/quizzes')
} else {
alert(`Error: ${data.error}`)
}
} catch (error) {
alert('Network error: Could not create quiz')
} finally {
setLoading(false)
}
}
return (
<div className="min-h-screen bg-gray-900 text-white">
<div className="max-w-4xl mx-auto p-6">
{/* Header */}
<div className="flex items-center space-x-4 mb-8">
<button
onClick={() => router.push('/quizzes')}
className="bg-gray-700 hover:bg-gray-600 p-2 rounded"
>
<ArrowLeft className="h-5 w-5" />
</button>
<h1 className="text-3xl font-bold">📝 Create New Quiz</h1>
</div>
{/* Quiz Details */}
<div className="bg-gray-800 p-6 rounded-lg mb-6">
<h2 className="text-xl font-bold mb-4">Quiz Information</h2>
<div className="space-y-4">
<input
type="text"
placeholder="Quiz title"
value={quiz.title}
onChange={(e) => setQuiz(prev => ({...prev, title: e.target.value}))}
className="w-full p-3 bg-gray-700 rounded border border-gray-600 focus:ring-2 focus:ring-blue-500 focus:outline-none"
/>
<textarea
placeholder="Quiz description"
value={quiz.description}
onChange={(e) => setQuiz(prev => ({...prev, description: e.target.value}))}
className="w-full p-3 bg-gray-700 rounded border border-gray-600 focus:ring-2 focus:ring-blue-500 focus:outline-none resize-none"
rows={3}
/>
<select
value={quiz.difficulty}
onChange={(e) => setQuiz(prev => ({...prev, difficulty: e.target.value}))}
className="w-full p-3 bg-gray-700 rounded border border-gray-600 focus:ring-2 focus:ring-blue-500 focus:outline-none"
>
<option value="easy">🟢 Easy</option>
<option value="medium">🟡 Medium</option>
<option value="hard">🔴 Hard</option>
</select>
</div>
</div>
{/* Add Question */}
<div className="bg-gray-800 p-6 rounded-lg mb-6">
<h2 className="text-xl font-bold mb-4">Add Question</h2>
<div className="space-y-4">
<textarea
placeholder="Question text"
value={currentQuestion.question_text}
onChange={(e) => setCurrentQuestion(prev => ({...prev, question_text: e.target.value}))}
className="w-full p-3 bg-gray-700 rounded border border-gray-600 focus:ring-2 focus:ring-blue-500 focus:outline-none resize-none"
rows={3}
/>
<div className="space-y-2">
<label className="text-sm font-medium">Options:</label>
{currentQuestion.options.map((option, index) => (
<input
key={index}
type="text"
placeholder={`Option ${String.fromCharCode(65 + index)}`}
value={option}
onChange={(e) => {
const newOptions = [...currentQuestion.options]
newOptions[index] = e.target.value
setCurrentQuestion(prev => ({...prev, options: newOptions}))
}}
className="w-full p-3 bg-gray-700 rounded border border-gray-600 focus:ring-2 focus:ring-blue-500 focus:outline-none"
/>
))}
</div>
<div className="grid grid-cols-2 gap-4">
<input
type="text"
placeholder="Correct answer"
value={currentQuestion.correct_answer}
onChange={(e) => setCurrentQuestion(prev => ({...prev, correct_answer: e.target.value}))}
className="p-3 bg-gray-700 rounded border border-gray-600 focus:ring-2 focus:ring-blue-500 focus:outline-none"
/>
<input
type="number"
placeholder="Points"
value={currentQuestion.points}
onChange={(e) => setCurrentQuestion(prev => ({...prev, points: parseInt(e.target.value) || 10}))}
className="p-3 bg-gray-700 rounded border border-gray-600 focus:ring-2 focus:ring-blue-500 focus:outline-none"
min="1"
/>
</div>
<button
onClick={addQuestion}
className="bg-green-600 hover:bg-green-700 px-6 py-2 rounded font-semibold flex items-center space-x-2"
>
<Plus className="h-4 w-4" />
<span>Add Question</span>
</button>
</div>
</div>
{/* Questions List */}
{questions.length > 0 && (
<div className="bg-gray-800 p-6 rounded-lg mb-6">
<h2 className="text-xl font-bold mb-4">Questions ({questions.length})</h2>
<div className="space-y-4">
{questions.map((question, index) => (
<div key={index} className="bg-gray-700 p-4 rounded-lg">
<div className="flex justify-between items-start">
<div className="flex-1">
<h3 className="font-semibold mb-2">Q{index + 1}: {question.question_text}</h3>
<div className="grid grid-cols-2 gap-2 text-sm text-gray-400 mb-2">
{question.options.map((option, optIndex) => (
<span key={optIndex} className={`${option === question.correct_answer ? 'text-green-400 font-semibold' : ''}`}>
{String.fromCharCode(65 + optIndex)}) {option}
</span>
))}
</div>
<div className="text-xs text-gray-500">
Points: {question.points} | Correct: {question.correct_answer}
</div>
</div>
<button
onClick={() => removeQuestion(index)}
className="text-red-400 hover:text-red-300 ml-4"
>
<Trash2 className="h-4 w-4" />
</button>
</div>
</div>
))}
</div>
</div>
)}
{/* Create Button */}
<div className="text-center">
<button
onClick={createQuiz}
disabled={loading || !quiz.title || questions.length === 0}
className="bg-blue-600 hover:bg-blue-700 disabled:bg-gray-600 px-8 py-3 rounded-lg font-semibold flex items-center space-x-2 mx-auto"
>
{loading ? (
<div className="animate-spin rounded-full h-5 w-5 border-b-2 border-white"></div>
) : (
<>
<Save className="h-5 w-5" />
<span>Create Quiz</span>
</>
)}
</button>
</div>
</div>
</div>
)
}
+180
View File
@@ -0,0 +1,180 @@
'use client'
import React, { useState } from 'react'
import { useRouter } from 'next/navigation'
import { Brain, Sparkles, Settings, Clock, Trophy, AlertCircle } from 'lucide-react'
export default function AIQuizGenerator() {
const [loading, setLoading] = useState(false)
const [formData, setFormData] = useState({
topic: '',
difficulty: 'medium',
num_questions: 5
})
const [generatedQuiz, setGeneratedQuiz] = useState(null)
const [error, setError] = useState('')
const router = useRouter()
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault()
setLoading(true)
setError('')
try {
const response = await fetch('http://127.0.0.1:5000/api/quizzes/generate-ai', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(formData)
})
const data = await response.json()
if (data.success) {
setGeneratedQuiz(data.quiz)
// Redirect to the generated quiz
router.push(`/quizzes/${data.quiz.id}`)
} else {
setError(data.error || 'Failed to generate quiz')
}
} catch (err) {
setError('Network error: Could not generate quiz')
} finally {
setLoading(false)
}
}
return (
<div className="min-h-screen bg-gray-900 text-white">
<div className="max-w-4xl mx-auto p-6">
{/* Header */}
<div className="text-center mb-8">
<div className="flex items-center justify-center space-x-3 mb-4">
<Brain className="h-12 w-12 text-purple-400" />
<Sparkles className="h-8 w-8 text-yellow-400" />
</div>
<h1 className="text-3xl font-bold mb-2">🤖 AI Quiz Generator</h1>
<p className="text-gray-400">
Generate intelligent quizzes using our trained CNN model
</p>
</div>
{/* Error Display */}
{error && (
<div className="bg-red-900 border border-red-600 p-4 rounded-lg mb-6 flex items-center space-x-2">
<AlertCircle className="h-5 w-5 text-red-400" />
<span>{error}</span>
</div>
)}
{/* Generator Form */}
<div className="bg-gray-800 rounded-lg p-6 mb-6">
<h2 className="text-xl font-bold mb-4 flex items-center space-x-2">
<Settings className="h-5 w-5 text-blue-400" />
<span>Quiz Configuration</span>
</h2>
<form onSubmit={handleSubmit} className="space-y-6">
{/* Topic Input */}
<div>
<label className="block text-sm font-medium mb-2">
Topic/Subject
</label>
<input
type="text"
value={formData.topic}
onChange={(e) => setFormData(prev => ({...prev, topic: e.target.value}))}
placeholder="e.g., Science, History, Technology"
className="w-full p-3 bg-gray-700 rounded border border-gray-600 focus:ring-2 focus:ring-purple-500 focus:outline-none"
required
/>
<p className="text-sm text-gray-400 mt-1">
AI will generate questions related to this topic
</p>
</div>
{/* Difficulty Selection */}
<div>
<label className="block text-sm font-medium mb-2">
Difficulty Level
</label>
<select
value={formData.difficulty}
onChange={(e) => setFormData(prev => ({...prev, difficulty: e.target.value}))}
className="w-full p-3 bg-gray-700 rounded border border-gray-600 focus:ring-2 focus:ring-purple-500 focus:outline-none"
>
<option value="easy">🟢 Easy</option>
<option value="medium">🟡 Medium</option>
<option value="hard">🔴 Hard</option>
</select>
</div>
{/* Number of Questions */}
<div>
<label className="block text-sm font-medium mb-2">
Number of Questions
</label>
<div className="flex items-center space-x-4">
<input
type="range"
min="3"
max="20"
value={formData.num_questions}
onChange={(e) => setFormData(prev => ({...prev, num_questions: parseInt(e.target.value)}))}
className="flex-1"
/>
<span className="bg-gray-700 px-3 py-1 rounded font-bold">
{formData.num_questions}
</span>
</div>
</div>
{/* Generate Button */}
<button
type="submit"
disabled={loading}
className="w-full bg-gradient-to-r from-purple-600 to-blue-600 hover:from-purple-700 hover:to-blue-700 disabled:from-gray-600 disabled:to-gray-600 p-4 rounded-lg font-semibold flex items-center justify-center space-x-2 transition-colors"
>
{loading ? (
<>
<div className="animate-spin rounded-full h-5 w-5 border-b-2 border-white"></div>
<span>Generating Quiz...</span>
</>
) : (
<>
<Brain className="h-5 w-5" />
<span>🚀 Generate AI Quiz</span>
</>
)}
</button>
</form>
</div>
{/* Features */}
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
<div className="bg-gray-800 p-4 rounded-lg text-center">
<Brain className="h-8 w-8 text-purple-400 mx-auto mb-2" />
<h3 className="font-semibold mb-1">AI-Powered</h3>
<p className="text-sm text-gray-400">
Uses trained CNN model for intelligent question selection
</p>
</div>
<div className="bg-gray-800 p-4 rounded-lg text-center">
<Clock className="h-8 w-8 text-blue-400 mx-auto mb-2" />
<h3 className="font-semibold mb-1">Instant Generation</h3>
<p className="text-sm text-gray-400">
Generate quizzes in seconds with AI processing
</p>
</div>
<div className="bg-gray-800 p-4 rounded-lg text-center">
<Trophy className="h-8 w-8 text-yellow-400 mx-auto mb-2" />
<h3 className="font-semibold mb-1">Smart Feedback</h3>
<p className="text-sm text-gray-400">
AI provides intelligent feedback on answers
</p>
</div>
</div>
</div>
</div>
)
}
+433 -2
View File
@@ -1,5 +1,436 @@
import { QuizList } from "@/components/quiz-list"
'use client'
import React, { useState, useEffect } from 'react'
import { useRouter } from 'next/navigation'
import { Brain, Plus, Clock, Trophy, Users, Sparkles, Crown, Target, Play, Globe, Lock } from 'lucide-react'
interface Quiz {
_id: string
id: string
title: string
description: string
difficulty: string
questions: any[]
generated_by?: string
created_at: string
total_points: number
}
interface QuizRoom {
room_id: string
room_code: string
title: string
host_name: string
is_private: boolean
status: string
participants_count: number
questions_count: number
questions_by_difficulty: {
easy: number
medium: number
hard: number
}
}
export default function QuizzesPage() {
return <QuizList />
const [activeTab, setActiveTab] = useState<'traditional' | 'rooms' | 'adaptive'>('rooms')
const [quizzes, setQuizzes] = useState<Quiz[]>([])
const [publicRooms, setPublicRooms] = useState<QuizRoom[]>([])
const [loading, setLoading] = useState(true)
const [aiAvailable, setAiAvailable] = useState(false)
const router = useRouter()
useEffect(() => {
if (activeTab === 'traditional') {
fetchTraditionalQuizzes()
} else if (activeTab === 'rooms') {
fetchPublicRooms()
}
}, [activeTab])
const fetchTraditionalQuizzes = async () => {
setLoading(true)
try {
const response = await fetch('http://127.0.0.1:5000/api/quizzes')
const data = await response.json()
if (data.success) {
setQuizzes(data.quizzes)
setAiAvailable(data.ai_available)
}
} catch (err) {
console.error('Failed to fetch quizzes:', err)
} finally {
setLoading(false)
}
}
const fetchPublicRooms = async () => {
setLoading(true)
try {
const response = await fetch('http://127.0.0.1:5000/api/quizzes/public-rooms')
const data = await response.json()
if (data.success) {
setPublicRooms(data.public_rooms)
}
} catch (err) {
console.error('Failed to fetch public rooms:', err)
} finally {
setLoading(false)
}
}
const getDifficultyColor = (difficulty: string) => {
switch (difficulty.toLowerCase()) {
case 'easy': return 'text-green-400 bg-green-900'
case 'medium': return 'text-yellow-400 bg-yellow-900'
case 'hard': return 'text-red-400 bg-red-900'
default: return 'text-gray-400 bg-gray-700'
}
}
const getStatusColor = (status: string) => {
switch (status) {
case 'waiting': return 'text-yellow-400 bg-yellow-900'
case 'active': return 'text-green-400 bg-green-900'
case 'completed': return 'text-gray-400 bg-gray-700'
default: return 'text-gray-400 bg-gray-700'
}
}
if (loading && activeTab === 'traditional' && quizzes.length === 0) {
return (
<div className="min-h-screen bg-gray-900 text-white flex items-center justify-center">
<div className="text-center">
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-purple-600 mx-auto mb-4"></div>
<p>Loading quizzes...</p>
</div>
</div>
)
}
return (
<div className="min-h-screen bg-gray-900 text-white">
<div className="max-w-7xl mx-auto p-6">
{/* Header */}
<div className="text-center mb-8">
<h1 className="text-4xl font-bold mb-4 flex items-center justify-center space-x-3">
<Trophy className="h-10 w-10 text-yellow-400" />
<span>🧠 OpenLearnX Quiz Platform</span>
</h1>
<p className="text-gray-400 max-w-2xl mx-auto">
Experience adaptive quizzes with AI-powered questions and real-time difficulty adjustment
</p>
</div>
{/* Tab Navigation */}
<div className="flex justify-center space-x-1 mb-8">
{[
{ id: 'rooms', label: 'Live Quiz Rooms', icon: Users, description: 'Join or host live quizzes' },
{ id: 'adaptive', label: 'Adaptive Quiz', icon: Brain, description: 'AI-powered adaptive difficulty' },
{ id: 'traditional', label: 'Traditional Quizzes', icon: Target, description: 'Fixed question sets' }
].map(tab => (
<button
key={tab.id}
onClick={() => setActiveTab(tab.id as any)}
className={`px-6 py-3 rounded-lg flex items-center space-x-2 transition-colors ${
activeTab === tab.id
? 'bg-blue-600 text-white'
: 'bg-gray-700 text-gray-300 hover:bg-gray-600'
}`}
>
<tab.icon className="h-5 w-5" />
<div className="text-left">
<div className="font-semibold">{tab.label}</div>
<div className="text-xs opacity-75">{tab.description}</div>
</div>
</button>
))}
</div>
{/* Live Quiz Rooms Tab */}
{activeTab === 'rooms' && (
<div>
{/* Action Buttons */}
<div className="flex flex-col sm:flex-row gap-4 mb-8 justify-center items-center">
<button
onClick={() => router.push('/quiz-host')}
className="bg-gradient-to-r from-purple-600 to-blue-600 hover:from-purple-700 hover:to-blue-700 px-6 py-3 rounded-lg font-semibold flex items-center space-x-2 transition-colors"
>
<Crown className="h-5 w-5" />
<span>👑 Host a Quiz</span>
</button>
<button
onClick={() => router.push('/quiz-join')}
className="bg-green-600 hover:bg-green-700 px-6 py-3 rounded-lg font-semibold flex items-center space-x-2"
>
<Users className="h-5 w-5" />
<span>🎯 Join Quiz</span>
</button>
</div>
{/* Public Rooms Grid */}
<div className="mb-6">
<div className="flex justify-between items-center mb-4">
<h2 className="text-2xl font-bold flex items-center space-x-2">
<Globe className="h-6 w-6 text-green-400" />
<span>🌍 Public Quiz Rooms</span>
</h2>
<button
onClick={fetchPublicRooms}
className="bg-blue-600 hover:bg-blue-700 px-4 py-2 rounded flex items-center space-x-2"
>
<span>🔄 Refresh</span>
</button>
</div>
{loading ? (
<div className="text-center py-8">
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-blue-600 mx-auto mb-4"></div>
<p>Loading rooms...</p>
</div>
) : publicRooms.length === 0 ? (
<div className="text-center py-12 bg-gray-800 rounded-lg">
<Globe className="h-16 w-16 text-gray-600 mx-auto mb-4" />
<h3 className="text-xl font-semibold mb-2">No Public Rooms Available</h3>
<p className="text-gray-400 mb-6">
Be the first to create a public quiz room!
</p>
<button
onClick={() => router.push('/quiz-host')}
className="bg-purple-600 hover:bg-purple-700 px-6 py-3 rounded-lg font-semibold"
>
🚀 Create Room
</button>
</div>
) : (
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
{publicRooms.map((room) => (
<div
key={room.room_id}
className="bg-gray-800 rounded-lg p-6 hover:bg-gray-750 transition-colors border border-gray-700"
>
{/* Room Header */}
<div className="flex items-start justify-between mb-4">
<div className="flex-1">
<h3 className="text-lg font-semibold flex items-center space-x-2">
<Globe className="h-5 w-5 text-green-400" />
<span>{room.title}</span>
</h3>
<p className="text-gray-400 text-sm">Host: {room.host_name}</p>
</div>
<span className={`px-2 py-1 rounded text-xs font-medium ${getStatusColor(room.status)}`}>
{room.status}
</span>
</div>
{/* Room Stats */}
<div className="grid grid-cols-2 gap-4 mb-4 text-sm">
<div className="bg-gray-700 p-3 rounded text-center">
<div className="font-bold text-blue-400">{room.participants_count}</div>
<div className="text-gray-400">Participants</div>
</div>
<div className="bg-gray-700 p-3 rounded text-center">
<div className="font-bold text-purple-400">{room.questions_count}</div>
<div className="text-gray-400">Questions</div>
</div>
</div>
{/* Difficulty Breakdown */}
<div className="flex justify-between text-xs mb-4">
<span className="text-green-400">Easy: {room.questions_by_difficulty?.easy || 0}</span>
<span className="text-yellow-400">Medium: {room.questions_by_difficulty?.medium || 0}</span>
<span className="text-red-400">Hard: {room.questions_by_difficulty?.hard || 0}</span>
</div>
{/* Room Code */}
<div className="text-center mb-4">
<span className="bg-gray-700 px-3 py-1 rounded font-mono text-blue-400">
Code: {room.room_code}
</span>
</div>
{/* Join Button */}
<button
onClick={() => router.push(`/quiz-join?room=${room.room_code}`)}
className="w-full bg-green-600 hover:bg-green-700 p-3 rounded font-semibold flex items-center justify-center space-x-2"
>
<Play className="h-4 w-4" />
<span>Join Room</span>
</button>
</div>
))}
</div>
)}
</div>
</div>
)}
{/* Adaptive Quiz Tab */}
{activeTab === 'adaptive' && (
<div className="text-center">
<div className="max-w-2xl mx-auto mb-8">
<Brain className="h-16 w-16 text-purple-400 mx-auto mb-4" />
<h2 className="text-3xl font-bold mb-4">🧠 Adaptive AI Quiz</h2>
<p className="text-gray-400 mb-6">
Experience an intelligent quiz that adapts to your skill level in real-time using our trained CNN model.
</p>
<div className="grid grid-cols-1 md:grid-cols-3 gap-6 mb-8">
<div className="bg-gray-800 p-4 rounded-lg">
<Target className="h-8 w-8 text-blue-400 mx-auto mb-2" />
<h3 className="font-semibold mb-1">Adaptive Difficulty</h3>
<p className="text-sm text-gray-400">
Questions adjust based on your performance
</p>
</div>
<div className="bg-gray-800 p-4 rounded-lg">
<Brain className="h-8 w-8 text-purple-400 mx-auto mb-2" />
<h3 className="font-semibold mb-1">AI Predictions</h3>
<p className="text-sm text-gray-400">
See how our AI model would answer
</p>
</div>
<div className="bg-gray-800 p-4 rounded-lg">
<Sparkles className="h-8 w-8 text-green-400 mx-auto mb-2" />
<h3 className="font-semibold mb-1">Smart Analytics</h3>
<p className="text-sm text-gray-400">
Track performance across difficulty levels
</p>
</div>
</div>
<button
onClick={() => router.push('/adaptive-quiz')}
className="bg-gradient-to-r from-purple-600 to-blue-600 hover:from-purple-700 hover:to-blue-700 px-8 py-4 rounded-lg font-semibold flex items-center justify-center space-x-2 mx-auto"
>
<Sparkles className="h-5 w-5" />
<span>🚀 Start Adaptive Quiz</span>
</button>
</div>
</div>
)}
{/* Traditional Quizzes Tab */}
{activeTab === 'traditional' && (
<div>
{/* AI Status & Create Buttons */}
<div className="flex flex-col sm:flex-row gap-4 mb-8 justify-center items-center">
{aiAvailable && (
<button
onClick={() => router.push('/quizzes/generate')}
className="bg-gradient-to-r from-purple-600 to-blue-600 hover:from-purple-700 hover:to-blue-700 px-6 py-3 rounded-lg font-semibold flex items-center space-x-2 transition-colors"
>
<Brain className="h-5 w-5" />
<Sparkles className="h-4 w-4" />
<span>🚀 Generate AI Quiz</span>
</button>
)}
<button
onClick={() => router.push('/quizzes/create')}
className="bg-green-600 hover:bg-green-700 px-6 py-3 rounded-lg font-semibold flex items-center space-x-2"
>
<Plus className="h-5 w-5" />
<span>Create Manual Quiz</span>
</button>
</div>
{/* AI Status Banner */}
{aiAvailable && (
<div className="bg-gradient-to-r from-purple-900 to-blue-900 border border-purple-600 p-4 rounded-lg mb-8">
<div className="flex items-center space-x-3">
<Brain className="h-6 w-6 text-purple-400" />
<div>
<h3 className="font-semibold">🤖 AI Service Active</h3>
<p className="text-sm text-gray-300">
Our trained CNN model is ready to generate intelligent quizzes and provide feedback
</p>
</div>
</div>
</div>
)}
{/* Traditional Quizzes Grid */}
{quizzes.length === 0 ? (
<div className="text-center py-12">
<Brain className="h-16 w-16 text-gray-600 mx-auto mb-4" />
<h3 className="text-xl font-semibold mb-2">No Traditional Quizzes Yet</h3>
<p className="text-gray-400 mb-6">
Create your first quiz or generate one using AI
</p>
{aiAvailable && (
<button
onClick={() => router.push('/quizzes/generate')}
className="bg-purple-600 hover:bg-purple-700 px-6 py-3 rounded-lg font-semibold"
>
🚀 Generate AI Quiz
</button>
)}
</div>
) : (
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
{quizzes.map((quiz) => (
<div
key={quiz._id}
className="bg-gray-800 rounded-lg p-6 hover:bg-gray-750 transition-colors cursor-pointer"
onClick={() => router.push(`/quizzes/${quiz.id}`)}
>
{/* Quiz Header */}
<div className="flex items-start justify-between mb-4">
<h3 className="text-lg font-semibold flex items-center space-x-2">
{quiz.generated_by === 'AI' && (
<Brain className="h-5 w-5 text-purple-400" />
)}
<span>{quiz.title}</span>
</h3>
<span className={`px-2 py-1 rounded text-xs font-medium ${getDifficultyColor(quiz.difficulty)}`}>
{quiz.difficulty}
</span>
</div>
{/* Description */}
<p className="text-gray-400 text-sm mb-4 line-clamp-2">
{quiz.description}
</p>
{/* Stats */}
<div className="flex items-center justify-between text-sm text-gray-500">
<div className="flex items-center space-x-4">
<span className="flex items-center space-x-1">
<Users className="h-4 w-4" />
<span>{quiz.questions?.length || 0} questions</span>
</span>
<span className="flex items-center space-x-1">
<Trophy className="h-4 w-4" />
<span>{quiz.total_points} pts</span>
</span>
</div>
{quiz.generated_by === 'AI' && (
<div className="flex items-center space-x-1 text-purple-400">
<Sparkles className="h-3 w-3" />
<span className="text-xs">AI Generated</span>
</div>
)}
</div>
{/* Date */}
<div className="mt-3 pt-3 border-t border-gray-700">
<span className="text-xs text-gray-500">
Created {new Date(quiz.created_at).toLocaleDateString()}
</span>
</div>
</div>
))}
</div>
)}
</div>
)}
</div>
</div>
)
}