chore: Secure routes with auth middleware, update text and fix minor bugs

This commit is contained in:
2026-08-03 17:55:20 +05:30
parent 62d15c85f6
commit c2fab50749
68 changed files with 2515 additions and 6808 deletions

View File

@@ -5,7 +5,7 @@ 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 } from "lucide-react";
import { Activity, Clock, PlayCircle, Search, Lock, MonitorPlay } from "lucide-react";
import { useActiveUser } from "@/hooks/useActiveUser";
interface TestData {
@@ -45,7 +45,7 @@ export default function CBTPage() {
// Only set the initial pattern if it hasn't been changed by the user
if (!selectedPattern) {
setSelectedPattern(pattern);
setSelectedPattern(pattern.startsWith("JEE") ? "JEE" : "NEET");
}
} catch (err) {
console.error("Error loading user:", err);
@@ -60,9 +60,16 @@ export default function CBTPage() {
async function loadTests() {
setLoading(true);
try {
const testRes = await fetch(`/api/tests?exam_pattern=${selectedPattern}&user_id=${userId}`);
let url = `/api/tests?user_id=${userId}`;
if (selectedPattern !== "JEE") {
url += `&exam_pattern=${selectedPattern}`;
}
const testRes = await fetch(url);
if (testRes.ok) {
const testsData = await testRes.json();
let testsData = await testRes.json();
if (selectedPattern === "JEE") {
testsData = testsData.filter((t: any) => t.exam_pattern === "JEE_Main" || t.exam_pattern === "JEE_Advanced");
}
setExams(testsData);
}
@@ -108,12 +115,26 @@ export default function CBTPage() {
return acc;
}, {} as Record<string, any>);
if (userLoading || loading) {
return (
<div className="h-[70vh] flex flex-col items-center justify-center p-8">
<div className="h-8 w-8 border-4 border-[#00F0FF] border-t-transparent rounded-full animate-spin mb-4"></div>
<p className="text-muted-foreground font-medium animate-pulse">Loading exams...</p>
</div>
)
}
return (
<div className="p-8 space-y-8 animate-in fade-in slide-in-from-bottom-4 duration-500 max-w-6xl mx-auto">
<div className="flex justify-between items-end">
<div className="p-8 space-y-8 animate-in fade-in slide-in-from-bottom-4 duration-500 max-w-7xl mx-auto">
<div className="flex flex-col md:flex-row md:items-end justify-between gap-4">
<div>
<h1 className="text-3xl font-bold tracking-tight">Computer Based Tests (CBT)</h1>
<p className="text-muted-foreground mt-1 font-medium">
<h1 className="text-3xl md:text-4xl font-black tracking-tight flex items-center gap-3 mb-1">
<span className="p-2.5 rounded-xl bg-[#00F0FF]/10 border border-[#00F0FF]/20">
<MonitorPlay className="text-[#00F0FF]" size={28} />
</span>
Computer Based Tests (CBT)
</h1>
<p className="text-muted-foreground text-base font-medium max-w-2xl pl-1">
Real exam environment simulations to conquer {targetExam || "NEET/JEE"}.
</p>
</div>
@@ -123,7 +144,7 @@ export default function CBTPage() {
type="date"
value={dateFilter}
onChange={(e) => setDateFilter(e.target.value)}
className="w-full px-4 py-2 bg-background border border-border rounded-xl text-sm focus:ring-2 focus:ring-[#00F0FF] outline-none text-muted-foreground"
className="w-full px-4 py-2 bg-background border border-border rounded-xl text-sm focus:ring-2 focus:ring-[#00F0FF] outline-none text-foreground [color-scheme:dark]"
/>
</div>
<div className="relative w-full sm:w-64">
@@ -140,17 +161,15 @@ export default function CBTPage() {
</div>
<div className="flex gap-3 overflow-x-auto pb-2 scrollbar-hide">
{["JEE_MAIN", "JEE_ADVANCED", "NEET"].map(p => {
// Normalize to match db/backend pattern
const dbPattern = p === "JEE_MAIN" ? "JEE_Main" : p === "JEE_ADVANCED" ? "JEE_Advanced" : "NEET";
{["JEE", "NEET"].map(p => {
return (
<Button
key={p}
variant={selectedPattern === dbPattern ? "default" : "outline"}
onClick={() => setSelectedPattern(dbPattern)}
className="rounded-full shadow-sm"
variant={selectedPattern === p ? "default" : "outline"}
onClick={() => setSelectedPattern(p)}
className="rounded-full shadow-sm px-6 font-bold"
>
{p === "JEE_MAIN" ? "JEE Main" : p === "JEE_ADVANCED" ? "JEE Advanced" : "NEET"}
{p}
</Button>
)
})}
@@ -386,11 +405,11 @@ export default function CBTPage() {
<Activity size={20} className="text-muted-foreground" /> Past Attempts
</h2>
<div className="space-y-4">
{attempts.length === 0 ? (
{attempts.filter(a => a.source_type === "Test" && exams.some(e => e.id === a.source_id)).length === 0 ? (
<div className="py-8 text-center text-muted-foreground bg-secondary/10 border border-dashed border-border rounded-2xl">
You haven't attempted any CBT exams yet.
</div>
) : attempts.map((attempt, i) => {
) : 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);
const scoreCalc = (attempt.score * 4) - ((attempt.total_questions - attempt.score) * 1); // rough calc for display