Fix UI issues, timer logic, theme switcher, view solutions, and markdown rendering
This commit is contained in:
@@ -3,13 +3,15 @@
|
||||
import { useEffect, useState, use, useRef } from "react"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { Badge } from "@/components/ui/badge"
|
||||
import { Clock, Flag, ChevronLeft, ChevronRight, Pause as PauseIcon, MoreVertical, Calculator as CalculatorIcon, FlaskConical, Edit3, XCircle, Play, Shield, AlertTriangle } from "lucide-react"
|
||||
import { Clock, Flag, ChevronLeft, ChevronRight, Pause as PauseIcon, MoreVertical, Calculator as CalculatorIcon, FlaskConical, Edit3, XCircle, Play, Shield, AlertTriangle, Moon, Sun } from "lucide-react"
|
||||
import Image from "next/image"
|
||||
import { useTheme } from "next-themes"
|
||||
import { useCBTStore, QuestionStatus } from "@/store/cbtStore"
|
||||
import { useExamTimer } from "@/hooks/useExamTimer"
|
||||
import ReactMarkdown from "react-markdown"
|
||||
import remarkMath from "remark-math"
|
||||
import rehypeKatex from "rehype-katex"
|
||||
import rehypeRaw from "rehype-raw"
|
||||
import "katex/dist/katex.min.css"
|
||||
import { Calculator } from "@/components/CBT/Calculator"
|
||||
import { Scratchpad } from "@/components/CBT/Scratchpad"
|
||||
@@ -35,9 +37,9 @@ export default function CBTExamPage({ params }: { params: Promise<{ examId: stri
|
||||
const [showPeriodicTable, setShowPeriodicTable] = useState(false);
|
||||
const [isPaused, setIsPaused] = useState(false);
|
||||
const [showSubmitConfirm, setShowSubmitConfirm] = useState(false);
|
||||
const { theme, setTheme } = useTheme();
|
||||
|
||||
// Anti-cheat state
|
||||
const [testStarted, setTestStarted] = useState(false);
|
||||
const [violationCount, setViolationCount] = useState(0);
|
||||
const [showWarning, setShowWarning] = useState(false);
|
||||
const hasShownWarning = useRef(false);
|
||||
@@ -45,6 +47,7 @@ export default function CBTExamPage({ params }: { params: Promise<{ examId: stri
|
||||
|
||||
const {
|
||||
initExam,
|
||||
startExam,
|
||||
questions,
|
||||
answers,
|
||||
currentQuestionIndex,
|
||||
@@ -57,7 +60,8 @@ export default function CBTExamPage({ params }: { params: Promise<{ examId: stri
|
||||
getAttemptPayload,
|
||||
submitAttemptToServer,
|
||||
resetExamState,
|
||||
status
|
||||
status,
|
||||
attemptId
|
||||
} = useCBTStore();
|
||||
|
||||
const { formattedTime, isTimedOut } = useExamTimer();
|
||||
@@ -109,14 +113,14 @@ export default function CBTExamPage({ params }: { params: Promise<{ examId: stri
|
||||
|
||||
// Track time spent per question
|
||||
useEffect(() => {
|
||||
if (testStarted) {
|
||||
if (status === 'IN_PROGRESS') {
|
||||
setQuestionStartTime(Date.now());
|
||||
}
|
||||
}, [currentQuestionIndex, testStarted]);
|
||||
}, [currentQuestionIndex, status]);
|
||||
|
||||
// Anti-Cheat: Tab switching listener
|
||||
useEffect(() => {
|
||||
if (!testStarted || status === 'SUBMITTED' || status === 'TIMED_OUT') return;
|
||||
if (status !== 'IN_PROGRESS') return;
|
||||
|
||||
const handleVisibilityChange = () => {
|
||||
if (document.visibilityState === 'hidden') {
|
||||
@@ -126,7 +130,7 @@ export default function CBTExamPage({ params }: { params: Promise<{ examId: stri
|
||||
|
||||
document.addEventListener("visibilitychange", handleVisibilityChange);
|
||||
return () => document.removeEventListener("visibilitychange", handleVisibilityChange);
|
||||
}, [testStarted, status]);
|
||||
}, [status]);
|
||||
|
||||
// Handle side-effects of violation count changing
|
||||
useEffect(() => {
|
||||
@@ -155,7 +159,7 @@ export default function CBTExamPage({ params }: { params: Promise<{ examId: stri
|
||||
} catch (e) {
|
||||
console.warn("Fullscreen API failed", e);
|
||||
}
|
||||
setTestStarted(true);
|
||||
startExam();
|
||||
};
|
||||
|
||||
const handleNext = () => {
|
||||
@@ -199,7 +203,7 @@ export default function CBTExamPage({ params }: { params: Promise<{ examId: stri
|
||||
|
||||
if (!mounted || questions.length === 0) return <div className="p-8">Loading Exam Engine...</div>;
|
||||
|
||||
if (!testStarted && status !== 'SUBMITTED' && status !== 'TIMED_OUT') {
|
||||
if (status === 'NOT_STARTED') {
|
||||
return (
|
||||
<div className="h-screen flex flex-col items-center justify-center bg-background p-8 text-center animate-in fade-in duration-500">
|
||||
<div className="max-w-2xl bg-secondary/10 border border-border p-8 rounded-2xl shadow-2xl">
|
||||
@@ -216,6 +220,17 @@ export default function CBTExamPage({ params }: { params: Promise<{ examId: stri
|
||||
<li>Ensure your internet connection is stable before starting.</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div className="mb-8 p-4 bg-muted rounded-xl flex items-center justify-between">
|
||||
<div>
|
||||
<p className="font-semibold">Preferred Theme</p>
|
||||
<p className="text-sm text-muted-foreground">Choose dark or light mode for the exam</p>
|
||||
</div>
|
||||
<Button variant="outline" size="icon" onClick={() => setTheme(theme === 'dark' ? 'light' : 'dark')}>
|
||||
{theme === 'dark' ? <Sun className="h-5 w-5" /> : <Moon className="h-5 w-5" />}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<Button size="lg" className="w-full text-lg font-bold h-14 shadow-[0_0_20px_-5px_var(--color-primary)]" onClick={startTest}>
|
||||
I Understand, Start Test
|
||||
</Button>
|
||||
@@ -231,6 +246,7 @@ export default function CBTExamPage({ params }: { params: Promise<{ examId: stri
|
||||
return (
|
||||
<ExamResult
|
||||
payload={payload}
|
||||
attemptId={attemptId}
|
||||
onRetake={() => {
|
||||
resetExamState();
|
||||
window.location.reload();
|
||||
@@ -346,7 +362,7 @@ export default function CBTExamPage({ params }: { params: Promise<{ examId: stri
|
||||
</div>
|
||||
|
||||
<div className="prose prose-neutral dark:prose-invert max-w-none text-lg leading-relaxed text-foreground font-medium">
|
||||
<ReactMarkdown remarkPlugins={[remarkMath]} rehypePlugins={[rehypeKatex]}>
|
||||
<ReactMarkdown remarkPlugins={[remarkMath]} rehypePlugins={[rehypeKatex, rehypeRaw]}>
|
||||
{currentQuestion.text_en}
|
||||
</ReactMarkdown>
|
||||
</div>
|
||||
@@ -377,7 +393,7 @@ export default function CBTExamPage({ params }: { params: Promise<{ examId: stri
|
||||
{letter}
|
||||
</div>
|
||||
<span className={`text-base font-semibold flex-1 ${isSelected ? 'text-primary' : ''}`}>
|
||||
<ReactMarkdown remarkPlugins={[remarkMath]} rehypePlugins={[rehypeKatex]}>
|
||||
<ReactMarkdown remarkPlugins={[remarkMath]} rehypePlugins={[rehypeKatex, rehypeRaw]}>
|
||||
{option}
|
||||
</ReactMarkdown>
|
||||
</span>
|
||||
@@ -402,7 +418,7 @@ export default function CBTExamPage({ params }: { params: Promise<{ examId: stri
|
||||
</div>
|
||||
</div>
|
||||
<div className="prose prose-neutral dark:prose-invert max-w-none text-lg leading-relaxed text-foreground font-medium">
|
||||
<ReactMarkdown remarkPlugins={[remarkMath]} rehypePlugins={[rehypeKatex]}>
|
||||
<ReactMarkdown remarkPlugins={[remarkMath]} rehypePlugins={[rehypeKatex, rehypeRaw]}>
|
||||
{currentQuestion.text_hi}
|
||||
</ReactMarkdown>
|
||||
</div>
|
||||
@@ -433,7 +449,7 @@ export default function CBTExamPage({ params }: { params: Promise<{ examId: stri
|
||||
{letter}
|
||||
</div>
|
||||
<span className={`text-base font-semibold flex-1 ${isSelected ? 'text-primary' : ''}`}>
|
||||
<ReactMarkdown remarkPlugins={[remarkMath]} rehypePlugins={[rehypeKatex]}>
|
||||
<ReactMarkdown remarkPlugins={[remarkMath]} rehypePlugins={[rehypeKatex, rehypeRaw]}>
|
||||
{option}
|
||||
</ReactMarkdown>
|
||||
</span>
|
||||
|
||||
Reference in New Issue
Block a user