project-standalo-todo-super/app/quiz/[id]/page.tsx

242 lines
7.9 KiB
TypeScript

"use client";
import { useEffect, useState } from "react";
import { useRouter, useParams } from "next/navigation";
import DarkThemeLayout from "../../components/DarkThemeLayout";
import Navbar from "../../components/Navbar";
import QuizQuestion from "../../components/QuizQuestion";
interface User {
username: string;
points: number;
}
interface Quiz {
id: string;
title: string;
description: string;
questions: Array<{
id: string;
text: string;
options: string[];
correctAnswer: number;
}>;
pointsPerQuestion: number;
}
export default function QuizPage() {
const router = useRouter();
const params = useParams();
const quizId = params.id as string;
const [user, setUser] = useState<User | null>(null);
const [quiz, setQuiz] = useState<Quiz | null>(null);
const [loading, setLoading] = useState(true);
const [currentQuestion, setCurrentQuestion] = useState(0);
const [answers, setAnswers] = useState<Array<{ selected: number; correct: boolean }>>([]);
const [completed, setCompleted] = useState(false);
const [totalPoints, setTotalPoints] = useState(0);
useEffect(() => {
const token = localStorage.getItem("token");
if (!token) {
router.push("/login");
return;
}
fetchData();
}, [router, quizId]);
const fetchData = async () => {
try {
const token = localStorage.getItem("token");
const headers = { Authorization: `Bearer ${token}` };
const [userRes, quizRes] = await Promise.all([
fetch("/api/users/me", { headers }),
fetch(`/api/quizzes/${quizId}`, { headers })
]);
if (!userRes.ok || !quizRes.ok) {
throw new Error("Failed to fetch data");
}
const userData = await userRes.json();
const quizData = await quizRes.json();
if (userData.success && userData.data) {
setUser({
username: userData.data.name,
points: userData.data.pointsBalance || 0,
});
}
if (quizData.success && quizData.data) {
setQuiz(quizData.data);
}
} catch (error) {
console.error("Error fetching data:", error);
router.push("/tasks");
} finally {
setLoading(false);
}
};
const handleAnswer = (selectedIndex: number, isCorrect: boolean) => {
setAnswers([...answers, { selected: selectedIndex, correct: isCorrect }]);
if (isCorrect && quiz) {
setTotalPoints(totalPoints + quiz.pointsPerQuestion);
}
};
const handleNext = () => {
if (quiz && currentQuestion < quiz.questions.length - 1) {
setCurrentQuestion(currentQuestion + 1);
} else {
handleComplete();
}
};
const handleComplete = async () => {
setCompleted(true);
try {
await fetch(`/api/quizzes/${quizId}/complete`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
answers,
points: totalPoints
})
});
if (user) {
setUser({ ...user, points: user.points + totalPoints });
}
} catch (error) {
console.error("Error completing quiz:", error);
}
};
if (loading) {
return (
<DarkThemeLayout>
<div className="flex items-center justify-center min-h-screen">
<div className="animate-spin rounded-full h-16 w-16 border-b-2 border-yellow-500"></div>
</div>
</DarkThemeLayout>
);
}
if (!user || !quiz) {
return null;
}
const correctAnswers = answers.filter(a => a.correct).length;
const totalQuestions = quiz.questions.length;
const progressPercent = ((currentQuestion + (completed ? 1 : 0)) / totalQuestions) * 100;
if (completed) {
return (
<>
<Navbar user={user} />
<DarkThemeLayout>
<div className="max-w-3xl mx-auto space-y-8">
<div className="text-center">
<div className="inline-flex items-center justify-center w-24 h-24 rounded-full bg-gradient-to-br from-yellow-400 to-orange-500 mb-6 shadow-2xl shadow-yellow-500/50">
<svg className="w-12 h-12 text-white" fill="currentColor" viewBox="0 0 20 20">
<path fillRule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clipRule="evenodd" />
</svg>
</div>
<h1 className="text-4xl font-bold text-white mb-2">Quiz Complete!</h1>
<p className="text-gray-400 text-lg">Great job completing the quiz</p>
</div>
<div className="bg-gray-800 border border-gray-700 rounded-xl p-8">
<div className="grid grid-cols-3 gap-6 mb-8">
<div className="text-center">
<p className="text-gray-400 text-sm mb-2">Score</p>
<p className="text-3xl font-bold text-white">
{correctAnswers}/{totalQuestions}
</p>
</div>
<div className="text-center">
<p className="text-gray-400 text-sm mb-2">Accuracy</p>
<p className="text-3xl font-bold text-yellow-400">
{Math.round((correctAnswers / totalQuestions) * 100)}%
</p>
</div>
<div className="text-center">
<p className="text-gray-400 text-sm mb-2">Points Earned</p>
<p className="text-3xl font-bold text-green-400">+{totalPoints}</p>
</div>
</div>
<div className="space-y-4">
<button
onClick={() => router.push("/tasks")}
className="w-full bg-gradient-to-r from-yellow-500 to-orange-500 text-white font-semibold py-3 rounded-lg hover:from-yellow-600 hover:to-orange-600 transition transform hover:scale-105"
>
Back to Tasks
</button>
<button
onClick={() => router.push("/")}
className="w-full bg-gray-700 text-white font-semibold py-3 rounded-lg hover:bg-gray-600 transition"
>
Go to Dashboard
</button>
</div>
</div>
</div>
</DarkThemeLayout>
</>
);
}
return (
<>
<Navbar user={user} />
<DarkThemeLayout>
<div className="max-w-3xl mx-auto space-y-8">
<div>
<h1 className="text-4xl font-bold text-white mb-2">{quiz.title}</h1>
<p className="text-gray-400 text-lg">{quiz.description}</p>
</div>
<div className="bg-gray-800 border border-gray-700 rounded-xl p-6">
<div className="flex items-center justify-between mb-4">
<span className="text-gray-400">
Question {currentQuestion + 1} of {totalQuestions}
</span>
<span className="text-yellow-400 font-semibold">
{quiz.pointsPerQuestion} points
</span>
</div>
<div className="w-full bg-gray-700 rounded-full h-2 mb-6 overflow-hidden">
<div
className="bg-gradient-to-r from-yellow-500 to-orange-500 h-full rounded-full transition-all duration-500"
style={{ width: `${progressPercent}%` }}
></div>
</div>
<QuizQuestion
question={quiz.questions[currentQuestion]}
onAnswer={handleAnswer}
/>
{answers[currentQuestion] && (
<button
onClick={handleNext}
className="w-full mt-6 bg-gradient-to-r from-yellow-500 to-orange-500 text-white font-semibold py-3 rounded-lg hover:from-yellow-600 hover:to-orange-600 transition transform hover:scale-105"
>
{currentQuestion < totalQuestions - 1 ? "Next Question" : "Complete Quiz"}
</button>
)}
</div>
</div>
</DarkThemeLayout>
</>
);
}