"use client"; import { fetchWithAuth } from "@/lib/apiClient"; import { useEffect, useState } from "react"; import Link from "next/link"; import { Button } from "@/components/ui/button"; import { Card, CardContent } from "@/components/ui/card"; import { Badge } from "@/components/ui/badge"; import { Activity, Clock, PlayCircle, Search, Lock, CheckSquare } from "lucide-react"; import { useActiveUser } from "@/hooks/useActiveUser"; import { PremiumGate } from "@/components/PremiumGate"; import { useMemo } from "react"; import { CalendarFilter, getLocalDateString } from "@/components/CalendarFilter"; interface TestData { id: string; title: string; type: string; exam_pattern: string; total_time: number; difficulty: string; scheduled_date?: string; end_date?: string; } export default function CBTPage() { const [exams, setExams] = useState([]); const [loading, setLoading] = useState(true); const [search, setSearch] = useState(""); const [targetExam, setTargetExam] = useState(""); const [attempts, setAttempts] = useState([]); const [showUpgradeModal, setShowUpgradeModal] = useState(false); const [showAllExams, setShowAllExams] = useState(false); const [selectedPattern, setSelectedPattern] = useState(""); const [dateFilter, setDateFilter] = useState(""); const { userId, user, hasAccessTo, loading: userLoading } = useActiveUser(); useEffect(() => { if (userLoading || !userId) return; async function loadUser() { try { const userRes = await fetchWithAuth(`/api/users/${userId}`); if (!userRes.ok) throw new Error("Failed to load user target exam"); const userData = await userRes.json(); const pattern = userData.target_exam; setTargetExam(pattern === "JEE_NEET" ? "JEE & NEET" : pattern === "NEET" ? "NEET" : pattern === "JEE_Advanced" ? "JEE Advanced" : "JEE"); // Only set the initial pattern if it hasn't been changed by the user if (!selectedPattern) { setSelectedPattern(pattern === "JEE_NEET" ? "JEE_NEET" : pattern.startsWith("JEE") ? "JEE" : "NEET"); } } catch (err) { console.error("Error loading user:", err); } } loadUser(); }, [userId, userLoading]); useEffect(() => { if (!userId || !selectedPattern) return; async function loadTests() { setLoading(true); try { let url = `/api/tests?user_id=${userId}&type=Mock`; if (selectedPattern) { url += `&exam_pattern=${selectedPattern}`; } const testRes = await fetchWithAuth(url); if (testRes.ok) { let testsData = await testRes.json(); if (selectedPattern === "JEE") { testsData = testsData.filter((t: any) => t.exam_pattern?.includes("JEE")); } else if (selectedPattern === "NEET") { testsData = testsData.filter((t: any) => t.exam_pattern === "NEET" || t.exam_pattern === "JEE_NEET"); } setExams(testsData); } const attemptsRes = await fetchWithAuth(`/api/users/${userId}/attempts`); if (attemptsRes.ok) { const attemptsData = await attemptsRes.json(); setAttempts(attemptsData); } } catch (err) { console.error("Error loading exams:", err); } finally { setLoading(false); } } loadTests(); }, [userId, selectedPattern]); const scheduledDates = useMemo(() => { return exams .filter((e) => !!e.scheduled_date) .map((e) => getLocalDateString(e.scheduled_date)) .filter(Boolean) }, [exams]); const filteredExams = exams.filter(exam => { const matchesSearch = exam.title.toLowerCase().includes(search.toLowerCase()); if (!matchesSearch) return false; if (dateFilter) { if (!exam.scheduled_date) return false; const scheduledDate = getLocalDateString(exam.scheduled_date); if (scheduledDate !== dateFilter) return false; } return true; }); const formatDateTime = (dateStr: string) => { return new Date(dateStr).toLocaleString("en-US", { month: 'short', day: 'numeric', year: 'numeric', hour: 'numeric', minute: '2-digit', hour12: true }); } const formatDate = (dateStr: string) => { return new Date(dateStr).toLocaleDateString("en-US", { month: 'short', day: 'numeric', year: 'numeric' }); } const formatTime = (dateStr: string) => { return new Date(dateStr).toLocaleTimeString("en-US", { hour: 'numeric', minute: '2-digit', hour12: true }); } // Get the highest score for each test if attempted multiple times const attemptsByTest = attempts.reduce((acc, curr) => { // Isolate analytics to first attempt only - ignore retakes if (curr.is_retake) return acc; if (!acc[curr.source_id] || acc[curr.source_id].score < curr.score) { acc[curr.source_id] = curr; } return acc; }, {} as Record); if (userLoading || loading) { return (

Loading exams...

) } return ( <>

Mock Tests (CBT)

Real exam environment simulations to conquer {selectedPattern === "NEET" ? "NEET" : (targetExam || "JEE")}.

setSearch(e.target.value)} className="w-full pl-10 pr-4 py-2 bg-background border border-border rounded-xl text-sm focus:ring-2 focus:ring-[var(--cyan-ai)] outline-none" />

Upcoming & Active Exams

{/* BUG-08 fix: Filter out already attempted exams from Upcoming & Active list */} {(() => { const unattemptedExams = filteredExams.filter(exam => !attemptsByTest[exam.id]); if (loading) { return
Loading test catalog...
; } if (unattemptedExams.length === 0) { return (
{filteredExams.length > 0 ? ( <>

All available exams in this section have been attempted!

Review your scores, solution explanations, and performance in the Past Attempts section below.

) : (

No active exams found for {selectedPattern === "NEET" ? "NEET" : "JEE"}. Try adjusting your filters!

)}
); } return (
{(showAllExams ? unattemptedExams : unattemptedExams.slice(0, 3)).map((exam) => { const isLocked = !hasAccessTo("weekly-test-series"); // Date logic const now = new Date(); const scheduledTime = exam.scheduled_date ? new Date(exam.scheduled_date) : now; const endTime = exam.end_date ? new Date(exam.end_date) : null; const isUpcoming = scheduledTime > now; const isExpired = endTime ? now > endTime : false; return (
{exam.type} Test {exam.exam_pattern && ( {exam.exam_pattern === 'JEE_Main' ? 'JEE Main' : exam.exam_pattern === 'JEE_Advanced' ? 'JEE Advanced' : exam.exam_pattern} )}
{isUpcoming ? ( Upcoming ) : isLocked ? ( Premium ) : isExpired ? ( Expired ) : null}

{exam.title}

{isUpcoming && exam.scheduled_date ? ( Opens: {formatDateTime(exam.scheduled_date)} ) : isExpired && exam.end_date ? ( Expired on {formatDateTime(exam.end_date)} ) : exam.end_date ? ( Live Now • Closes {formatDateTime(exam.end_date)} ) : exam.scheduled_date ? ( Available since {formatDateTime(exam.scheduled_date)} ) : ( Available Now )}
{exam.total_time} Min • {exam.difficulty} Difficulty
{isUpcoming ? ( ) : isLocked ? ( ) : isExpired ? ( ) : ( )}
)})}
{unattemptedExams.length > 3 && !showAllExams && (
)}
); })()}

Past Attempts

{attempts.filter(a => a.source_type === "Test" && exams.some(e => e.id === a.source_id)).length === 0 ? (
You haven't attempted any CBT exams yet.
) : attempts.filter(a => a.source_type === "Test" && exams.some(e => e.id === a.source_id)).map((attempt, i) => { const exam = exams.find(e => e.id === attempt.source_id); // BUG-07 fix: use actual final_score from backend if available, else derive correctly // backend stores final_score = (correct*4) - (incorrect*1) const correctCount = attempt.answers ? attempt.answers.filter((a: any) => a.is_correct).length : attempt.score; const incorrectCount = attempt.answers ? (attempt.answers.filter((a: any) => a.selected_answer !== null).length - correctCount) : 0; const scoreCalc = attempt.final_score ?? ((correctCount * 4) - (incorrectCount * 1)); const maxScore = attempt.total_questions * 4; const scorePercent = maxScore > 0 ? (scoreCalc / maxScore) * 100 : 0; const scoreColor = scorePercent >= 60 ? 'text-green-500' : scorePercent >= 35 ? 'text-amber-500' : 'text-red-500'; return (

{exam?.title || 'Unknown Test'}

{formatDate(attempt.submitted_at)}

Score
{scoreCalc} /{maxScore}
Accuracy
{Math.round(attempt.accuracy)}%
)})}
{showUpgradeModal && (

Upgrade to Premium Plan

This CBT Mock Test is a premium feature. Free users get access to 1 Daily Practice Sheet and 5 Topic-wise Micro-tests. Upgrade to Pro or AI Coach to unlock unlimited practice and full-length weekly test series!

)}
); }