UI/UX Polishing: CBT dashboard, sidebar tiers, landing pricing, and exam disclaimer

This commit is contained in:
2026-07-09 17:05:40 +05:30
parent b80d73ec26
commit 22b167b985
15 changed files with 481 additions and 202 deletions

View File

@@ -25,6 +25,7 @@ export default function CBTPage() {
const [targetExam, setTargetExam] = useState("");
const [attempts, setAttempts] = useState<any[]>([]);
const [showUpgradeModal, setShowUpgradeModal] = useState(false);
const [showAllExams, setShowAllExams] = useState(false);
const { userId, user, hasAccessTo, loading: userLoading } = useActiveUser();
@@ -111,76 +112,165 @@ export default function CBTPage() {
No active exams found for {targetExam || "your target exam"}. Try switching it in settings!
</div>
) : (
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
{filteredExams.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 isUpcoming = scheduledTime > now;
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 ? '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}
<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 isUpcoming = scheduledTime > now;
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 ? '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>
) : 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!)}` : '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>
) : 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>
) : 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!)}` : '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>
) : (
<Link href={`/cbt/${exam.id}`}>
<Button className={`w-full gap-2 rounded-xl transition-colors font-bold ${isAttempted ? 'bg-secondary border border-border text-foreground' : 'bg-secondary text-foreground hover:bg-[#00F0FF] hover:text-background'}`}>
<PlayCircle size={16} /> {isAttempted ? 'Retake Exam' : 'Start Exam'}
<Button disabled className="w-full gap-2 rounded-xl bg-secondary text-muted-foreground font-bold">
<Clock size={16} /> Starts Soon
</Button>
</Link>
)}
</CardContent>
</Card>
)})}
) : 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>
) : (
<Link href={isAttempted ? `/cbt/${exam.id}/result` : `/cbt/${exam.id}`}>
<Button className={`w-full gap-2 rounded-xl transition-colors font-bold ${isAttempted ? 'bg-secondary border border-border text-foreground hover:bg-secondary/80' : 'bg-secondary text-foreground hover:bg-[#00F0FF] hover:text-background'}`}>
<PlayCircle size={16} /> {isAttempted ? 'View Result' : '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>
</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 isUpcoming = scheduledTime > now;
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 ? '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>
) : 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!)}` : '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>
) : (
<Link href={isAttempted ? `/cbt/${exam.id}/result` : `/cbt/${exam.id}`}>
<Button className={`w-full gap-2 rounded-xl transition-colors font-bold ${isAttempted ? 'bg-secondary border border-border text-foreground hover:bg-secondary/80' : 'bg-secondary text-foreground hover:bg-[#00F0FF] hover:text-background'}`}>
<PlayCircle size={16} /> {isAttempted ? 'View Result' : 'Start Exam'}
</Button>
</Link>
)}
</CardContent>
</Card>
)})}
</div>
</div>
)}
</div>
)}
</div>