chore: Secure routes with auth middleware, update text and fix minor bugs
This commit is contained in:
@@ -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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user