fix: QA report BUG-01 to BUG-12 full resolution - demo switcher, AI tutor stream, timeline alignment, cbt filtering, danger styling, streak celebration
This commit is contained in:
@@ -102,7 +102,17 @@ export default function AttemptHistoryPage({ params }: { params: Promise<{ attem
|
||||
<CardContent className="p-4 flex flex-col items-center justify-center">
|
||||
<Award className="text-[#00F0FF] mb-2" size={24} />
|
||||
<div className="text-sm text-muted-foreground font-bold uppercase">Score</div>
|
||||
<div className="text-2xl font-bold">{scoreCalc} <span className="text-sm text-muted-foreground">/ {maxScore}</span></div>
|
||||
<div className="text-2xl font-bold flex items-center justify-center gap-1.5">
|
||||
<span className={scoreCalc <= 0 ? "text-red-500 font-black" : (scoreCalc / maxScore) >= 0.6 ? "text-green-500" : "text-amber-500"}>
|
||||
{scoreCalc}
|
||||
</span>
|
||||
<span className="text-sm text-muted-foreground">/ {maxScore}</span>
|
||||
{scoreCalc <= 0 && (
|
||||
<span className="text-[10px] font-extrabold uppercase px-1.5 py-0.5 rounded bg-red-500/10 text-red-500 border border-red-500/20">
|
||||
Fail
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<Card className="bg-secondary/30 border-border flex-1 md:min-w-48">
|
||||
|
||||
@@ -134,8 +134,9 @@ export default function CBTPage() {
|
||||
</span>
|
||||
Computer Based Tests (CBT)
|
||||
</h1>
|
||||
{/* BUG-09 fix: Dynamic subtitle reflecting current exam pattern tab (NEET vs JEE) */}
|
||||
<p className="text-muted-foreground text-base font-medium max-w-2xl pl-1">
|
||||
Real exam environment simulations to conquer {targetExam || "NEET/JEE"}.
|
||||
Real exam environment simulations to conquer {selectedPattern === "NEET" ? "NEET" : (targetExam || "JEE Main & Advanced")}.
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex flex-col sm:flex-row gap-3">
|
||||
@@ -180,224 +181,117 @@ export default function CBTPage() {
|
||||
<Clock size={20} className="text-[#00F0FF]" /> Upcoming & Active Exams
|
||||
</h2>
|
||||
|
||||
{loading ? (
|
||||
<div className="py-12 text-center text-muted-foreground">Loading test catalog...</div>
|
||||
) : filteredExams.length === 0 ? (
|
||||
<div className="py-12 text-center text-muted-foreground bg-secondary/10 border border-dashed border-border rounded-2xl">
|
||||
No active exams found for {targetExam || "your target exam"}. Try switching it in settings!
|
||||
</div>
|
||||
) : (
|
||||
<div className="space-y-8">
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||
{filteredExams.slice(0, 3).map((exam) => {
|
||||
const attempt = attemptsByTest[exam.id];
|
||||
const isAttempted = !!attempt;
|
||||
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 (
|
||||
<Card key={exam.id} className={`hover:border-[#00F0FF]/50 hover:shadow-[0_0_15px_-5px_#00F0FF] transition-all group ${isAttempted ? 'border-primary/20' : ''} ${isLocked || isExpired ? 'opacity-80' : ''}`}>
|
||||
<CardContent className="p-6">
|
||||
<div className="flex justify-between items-start mb-4">
|
||||
<div className="flex gap-2 flex-wrap">
|
||||
<Badge variant="outline" className="bg-[#00F0FF]/10 text-[#00F0FF] border-[#00F0FF]/20">
|
||||
{exam.type} Test
|
||||
</Badge>
|
||||
{exam.exam_pattern && (
|
||||
<Badge variant="outline" className="bg-primary/20 text-primary border-primary/30 uppercase tracking-wider text-[10px]">
|
||||
{exam.exam_pattern === 'JEE_Main' ? 'JEE Main' : exam.exam_pattern === 'JEE_Advanced' ? 'JEE Advanced' : exam.exam_pattern}
|
||||
{/* BUG-08 fix: Filter out already attempted exams from Upcoming & Active list */}
|
||||
{(() => {
|
||||
const unattemptedExams = filteredExams.filter(exam => !attemptsByTest[exam.id]);
|
||||
|
||||
if (loading) {
|
||||
return <div className="py-12 text-center text-muted-foreground">Loading test catalog...</div>;
|
||||
}
|
||||
|
||||
if (unattemptedExams.length === 0) {
|
||||
return (
|
||||
<div className="py-8 text-center text-muted-foreground bg-secondary/10 border border-dashed border-border rounded-2xl p-6">
|
||||
{filteredExams.length > 0 ? (
|
||||
<>
|
||||
<p className="font-bold text-foreground mb-1">All available exams in this section have been attempted!</p>
|
||||
<p className="text-xs text-muted-foreground">Review your scores, solution explanations, and performance in the Past Attempts section below.</p>
|
||||
</>
|
||||
) : (
|
||||
<p>No active exams found for {selectedPattern === "NEET" ? "NEET" : "JEE"}. Try adjusting your filters!</p>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="space-y-8">
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||
{(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 (
|
||||
<Card key={exam.id} className={`hover:border-[#00F0FF]/50 hover:shadow-[0_0_15px_-5px_#00F0FF] transition-all group ${isLocked || isExpired ? 'opacity-80' : ''}`}>
|
||||
<CardContent className="p-6">
|
||||
<div className="flex justify-between items-start mb-4">
|
||||
<div className="flex gap-2 flex-wrap">
|
||||
<Badge variant="outline" className="bg-[#00F0FF]/10 text-[#00F0FF] border-[#00F0FF]/20">
|
||||
{exam.type} Test
|
||||
</Badge>
|
||||
)}
|
||||
{exam.exam_pattern && (
|
||||
<Badge variant="outline" className="bg-primary/20 text-primary border-primary/30 uppercase tracking-wider text-[10px]">
|
||||
{exam.exam_pattern === 'JEE_Main' ? 'JEE Main' : exam.exam_pattern === 'JEE_Advanced' ? 'JEE Advanced' : exam.exam_pattern}
|
||||
</Badge>
|
||||
)}
|
||||
</div>
|
||||
{isUpcoming ? (
|
||||
<Badge variant="secondary" className="bg-yellow-500/10 text-yellow-500 border-yellow-500/20">
|
||||
Upcoming
|
||||
</Badge>
|
||||
) : isLocked ? (
|
||||
<Badge variant="outline" className="bg-red-500/10 text-red-500 border-red-500/20 flex items-center gap-1">
|
||||
<Lock size={12} /> Premium
|
||||
</Badge>
|
||||
) : isExpired ? (
|
||||
<Badge variant="outline" className="bg-muted text-muted-foreground border-muted-foreground/30">
|
||||
Expired
|
||||
</Badge>
|
||||
) : null}
|
||||
</div>
|
||||
<h3 className="font-bold text-lg leading-tight mb-2 group-hover:text-[#00F0FF] transition-colors">
|
||||
{exam.title}
|
||||
</h3>
|
||||
<div className="text-sm text-muted-foreground space-y-1 mb-6">
|
||||
<div className="font-medium text-foreground">
|
||||
{isUpcoming ? `Available on ${formatDate(exam.scheduled_date!)}` : isExpired ? `Expired on ${formatDate(exam.end_date!)}` : 'Available Now'}
|
||||
</div>
|
||||
<div>{exam.total_time} Min • {exam.difficulty} Difficulty</div>
|
||||
</div>
|
||||
|
||||
{isUpcoming ? (
|
||||
<Badge variant="secondary" className="bg-yellow-500/10 text-yellow-500 border-yellow-500/20">
|
||||
Upcoming
|
||||
</Badge>
|
||||
<Button disabled className="w-full gap-2 rounded-xl bg-secondary text-muted-foreground font-bold">
|
||||
<Clock size={16} /> Starts Soon
|
||||
</Button>
|
||||
) : isLocked ? (
|
||||
<Badge variant="outline" className="bg-red-500/10 text-red-500 border-red-500/20 flex items-center gap-1">
|
||||
<Lock size={12} /> Premium
|
||||
</Badge>
|
||||
<Button
|
||||
onClick={() => setShowUpgradeModal(true)}
|
||||
className="w-full gap-2 rounded-xl bg-secondary text-muted-foreground border border-dashed border-border hover:border-[#00F0FF]/50 hover:text-foreground font-bold"
|
||||
>
|
||||
<Lock size={16} /> Unlock Test
|
||||
</Button>
|
||||
) : isExpired ? (
|
||||
<Badge variant="outline" className="bg-muted text-muted-foreground border-muted-foreground/30">
|
||||
<Button disabled className="w-full gap-2 rounded-xl bg-secondary/50 text-muted-foreground border border-border font-bold">
|
||||
Expired
|
||||
</Badge>
|
||||
) : isAttempted ? (
|
||||
<Badge variant="secondary" className="bg-green-500/10 text-green-500 border-green-500/20">
|
||||
Attempted
|
||||
</Badge>
|
||||
) : null}
|
||||
</div>
|
||||
<h3 className="font-bold text-lg leading-tight mb-2 group-hover:text-[#00F0FF] transition-colors">
|
||||
{exam.title}
|
||||
</h3>
|
||||
<div className="text-sm text-muted-foreground space-y-1 mb-6">
|
||||
<div className="font-medium text-foreground">
|
||||
{isUpcoming ? `Available on ${formatDate(exam.scheduled_date!)}` : isExpired ? `Expired on ${formatDate(exam.end_date!)}` : 'Available Now'}
|
||||
</div>
|
||||
<div>{exam.total_time} Min • {exam.difficulty} Difficulty</div>
|
||||
</div>
|
||||
|
||||
{isUpcoming ? (
|
||||
<Button disabled className="w-full gap-2 rounded-xl bg-secondary text-muted-foreground font-bold">
|
||||
<Clock size={16} /> Starts Soon
|
||||
</Button>
|
||||
) : isLocked ? (
|
||||
<Button
|
||||
onClick={() => setShowUpgradeModal(true)}
|
||||
className="w-full gap-2 rounded-xl bg-secondary text-muted-foreground border border-dashed border-border hover:border-[#00F0FF]/50 hover:text-foreground font-bold"
|
||||
>
|
||||
<Lock size={16} /> Unlock Test
|
||||
</Button>
|
||||
) : isExpired && !isAttempted ? (
|
||||
<Button disabled className="w-full gap-2 rounded-xl bg-secondary/50 text-muted-foreground border border-border font-bold">
|
||||
Expired
|
||||
</Button>
|
||||
) : isAttempted ? (
|
||||
<div className="flex gap-2">
|
||||
<Link href={`/cbt/${exam.id}/result`} className="flex-1">
|
||||
<Button className="w-full gap-2 rounded-xl bg-secondary border border-border text-foreground hover:bg-secondary/80 font-bold">
|
||||
<PlayCircle size={16} /> Result
|
||||
</Button>
|
||||
) : (
|
||||
<Link href={`/cbt/${exam.id}`}>
|
||||
<Button className="w-full gap-2 rounded-xl bg-secondary text-foreground hover:bg-[#00F0FF] hover:text-background font-bold transition-colors">
|
||||
<PlayCircle size={16} /> Start Exam
|
||||
</Button>
|
||||
</Link>
|
||||
{!isExpired && (
|
||||
<Link href={`/cbt/${exam.id}`} className="flex-1">
|
||||
<Button className="w-full gap-2 rounded-xl bg-[#00F0FF]/10 border border-[#00F0FF]/30 text-[#00F0FF] hover:bg-[#00F0FF]/20 font-bold">
|
||||
Retake
|
||||
</Button>
|
||||
</Link>
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
<Link href={`/cbt/${exam.id}`}>
|
||||
<Button className="w-full gap-2 rounded-xl bg-secondary text-foreground hover:bg-[#00F0FF] hover:text-background font-bold transition-colors">
|
||||
<PlayCircle size={16} /> Start Exam
|
||||
</Button>
|
||||
</Link>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
)})}
|
||||
</div>
|
||||
|
||||
{filteredExams.length > 3 && !showAllExams && (
|
||||
<div className="flex justify-center mt-6">
|
||||
<Button variant="outline" onClick={() => setShowAllExams(true)} className="rounded-xl border-dashed">
|
||||
Show More Earlier Exams ({filteredExams.length - 3})
|
||||
</Button>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
)})}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{showAllExams && filteredExams.length > 3 && (
|
||||
<div className="space-y-6 pt-4 animate-in fade-in duration-500">
|
||||
<h3 className="text-lg font-bold border-b border-border pb-2 flex items-center gap-2">
|
||||
<Clock size={18} className="text-muted-foreground" /> Earlier Exams
|
||||
</h3>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||
{filteredExams.slice(3).map((exam) => {
|
||||
const attempt = attemptsByTest[exam.id];
|
||||
const isAttempted = !!attempt;
|
||||
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 (
|
||||
<Card key={exam.id} className={`hover:border-[#00F0FF]/50 hover:shadow-[0_0_15px_-5px_#00F0FF] transition-all group ${isAttempted ? 'border-primary/20' : ''} ${isLocked || isExpired ? 'opacity-80' : ''}`}>
|
||||
<CardContent className="p-6">
|
||||
<div className="flex justify-between items-start mb-4">
|
||||
<div className="flex gap-2 flex-wrap">
|
||||
<Badge variant="outline" className="bg-[#00F0FF]/10 text-[#00F0FF] border-[#00F0FF]/20">
|
||||
{exam.type} Test
|
||||
</Badge>
|
||||
{exam.exam_pattern && (
|
||||
<Badge variant="outline" className="bg-primary/20 text-primary border-primary/30 uppercase tracking-wider text-[10px]">
|
||||
{exam.exam_pattern === 'JEE_Main' ? 'JEE Main' : exam.exam_pattern === 'JEE_Advanced' ? 'JEE Advanced' : exam.exam_pattern}
|
||||
</Badge>
|
||||
)}
|
||||
</div>
|
||||
{isUpcoming ? (
|
||||
<Badge variant="secondary" className="bg-yellow-500/10 text-yellow-500 border-yellow-500/20">
|
||||
Upcoming
|
||||
</Badge>
|
||||
) : isLocked ? (
|
||||
<Badge variant="outline" className="bg-red-500/10 text-red-500 border-red-500/20 flex items-center gap-1">
|
||||
<Lock size={12} /> Premium
|
||||
</Badge>
|
||||
) : isExpired ? (
|
||||
<Badge variant="outline" className="bg-muted text-muted-foreground border-muted-foreground/30">
|
||||
Expired
|
||||
</Badge>
|
||||
) : isAttempted ? (
|
||||
<Badge variant="secondary" className="bg-green-500/10 text-green-500 border-green-500/20">
|
||||
Attempted
|
||||
</Badge>
|
||||
) : null}
|
||||
</div>
|
||||
<h3 className="font-bold text-lg leading-tight mb-2 group-hover:text-[#00F0FF] transition-colors">
|
||||
{exam.title}
|
||||
</h3>
|
||||
<div className="text-sm text-muted-foreground space-y-1 mb-6">
|
||||
<div className="font-medium text-foreground">
|
||||
{isUpcoming ? `Available on ${formatDate(exam.scheduled_date!)}` : isExpired ? `Expired on ${formatDate(exam.end_date!)}` : 'Available Now'}
|
||||
</div>
|
||||
<div>{exam.total_time} Min • {exam.difficulty} Difficulty</div>
|
||||
</div>
|
||||
|
||||
{isUpcoming ? (
|
||||
<Button disabled className="w-full gap-2 rounded-xl bg-secondary text-muted-foreground font-bold">
|
||||
<Clock size={16} /> Starts Soon
|
||||
</Button>
|
||||
) : isLocked ? (
|
||||
<Button
|
||||
onClick={() => setShowUpgradeModal(true)}
|
||||
className="w-full gap-2 rounded-xl bg-secondary text-muted-foreground border border-dashed border-border hover:border-[#00F0FF]/50 hover:text-foreground font-bold"
|
||||
>
|
||||
<Lock size={16} /> Unlock Test
|
||||
</Button>
|
||||
) : isExpired && !isAttempted ? (
|
||||
<Button disabled className="w-full gap-2 rounded-xl bg-secondary/50 text-muted-foreground border border-border font-bold">
|
||||
Expired
|
||||
</Button>
|
||||
) : isAttempted ? (
|
||||
<div className="flex gap-2">
|
||||
<Link href={`/dashboard/cbt/history/${attempt.id}`} className="flex-1">
|
||||
<Button className="w-full gap-2 rounded-xl bg-secondary border border-border text-foreground hover:bg-secondary/80 font-bold">
|
||||
<PlayCircle size={16} /> Result
|
||||
</Button>
|
||||
</Link>
|
||||
{!isExpired && (
|
||||
<Link href={`/cbt/${exam.id}`} className="flex-1">
|
||||
<Button className="w-full gap-2 rounded-xl bg-[#00F0FF]/10 border border-[#00F0FF]/30 text-[#00F0FF] hover:bg-[#00F0FF]/20 font-bold">
|
||||
Retake
|
||||
</Button>
|
||||
</Link>
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
<Link href={`/cbt/${exam.id}`}>
|
||||
<Button className="w-full gap-2 rounded-xl bg-secondary text-foreground hover:bg-[#00F0FF] hover:text-background font-bold transition-colors">
|
||||
<PlayCircle size={16} /> Start Exam
|
||||
</Button>
|
||||
</Link>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
)})}
|
||||
{unattemptedExams.length > 3 && !showAllExams && (
|
||||
<div className="flex justify-center mt-6">
|
||||
<Button variant="outline" onClick={() => setShowAllExams(true)} className="rounded-xl border-dashed">
|
||||
Show More Available Exams ({unattemptedExams.length - 3})
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
})()}
|
||||
</div>
|
||||
|
||||
<div className="space-y-6 pt-8">
|
||||
|
||||
Reference in New Issue
Block a user