feat: complete pragativeer v2 frontend and backend, docker setup, and cbt engine

This commit is contained in:
2026-07-06 17:08:13 +05:30
parent ce01795aa9
commit 426b98ddab
66 changed files with 7949 additions and 363 deletions

49
src/hooks/useExamTimer.ts Normal file
View File

@@ -0,0 +1,49 @@
import { useEffect, useState, useRef } from 'react';
import { useCBTStore } from '@/store/cbtStore';
export function useExamTimer() {
const { startTime, totalDuration, status, submitExam } = useCBTStore();
const [timeRemaining, setTimeRemaining] = useState<number>(totalDuration);
const frameRef = useRef<number | null>(null);
useEffect(() => {
if (status !== 'IN_PROGRESS' || !startTime) {
return;
}
const calculateTime = () => {
const now = Date.now();
const elapsedSeconds = Math.floor((now - startTime) / 1000);
const remaining = Math.max(0, totalDuration - elapsedSeconds);
setTimeRemaining(remaining);
if (remaining === 0) {
submitExam('TIMED_OUT');
} else {
// Use requestAnimationFrame for a high-performance loop instead of setInterval
// We throttle updates to when the second actually changes
frameRef.current = requestAnimationFrame(calculateTime);
}
};
frameRef.current = requestAnimationFrame(calculateTime);
return () => {
if (frameRef.current) cancelAnimationFrame(frameRef.current);
};
}, [startTime, totalDuration, status, submitExam]);
// Format HH:MM:SS
const hours = Math.floor(timeRemaining / 3600);
const minutes = Math.floor((timeRemaining % 3600) / 60);
const seconds = timeRemaining % 60;
const formattedTime = [
hours.toString().padStart(2, '0'),
minutes.toString().padStart(2, '0'),
seconds.toString().padStart(2, '0'),
].join(':');
return { timeRemaining, formattedTime, isTimedOut: timeRemaining === 0 };
}