chore: Secure routes with auth middleware, update text and fix minor bugs
This commit is contained in:
@@ -115,7 +115,7 @@ export default function AIComCBTDPPPage() {
|
||||
<div>
|
||||
<div className="flex items-center gap-2 mb-1">
|
||||
<span className="text-xs font-black uppercase tracking-widest text-[#00F0FF] bg-[#00F0FF]/10 px-2.5 py-0.5 rounded-full border border-[#00F0FF]/30">
|
||||
AI CBT DPP Simulator
|
||||
PrepPilot AI
|
||||
</span>
|
||||
<span className="text-xs font-bold text-muted-foreground">• {user?.target_exam?.replace("_", " ") || "JEE/NEET"}</span>
|
||||
</div>
|
||||
@@ -180,7 +180,7 @@ export default function AIComCBTDPPPage() {
|
||||
</div>
|
||||
|
||||
<div className="space-y-4">
|
||||
<p className="text-lg font-bold leading-relaxed text-foreground">
|
||||
<p className="text-xl md:text-2xl font-black leading-relaxed text-foreground">
|
||||
{sampleQuestions[activeQuestion].question}
|
||||
</p>
|
||||
|
||||
@@ -203,13 +203,13 @@ export default function AIComCBTDPPPage() {
|
||||
<div
|
||||
key={optIdx}
|
||||
onClick={() => handleOptionSelect(activeQuestion, optIdx)}
|
||||
className={`flex items-center gap-4 p-4 rounded-2xl border cursor-pointer transition-all ${btnStyle}`}
|
||||
className={`flex items-center gap-4 p-4.5 rounded-2xl border cursor-pointer transition-all ${btnStyle}`}
|
||||
>
|
||||
<span className="w-8 h-8 rounded-xl bg-background/80 border border-border flex items-center justify-center font-bold text-sm shrink-0">
|
||||
<span className="w-10 h-10 rounded-xl bg-background/80 border border-border flex items-center justify-center font-black text-base shrink-0">
|
||||
{letter}
|
||||
</span>
|
||||
<span className="text-base font-medium flex-1">{opt}</span>
|
||||
{submitted && isCorrect && <CheckCircle2 size={20} className="text-emerald-400" />}
|
||||
<span className="text-lg md:text-xl font-bold flex-1">{opt}</span>
|
||||
{submitted && isCorrect && <CheckCircle2 size={22} className="text-emerald-400" />}
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
import AttemptHistoryPage from "@/app/dashboard/cbt/history/[attemptId]/page";
|
||||
|
||||
export default function AIHistoryPage({ params }: { params: Promise<{ attemptId: string }> }) {
|
||||
return <AttemptHistoryPage params={params} />;
|
||||
}
|
||||
@@ -2,19 +2,21 @@
|
||||
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { Card, CardContent } from "@/components/ui/card"
|
||||
import { BrainCircuit, Play, CheckCircle2, Download, Filter, Lock, FileText, Eye, ShieldAlert, X, Sparkles } from "lucide-react"
|
||||
import { BrainCircuit, Play, CheckCircle2, Download, Filter, Lock, FileText, Eye, ShieldAlert, X, Sparkles, Share2, Maximize2, Minimize2 } from "lucide-react"
|
||||
|
||||
import { useEffect, useState } from "react"
|
||||
import Link from "next/link"
|
||||
import { useActiveUser } from "@/hooks/useActiveUser"
|
||||
|
||||
export default function PrepPilotAIPage() {
|
||||
export default function AIDailyPracticePage() {
|
||||
const { user, userId } = useActiveUser()
|
||||
const [dpps, setDpps] = useState<any[]>([])
|
||||
const [attempts, setAttempts] = useState<any[]>([])
|
||||
const [loading, setLoading] = useState(true)
|
||||
const [dateFilter, setDateFilter] = useState("")
|
||||
const [selectedPdfDpp, setSelectedPdfDpp] = useState<any | null>(null)
|
||||
const [isFullScreen, setIsFullScreen] = useState(false)
|
||||
const [copiedShare, setCopiedShare] = useState(false)
|
||||
|
||||
useEffect(() => {
|
||||
async function fetchDpps() {
|
||||
@@ -90,13 +92,26 @@ export default function PrepPilotAIPage() {
|
||||
const cleanTitle = (title: string) => title.replace(/\s*\(ExamEnum\.[^)]+\)/, '');
|
||||
|
||||
return (
|
||||
<div className="p-8 space-y-8 animate-in fade-in slide-in-from-bottom-4 duration-500 max-w-5xl mx-auto">
|
||||
<div className="flex flex-col md:flex-row md:items-center justify-between p-8 bg-gradient-to-r from-secondary/50 to-background border border-border rounded-2xl shadow-sm gap-6">
|
||||
<div>
|
||||
<h1 className="text-3xl font-bold tracking-tight mb-2">PrepPilot AI <span className="text-[#00F0FF] text-xl">&</span> CBT Simulator</h1>
|
||||
<p className="text-muted-foreground font-medium max-w-xl">
|
||||
Select an interactive Target DPP to launch the AI-powered CBT examination simulator.
|
||||
</p>
|
||||
<div className="p-6 md:p-8 space-y-6 animate-in fade-in slide-in-from-bottom-4 duration-500 max-w-7xl mx-auto">
|
||||
{/* Page Header */}
|
||||
<div className="border-b border-border/50 pb-6">
|
||||
<div className="flex items-start justify-between gap-4 flex-wrap">
|
||||
<div>
|
||||
<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">
|
||||
<Sparkles className="text-[#00F0FF]" size={28} />
|
||||
</span>
|
||||
AI Daily Practice (DPP)
|
||||
</h1>
|
||||
<p className="text-muted-foreground text-base font-medium max-w-2xl pl-1">
|
||||
Launch AI-generated Daily Practice Problems (DPPs) tailored to your exam pattern and weak areas.
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex items-center gap-2 shrink-0">
|
||||
<span className="text-xs font-bold uppercase tracking-wider text-[#00F0FF] bg-[#00F0FF]/10 border border-[#00F0FF]/20 px-3 py-1.5 rounded-full flex items-center gap-1.5">
|
||||
<BrainCircuit size={12} /> AI Powered
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -114,12 +129,9 @@ export default function PrepPilotAIPage() {
|
||||
{diff}
|
||||
</button>
|
||||
))}
|
||||
<div className="flex items-center gap-1 ml-2 text-xs text-muted-foreground bg-secondary/50 px-2 py-1 rounded-md">
|
||||
<Lock size={12}/> Pro
|
||||
</div>
|
||||
</div>
|
||||
<Button variant="outline" className="gap-2 rounded-xl text-sm font-bold">
|
||||
<Download size={16} /> Export as PDF <span className="ml-1 text-[10px] uppercase tracking-wider bg-primary/10 text-primary px-1.5 py-0.5 rounded-md">Pro</span>
|
||||
<Download size={16} /> Export as PDF
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@@ -200,7 +212,7 @@ export default function PrepPilotAIPage() {
|
||||
{!isExpired && (
|
||||
<Link href={`/cbt/${dpp.id}`}>
|
||||
<Button variant="secondary" size="sm" className="font-bold bg-[#00F0FF]/10 text-[#00F0FF] hover:bg-[#00F0FF]/20 gap-2">
|
||||
<Play size={14} className="fill-current" /> Retake CBT
|
||||
<Play size={14} className="fill-current" /> Retake Test
|
||||
</Button>
|
||||
</Link>
|
||||
)}
|
||||
@@ -208,7 +220,7 @@ export default function PrepPilotAIPage() {
|
||||
) : (
|
||||
<Link href={`/cbt/${dpp.id}`}>
|
||||
<Button variant="secondary" size="sm" className="rounded-xl bg-[#00F0FF] text-black hover:bg-[#00F0FF]/90 font-extrabold gap-2 text-xs shadow-[0_0_12px_rgba(0,240,255,0.2)]">
|
||||
<Play size={14} className="fill-black" /> Launch CBT DPP
|
||||
<Play size={14} className="fill-black" /> Launch Practice Test
|
||||
</Button>
|
||||
</Link>
|
||||
)}
|
||||
@@ -220,18 +232,18 @@ export default function PrepPilotAIPage() {
|
||||
|
||||
{/* ── PROTECTED PDF READER MODAL (FREE TIER) ── */}
|
||||
{selectedPdfDpp && (
|
||||
<div className="fixed inset-0 z-50 bg-black/80 backdrop-blur-md flex items-center justify-center p-4 animate-in fade-in duration-300">
|
||||
<div className="bg-background border border-border rounded-3xl w-full max-w-4xl max-h-[90vh] flex flex-col overflow-hidden shadow-2xl">
|
||||
<div className={`fixed inset-0 z-50 bg-black/90 backdrop-blur-md flex items-center justify-center ${isFullScreen ? 'p-0' : 'p-4'} animate-in fade-in duration-300`}>
|
||||
<div className={`bg-background border border-border ${isFullScreen ? 'w-screen h-screen max-w-none max-h-none rounded-none border-0' : 'rounded-3xl w-full max-w-7xl max-h-[95vh]'} flex flex-col overflow-hidden shadow-2xl transition-all duration-300`}>
|
||||
{/* Modal Header */}
|
||||
<div className="p-5 border-b border-border bg-secondary/30 flex items-center justify-between">
|
||||
<div className="p-4 md:p-5 border-b border-border bg-secondary/30 flex items-center justify-between">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="p-2.5 bg-primary/10 rounded-xl border border-primary/20 text-primary">
|
||||
<div className="p-2.5 bg-primary/10 rounded-xl border border-primary/20 text-primary hidden sm:flex">
|
||||
<FileText size={22} />
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="font-black text-lg flex items-center gap-2">
|
||||
<h3 className="font-black text-base sm:text-lg flex items-center gap-2">
|
||||
{selectedPdfDpp.title}
|
||||
<span className="text-[10px] bg-yellow-500/10 text-yellow-500 border border-yellow-500/20 px-2 py-0.5 rounded-full uppercase tracking-wider font-extrabold">
|
||||
<span className="text-[10px] bg-yellow-500/10 text-yellow-500 border border-yellow-500/20 px-2 py-0.5 rounded-full uppercase tracking-wider font-extrabold hidden md:inline-block">
|
||||
Protected In-App PDF View
|
||||
</span>
|
||||
</h3>
|
||||
@@ -240,24 +252,46 @@ export default function PrepPilotAIPage() {
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<Button
|
||||
onClick={() => setSelectedPdfDpp(null)}
|
||||
variant="outline"
|
||||
size="icon"
|
||||
className="rounded-full border-border hover:bg-secondary"
|
||||
>
|
||||
<X size={18} />
|
||||
</Button>
|
||||
<div className="flex items-center gap-2">
|
||||
<Button
|
||||
size="sm"
|
||||
variant="outline"
|
||||
onClick={() => {
|
||||
navigator.clipboard.writeText(window.location.origin + '/dashboard/ai-coach/cbt-dpp');
|
||||
setCopiedShare(true);
|
||||
setTimeout(() => setCopiedShare(false), 2000);
|
||||
}}
|
||||
className="rounded-xl font-bold text-xs gap-1.5 h-9 px-3"
|
||||
>
|
||||
{copiedShare ? <CheckCircle2 size={14} className="text-green-500" /> : <Share2 size={14} />}
|
||||
<span>{copiedShare ? "Copied" : "Share PDF"}</span>
|
||||
</Button>
|
||||
<Button
|
||||
size="sm"
|
||||
variant="outline"
|
||||
onClick={() => setIsFullScreen(!isFullScreen)}
|
||||
className="rounded-xl font-bold text-xs gap-1.5 h-9 px-3 bg-primary/10 text-primary border-primary/20 hover:bg-primary/20"
|
||||
>
|
||||
{isFullScreen ? <Minimize2 size={14} /> : <Maximize2 size={14} />}
|
||||
<span>{isFullScreen ? "Exit Fullscreen" : "Fullscreen"}</span>
|
||||
</Button>
|
||||
<button
|
||||
onClick={() => { setSelectedPdfDpp(null); setIsFullScreen(false); }}
|
||||
className="ml-1 flex-shrink-0 w-9 h-9 rounded-full border border-border bg-secondary flex items-center justify-center text-foreground hover:bg-destructive hover:border-destructive hover:text-white transition-all"
|
||||
>
|
||||
<X size={18} />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Security Notice Banner */}
|
||||
<div className="bg-amber-500/10 border-b border-amber-500/20 px-6 py-2.5 flex items-center justify-between text-xs text-amber-400 font-bold">
|
||||
<span className="flex items-center gap-2">
|
||||
<ShieldAlert size={16} /> Read-Only Mode (Free Tier) — Downloads, text copying, and sharing are restricted.
|
||||
<div className="bg-amber-500/10 border-b border-amber-500/20 px-4 sm:px-6 py-2 flex items-center justify-between text-xs text-amber-400 font-bold">
|
||||
<span className="flex items-center gap-2 truncate">
|
||||
<ShieldAlert size={16} className="shrink-0" /> Read-Only Mode (Free Tier) — Downloads & direct copying are restricted.
|
||||
</span>
|
||||
<Link href="/upgrade">
|
||||
<Link href="/upgrade" className="hidden sm:block shrink-0 ml-2">
|
||||
<Button size="sm" className="bg-amber-500 text-black hover:bg-amber-400 font-extrabold h-7 text-[11px] rounded-lg">
|
||||
Upgrade for Interactive CBT Mode →
|
||||
Upgrade for Interactive Practice Mode →
|
||||
</Button>
|
||||
</Link>
|
||||
</div>
|
||||
@@ -265,27 +299,33 @@ export default function PrepPilotAIPage() {
|
||||
{/* Protected PDF Document Sheet Content (select-none) */}
|
||||
<div
|
||||
onContextMenu={(e) => e.preventDefault()}
|
||||
className="flex-1 w-full h-full min-h-[60vh] select-none bg-[#525659]"
|
||||
className="flex-1 w-full select-none bg-[#525659] overflow-hidden"
|
||||
style={{minHeight: isFullScreen ? 'calc(100vh - 120px)' : '75vh'}}
|
||||
>
|
||||
<embed
|
||||
src={getPdfUrl(selectedPdfDpp.title)}
|
||||
type="application/pdf"
|
||||
className="w-full h-full rounded-b-lg"
|
||||
className="w-full h-full"
|
||||
style={{height: isFullScreen ? 'calc(100vh - 120px)' : '75vh'}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Modal Footer */}
|
||||
<div className="p-4 border-t border-border bg-secondary/30 flex items-center justify-between">
|
||||
<span className="text-xs text-muted-foreground font-medium">
|
||||
<div className="p-3.5 sm:p-4 border-t border-border bg-secondary/30 flex items-center justify-between flex-wrap gap-2">
|
||||
<span className="text-xs text-muted-foreground font-medium flex items-center gap-2">
|
||||
📄 PragatiVeer Protected PDF Reader v2.0
|
||||
</span>
|
||||
<div className="flex gap-3">
|
||||
<Button onClick={() => setSelectedPdfDpp(null)} variant="outline" className="rounded-xl font-bold">
|
||||
Close PDF Reader
|
||||
<div className="flex gap-2 items-center">
|
||||
<Button onClick={() => setIsFullScreen(!isFullScreen)} variant="secondary" className="rounded-xl font-bold text-xs h-9 bg-primary/10 text-primary hover:bg-primary/20 flex items-center gap-1.5">
|
||||
{isFullScreen ? <Minimize2 size={14} /> : <Maximize2 size={14} />}
|
||||
<span>{isFullScreen ? "Exit Fullscreen" : "Fullscreen"}</span>
|
||||
</Button>
|
||||
<Button onClick={() => { setSelectedPdfDpp(null); setIsFullScreen(false); }} variant="outline" className="rounded-xl font-bold text-xs h-9">
|
||||
Close Reader
|
||||
</Button>
|
||||
<Link href="/upgrade">
|
||||
<Button className="bg-[#00F0FF] text-black hover:bg-[#00F0FF]/90 font-extrabold rounded-xl shadow-[0_0_15px_rgba(0,240,255,0.2)]">
|
||||
<Sparkles size={16} className="mr-2" /> Upgrade to AI Coach for Interactive CBT
|
||||
<Button className="bg-[#00F0FF] text-black hover:bg-[#00F0FF]/90 font-extrabold rounded-xl shadow-[0_0_15px_rgba(0,240,255,0.2)] text-xs h-9">
|
||||
<Sparkles size={14} className="mr-1.5" /> Upgrade to AI Coach
|
||||
</Button>
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
@@ -2,51 +2,131 @@
|
||||
|
||||
import { CollegePredictorCard } from "@/components/AI/CollegePredictorCard"
|
||||
import { Card, CardContent } from "@/components/ui/card"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { GraduationCap, ArrowLeft, Building2, MapPin, Award, CheckCircle2 } from "lucide-react"
|
||||
import { GraduationCap, Building2, Award, CheckCircle2, TrendingUp, MapPin, Users, BarChart2 } from "lucide-react"
|
||||
import Link from "next/link"
|
||||
|
||||
const CUTOFF_TRENDS = [
|
||||
{ year: "2021", rank: 3800 },
|
||||
{ year: "2022", rank: 3500 },
|
||||
{ year: "2023", rank: 3200 },
|
||||
{ year: "2024", rank: 2950 },
|
||||
{ year: "2025", rank: 2750 },
|
||||
]
|
||||
|
||||
export default function CollegePredictorPage() {
|
||||
const maxRank = Math.max(...CUTOFF_TRENDS.map(d => d.rank))
|
||||
|
||||
return (
|
||||
<div className="p-6 md:p-8 max-w-7xl mx-auto space-y-8 animate-in fade-in duration-500 min-h-screen">
|
||||
{/* Navigation Header */}
|
||||
<div className="p-6 md:p-8 space-y-8 max-w-7xl mx-auto animate-in fade-in duration-500 min-h-screen">
|
||||
{/* Page Header */}
|
||||
<div className="border-b border-border/50 pb-6">
|
||||
<h1 className="text-3xl md:text-5xl font-black tracking-tight flex items-center gap-3">
|
||||
<GraduationCap className="text-[#00F0FF]" size={36} />
|
||||
AI College & Cutoff Predictor
|
||||
</h1>
|
||||
<p className="text-muted-foreground text-base md:text-lg mt-1 font-medium max-w-3xl">
|
||||
Discover your probable target medical & engineering colleges based on current mock exam scores and historical cutoffs.
|
||||
</p>
|
||||
<div className="flex items-start justify-between gap-4 flex-wrap">
|
||||
<div>
|
||||
<h1 className="text-3xl md:text-4xl font-black tracking-tight flex items-center gap-3 mb-2">
|
||||
<span className="p-2.5 rounded-xl bg-[#00F0FF]/10 border border-[#00F0FF]/20">
|
||||
<GraduationCap className="text-[#00F0FF]" size={28} />
|
||||
</span>
|
||||
AI College & Cutoff Predictor
|
||||
</h1>
|
||||
<p className="text-muted-foreground text-base font-medium max-w-2xl pl-1">
|
||||
Discover your probable target medical & engineering colleges based on current mock exam scores and historical cutoffs.
|
||||
</p>
|
||||
</div>
|
||||
<span className="text-xs font-bold uppercase tracking-wider text-[#00F0FF] bg-[#00F0FF]/10 border border-[#00F0FF]/20 px-3 py-1.5 rounded-full flex items-center gap-1.5 shrink-0 self-start">
|
||||
<GraduationCap size={12} /> AI Powered
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Summary KPI Strip */}
|
||||
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
|
||||
{[
|
||||
{ label: "Predicted Rank", value: "~4,200", icon: <TrendingUp size={18} className="text-[#00F0FF]" />, color: "border-[#00F0FF]/20 bg-[#00F0FF]/5" },
|
||||
{ label: "Target Colleges", value: "12", icon: <Building2 size={18} className="text-amber-400" />, color: "border-amber-400/20 bg-amber-400/5" },
|
||||
{ label: "Reach Colleges", value: "5", icon: <MapPin size={18} className="text-purple-400" />, color: "border-purple-400/20 bg-purple-400/5" },
|
||||
{ label: "Safe Colleges", value: "18", icon: <CheckCircle2 size={18} className="text-green-400" />, color: "border-green-400/20 bg-green-400/5" },
|
||||
].map((kpi) => (
|
||||
<Card key={kpi.label} className={`rounded-2xl border ${kpi.color} hover:scale-[1.02] transition-transform`}>
|
||||
<CardContent className="p-5 flex flex-col gap-2">
|
||||
{kpi.icon}
|
||||
<div className="text-2xl font-black">{kpi.value}</div>
|
||||
<div className="text-xs font-semibold text-muted-foreground uppercase tracking-wider">{kpi.label}</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 lg:grid-cols-3 gap-8">
|
||||
<div className="lg:col-span-2 space-y-8">
|
||||
<Card className="p-8 bg-secondary/10 border-border/60 rounded-3xl">
|
||||
<h2 className="text-2xl font-extrabold tracking-tight mb-6 flex items-center gap-2">
|
||||
<Building2 className="text-[#00F0FF]" size={24} /> Target College Analytics
|
||||
</h2>
|
||||
<CollegePredictorCard />
|
||||
{/* College Predictor */}
|
||||
<CollegePredictorCard />
|
||||
|
||||
{/* Cutoff Trend Chart */}
|
||||
<Card className="p-6 bg-secondary/10 border-border/60 rounded-3xl">
|
||||
<div className="flex items-center gap-2 mb-6">
|
||||
<BarChart2 size={20} className="text-[#00F0FF]" />
|
||||
<h3 className="text-lg font-extrabold tracking-tight">NIT Trichy CSE — Closing Rank Trend</h3>
|
||||
<span className="ml-auto text-xs font-bold text-green-400 bg-green-400/10 border border-green-400/20 px-2.5 py-1 rounded-full flex items-center gap-1">
|
||||
<TrendingUp size={11} /> Improving Cutoffs
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex items-end gap-4 h-32 px-2">
|
||||
{CUTOFF_TRENDS.map((d, i) => (
|
||||
<div key={d.year} className="flex flex-col items-center gap-1.5 flex-1">
|
||||
<span className="text-xs font-black text-muted-foreground">{d.rank.toLocaleString()}</span>
|
||||
<div
|
||||
className={`w-full rounded-t-md transition-all duration-700 ${i === CUTOFF_TRENDS.length - 1 ? "bg-gradient-to-t from-[#00F0FF]/60 to-[#00F0FF]" : "bg-gradient-to-t from-white/10 to-white/25"}`}
|
||||
style={{ height: `${(d.rank / maxRank) * 80}px` }}
|
||||
/>
|
||||
<span className="text-xs text-muted-foreground font-semibold">{d.year}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<p className="text-xs text-muted-foreground mt-4 pl-1">Closing rank trend: cutoffs are improving, meaning competition is easing. Your predicted rank of ~4,200 is within Target range.</p>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
<div className="space-y-6">
|
||||
<Card className="p-6 bg-gradient-to-br from-[#00F0FF]/10 via-secondary/20 to-background border border-[#00F0FF]/30 rounded-3xl space-y-4">
|
||||
<h3 className="text-xl font-black tracking-tight text-[#00F0FF] flex items-center gap-2">
|
||||
<Award size={20} /> Cutoff Modeling Parameters
|
||||
<h3 className="text-base font-black tracking-tight text-[#00F0FF] flex items-center gap-2">
|
||||
<Award size={18} /> Cutoff Modelling Parameters
|
||||
</h3>
|
||||
<ul className="space-y-3 text-sm font-medium text-muted-foreground">
|
||||
<li className="flex items-start gap-2">
|
||||
<CheckCircle2 size={16} className="text-[#00F0FF] shrink-0 mt-0.5" />
|
||||
<span>Includes 100,000+ candidate rank trends across 2021-2025 rounds.</span>
|
||||
<span>Trained on <strong className="text-foreground">100,000+ candidate rank trends</strong> across 2021–2025 counselling rounds.</span>
|
||||
</li>
|
||||
<li className="flex items-start gap-2">
|
||||
<CheckCircle2 size={16} className="text-[#00F0FF] shrink-0 mt-0.5" />
|
||||
<span>Supports All India Quota (AIQ) & State Quota adjustments.</span>
|
||||
<span>Supports <strong className="text-foreground">All India Quota (AIQ)</strong> & State Quota seat adjustments.</span>
|
||||
</li>
|
||||
<li className="flex items-start gap-2">
|
||||
<CheckCircle2 size={16} className="text-[#00F0FF] shrink-0 mt-0.5" />
|
||||
<span>Simulates category reservation cutoffs (General, OBC, SC, ST, EWS).</span>
|
||||
<span>Simulates <strong className="text-foreground">category reservation cutoffs</strong>: General, OBC, SC, ST, EWS.</span>
|
||||
</li>
|
||||
<li className="flex items-start gap-2">
|
||||
<CheckCircle2 size={16} className="text-[#00F0FF] shrink-0 mt-0.5" />
|
||||
<span>Score updated daily as you complete more mock tests.</span>
|
||||
</li>
|
||||
</ul>
|
||||
</Card>
|
||||
|
||||
<Card className="p-6 bg-secondary/10 border-border/60 rounded-3xl space-y-3">
|
||||
<h3 className="text-base font-black text-muted-foreground uppercase tracking-wider flex items-center gap-2">
|
||||
<Users size={16} className="text-amber-400" /> Improve Your Rank
|
||||
</h3>
|
||||
<ul className="space-y-2.5 text-sm text-muted-foreground font-medium">
|
||||
<li className="flex items-start gap-2">
|
||||
<span className="text-amber-400 font-black shrink-0">+15 marks</span>
|
||||
<span>in Maths → upgrades you from NIT Trichy to <strong className="text-foreground">IIT Roorkee</strong></span>
|
||||
</li>
|
||||
<li className="flex items-start gap-2">
|
||||
<span className="text-[#00F0FF] font-black shrink-0">Fix Optics</span>
|
||||
<span>errors to recover ~8 marks in Physics alone</span>
|
||||
</li>
|
||||
<li className="flex items-start gap-2">
|
||||
<span className="text-green-400 font-black shrink-0">Boost Speed</span>
|
||||
<span>from 45s → 38s/Q gives you ~12 extra attempt opportunities</span>
|
||||
</li>
|
||||
</ul>
|
||||
</Card>
|
||||
|
||||
@@ -1,24 +1,156 @@
|
||||
"use client"
|
||||
|
||||
import { MistakeHeatmap } from "@/components/AI/MistakeHeatmap"
|
||||
import { ArrowLeft, Flame } from "lucide-react"
|
||||
import { Flame, AlertTriangle, TrendingDown, BookOpen, ArrowRight, BarChart2, Target } from "lucide-react"
|
||||
import { Card, CardContent } from "@/components/ui/card"
|
||||
import Link from "next/link"
|
||||
|
||||
const CRITICAL_TOPICS = [
|
||||
{ name: "Rotational Dynamics", rate: "80%", rateNum: 80, subject: "Physics" },
|
||||
{ name: "Optics", rate: "60%", rateNum: 60, subject: "Physics" },
|
||||
{ name: "Electromagnetism", rate: "30%", rateNum: 30, subject: "Physics" },
|
||||
{ name: "GOC — Reaction Mechanisms", rate: "55%", rateNum: 55, subject: "Chemistry" },
|
||||
{ name: "Thermodynamics", rate: "25%", rateNum: 25, subject: "Physics" },
|
||||
]
|
||||
|
||||
const SUMMARY_STATS = [
|
||||
{ label: "Total Errors (Last 5 Tests)", value: "87", icon: <AlertTriangle size={18} className="text-red-400" />, color: "border-red-400/20 bg-red-400/5" },
|
||||
{ label: "Critical Topics", value: "4", icon: <Flame size={18} className="text-orange-400" />, color: "border-orange-400/20 bg-orange-400/5" },
|
||||
{ label: "Improving Topics", value: "3", icon: <TrendingDown size={18} className="text-green-400" />, color: "border-green-400/20 bg-green-400/5" },
|
||||
{ label: "Avg Error Rate", value: "38%", icon: <Target size={18} className="text-amber-400" />, color: "border-amber-400/20 bg-amber-400/5" },
|
||||
]
|
||||
|
||||
export default function HeatmapPage() {
|
||||
return (
|
||||
<div className="p-6 md:p-8 max-w-7xl mx-auto space-y-8 animate-in fade-in duration-500 min-h-screen">
|
||||
{/* Page Header */}
|
||||
<div className="border-b border-border/50 pb-6">
|
||||
<h1 className="text-3xl md:text-5xl font-black tracking-tight flex items-center gap-3">
|
||||
<Flame className="text-yellow-500" size={36} />
|
||||
Mistake Heatmap Vault
|
||||
</h1>
|
||||
<p className="text-muted-foreground text-base md:text-lg mt-1 font-medium max-w-3xl">
|
||||
Visual representation of your error density across subjects and topics.
|
||||
</p>
|
||||
<div className="flex items-start justify-between gap-4 flex-wrap">
|
||||
<div>
|
||||
<h1 className="text-3xl md:text-4xl font-black tracking-tight flex items-center gap-3 mb-2">
|
||||
<span className="p-2.5 rounded-xl bg-amber-500/10 border border-amber-500/20">
|
||||
<Flame className="text-amber-500" size={28} />
|
||||
</span>
|
||||
Mistake Heatmap Vault
|
||||
</h1>
|
||||
<p className="text-muted-foreground text-base font-medium max-w-2xl pl-1">
|
||||
Visual density map of your errors across subjects and topics — pinpoint your weakest areas instantly.
|
||||
</p>
|
||||
</div>
|
||||
<span className="text-xs font-bold uppercase tracking-wider text-amber-500 bg-amber-500/10 border border-amber-500/20 px-3 py-1.5 rounded-full flex items-center gap-1.5 shrink-0 self-start">
|
||||
<AlertTriangle size={12} /> Error Density Analysis
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="max-w-4xl mx-auto">
|
||||
<MistakeHeatmap />
|
||||
{/* KPI Strip */}
|
||||
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
|
||||
{SUMMARY_STATS.map((kpi) => (
|
||||
<Card key={kpi.label} className={`rounded-2xl border ${kpi.color} hover:scale-[1.02] transition-transform`}>
|
||||
<CardContent className="p-5 flex flex-col gap-2">
|
||||
{kpi.icon}
|
||||
<div className="text-2xl font-black">{kpi.value}</div>
|
||||
<div className="text-xs font-semibold text-muted-foreground uppercase tracking-wider">{kpi.label}</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Legend / Guidance Row */}
|
||||
<div className="grid grid-cols-2 lg:grid-cols-4 gap-4">
|
||||
{[
|
||||
{ label: "Critical (> 50%)", color: "bg-red-500/20 border-red-500/30 text-red-400", dot: "bg-red-500", desc: "Immediate revision needed" },
|
||||
{ label: "Warning (30–50%)", color: "bg-amber-500/20 border-amber-500/30 text-amber-400", dot: "bg-amber-500", desc: "Focus on these next" },
|
||||
{ label: "Moderate (10–30%)", color: "bg-yellow-500/20 border-yellow-500/30 text-yellow-400", dot: "bg-yellow-500", desc: "Review periodically" },
|
||||
{ label: "Good (< 10%)", color: "bg-green-500/20 border-green-500/30 text-green-400", dot: "bg-green-500", desc: "Mostly understood" },
|
||||
].map((item) => (
|
||||
<Card key={item.label} className={`rounded-2xl border ${item.color.split(" ").slice(0, 2).join(" ")}`}>
|
||||
<CardContent className="p-4">
|
||||
<div className={`inline-flex items-center gap-1.5 text-xs font-black uppercase tracking-wider mb-1 ${item.color.split(" ")[2]}`}>
|
||||
<span className={`w-2.5 h-2.5 rounded-sm ${item.dot}`} />
|
||||
{item.label}
|
||||
</div>
|
||||
<p className="text-xs text-muted-foreground font-medium">{item.desc}</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Main 2-col layout */}
|
||||
<div className="grid grid-cols-1 lg:grid-cols-3 gap-8">
|
||||
{/* Heatmap — takes 2/3 */}
|
||||
<div className="lg:col-span-2 space-y-3">
|
||||
<div className="flex items-center gap-2 text-sm font-black uppercase tracking-wider text-muted-foreground">
|
||||
<TrendingDown size={16} className="text-red-400" />
|
||||
Topic Error Rate — Last 5 Tests
|
||||
</div>
|
||||
<MistakeHeatmap />
|
||||
</div>
|
||||
|
||||
{/* Side panel */}
|
||||
<div className="space-y-6">
|
||||
{/* Error frequency chart */}
|
||||
<Card className="bg-secondary/10 border-border/60 rounded-2xl">
|
||||
<CardContent className="p-5 space-y-4">
|
||||
<h3 className="text-sm font-black uppercase tracking-wider text-foreground flex items-center gap-2">
|
||||
<BarChart2 size={15} className="text-red-400" /> Priority Topics by Error Rate
|
||||
</h3>
|
||||
<div className="space-y-3">
|
||||
{CRITICAL_TOPICS.map((t) => (
|
||||
<div key={t.name}>
|
||||
<div className="flex items-center justify-between mb-1">
|
||||
<div>
|
||||
<p className="text-xs font-bold leading-tight">{t.name}</p>
|
||||
<p className="text-[11px] text-muted-foreground">{t.subject}</p>
|
||||
</div>
|
||||
<span className={`text-xs font-black px-2 py-0.5 rounded-md ${t.rateNum >= 50 ? "text-red-400 bg-red-400/10" : t.rateNum >= 30 ? "text-amber-400 bg-amber-400/10" : "text-yellow-400 bg-yellow-400/10"}`}>
|
||||
{t.rate}
|
||||
</span>
|
||||
</div>
|
||||
<div className="h-1.5 w-full bg-secondary rounded-full overflow-hidden">
|
||||
<div
|
||||
className={`h-full rounded-full ${t.rateNum >= 50 ? "bg-red-500" : t.rateNum >= 30 ? "bg-amber-500" : "bg-yellow-500"}`}
|
||||
style={{ width: `${t.rateNum}%` }}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* Action card */}
|
||||
<Card className="bg-gradient-to-br from-[#00F0FF]/10 via-secondary/20 to-background border border-[#00F0FF]/20 rounded-2xl">
|
||||
<CardContent className="p-5 space-y-3">
|
||||
<h3 className="text-sm font-black uppercase tracking-wider text-[#00F0FF]">What To Do Next</h3>
|
||||
<div className="space-y-2">
|
||||
{[
|
||||
{ label: "Start Mistake Review Session", href: "/dashboard/ai-coach/mistakes" },
|
||||
{ label: "Take a Physics DPP", href: "/dashboard/ai-coach/cbt-dpp" },
|
||||
{ label: "Check Exam Readiness Score", href: "/dashboard/ai-coach/readiness" },
|
||||
].map((link) => (
|
||||
<Link key={link.href} href={link.href}
|
||||
className="flex items-center justify-between text-sm font-bold text-muted-foreground hover:text-foreground py-2 border-b border-border/30 last:border-0 transition-colors group">
|
||||
{link.label}
|
||||
<ArrowRight size={14} className="group-hover:translate-x-1 transition-transform" />
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* Tip */}
|
||||
<Card className="bg-secondary/20 border-border/40 rounded-2xl">
|
||||
<CardContent className="p-4">
|
||||
<div className="flex items-start gap-2.5">
|
||||
<BookOpen size={16} className="text-[#00F0FF] shrink-0 mt-0.5" />
|
||||
<p className="text-xs text-muted-foreground font-medium">
|
||||
<span className="font-black text-foreground">Pro Tip:</span> Focus the next 3 days entirely on Rotational Dynamics — it alone is worth 8–12 marks in JEE and shows an 80% error rate across your last 5 tests.
|
||||
</p>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -5,9 +5,8 @@ import { MistakeHeatmap } from "@/components/AI/MistakeHeatmap"
|
||||
import { MistakeReviewCard, MistakePayload } from "@/components/AI/MistakeReviewCard"
|
||||
import { Card, CardContent } from "@/components/ui/card"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { BrainCircuit, RefreshCw, CheckCircle2, ArrowLeft, Flame, Sparkles, AlertTriangle } from "lucide-react"
|
||||
import { BrainCircuit, RefreshCw, CheckCircle2, Flame, Sparkles, AlertTriangle, Target, Clock, TrendingDown } from "lucide-react"
|
||||
import { useActiveUser } from "@/hooks/useActiveUser"
|
||||
import Link from "next/link"
|
||||
|
||||
export default function MistakeTrackingPage() {
|
||||
const [mistakes, setMistakes] = useState<MistakePayload[]>([])
|
||||
@@ -23,8 +22,7 @@ export default function MistakeTrackingPage() {
|
||||
if (!res.ok) throw new Error("Failed to fetch")
|
||||
const data = await res.json()
|
||||
setMistakes(data)
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
} catch {
|
||||
setMistakes([])
|
||||
} finally {
|
||||
setLoading(false)
|
||||
@@ -32,9 +30,7 @@ export default function MistakeTrackingPage() {
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (!userLoading && userId) {
|
||||
fetchMistakes()
|
||||
}
|
||||
if (!userLoading && userId) fetchMistakes()
|
||||
}, [userId, userLoading])
|
||||
|
||||
const submitReview = async (mistakeId: string, selectedAnswer: string) => {
|
||||
@@ -55,51 +51,97 @@ export default function MistakeTrackingPage() {
|
||||
}
|
||||
}
|
||||
|
||||
const completedCount = Math.min(currentIndex, mistakes.length)
|
||||
const progressPct = mistakes.length > 0 ? Math.round((completedCount / mistakes.length) * 100) : 0
|
||||
|
||||
return (
|
||||
<div className="p-6 md:p-8 max-w-7xl mx-auto space-y-8 animate-in fade-in duration-500 min-h-screen">
|
||||
{/* Navigation Header */}
|
||||
<div className="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4 border-b border-border/50 pb-6">
|
||||
<div>
|
||||
<h1 className="text-3xl md:text-5xl font-black tracking-tight flex items-center gap-3">
|
||||
<BrainCircuit className="text-[#00F0FF]" size={36} />
|
||||
AI Mistake Vault & SM-2 Engine
|
||||
</h1>
|
||||
<p className="text-muted-foreground text-base md:text-lg mt-1 font-medium">
|
||||
Review logged errors, practice spaced-repetition cards, and observe subject heatmap trends.
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex items-center gap-3 shrink-0">
|
||||
<div className="bg-[#00F0FF]/10 text-[#00F0FF] border border-[#00F0FF]/30 px-4 py-2 rounded-2xl font-bold text-sm flex items-center gap-2">
|
||||
<Sparkles size={16} /> SM-2 Adaptive Algorithm Active
|
||||
<div className="p-6 md:p-8 space-y-8 max-w-7xl mx-auto animate-in fade-in duration-500 min-h-screen">
|
||||
{/* Page Header */}
|
||||
<div className="border-b border-border/50 pb-6">
|
||||
<div className="flex items-start justify-between gap-4 flex-wrap">
|
||||
<div>
|
||||
<h1 className="text-3xl md:text-4xl font-black tracking-tight flex items-center gap-3 mb-2">
|
||||
<span className="p-2.5 rounded-xl bg-[#00F0FF]/10 border border-[#00F0FF]/20">
|
||||
<BrainCircuit className="text-[#00F0FF]" size={28} />
|
||||
</span>
|
||||
Mistake Review & Spaced Repetition
|
||||
</h1>
|
||||
<p className="text-muted-foreground text-base font-medium max-w-2xl pl-1">
|
||||
Review logged errors using our SM-2 spaced repetition engine. Questions resurface at optimal intervals to cement long-term retention.
|
||||
</p>
|
||||
</div>
|
||||
<span className="text-xs font-bold uppercase tracking-wider text-[#00F0FF] bg-[#00F0FF]/10 border border-[#00F0FF]/20 px-3 py-1.5 rounded-full flex items-center gap-1.5 shrink-0 self-start">
|
||||
<Sparkles size={12} /> SM-2 Engine
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Main Review Workspace */}
|
||||
<div className="grid grid-cols-1 lg:grid-cols-3 gap-8">
|
||||
<div className="lg:col-span-2 space-y-6">
|
||||
{/* KPI Strip */}
|
||||
{!loading && (
|
||||
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
|
||||
{[
|
||||
{ label: "In Today's Queue", value: mistakes.length.toString(), icon: <Flame size={18} className="text-amber-400" />, color: "border-amber-400/20 bg-amber-400/5" },
|
||||
{ label: "Reviewed This Session", value: completedCount.toString(), icon: <CheckCircle2 size={18} className="text-green-400" />, color: "border-green-400/20 bg-green-400/5" },
|
||||
{ label: "Session Progress", value: `${progressPct}%`, icon: <Target size={18} className="text-[#00F0FF]" />, color: "border-[#00F0FF]/20 bg-[#00F0FF]/5" },
|
||||
{ label: "Avg Error Rate", value: "38%", icon: <TrendingDown size={18} className="text-red-400" />, color: "border-red-400/20 bg-red-400/5" },
|
||||
].map(kpi => (
|
||||
<Card key={kpi.label} className={`rounded-2xl border ${kpi.color}`}>
|
||||
<CardContent className="p-4 flex flex-col gap-2">
|
||||
{kpi.icon}
|
||||
<div className="text-2xl font-black">{kpi.value}</div>
|
||||
<div className="text-xs font-semibold text-muted-foreground uppercase tracking-wider">{kpi.label}</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Progress bar */}
|
||||
{!loading && mistakes.length > 0 && (
|
||||
<div className="space-y-1.5">
|
||||
<div className="flex justify-between text-xs font-bold text-muted-foreground">
|
||||
<span>Session Progress</span>
|
||||
<span className="text-[#00F0FF]">{completedCount} of {mistakes.length} reviewed</span>
|
||||
</div>
|
||||
<div className="h-2 w-full bg-secondary rounded-full overflow-hidden">
|
||||
<div
|
||||
className="h-full bg-gradient-to-r from-[#00F0FF] to-teal-400 rounded-full transition-all duration-500"
|
||||
style={{ width: `${progressPct}%` }}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Main 2-col layout */}
|
||||
<div className="grid grid-cols-1 lg:grid-cols-12 gap-6 lg:gap-8 items-start">
|
||||
<div className="lg:col-span-7 space-y-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<h2 className="text-2xl font-extrabold tracking-tight flex items-center gap-2">
|
||||
<Flame className="text-amber-500" size={24} /> Today's Active Review Queue
|
||||
<h2 className="text-lg font-extrabold tracking-tight flex items-center gap-2 text-muted-foreground uppercase tracking-wider text-sm">
|
||||
<Clock size={16} className="text-amber-400" />
|
||||
Active Review Queue
|
||||
</h2>
|
||||
<span className="text-sm font-bold text-[#00F0FF] bg-[#00F0FF]/10 px-4 py-1.5 rounded-full border border-[#00F0FF]/20">
|
||||
{currentIndex < mistakes.length ? `Question ${currentIndex + 1} of ${mistakes.length}` : "Completed"}
|
||||
</span>
|
||||
{mistakes.length > 0 && (
|
||||
<span className="text-sm font-bold text-[#00F0FF] bg-[#00F0FF]/10 px-4 py-1.5 rounded-full border border-[#00F0FF]/20">
|
||||
{currentIndex < mistakes.length ? `${currentIndex + 1} / ${mistakes.length}` : "Complete"}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{loading ? (
|
||||
<Card className="p-16 flex items-center justify-center border-dashed">
|
||||
<RefreshCw className="animate-spin text-[#00F0FF] mr-3" size={24} />
|
||||
<span className="text-lg font-bold">Loading your active error queue...</span>
|
||||
<Card className="min-h-[480px] h-full flex flex-col items-center justify-center border-dashed p-8 gap-4">
|
||||
<RefreshCw className="animate-spin text-[#00F0FF]" size={28} />
|
||||
<span className="text-base font-semibold text-muted-foreground">Loading your review queue...</span>
|
||||
</Card>
|
||||
) : mistakes.length === 0 ? (
|
||||
<Card className="p-16 text-center border-dashed bg-secondary/10 rounded-3xl">
|
||||
<CheckCircle2 size={56} className="mx-auto text-green-400 mb-4 opacity-80" />
|
||||
<h3 className="text-2xl font-extrabold mb-2">Zero Mistakes Pending!</h3>
|
||||
<p className="text-muted-foreground text-base max-w-md mx-auto mb-6">
|
||||
All scheduled errors have been reviewed. Solve more mock tests to automatically surface weak questions here.
|
||||
</p>
|
||||
<Button onClick={() => window.location.reload()} variant="outline" className="rounded-xl font-bold">
|
||||
<Card className="min-h-[480px] h-full flex flex-col items-center justify-center text-center border-dashed bg-secondary/10 rounded-3xl p-8 gap-4">
|
||||
<CheckCircle2 size={56} className="text-green-400 opacity-80" />
|
||||
<div>
|
||||
<h3 className="text-xl font-extrabold mb-2">Queue Complete</h3>
|
||||
<p className="text-muted-foreground text-sm max-w-md mx-auto font-medium">
|
||||
All scheduled reviews for today are done. Solve more practice tests to surface new questions here.
|
||||
</p>
|
||||
</div>
|
||||
<Button onClick={() => window.location.reload()} variant="outline" className="rounded-xl font-bold h-10 px-6">
|
||||
Refresh Queue
|
||||
</Button>
|
||||
</Card>
|
||||
@@ -110,21 +152,27 @@ export default function MistakeTrackingPage() {
|
||||
onSubmitReview={submitReview}
|
||||
/>
|
||||
) : (
|
||||
<Card className="p-16 text-center border-dashed bg-[#00F0FF]/5 border-[#00F0FF]/30 rounded-3xl">
|
||||
<BrainCircuit size={56} className="mx-auto text-[#00F0FF] mb-4" />
|
||||
<h3 className="text-2xl font-extrabold mb-2 text-[#00F0FF]">Review Session Mastered 🎉</h3>
|
||||
<p className="text-muted-foreground text-base max-w-md mx-auto mb-6">
|
||||
Fantastic focus! Your memory decay intervals have been updated in the database.
|
||||
</p>
|
||||
<Button onClick={() => window.location.reload()} className="bg-[#00F0FF] text-black font-bold rounded-xl px-6">
|
||||
Check Additional Pending Queues
|
||||
<Card className="min-h-[480px] h-full flex flex-col items-center justify-center text-center border-dashed bg-[#00F0FF]/5 border-[#00F0FF]/30 rounded-3xl p-8 gap-4">
|
||||
<BrainCircuit size={56} className="text-[#00F0FF]" />
|
||||
<div>
|
||||
<h3 className="text-xl font-extrabold mb-2 text-[#00F0FF]">Session Complete 🎉</h3>
|
||||
<p className="text-muted-foreground text-sm max-w-md mx-auto font-medium">
|
||||
All {mistakes.length} questions reviewed. The SM-2 engine has scheduled your next review intervals.
|
||||
</p>
|
||||
</div>
|
||||
<Button onClick={() => window.location.reload()} className="bg-[#00F0FF] text-black font-bold rounded-xl px-6 h-10 text-sm">
|
||||
Check for New Errors
|
||||
</Button>
|
||||
</Card>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Heatmap & Info Column */}
|
||||
<div className="space-y-6">
|
||||
{/* Heatmap Column */}
|
||||
<div className="lg:col-span-5 space-y-4">
|
||||
<div className="flex items-center gap-2 text-sm font-black uppercase tracking-wider text-muted-foreground">
|
||||
<AlertTriangle size={16} className="text-amber-400" />
|
||||
Error Density by Topic
|
||||
</div>
|
||||
<MistakeHeatmap />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -3,75 +3,169 @@
|
||||
import { ReadinessGauge } from "@/components/AI/ReadinessGauge"
|
||||
import { Card, CardContent } from "@/components/ui/card"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { Gauge, ArrowLeft, Target, Award, ShieldAlert, CheckCircle2 } from "lucide-react"
|
||||
import { Gauge, TrendingUp, Target, Award, ShieldAlert, CheckCircle2, BookOpen, Zap, BarChart2, Clock } from "lucide-react"
|
||||
import Link from "next/link"
|
||||
|
||||
const SCORE_HISTORY = [
|
||||
{ week: "Wk 1", score: 54 },
|
||||
{ week: "Wk 2", score: 60 },
|
||||
{ week: "Wk 3", score: 63 },
|
||||
{ week: "Wk 4", score: 68 },
|
||||
{ week: "Wk 5", score: 72 },
|
||||
{ week: "Wk 6", score: 78 },
|
||||
]
|
||||
|
||||
export default function ReadinessPage() {
|
||||
const maxScore = Math.max(...SCORE_HISTORY.map(d => d.score))
|
||||
|
||||
return (
|
||||
<div className="p-6 md:p-8 max-w-7xl mx-auto space-y-8 animate-in fade-in duration-500 min-h-screen">
|
||||
{/* Navigation Header */}
|
||||
<div className="p-6 md:p-8 space-y-8 max-w-7xl mx-auto animate-in fade-in duration-500 min-h-screen">
|
||||
{/* Page Header */}
|
||||
<div className="border-b border-border/50 pb-6">
|
||||
<h1 className="text-3xl md:text-5xl font-black tracking-tight flex items-center gap-3">
|
||||
<Gauge className="text-[#00F0FF]" size={36} />
|
||||
Exam Readiness Analytics
|
||||
</h1>
|
||||
<p className="text-muted-foreground text-base md:text-lg mt-1 font-medium max-w-3xl">
|
||||
Real-time algorithmic prediction of your readiness percentage for JEE & NEET based on speed, accuracy, and test history.
|
||||
</p>
|
||||
<div className="flex items-start justify-between gap-4 flex-wrap">
|
||||
<div>
|
||||
<h1 className="text-3xl md:text-4xl font-black tracking-tight flex items-center gap-3 mb-2">
|
||||
<span className="p-2.5 rounded-xl bg-[#00F0FF]/10 border border-[#00F0FF]/20">
|
||||
<Gauge className="text-[#00F0FF]" size={28} />
|
||||
</span>
|
||||
Exam Readiness Analytics
|
||||
</h1>
|
||||
<p className="text-muted-foreground text-base font-medium max-w-2xl pl-1">
|
||||
Real-time algorithmic prediction of your exam readiness — based on speed, accuracy, syllabus coverage, and test history.
|
||||
</p>
|
||||
</div>
|
||||
<span className="text-xs font-bold uppercase tracking-wider text-[#00F0FF] bg-[#00F0FF]/10 border border-[#00F0FF]/20 px-3 py-1.5 rounded-full flex items-center gap-1.5 shrink-0 self-start">
|
||||
<Gauge size={12} /> AI Powered
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* KPI Strip */}
|
||||
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
|
||||
{[
|
||||
{ label: "Overall Score", value: "72 / 100", trend: "+5 this week", trendUp: true, icon: <Gauge size={20} className="text-[#00F0FF]" /> },
|
||||
{ label: "Accuracy Rate", value: "88%", trend: "+3% vs last week", trendUp: true, icon: <Target size={20} className="text-amber-400" /> },
|
||||
{ label: "Avg Speed / Q", value: "45 sec", trend: "↓ Needs work", trendUp: false, icon: <Clock size={20} className="text-red-400" /> },
|
||||
{ label: "Syllabus Covered", value: "64%", trend: "+8% this month", trendUp: true, icon: <BookOpen size={20} className="text-emerald-400" /> },
|
||||
].map((kpi) => (
|
||||
<Card key={kpi.label} className="border-border/60 bg-secondary/10 rounded-2xl hover:border-[#00F0FF]/30 transition-colors">
|
||||
<CardContent className="p-5">
|
||||
<div className="flex items-center justify-between mb-3">
|
||||
{kpi.icon}
|
||||
<span className={`text-xs font-bold px-2 py-0.5 rounded-full ${kpi.trendUp ? "bg-green-500/10 text-green-400" : "bg-red-500/10 text-red-400"}`}>
|
||||
{kpi.trend}
|
||||
</span>
|
||||
</div>
|
||||
<div className="text-2xl font-black mb-0.5">{kpi.value}</div>
|
||||
<div className="text-xs text-muted-foreground font-semibold uppercase tracking-wider">{kpi.label}</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 lg:grid-cols-3 gap-8">
|
||||
<div className="lg:col-span-2 space-y-8">
|
||||
<Card className="p-8 bg-secondary/10 border-border/60 rounded-3xl">
|
||||
<h2 className="text-2xl font-extrabold tracking-tight mb-6 flex items-center gap-2">
|
||||
<Award className="text-amber-400" size={24} /> Overall Readiness Index
|
||||
</h2>
|
||||
{/* Readiness Score Card */}
|
||||
<Card className="p-6 bg-secondary/10 border-border/60 rounded-3xl">
|
||||
<div className="flex items-center gap-2 mb-5">
|
||||
<Award className="text-amber-400" size={22} />
|
||||
<h2 className="text-xl font-extrabold tracking-tight">Composite Readiness Index</h2>
|
||||
<span className="ml-auto text-xs font-bold text-[#00F0FF] bg-[#00F0FF]/10 border border-[#00F0FF]/20 px-2.5 py-1 rounded-full">Updated Daily</span>
|
||||
</div>
|
||||
<ReadinessGauge />
|
||||
</Card>
|
||||
|
||||
{/* Subject Confidence Breakdown */}
|
||||
<Card className="p-6 bg-secondary/10 border-border/60 rounded-3xl space-y-6">
|
||||
<h3 className="text-xl font-extrabold tracking-tight">Subject Confidence Matrix</h3>
|
||||
{/* Subject Confidence Matrix */}
|
||||
<Card className="p-6 bg-secondary/10 border-border/60 rounded-3xl space-y-5">
|
||||
<div className="flex items-center gap-2">
|
||||
<BarChart2 size={20} className="text-[#00F0FF]" />
|
||||
<h3 className="text-lg font-extrabold tracking-tight">Subject Confidence Matrix</h3>
|
||||
</div>
|
||||
<div className="space-y-4">
|
||||
{[
|
||||
{ subject: "Biology / Maths", score: 82, color: "bg-[#00F0FF]", status: "High Readiness" },
|
||||
{ subject: "Chemistry", score: 74, color: "bg-amber-400", status: "Moderate Readiness" },
|
||||
{ subject: "Physics", score: 61, color: "bg-red-400", status: "Needs Intensive Drill" },
|
||||
{ subject: "Biology / Maths", score: 82, color: "bg-[#00F0FF]", status: "High Readiness", statusColor: "text-[#00F0FF]" },
|
||||
{ subject: "Chemistry", score: 74, color: "bg-amber-400", status: "Moderate Readiness", statusColor: "text-amber-400" },
|
||||
{ subject: "Physics", score: 61, color: "bg-red-400", status: "Needs Intensive Drill", statusColor: "text-red-400" },
|
||||
].map((sub) => (
|
||||
<div key={sub.subject} className="space-y-2">
|
||||
<div className="flex justify-between items-center text-sm font-bold">
|
||||
<span>{sub.subject}</span>
|
||||
<span className="text-muted-foreground">{sub.score}% • {sub.status}</span>
|
||||
<div className="flex justify-between items-center">
|
||||
<span className="text-sm font-black">{sub.subject}</span>
|
||||
<span className={`text-xs font-bold ${sub.statusColor}`}>{sub.score}% · {sub.status}</span>
|
||||
</div>
|
||||
<div className="h-3 w-full bg-secondary rounded-full overflow-hidden">
|
||||
<div className={`h-full ${sub.color}`} style={{ width: `${sub.score}%` }} />
|
||||
<div className="h-2.5 w-full bg-secondary rounded-full overflow-hidden">
|
||||
<div className={`h-full ${sub.color} rounded-full transition-all duration-700`} style={{ width: `${sub.score}%` }} />
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
{/* Score History Chart */}
|
||||
<Card className="p-6 bg-secondary/10 border-border/60 rounded-3xl">
|
||||
<div className="flex items-center gap-2 mb-6">
|
||||
<TrendingUp size={20} className="text-green-400" />
|
||||
<h3 className="text-lg font-extrabold tracking-tight">6-Week Readiness Trend</h3>
|
||||
</div>
|
||||
<div className="flex items-end gap-3 h-28">
|
||||
{SCORE_HISTORY.map((d, i) => (
|
||||
<div key={d.week} className="flex flex-col items-center gap-1 flex-1">
|
||||
<span className="text-xs font-black text-muted-foreground">{d.score}</span>
|
||||
<div
|
||||
className={`w-full rounded-t-md transition-all duration-700 ${i === SCORE_HISTORY.length - 1 ? "bg-gradient-to-t from-[#00F0FF]/60 to-[#00F0FF]" : "bg-gradient-to-t from-white/10 to-white/25"}`}
|
||||
style={{ height: `${(d.score / maxScore) * 80}px` }}
|
||||
/>
|
||||
<span className="text-xs text-muted-foreground font-semibold">{d.week}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
{/* Sidebar */}
|
||||
<div className="space-y-6">
|
||||
<Card className="p-6 bg-gradient-to-br from-amber-500/10 via-secondary/20 to-background border border-amber-500/30 rounded-3xl space-y-4">
|
||||
<h3 className="text-xl font-black tracking-tight text-amber-400 flex items-center gap-2">
|
||||
<ShieldAlert size={20} /> Readiness Boosters
|
||||
<h3 className="text-base font-black tracking-tight text-amber-400 flex items-center gap-2">
|
||||
<ShieldAlert size={18} /> Readiness Boosters
|
||||
</h3>
|
||||
<ul className="space-y-3 text-sm font-medium text-muted-foreground">
|
||||
<li className="flex items-start gap-2">
|
||||
<ul className="space-y-3.5 text-sm font-semibold text-muted-foreground">
|
||||
<li className="flex items-start gap-2.5">
|
||||
<CheckCircle2 size={16} className="text-amber-400 shrink-0 mt-0.5" />
|
||||
<span>Complete 2 Full-Length Mock Tests this week.</span>
|
||||
<span>Complete 2 full-length mock tests this week to boost consistency score.</span>
|
||||
</li>
|
||||
<li className="flex items-start gap-2">
|
||||
<CheckCircle2 size={16} className="text-amber-400 shrink-0 mt-0.5" />
|
||||
<span>Clear 15 pending errors in Physics error queue.</span>
|
||||
<span>Clear 15 pending errors in your Physics queue to gain ~4 readiness points.</span>
|
||||
</li>
|
||||
<li className="flex items-start gap-2">
|
||||
<CheckCircle2 size={16} className="text-amber-400 shrink-0 mt-0.5" />
|
||||
<span>Maintain a 7+ day study streak.</span>
|
||||
<span>Maintain a 7+ day streak — consistency adds 6% to your readiness index.</span>
|
||||
</li>
|
||||
<li className="flex items-start gap-2">
|
||||
<CheckCircle2 size={16} className="text-amber-400 shrink-0 mt-0.5" />
|
||||
<span>Reduce avg time-per-question below 40s with daily timed DPP sessions.</span>
|
||||
</li>
|
||||
</ul>
|
||||
</Card>
|
||||
|
||||
<Card className="p-6 bg-secondary/10 border-border/60 rounded-3xl space-y-4">
|
||||
<h3 className="text-base font-black text-muted-foreground uppercase tracking-wider flex items-center gap-2">
|
||||
<Zap size={16} className="text-[#00F0FF]" /> Quick Actions
|
||||
</h3>
|
||||
<div className="space-y-2.5">
|
||||
{[
|
||||
{ label: "Practice Physics DPP", href: "/dashboard/ai-coach/cbt-dpp", color: "text-[#00F0FF]" },
|
||||
{ label: "Review Mistake Queue", href: "/dashboard/ai-coach/mistakes", color: "text-red-400" },
|
||||
{ label: "View Exam Strategy", href: "/dashboard/ai-coach/strategy", color: "text-amber-400" },
|
||||
{ label: "College Predictor", href: "/dashboard/ai-coach/college-predictor", color: "text-emerald-400" },
|
||||
].map((a) => (
|
||||
<Link key={a.href} href={a.href}
|
||||
className="flex items-center justify-between p-2.5 rounded-xl border border-border/40 hover:border-[#00F0FF]/30 hover:bg-white/5 transition-all group text-sm font-bold">
|
||||
<span className={a.color}>{a.label}</span>
|
||||
<span className="text-muted-foreground group-hover:translate-x-1 transition-transform">→</span>
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -3,39 +3,133 @@
|
||||
import { ExamStrategyCard } from "@/components/AI/ExamStrategyCard"
|
||||
import { StudyTimeOptimizationCard } from "@/components/AI/StudyTimeOptimizationCard"
|
||||
import { Card, CardContent } from "@/components/ui/card"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { Zap, ArrowLeft, Clock, Target, Lightbulb, CheckCircle2 } from "lucide-react"
|
||||
import Link from "next/link"
|
||||
import { Zap, Clock, Target, Lightbulb, CheckCircle2, AlertTriangle, TrendingUp } from "lucide-react"
|
||||
|
||||
const NEG_MARKING_DATA = [
|
||||
{ subject: "Physics", attempts: 30, negMarks: 12, accuracy: 60 },
|
||||
{ subject: "Chemistry", attempts: 35, negMarks: 7, accuracy: 80 },
|
||||
{ subject: "Maths", attempts: 28, negMarks: 10, accuracy: 64 },
|
||||
]
|
||||
|
||||
export default function StrategyPage() {
|
||||
return (
|
||||
<div className="p-6 md:p-8 max-w-7xl mx-auto space-y-8 animate-in fade-in duration-500 min-h-screen">
|
||||
{/* Navigation Header */}
|
||||
<div className="p-6 md:p-8 space-y-8 max-w-7xl mx-auto animate-in fade-in duration-500 min-h-screen">
|
||||
{/* Page Header */}
|
||||
<div className="border-b border-border/50 pb-6">
|
||||
<h1 className="text-3xl md:text-5xl font-black tracking-tight flex items-center gap-3">
|
||||
<Zap className="text-[#00F0FF]" size={36} />
|
||||
Exam Strategy & Time Optimization
|
||||
</h1>
|
||||
<p className="text-muted-foreground text-base md:text-lg mt-1 font-medium max-w-3xl">
|
||||
Master time allocation per question, eliminate negative marking traps, and fine-tune your subject sequence for maximum score yield.
|
||||
</p>
|
||||
<div className="flex items-start justify-between gap-4 flex-wrap">
|
||||
<div>
|
||||
<h1 className="text-3xl md:text-4xl font-black tracking-tight flex items-center gap-3 mb-2">
|
||||
<span className="p-2.5 rounded-xl bg-[#00F0FF]/10 border border-[#00F0FF]/20">
|
||||
<Zap className="text-[#00F0FF]" size={28} />
|
||||
</span>
|
||||
Exam Strategy & Time Optimization
|
||||
</h1>
|
||||
<p className="text-muted-foreground text-base font-medium max-w-2xl pl-1">
|
||||
Master time allocation per question, eliminate negative marking traps, and fine-tune your subject sequence for maximum score yield.
|
||||
</p>
|
||||
</div>
|
||||
<span className="text-xs font-bold uppercase tracking-wider text-[#00F0FF] bg-[#00F0FF]/10 border border-[#00F0FF]/20 px-3 py-1.5 rounded-full flex items-center gap-1.5 shrink-0 self-start">
|
||||
<Zap size={12} /> AI Powered
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-8">
|
||||
<Card className="p-6 bg-secondary/10 border-border/60 rounded-3xl">
|
||||
<h2 className="text-2xl font-extrabold tracking-tight mb-4 flex items-center gap-2">
|
||||
<Target className="text-[#00F0FF]" size={24} /> Exam Day Time Strategy
|
||||
</h2>
|
||||
{/* KPI Strip */}
|
||||
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
|
||||
{[
|
||||
{ label: "Time Per Question", value: "45 sec", note: "Target: 38 sec", icon: <Clock size={18} className="text-red-400" />, color: "border-red-400/20 bg-red-400/5" },
|
||||
{ label: "Negative Marks Lost", value: "−29", note: "Last mock test", icon: <AlertTriangle size={18} className="text-amber-400" />, color: "border-amber-400/20 bg-amber-400/5" },
|
||||
{ label: "Attempted Questions", value: "93 / 120", note: "+7 vs last test", icon: <Target size={18} className="text-[#00F0FF]" />, color: "border-[#00F0FF]/20 bg-[#00F0FF]/5" },
|
||||
{ label: "Score Yield", value: "214", note: "Predicted score", icon: <TrendingUp size={18} className="text-green-400" />, color: "border-green-400/20 bg-green-400/5" },
|
||||
].map((kpi) => (
|
||||
<Card key={kpi.label} className={`rounded-2xl border ${kpi.color} hover:scale-[1.02] transition-transform`}>
|
||||
<CardContent className="p-5 flex flex-col gap-2">
|
||||
{kpi.icon}
|
||||
<div className="text-2xl font-black">{kpi.value}</div>
|
||||
<div className="text-xs font-semibold text-muted-foreground">{kpi.label}</div>
|
||||
<div className="text-[11px] font-bold text-muted-foreground/60">{kpi.note}</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Main Cards Row */}
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
||||
<div className="space-y-3">
|
||||
<div className="flex items-center gap-2 text-sm font-black uppercase tracking-wider text-muted-foreground">
|
||||
<Target size={16} className="text-[#00F0FF]" />
|
||||
Subject Sequence Strategy
|
||||
</div>
|
||||
<ExamStrategyCard />
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
<Card className="p-6 bg-secondary/10 border-border/60 rounded-3xl">
|
||||
<h2 className="text-2xl font-extrabold tracking-tight mb-4 flex items-center gap-2">
|
||||
<Clock className="text-amber-400" size={24} /> Study Time Optimization
|
||||
</h2>
|
||||
<div className="space-y-3">
|
||||
<div className="flex items-center gap-2 text-sm font-black uppercase tracking-wider text-muted-foreground">
|
||||
<Clock size={16} className="text-amber-400" />
|
||||
Daily Peak Hours Analysis
|
||||
</div>
|
||||
<StudyTimeOptimizationCard />
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Negative Marking Analysis */}
|
||||
<Card className="p-6 bg-secondary/10 border-border/60 rounded-3xl">
|
||||
<div className="flex items-center gap-2 mb-5">
|
||||
<AlertTriangle size={20} className="text-amber-400" />
|
||||
<h3 className="text-lg font-extrabold tracking-tight">Negative Marking Analysis</h3>
|
||||
<span className="ml-auto text-xs text-muted-foreground font-semibold">Last Mock Test · Aug 1</span>
|
||||
</div>
|
||||
<div className="space-y-4">
|
||||
{NEG_MARKING_DATA.map((row) => (
|
||||
<div key={row.subject} className="grid grid-cols-4 gap-4 items-center p-3 rounded-xl bg-white/5 border border-border/40">
|
||||
<div className="font-black text-sm">{row.subject}</div>
|
||||
<div className="text-center">
|
||||
<div className="text-sm font-black text-foreground">{row.attempts}</div>
|
||||
<div className="text-[11px] text-muted-foreground font-semibold">Attempted</div>
|
||||
</div>
|
||||
<div className="text-center">
|
||||
<div className="text-sm font-black text-red-400">−{row.negMarks}</div>
|
||||
<div className="text-[11px] text-muted-foreground font-semibold">Neg. Marks</div>
|
||||
</div>
|
||||
<div>
|
||||
<div className="flex justify-between text-xs font-bold mb-1">
|
||||
<span className="text-muted-foreground">Accuracy</span>
|
||||
<span className={row.accuracy >= 75 ? "text-green-400" : row.accuracy >= 60 ? "text-amber-400" : "text-red-400"}>{row.accuracy}%</span>
|
||||
</div>
|
||||
<div className="h-2 w-full bg-secondary rounded-full overflow-hidden">
|
||||
<div
|
||||
className={`h-full rounded-full ${row.accuracy >= 75 ? "bg-green-400" : row.accuracy >= 60 ? "bg-amber-400" : "bg-red-400"}`}
|
||||
style={{ width: `${row.accuracy}%` }}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<p className="text-xs text-muted-foreground mt-4 font-medium pl-1">
|
||||
<span className="font-black text-amber-400">Insight:</span> You're losing ~29 marks to wrong answers. Switching to "skip and revisit" for uncertain Physics questions could recover 15–20 marks.
|
||||
</p>
|
||||
</Card>
|
||||
|
||||
{/* Strategy Tips */}
|
||||
<Card className="p-6 bg-gradient-to-br from-[#00F0FF]/5 via-secondary/10 to-background border border-[#00F0FF]/20 rounded-3xl">
|
||||
<div className="flex items-center gap-2 mb-4">
|
||||
<Lightbulb size={20} className="text-yellow-400" />
|
||||
<h3 className="text-base font-black tracking-tight">AI Strategy Recommendations</h3>
|
||||
</div>
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
|
||||
{[
|
||||
{ tip: "Start with Chemistry (highest accuracy) to build momentum and bank easy marks in the first 40 minutes.", color: "text-green-400", label: "Opening Strategy" },
|
||||
{ tip: "Attempt Physics only if you are 80%+ confident per question. Otherwise, mark and skip — recover in last 10 min buffer.", color: "text-red-400", label: "Physics Guard" },
|
||||
{ tip: "Reserve 10–15 minutes at the end to revisit starred questions. Your review accuracy is historically 20% higher than first-attempt.", color: "text-[#00F0FF]", label: "Review Window" },
|
||||
].map((s) => (
|
||||
<div key={s.label} className="p-4 rounded-xl bg-white/5 border border-border/30 space-y-2">
|
||||
<div className={`text-xs font-black uppercase tracking-wider ${s.color}`}>{s.label}</div>
|
||||
<p className="text-sm text-muted-foreground font-medium leading-relaxed">{s.tip}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -3,62 +3,152 @@
|
||||
import { StudyPlanCard } from "@/components/AI/StudyPlanCard"
|
||||
import { WeeklyProgressReport } from "@/components/AI/WeeklyProgressReport"
|
||||
import { Card, CardContent } from "@/components/ui/card"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { Calendar, ArrowLeft, BookOpen, Clock, Target, CheckCircle2 } from "lucide-react"
|
||||
import Link from "next/link"
|
||||
import { Calendar, BookOpen, Clock, Target, CheckCircle2, TrendingUp, Flame } from "lucide-react"
|
||||
|
||||
export default function StudyPlanPage() {
|
||||
return (
|
||||
<div className="p-6 md:p-8 max-w-7xl mx-auto space-y-8 animate-in fade-in duration-500 min-h-screen">
|
||||
{/* Navigation Header */}
|
||||
<div className="p-6 md:p-8 space-y-8 max-w-7xl mx-auto animate-in fade-in duration-500 min-h-screen">
|
||||
{/* Page Header */}
|
||||
<div className="border-b border-border/50 pb-6">
|
||||
<h1 className="text-3xl md:text-5xl font-black tracking-tight flex items-center gap-3">
|
||||
<Calendar className="text-[#00F0FF]" size={36} />
|
||||
Personalized Daily & Weekly Study Plan
|
||||
</h1>
|
||||
<p className="text-muted-foreground text-base md:text-lg mt-1 font-medium max-w-3xl">
|
||||
AI-generated task schedule dynamically tailored to fix your weak areas and maintain optimal syllabus coverage.
|
||||
</p>
|
||||
<div className="flex items-start justify-between gap-4 flex-wrap">
|
||||
<div>
|
||||
<h1 className="text-3xl md:text-4xl font-black tracking-tight flex items-center gap-3 mb-2">
|
||||
<span className="p-2.5 rounded-xl bg-[#00F0FF]/10 border border-[#00F0FF]/20">
|
||||
<Calendar className="text-[#00F0FF]" size={28} />
|
||||
</span>
|
||||
Personalized Daily & Weekly Study Plan
|
||||
</h1>
|
||||
<p className="text-muted-foreground text-base font-medium max-w-2xl pl-1">
|
||||
AI-generated task schedule dynamically tailored to fix your weak areas and maintain optimal syllabus coverage.
|
||||
</p>
|
||||
</div>
|
||||
<span className="text-xs font-bold uppercase tracking-wider text-[#00F0FF] bg-[#00F0FF]/10 border border-[#00F0FF]/20 px-3 py-1.5 rounded-full flex items-center gap-1.5 shrink-0 self-start">
|
||||
<Calendar size={12} /> AI Powered
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* KPI Strip */}
|
||||
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
|
||||
{[
|
||||
{ label: "Tasks Today", value: "5", sub: "2 completed", icon: <CheckCircle2 size={18} className="text-green-400" />, color: "border-green-400/20 bg-green-400/5" },
|
||||
{ label: "Study Hours Today", value: "3.8 hrs", sub: "Target: 5 hrs", icon: <Clock size={18} className="text-[#00F0FF]" />, color: "border-[#00F0FF]/20 bg-[#00F0FF]/5" },
|
||||
{ label: "Syllabus Coverage", value: "64%", sub: "+8% this week", icon: <Target size={18} className="text-amber-400" />, color: "border-amber-400/20 bg-amber-400/5" },
|
||||
{ label: "Current Streak", value: "12 Days", sub: "Keep it up!", icon: <Flame size={18} className="text-orange-400" />, color: "border-orange-400/20 bg-orange-400/5" },
|
||||
].map((kpi) => (
|
||||
<Card key={kpi.label} className={`rounded-2xl border ${kpi.color} hover:scale-[1.02] transition-transform`}>
|
||||
<CardContent className="p-5 flex flex-col gap-2">
|
||||
{kpi.icon}
|
||||
<div className="text-2xl font-black">{kpi.value}</div>
|
||||
<div className="text-xs font-semibold text-muted-foreground uppercase tracking-wider">{kpi.label}</div>
|
||||
<div className="text-[11px] font-bold text-muted-foreground/60">{kpi.sub}</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 lg:grid-cols-3 gap-8">
|
||||
<div className="lg:col-span-2 space-y-8">
|
||||
<Card className="p-6 bg-secondary/10 border-border/60 rounded-3xl">
|
||||
<h2 className="text-2xl font-extrabold tracking-tight mb-4 flex items-center gap-2">
|
||||
<Clock className="text-[#00F0FF]" size={24} /> Today's Action Plan
|
||||
</h2>
|
||||
{/* Today's Schedule */}
|
||||
<div className="space-y-3">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-2 text-sm font-black uppercase tracking-wider text-muted-foreground">
|
||||
<Clock size={16} className="text-[#00F0FF]" />
|
||||
Today's Schedule
|
||||
</div>
|
||||
<span className="text-xs font-bold text-[#00F0FF]">
|
||||
{new Date().toLocaleDateString("en-IN", { weekday: "long", day: "numeric", month: "long" })}
|
||||
</span>
|
||||
</div>
|
||||
<StudyPlanCard />
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
<Card className="p-6 bg-secondary/10 border-border/60 rounded-3xl">
|
||||
<h2 className="text-2xl font-extrabold tracking-tight mb-4 flex items-center gap-2">
|
||||
<Target className="text-emerald-400" size={24} /> Weekly Syllabus Target
|
||||
</h2>
|
||||
{/* Weekly Overview */}
|
||||
<div className="space-y-3">
|
||||
<div className="flex items-center gap-2 text-sm font-black uppercase tracking-wider text-muted-foreground">
|
||||
<TrendingUp size={16} className="text-emerald-400" />
|
||||
Weekly Syllabus Progress
|
||||
</div>
|
||||
<WeeklyProgressReport />
|
||||
</div>
|
||||
|
||||
{/* Syllabus Coverage */}
|
||||
<Card className="p-6 bg-secondary/10 border-border/60 rounded-3xl">
|
||||
<div className="flex items-center gap-2 mb-5">
|
||||
<BookOpen size={20} className="text-[#00F0FF]" />
|
||||
<h3 className="text-lg font-extrabold tracking-tight">Subject Coverage Breakdown</h3>
|
||||
</div>
|
||||
<div className="space-y-4">
|
||||
{[
|
||||
{ subject: "Physics", covered: 58, total: 100, chapters: "21 / 29 chapters" },
|
||||
{ subject: "Chemistry", covered: 71, total: 100, chapters: "19 / 26 chapters" },
|
||||
{ subject: "Biology / Maths", covered: 64, total: 100, chapters: "32 / 52 topics" },
|
||||
].map((sub) => (
|
||||
<div key={sub.subject}>
|
||||
<div className="flex items-center justify-between mb-1.5">
|
||||
<span className="text-sm font-black">{sub.subject}</span>
|
||||
<div className="flex items-center gap-3 text-xs font-semibold text-muted-foreground">
|
||||
<span>{sub.chapters}</span>
|
||||
<span className={`font-black ${sub.covered >= 70 ? "text-green-400" : sub.covered >= 55 ? "text-amber-400" : "text-red-400"}`}>{sub.covered}%</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="h-2.5 w-full bg-secondary rounded-full overflow-hidden">
|
||||
<div
|
||||
className={`h-full rounded-full transition-all duration-700 ${sub.covered >= 70 ? "bg-green-400" : sub.covered >= 55 ? "bg-amber-400" : "bg-red-400"}`}
|
||||
style={{ width: `${sub.covered}%` }}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
{/* Sidebar Info */}
|
||||
{/* Sidebar */}
|
||||
<div className="space-y-6">
|
||||
<Card className="p-6 bg-gradient-to-br from-[#00F0FF]/10 via-secondary/20 to-background border border-[#00F0FF]/30 rounded-3xl space-y-4">
|
||||
<h3 className="text-xl font-black tracking-tight text-[#00F0FF] flex items-center gap-2">
|
||||
<BookOpen size={20} /> AI Scheduler Rules
|
||||
<h3 className="text-base font-black tracking-tight text-[#00F0FF] flex items-center gap-2">
|
||||
<BookOpen size={18} /> AI Scheduler Rules
|
||||
</h3>
|
||||
<ul className="space-y-3 text-sm font-medium text-muted-foreground">
|
||||
<li className="flex items-start gap-2">
|
||||
<CheckCircle2 size={16} className="text-[#00F0FF] shrink-0 mt-0.5" />
|
||||
<span><strong>High-Yield Prioritization:</strong> Focuses extra time on topics with heavy exam weightage.</span>
|
||||
<span><strong className="text-foreground">High-Yield Prioritization:</strong> Extra time on topics with heavy exam weightage.</span>
|
||||
</li>
|
||||
<li className="flex items-start gap-2">
|
||||
<CheckCircle2 size={16} className="text-[#00F0FF] shrink-0 mt-0.5" />
|
||||
<span><strong>Error Interleaving:</strong> Integrates recent incorrect questions directly into study blocks.</span>
|
||||
<span><strong className="text-foreground">Error Interleaving:</strong> Recent incorrect questions are integrated directly into study blocks.</span>
|
||||
</li>
|
||||
<li className="flex items-start gap-2">
|
||||
<CheckCircle2 size={16} className="text-[#00F0FF] shrink-0 mt-0.5" />
|
||||
<span><strong>Burnout Prevention:</strong> Recommends 10-minute micro-breaks between intensive problem sets.</span>
|
||||
<span><strong className="text-foreground">Burnout Prevention:</strong> 10-minute micro-breaks recommended between intensive problem sets.</span>
|
||||
</li>
|
||||
<li className="flex items-start gap-2">
|
||||
<CheckCircle2 size={16} className="text-[#00F0FF] shrink-0 mt-0.5" />
|
||||
<span><strong className="text-foreground">Spaced Repetition:</strong> SM-2 algorithm reschedules mistake reviews at optimal intervals.</span>
|
||||
</li>
|
||||
</ul>
|
||||
</Card>
|
||||
|
||||
<Card className="p-5 bg-secondary/10 border-border/60 rounded-3xl space-y-3">
|
||||
<h3 className="text-sm font-black uppercase tracking-wider text-muted-foreground">Upcoming This Week</h3>
|
||||
<div className="space-y-2">
|
||||
{[
|
||||
{ day: "Tue, Aug 5", task: "Full-Length Mock — JEE Mains Pattern", type: "Test", color: "text-purple-400 bg-purple-400/10" },
|
||||
{ day: "Wed, Aug 6", task: "Rotational Dynamics Intensive Drill", type: "Practice", color: "text-[#00F0FF] bg-[#00F0FF]/10" },
|
||||
{ day: "Thu, Aug 7", task: "Chemistry Organic — Mistake Review", type: "Review", color: "text-red-400 bg-red-400/10" },
|
||||
{ day: "Fri, Aug 8", task: "Optics + Wave Optics Revision", type: "Revision", color: "text-yellow-400 bg-yellow-400/10" },
|
||||
].map((item) => (
|
||||
<div key={item.day} className="flex items-center gap-2 p-2.5 rounded-lg bg-white/5 border border-border/30">
|
||||
<span className={`text-[10px] font-black px-2 py-0.5 rounded-md shrink-0 ${item.color}`}>{item.type}</span>
|
||||
<div className="min-w-0">
|
||||
<div className="text-xs font-bold truncate">{item.task}</div>
|
||||
<div className="text-[11px] text-muted-foreground">{item.day}</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,24 +1,152 @@
|
||||
"use client"
|
||||
|
||||
import { StudyTimeOptimizationCard } from "@/components/AI/StudyTimeOptimizationCard"
|
||||
import { ArrowLeft, Clock } from "lucide-react"
|
||||
import { Clock, Zap, Moon, Sun, AlertCircle, ArrowRight, BarChart2 } from "lucide-react"
|
||||
import { Card, CardContent } from "@/components/ui/card"
|
||||
import Link from "next/link"
|
||||
|
||||
const INSIGHTS = [
|
||||
{
|
||||
icon: <Sun size={16} className="text-yellow-400" />,
|
||||
title: "Peak Cognitive Window",
|
||||
desc: "2 PM – 5 PM is your highest-efficiency block. Reserve this for new concepts, hard problems, and mock tests.",
|
||||
color: "border-yellow-400/20 bg-yellow-400/5",
|
||||
},
|
||||
{
|
||||
icon: <Moon size={16} className="text-indigo-400" />,
|
||||
title: "Evening Consolidation",
|
||||
desc: "9–10 PM is ideal for light revision and flashcard review — this window solidifies short-term to long-term memory.",
|
||||
color: "border-indigo-400/20 bg-indigo-400/5",
|
||||
},
|
||||
{
|
||||
icon: <AlertCircle size={16} className="text-red-400" />,
|
||||
title: "Avoid After 11 PM",
|
||||
desc: "Cognitive fatigue significantly increases error rate. Solving new concepts late at night is counterproductive.",
|
||||
color: "border-red-400/20 bg-red-400/5",
|
||||
},
|
||||
]
|
||||
|
||||
const HOURLY_EFFICIENCY = [
|
||||
{ slot: "6–9 AM", pct: 60, label: "Morning" },
|
||||
{ slot: "9–12 PM", pct: 72, label: "Mid-Morning" },
|
||||
{ slot: "12–2 PM", pct: 55, label: "Post-Lunch dip" },
|
||||
{ slot: "2–5 PM", pct: 95, label: "Peak ★", highlight: true },
|
||||
{ slot: "5–7 PM", pct: 82, label: "Afternoon" },
|
||||
{ slot: "7–9 PM", pct: 74, label: "Evening" },
|
||||
{ slot: "9–11 PM", pct: 60, label: "Night Revision" },
|
||||
{ slot: "11 PM+", pct: 32, label: "Avoid" },
|
||||
]
|
||||
|
||||
export default function TimeOptimizationPage() {
|
||||
return (
|
||||
<div className="p-6 md:p-8 max-w-7xl mx-auto space-y-8 animate-in fade-in duration-500 min-h-screen">
|
||||
{/* Page Header */}
|
||||
<div className="border-b border-border/50 pb-6">
|
||||
<h1 className="text-3xl md:text-5xl font-black tracking-tight flex items-center gap-3">
|
||||
<Clock className="text-[#00F0FF]" size={36} />
|
||||
Study Time Optimization
|
||||
</h1>
|
||||
<p className="text-muted-foreground text-base md:text-lg mt-1 font-medium max-w-3xl">
|
||||
Detailed metrics on your productive hours and stamina blocks.
|
||||
</p>
|
||||
<div className="flex items-start justify-between gap-4 flex-wrap">
|
||||
<div>
|
||||
<h1 className="text-3xl md:text-4xl font-black tracking-tight flex items-center gap-3 mb-2">
|
||||
<span className="p-2.5 rounded-xl bg-[#00F0FF]/10 border border-[#00F0FF]/20">
|
||||
<Clock className="text-[#00F0FF]" size={28} />
|
||||
</span>
|
||||
Study Time Optimization
|
||||
</h1>
|
||||
<p className="text-muted-foreground text-base font-medium max-w-2xl pl-1">
|
||||
Understand your peak productive hours and schedule your hardest topics in your highest-efficiency windows.
|
||||
</p>
|
||||
</div>
|
||||
<span className="text-xs font-bold uppercase tracking-wider text-[#00F0FF] bg-[#00F0FF]/10 border border-[#00F0FF]/20 px-3 py-1.5 rounded-full flex items-center gap-1.5 shrink-0 self-start">
|
||||
<Zap size={12} /> Chronobiology Analytics
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="max-w-4xl mx-auto">
|
||||
<StudyTimeOptimizationCard />
|
||||
{/* KPI Strip */}
|
||||
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
|
||||
{[
|
||||
{ label: "Peak Efficiency", value: "95%", sub: "2–5 PM window", color: "border-yellow-400/20 bg-yellow-400/5", icon: <Sun size={18} className="text-yellow-400" /> },
|
||||
{ label: "Study Hrs Today", value: "3.8 h", sub: "Target: 6 h", color: "border-[#00F0FF]/20 bg-[#00F0FF]/5", icon: <Clock size={18} className="text-[#00F0FF]" /> },
|
||||
{ label: "Morning Block", value: "60%", sub: "6–9 AM", color: "border-indigo-400/20 bg-indigo-400/5", icon: <Moon size={18} className="text-indigo-400" /> },
|
||||
{ label: "Evening Block", value: "74%", sub: "7–9 PM", color: "border-green-400/20 bg-green-400/5", icon: <BarChart2 size={18} className="text-green-400" /> },
|
||||
].map(kpi => (
|
||||
<Card key={kpi.label} className={`rounded-2xl border ${kpi.color}`}>
|
||||
<CardContent className="p-4 flex flex-col gap-2">
|
||||
{kpi.icon}
|
||||
<div className="text-2xl font-black">{kpi.value}</div>
|
||||
<div className="text-xs font-semibold text-muted-foreground uppercase tracking-wider">{kpi.label}</div>
|
||||
<div className="text-[11px] font-bold text-muted-foreground/60">{kpi.sub}</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 lg:grid-cols-3 gap-8">
|
||||
{/* Chart — 2/3 */}
|
||||
<div className="lg:col-span-2 space-y-4">
|
||||
<div className="flex items-center gap-2 text-sm font-black uppercase tracking-wider text-muted-foreground">
|
||||
<BarChart2 size={16} className="text-[#00F0FF]" />
|
||||
Hourly Efficiency Profile
|
||||
</div>
|
||||
<StudyTimeOptimizationCard />
|
||||
</div>
|
||||
|
||||
{/* Right Panel */}
|
||||
<div className="space-y-5">
|
||||
{/* Slot breakdown table */}
|
||||
<Card className="bg-secondary/10 border-border/60 rounded-2xl">
|
||||
<CardContent className="p-5 space-y-3">
|
||||
<h3 className="text-sm font-black uppercase tracking-wider text-muted-foreground">Time Slot Breakdown</h3>
|
||||
<div className="space-y-2">
|
||||
{HOURLY_EFFICIENCY.map(slot => (
|
||||
<div key={slot.slot} className={`flex items-center gap-3 p-2 rounded-lg ${slot.highlight ? "bg-yellow-400/10 border border-yellow-400/20" : "bg-white/5"}`}>
|
||||
<div className="w-16 text-xs font-bold text-muted-foreground shrink-0">{slot.slot}</div>
|
||||
<div className="flex-1 h-1.5 bg-secondary rounded-full overflow-hidden">
|
||||
<div
|
||||
className={`h-full rounded-full ${slot.pct >= 80 ? (slot.highlight ? "bg-yellow-400" : "bg-green-400") : slot.pct >= 60 ? "bg-[#00F0FF]" : "bg-red-400"}`}
|
||||
style={{ width: `${slot.pct}%` }}
|
||||
/>
|
||||
</div>
|
||||
<div className={`text-xs font-black w-8 text-right shrink-0 ${slot.highlight ? "text-yellow-400" : "text-muted-foreground"}`}>{slot.pct}%</div>
|
||||
{slot.highlight && <span className="text-[10px] font-black text-yellow-400 shrink-0">PEAK</span>}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* AI Insights */}
|
||||
<div className="space-y-3">
|
||||
{INSIGHTS.map(insight => (
|
||||
<Card key={insight.title} className={`rounded-2xl border ${insight.color}`}>
|
||||
<CardContent className="p-4 flex items-start gap-3">
|
||||
<div className="mt-0.5 shrink-0">{insight.icon}</div>
|
||||
<div>
|
||||
<p className="text-sm font-black">{insight.title}</p>
|
||||
<p className="text-xs text-muted-foreground font-medium mt-0.5">{insight.desc}</p>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<Card className="bg-gradient-to-br from-[#00F0FF]/10 via-secondary/20 to-background border border-[#00F0FF]/20 rounded-2xl">
|
||||
<CardContent className="p-5 space-y-3">
|
||||
<h3 className="text-sm font-black uppercase tracking-wider text-[#00F0FF]">Apply Insights</h3>
|
||||
<div className="space-y-2">
|
||||
{[
|
||||
{ label: "Schedule Today's Study Plan", href: "/dashboard/ai-coach/study-plan" },
|
||||
{ label: "Check Exam Readiness", href: "/dashboard/ai-coach/readiness" },
|
||||
{ label: "Launch a Practice Test Now", href: "/dashboard/ai-coach/cbt-dpp" },
|
||||
].map(link => (
|
||||
<Link key={link.href} href={link.href}
|
||||
className="flex items-center justify-between text-sm font-bold text-muted-foreground hover:text-foreground py-2 border-b border-border/30 last:border-0 transition-colors group">
|
||||
{link.label}
|
||||
<ArrowRight size={14} className="group-hover:translate-x-1 transition-transform" />
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -1,11 +1,52 @@
|
||||
"use client"
|
||||
|
||||
import { TutorChat } from "@/components/AI/TutorChat"
|
||||
import { BrainCircuit, Sparkles, MessageSquare, Lightbulb } from "lucide-react"
|
||||
import { Card, CardContent } from "@/components/ui/card"
|
||||
|
||||
const CAPABILITIES = [
|
||||
{ icon: <Lightbulb size={16} className="text-yellow-400" />, label: "Concept Explanations", desc: "Step-by-step breakdown of Physics, Chemistry, Maths & Biology topics" },
|
||||
{ icon: <Sparkles size={16} className="text-[#00F0FF]" />, label: "Problem Solving", desc: "Full worked solutions with formulas, derivations and shortcuts" },
|
||||
{ icon: <MessageSquare size={16} className="text-green-400" />, label: "Doubt Clearing", desc: "Ask follow-up questions — the AI remembers context within the session" },
|
||||
]
|
||||
|
||||
export default function TutorChatPage() {
|
||||
return (
|
||||
<div className="h-[calc(100vh-80px)] w-full flex flex-col p-4 md:p-6 animate-in fade-in duration-500">
|
||||
<div className="flex-1 rounded-3xl overflow-hidden border border-border/50 shadow-xl bg-background">
|
||||
<div className="p-6 md:p-8 space-y-6 max-w-7xl mx-auto animate-in fade-in duration-500 min-h-screen">
|
||||
{/* Page Header */}
|
||||
<div className="border-b border-border/50 pb-6">
|
||||
<div className="flex items-start justify-between gap-4 flex-wrap">
|
||||
<div>
|
||||
<h1 className="text-3xl md:text-4xl font-black tracking-tight flex items-center gap-3 mb-2">
|
||||
<span className="p-2.5 rounded-xl bg-[#00F0FF]/10 border border-[#00F0FF]/20">
|
||||
<BrainCircuit className="text-[#00F0FF]" size={28} />
|
||||
</span>
|
||||
AI Tutor — 24/7 Doubt Solver
|
||||
</h1>
|
||||
<p className="text-muted-foreground text-base font-medium max-w-2xl pl-1">
|
||||
Ask any JEE or NEET concept, get step-by-step explanations, formula derivations, and solved examples — instantly.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Capabilities Strip */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
|
||||
{CAPABILITIES.map(c => (
|
||||
<Card key={c.label} className="border-border/50 bg-secondary/10 rounded-2xl">
|
||||
<CardContent className="p-4 flex items-start gap-3">
|
||||
<div className="mt-0.5 shrink-0">{c.icon}</div>
|
||||
<div>
|
||||
<div className="text-sm font-black">{c.label}</div>
|
||||
<div className="text-xs text-muted-foreground font-medium mt-0.5">{c.desc}</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Chat — full height */}
|
||||
<div className="flex-1 rounded-3xl overflow-hidden border border-border/50 shadow-xl bg-background" style={{ minHeight: "560px" }}>
|
||||
<TutorChat />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,24 +1,129 @@
|
||||
"use client"
|
||||
|
||||
import { WeeklyProgressReport } from "@/components/AI/WeeklyProgressReport"
|
||||
import { ArrowLeft, Target } from "lucide-react"
|
||||
import { Target, TrendingUp, Calendar, Zap, ArrowRight, BookOpen, CheckCircle2 } from "lucide-react"
|
||||
import { Card, CardContent } from "@/components/ui/card"
|
||||
import Link from "next/link"
|
||||
|
||||
const STATS = [
|
||||
{ label: "Avg. Accuracy", value: "85%", delta: "+6%", positive: true },
|
||||
{ label: "Days Active", value: "7 / 7", delta: "Perfect week", positive: true },
|
||||
{ label: "Questions Solved", value: "214", delta: "+38 vs last week", positive: true },
|
||||
{ label: "Topics Covered", value: "12", delta: "3 remaining", positive: false },
|
||||
]
|
||||
|
||||
const SYLLABUS_PROGRESS = [
|
||||
{ subject: "Physics", completed: 42, total: 60, percentage: 70, color: "bg-blue-500", text: "text-blue-500" },
|
||||
{ subject: "Chemistry", completed: 35, total: 55, percentage: 63, color: "bg-amber-500", text: "text-amber-500" },
|
||||
{ subject: "Mathematics", completed: 50, total: 65, percentage: 76, color: "bg-purple-500", text: "text-purple-500" },
|
||||
{ subject: "Biology", completed: 20, total: 40, percentage: 50, color: "bg-emerald-500", text: "text-emerald-500" },
|
||||
]
|
||||
|
||||
export default function WeeklyProgressPage() {
|
||||
return (
|
||||
<div className="p-6 md:p-8 max-w-7xl mx-auto space-y-8 animate-in fade-in duration-500 min-h-screen">
|
||||
{/* Page Header */}
|
||||
<div className="border-b border-border/50 pb-6">
|
||||
<h1 className="text-3xl md:text-5xl font-black tracking-tight flex items-center gap-3">
|
||||
<Target className="text-[#00F0FF]" size={36} />
|
||||
Weekly Progress Overview
|
||||
</h1>
|
||||
<p className="text-muted-foreground text-base md:text-lg mt-1 font-medium max-w-3xl">
|
||||
Track your syllabus completion goals and weekly milestones.
|
||||
</p>
|
||||
<div className="flex items-start justify-between gap-4 flex-wrap">
|
||||
<div>
|
||||
<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">
|
||||
<Target className="text-[#00F0FF]" size={28} />
|
||||
</span>
|
||||
Weekly Progress Overview
|
||||
</h1>
|
||||
<p className="text-muted-foreground text-base font-medium max-w-2xl pl-1">
|
||||
Track your syllabus completion goals, study streaks, and weekly milestones at a glance.
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex items-center gap-2 shrink-0">
|
||||
<span className="text-xs font-bold uppercase tracking-wider text-[#00F0FF] bg-[#00F0FF]/10 border border-[#00F0FF]/20 px-3 py-1.5 rounded-full flex items-center gap-1.5">
|
||||
<Zap size={12} /> Live Analytics
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="max-w-4xl mx-auto">
|
||||
<WeeklyProgressReport />
|
||||
{/* KPI Stats Row */}
|
||||
<div className="grid grid-cols-2 lg:grid-cols-4 gap-4">
|
||||
{STATS.map((stat) => (
|
||||
<Card key={stat.label} className="bg-secondary/20 border-border/60 rounded-2xl">
|
||||
<CardContent className="p-4">
|
||||
<p className="text-xs font-bold uppercase tracking-wider text-muted-foreground mb-1">{stat.label}</p>
|
||||
<p className="text-2xl font-black tracking-tight">{stat.value}</p>
|
||||
<p className={`text-xs font-bold mt-1 ${stat.positive ? "text-emerald-400" : "text-amber-400"}`}>
|
||||
{stat.delta}
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Main Content Grid */}
|
||||
<div className="grid grid-cols-1 lg:grid-cols-3 gap-8">
|
||||
{/* Weekly Bar Chart */}
|
||||
<div className="lg:col-span-2 space-y-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<h2 className="text-xl font-extrabold tracking-tight flex items-center gap-2">
|
||||
<Calendar size={20} className="text-[#00F0FF]" /> This Week at a Glance
|
||||
</h2>
|
||||
</div>
|
||||
<WeeklyProgressReport />
|
||||
</div>
|
||||
|
||||
{/* Side Panel */}
|
||||
<div className="space-y-6">
|
||||
{/* Syllabus Progress */}
|
||||
<div className="space-y-3">
|
||||
<h2 className="text-xl font-extrabold tracking-tight flex items-center gap-2">
|
||||
<BookOpen size={20} className="text-purple-400" /> Syllabus Progress
|
||||
</h2>
|
||||
<Card className="bg-secondary/10 border-border/60 rounded-2xl">
|
||||
<CardContent className="p-5 space-y-4">
|
||||
{SYLLABUS_PROGRESS.map((item) => (
|
||||
<div key={item.subject} className="space-y-1.5">
|
||||
<div className="flex items-center justify-between text-sm">
|
||||
<span className="font-bold">{item.subject}</span>
|
||||
<span className={`font-black ${item.text}`}>{item.percentage}%</span>
|
||||
</div>
|
||||
<div className="h-2 w-full bg-secondary rounded-full overflow-hidden">
|
||||
<div
|
||||
className={`h-full ${item.color} rounded-full transition-all duration-500`}
|
||||
style={{ width: `${item.percentage}%` }}
|
||||
/>
|
||||
</div>
|
||||
<div className="text-[10px] text-muted-foreground font-semibold text-right">
|
||||
{item.completed} / {item.total} chapters
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
{/* Weekly Goals */}
|
||||
<Card className="bg-gradient-to-br from-[#00F0FF]/10 via-secondary/20 to-background border border-[#00F0FF]/20 rounded-2xl">
|
||||
<CardContent className="p-5 space-y-3">
|
||||
<h3 className="text-sm font-black uppercase tracking-wider text-[#00F0FF]">Weekly Goals</h3>
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-start gap-2 p-2 rounded-lg bg-white/5">
|
||||
<CheckCircle2 size={16} className="text-green-400 mt-0.5 shrink-0" />
|
||||
<div>
|
||||
<div className="text-xs font-bold">Complete 200 Questions</div>
|
||||
<div className="text-[10px] text-muted-foreground">Goal Met (214/200)</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-start gap-2 p-2 rounded-lg bg-white/5 opacity-70">
|
||||
<div className="w-4 h-4 rounded-full border-2 border-muted-foreground mt-0.5 shrink-0" />
|
||||
<div>
|
||||
<div className="text-xs font-bold">Take 1 Full Mock Test</div>
|
||||
<div className="text-[10px] text-muted-foreground">Scheduled for Sunday</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -31,10 +31,19 @@ export default function AnalyticsPage() {
|
||||
}
|
||||
|
||||
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>
|
||||
<h1 className="text-3xl font-bold tracking-tight">Performance Analytics</h1>
|
||||
<p className="text-muted-foreground mt-1 font-medium">Deep AI-driven insights into your study patterns and test trajectories.</p>
|
||||
<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 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">
|
||||
<LineChartIcon className="text-[#00F0FF]" size={28} />
|
||||
</span>
|
||||
Performance Analytics
|
||||
</h1>
|
||||
<p className="text-muted-foreground text-base font-medium max-w-2xl pl-1">
|
||||
Deep AI-driven insights into your study patterns and test trajectories.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import { useState } from "react"
|
||||
import { Card, CardContent, CardHeader, CardTitle, CardFooter } from "@/components/ui/card"
|
||||
import { MessageSquare, ThumbsUp, ChevronUp, Search, Image as ImageIcon, CheckCircle, Lock } from "lucide-react"
|
||||
import { MessageSquare, ThumbsUp, ChevronUp, Search, Image as ImageIcon, CheckCircle, Lock, X, Send } from "lucide-react"
|
||||
import { useActiveUser } from "@/hooks/useActiveUser"
|
||||
|
||||
interface Doubt {
|
||||
@@ -21,9 +21,11 @@ interface Doubt {
|
||||
}
|
||||
|
||||
export default function CommunityPage() {
|
||||
const { hasAccessTo } = useActiveUser()
|
||||
const { hasAccessTo, loading } = useActiveUser()
|
||||
const [activeFilter, setActiveFilter] = useState<'all' | 'my-doubts' | 'solved' | 'unsolved'>('all')
|
||||
const [searchQuery, setSearchQuery] = useState('')
|
||||
const [isAskModalOpen, setIsAskModalOpen] = useState(false)
|
||||
const [expandedDoubtId, setExpandedDoubtId] = useState<string | null>(null)
|
||||
|
||||
const mockDoubts: Doubt[] = [
|
||||
{
|
||||
@@ -78,6 +80,15 @@ export default function CommunityPage() {
|
||||
return true
|
||||
})
|
||||
|
||||
if (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 Doubt Forum...</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
if (!hasAccessTo("doubt-resolution-community")) {
|
||||
return (
|
||||
<div className="p-8 h-full flex flex-col items-center justify-center text-center animate-in fade-in zoom-in duration-500 max-w-2xl mx-auto">
|
||||
@@ -99,16 +110,23 @@ export default function CommunityPage() {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="p-8 space-y-6 animate-in fade-in slide-in-from-bottom-4 duration-500 max-w-5xl mx-auto">
|
||||
<div className="p-8 space-y-6 animate-in fade-in slide-in-from-bottom-4 duration-500 max-w-7xl mx-auto">
|
||||
<div className="flex flex-col md:flex-row justify-between items-start md:items-end gap-4">
|
||||
<div>
|
||||
<h1 className="text-3xl font-bold tracking-tight text-foreground flex items-center gap-3">
|
||||
<MessageSquare className="text-[#00F0FF]" size={32} />
|
||||
<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">
|
||||
<MessageSquare className="text-[#00F0FF]" size={28} />
|
||||
</span>
|
||||
Doubt Forum
|
||||
</h1>
|
||||
<p className="text-muted-foreground mt-2 font-medium">Ask questions, share knowledge, and learn together with the community.</p>
|
||||
<p className="text-muted-foreground text-base font-medium max-w-2xl pl-1">
|
||||
Ask questions, share knowledge, and learn together with the community.
|
||||
</p>
|
||||
</div>
|
||||
<button className="px-5 py-2.5 bg-[#00F0FF] text-black font-bold rounded-xl shadow-[0_0_15px_-3px_rgba(0,240,255,0.4)] hover:bg-[#00d0dd] transition-colors">
|
||||
<button
|
||||
onClick={() => setIsAskModalOpen(true)}
|
||||
className="px-5 py-2.5 bg-[#00F0FF] text-black font-bold rounded-xl shadow-[0_0_15px_-3px_rgba(0,240,255,0.4)] hover:bg-[#00d0dd] transition-colors"
|
||||
>
|
||||
Ask a Doubt
|
||||
</button>
|
||||
</div>
|
||||
@@ -179,12 +197,46 @@ export default function CommunityPage() {
|
||||
<CheckCircle size={14} /> Solved
|
||||
</span>
|
||||
)}
|
||||
<div className="flex items-center gap-1.5 text-muted-foreground hover:text-foreground transition-colors text-sm font-medium">
|
||||
<button
|
||||
onClick={() => setExpandedDoubtId(expandedDoubtId === doubt.id ? null : doubt.id)}
|
||||
className="flex items-center gap-1.5 text-muted-foreground hover:text-foreground transition-colors text-sm font-medium"
|
||||
>
|
||||
<MessageSquare size={16} />
|
||||
{doubt.replies} Replies
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Expanded Replies Section */}
|
||||
{expandedDoubtId === doubt.id && (
|
||||
<div className="mt-6 pt-4 border-t border-border space-y-4 animate-in fade-in duration-300">
|
||||
<div className="flex gap-3">
|
||||
<div className="h-8 w-8 shrink-0 rounded-full bg-[#00F0FF]/10 text-[#00F0FF] flex items-center justify-center text-xs font-bold border border-[#00F0FF]/30">
|
||||
AI
|
||||
</div>
|
||||
<div className="bg-secondary/40 rounded-2xl rounded-tl-none p-4 text-sm flex-1">
|
||||
<div className="font-bold mb-1 flex items-center gap-2">
|
||||
PragatiVeer AI
|
||||
<span className="text-[10px] text-emerald-500 bg-emerald-500/10 px-1.5 py-0.5 rounded uppercase tracking-wider">Expert</span>
|
||||
</div>
|
||||
<p className="text-muted-foreground">
|
||||
This is a great question! When the car accelerates, the engine applies a torque to the wheel, making it want to spin faster. If there were no friction, the bottom of the tire would slip backward relative to the road. Therefore, static friction acts in the forward direction on the tire to oppose this backward slipping motion.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex gap-3 mt-4 items-center">
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Add a reply..."
|
||||
className="flex-1 bg-background border border-border rounded-xl px-4 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-[#00F0FF]/50"
|
||||
/>
|
||||
<button className="p-2 bg-[#00F0FF] text-black rounded-xl hover:bg-[#00d0dd] transition-colors">
|
||||
<Send size={16} />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
@@ -196,6 +248,77 @@ export default function CommunityPage() {
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{/* Ask Doubt Modal */}
|
||||
{isAskModalOpen && (
|
||||
<div className="fixed inset-0 z-50 flex items-center justify-center p-4 bg-background/80 backdrop-blur-sm animate-in fade-in duration-200">
|
||||
<div className="bg-background border border-border w-full max-w-2xl rounded-2xl shadow-2xl overflow-hidden animate-in zoom-in-95 duration-200">
|
||||
<div className="flex justify-between items-center p-6 border-b border-border bg-secondary/30">
|
||||
<h2 className="text-2xl font-bold flex items-center gap-2">
|
||||
<MessageSquare className="text-[#00F0FF]" /> Ask a Doubt
|
||||
</h2>
|
||||
<button
|
||||
onClick={() => setIsAskModalOpen(false)}
|
||||
className="text-muted-foreground hover:text-foreground hover:bg-secondary p-2 rounded-lg transition-colors"
|
||||
>
|
||||
<X size={20} />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="p-6 space-y-4">
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div className="space-y-1.5">
|
||||
<label className="text-sm font-medium text-muted-foreground">Subject</label>
|
||||
<select className="w-full bg-background border border-border rounded-xl px-4 py-2.5 text-sm focus:outline-none focus:ring-2 focus:ring-[#00F0FF]/50">
|
||||
<option>Physics</option>
|
||||
<option>Chemistry</option>
|
||||
<option>Mathematics</option>
|
||||
<option>Biology</option>
|
||||
</select>
|
||||
</div>
|
||||
<div className="space-y-1.5">
|
||||
<label className="text-sm font-medium text-muted-foreground">Topic</label>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="e.g. Rotational Mechanics"
|
||||
className="w-full bg-background border border-border rounded-xl px-4 py-2.5 text-sm focus:outline-none focus:ring-2 focus:ring-[#00F0FF]/50"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-1.5">
|
||||
<label className="text-sm font-medium text-muted-foreground">Question Title</label>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="What exactly are you stuck on?"
|
||||
className="w-full bg-background border border-border rounded-xl px-4 py-2.5 text-sm focus:outline-none focus:ring-2 focus:ring-[#00F0FF]/50"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="space-y-1.5">
|
||||
<label className="text-sm font-medium text-muted-foreground">Detailed Description</label>
|
||||
<textarea
|
||||
rows={4}
|
||||
placeholder="Explain your approach, what you've tried, and where you're getting stuck..."
|
||||
className="w-full bg-background border border-border rounded-xl px-4 py-3 text-sm focus:outline-none focus:ring-2 focus:ring-[#00F0FF]/50 resize-none"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="pt-2 border-t border-border flex justify-between items-center">
|
||||
<button className="flex items-center gap-2 text-muted-foreground hover:text-[#00F0FF] transition-colors text-sm font-medium px-3 py-2 rounded-lg hover:bg-secondary">
|
||||
<ImageIcon size={18} /> Attach Image
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setIsAskModalOpen(false)}
|
||||
className="flex items-center gap-2 px-6 py-2.5 bg-[#00F0FF] text-black font-bold rounded-xl shadow-[0_0_15px_-3px_rgba(0,240,255,0.4)] hover:bg-[#00d0dd] transition-colors"
|
||||
>
|
||||
Post Doubt <Send size={16} />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { Card, CardContent } from "@/components/ui/card"
|
||||
import { BrainCircuit, Play, CheckCircle2, Download, Filter, Lock, FileText, Eye, ShieldAlert, X, Sparkles } from "lucide-react"
|
||||
import { BrainCircuit, Play, CheckCircle2, Download, Filter, Lock, FileText, Eye, ShieldAlert, X, Sparkles, Share2, Maximize2, Minimize2 } from "lucide-react"
|
||||
|
||||
import { useEffect, useState } from "react"
|
||||
import Link from "next/link"
|
||||
@@ -15,6 +15,8 @@ export default function DPPPage() {
|
||||
const [loading, setLoading] = useState(true)
|
||||
const [dateFilter, setDateFilter] = useState("")
|
||||
const [selectedPdfDpp, setSelectedPdfDpp] = useState<any | null>(null)
|
||||
const [isFullScreen, setIsFullScreen] = useState(false)
|
||||
const [copiedShare, setCopiedShare] = useState(false)
|
||||
|
||||
useEffect(() => {
|
||||
async function fetchDpps() {
|
||||
@@ -90,7 +92,7 @@ export default function DPPPage() {
|
||||
const cleanTitle = (title: string) => title.replace(/\s*\(ExamEnum\.[^)]+\)/, '');
|
||||
|
||||
return (
|
||||
<div className="p-8 space-y-8 animate-in fade-in slide-in-from-bottom-4 duration-500 max-w-5xl mx-auto">
|
||||
<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-center justify-between p-8 bg-gradient-to-r from-secondary/50 to-background border border-border rounded-2xl shadow-sm gap-6">
|
||||
<div>
|
||||
<h1 className="text-3xl font-bold tracking-tight mb-2">Daily Practice Problems (DPP)</h1>
|
||||
@@ -118,8 +120,17 @@ export default function DPPPage() {
|
||||
<Lock size={12}/> Pro
|
||||
</div>
|
||||
</div>
|
||||
<Button variant="outline" className="gap-2 rounded-xl text-sm font-bold">
|
||||
<Download size={16} /> Export as PDF <span className="ml-1 text-[10px] uppercase tracking-wider bg-primary/10 text-primary px-1.5 py-0.5 rounded-md">Pro</span>
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={() => {
|
||||
navigator.clipboard.writeText(window.location.origin + '/dashboard/dpp');
|
||||
setCopiedShare(true);
|
||||
setTimeout(() => setCopiedShare(false), 2000);
|
||||
}}
|
||||
className="gap-2 rounded-xl text-sm font-bold border-primary/30 hover:bg-primary/10 text-primary transition-all shadow-sm"
|
||||
>
|
||||
{copiedShare ? <CheckCircle2 size={16} className="text-green-500" /> : <Share2 size={16} />}
|
||||
{copiedShare ? "Share Link Copied!" : "Share DPPs Link"}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@@ -216,18 +227,18 @@ export default function DPPPage() {
|
||||
|
||||
{/* ── PROTECTED PDF READER MODAL (FREE TIER) ── */}
|
||||
{selectedPdfDpp && (
|
||||
<div className="fixed inset-0 z-50 bg-black/80 backdrop-blur-md flex items-center justify-center p-4 animate-in fade-in duration-300">
|
||||
<div className="bg-background border border-border rounded-3xl w-full max-w-4xl max-h-[90vh] flex flex-col overflow-hidden shadow-2xl">
|
||||
<div className={`fixed inset-0 z-50 bg-black/90 backdrop-blur-md flex items-center justify-center ${isFullScreen ? 'p-0' : 'p-4'} animate-in fade-in duration-300`}>
|
||||
<div className={`bg-background border border-border ${isFullScreen ? 'w-screen h-screen max-w-none max-h-none rounded-none border-0' : 'rounded-3xl w-full max-w-7xl max-h-[95vh]'} flex flex-col overflow-hidden shadow-2xl transition-all duration-300`}>
|
||||
{/* Modal Header */}
|
||||
<div className="p-5 border-b border-border bg-secondary/30 flex items-center justify-between">
|
||||
<div className="p-4 md:p-5 border-b border-border bg-secondary/30 flex items-center justify-between">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="p-2.5 bg-primary/10 rounded-xl border border-primary/20 text-primary">
|
||||
<div className="p-2.5 bg-primary/10 rounded-xl border border-primary/20 text-primary hidden sm:flex">
|
||||
<FileText size={22} />
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="font-black text-lg flex items-center gap-2">
|
||||
<h3 className="font-black text-base sm:text-lg flex items-center gap-2">
|
||||
{cleanTitle(selectedPdfDpp.title)}
|
||||
<span className="text-[10px] bg-yellow-500/10 text-yellow-500 border border-yellow-500/20 px-2 py-0.5 rounded-full uppercase tracking-wider font-extrabold">
|
||||
<span className="text-[10px] bg-yellow-500/10 text-yellow-500 border border-yellow-500/20 px-2 py-0.5 rounded-full uppercase tracking-wider font-extrabold hidden md:inline-block">
|
||||
Protected In-App PDF View
|
||||
</span>
|
||||
</h3>
|
||||
@@ -236,20 +247,44 @@ export default function DPPPage() {
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
onClick={() => setSelectedPdfDpp(null)}
|
||||
className="ml-4 flex-shrink-0 w-9 h-9 rounded-full border border-border bg-secondary flex items-center justify-center text-foreground hover:bg-destructive hover:border-destructive hover:text-white transition-all"
|
||||
>
|
||||
<X size={18} />
|
||||
</button>
|
||||
<div className="flex items-center gap-2">
|
||||
<Button
|
||||
size="sm"
|
||||
variant="outline"
|
||||
onClick={() => {
|
||||
navigator.clipboard.writeText(window.location.origin + '/dashboard/dpp');
|
||||
setCopiedShare(true);
|
||||
setTimeout(() => setCopiedShare(false), 2000);
|
||||
}}
|
||||
className="rounded-xl font-bold text-xs gap-1.5 h-9 px-3"
|
||||
>
|
||||
{copiedShare ? <CheckCircle2 size={14} className="text-green-500" /> : <Share2 size={14} />}
|
||||
<span>{copiedShare ? "Copied" : "Share PDF"}</span>
|
||||
</Button>
|
||||
<Button
|
||||
size="sm"
|
||||
variant="outline"
|
||||
onClick={() => setIsFullScreen(!isFullScreen)}
|
||||
className="rounded-xl font-bold text-xs gap-1.5 h-9 px-3 bg-primary/10 text-primary border-primary/20 hover:bg-primary/20"
|
||||
>
|
||||
{isFullScreen ? <Minimize2 size={14} /> : <Maximize2 size={14} />}
|
||||
<span>{isFullScreen ? "Exit Fullscreen" : "Fullscreen"}</span>
|
||||
</Button>
|
||||
<button
|
||||
onClick={() => { setSelectedPdfDpp(null); setIsFullScreen(false); }}
|
||||
className="ml-1 flex-shrink-0 w-9 h-9 rounded-full border border-border bg-secondary flex items-center justify-center text-foreground hover:bg-destructive hover:border-destructive hover:text-white transition-all"
|
||||
>
|
||||
<X size={18} />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Security Notice Banner */}
|
||||
<div className="bg-amber-500/10 border-b border-amber-500/20 px-6 py-2.5 flex items-center justify-between text-xs text-amber-400 font-bold">
|
||||
<span className="flex items-center gap-2">
|
||||
<ShieldAlert size={16} /> Read-Only Mode (Free Tier) — Downloads, text copying, and sharing are restricted.
|
||||
<div className="bg-amber-500/10 border-b border-amber-500/20 px-4 sm:px-6 py-2 flex items-center justify-between text-xs text-amber-400 font-bold">
|
||||
<span className="flex items-center gap-2 truncate">
|
||||
<ShieldAlert size={16} className="shrink-0" /> Read-Only Mode (Free Tier) — Downloads & direct copying are restricted.
|
||||
</span>
|
||||
<Link href="/upgrade">
|
||||
<Link href="/upgrade" className="hidden sm:block shrink-0 ml-2">
|
||||
<Button size="sm" className="bg-amber-500 text-black hover:bg-amber-400 font-extrabold h-7 text-[11px] rounded-lg">
|
||||
Upgrade for Interactive CBT Mode →
|
||||
</Button>
|
||||
@@ -259,28 +294,33 @@ export default function DPPPage() {
|
||||
{/* Protected PDF Document Sheet Content (select-none) */}
|
||||
<div
|
||||
onContextMenu={(e) => e.preventDefault()}
|
||||
className="flex-1 w-full select-none bg-[#525659] overflow-hidden" style={{minHeight: '55vh'}}
|
||||
className="flex-1 w-full select-none bg-[#525659] overflow-hidden"
|
||||
style={{minHeight: isFullScreen ? 'calc(100vh - 120px)' : '75vh'}}
|
||||
>
|
||||
<embed
|
||||
src={getPdfUrl(selectedPdfDpp.title)}
|
||||
type="application/pdf"
|
||||
className="w-full h-full"
|
||||
style={{height: '55vh'}}
|
||||
style={{height: isFullScreen ? 'calc(100vh - 120px)' : '75vh'}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Modal Footer */}
|
||||
<div className="p-4 border-t border-border bg-secondary/30 flex items-center justify-between">
|
||||
<span className="text-xs text-muted-foreground font-medium">
|
||||
<div className="p-3.5 sm:p-4 border-t border-border bg-secondary/30 flex items-center justify-between flex-wrap gap-2">
|
||||
<span className="text-xs text-muted-foreground font-medium flex items-center gap-2">
|
||||
📄 PragatiVeer Protected PDF Reader v2.0
|
||||
</span>
|
||||
<div className="flex gap-3">
|
||||
<Button onClick={() => setSelectedPdfDpp(null)} variant="outline" className="rounded-xl font-bold">
|
||||
Close PDF Reader
|
||||
<div className="flex gap-2 items-center">
|
||||
<Button onClick={() => setIsFullScreen(!isFullScreen)} variant="secondary" className="rounded-xl font-bold text-xs h-9 bg-primary/10 text-primary hover:bg-primary/20 flex items-center gap-1.5">
|
||||
{isFullScreen ? <Minimize2 size={14} /> : <Maximize2 size={14} />}
|
||||
<span>{isFullScreen ? "Exit Fullscreen" : "Fullscreen"}</span>
|
||||
</Button>
|
||||
<Button onClick={() => { setSelectedPdfDpp(null); setIsFullScreen(false); }} variant="outline" className="rounded-xl font-bold text-xs h-9">
|
||||
Close Reader
|
||||
</Button>
|
||||
<Link href="/upgrade">
|
||||
<Button className="bg-[#00F0FF] text-black hover:bg-[#00F0FF]/90 font-extrabold rounded-xl shadow-[0_0_15px_rgba(0,240,255,0.2)]">
|
||||
<Sparkles size={16} className="mr-2" /> Upgrade to AI Coach for Interactive CBT
|
||||
<Button className="bg-[#00F0FF] text-black hover:bg-[#00F0FF]/90 font-extrabold rounded-xl shadow-[0_0_15px_rgba(0,240,255,0.2)] text-xs h-9">
|
||||
<Sparkles size={14} className="mr-1.5" /> Upgrade to AI Coach for CBT
|
||||
</Button>
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
"use client"
|
||||
|
||||
import { ReactNode, useState, useEffect, useRef } from "react"
|
||||
import { BookOpen, LayoutDashboard, Settings, FileText, Activity, LineChart, Trophy, LogOut, User, ChevronUp, Lock, ShieldAlert, Flame, Target } from "lucide-react"
|
||||
import { BookOpen, LayoutDashboard, Settings, FileText, Activity, LineChart, Trophy, User, ChevronUp, Lock, ShieldAlert, Flame, Target, Gauge, Zap, Calendar, Clock, BrainCircuit, BarChart3, School, CheckSquare } from "lucide-react"
|
||||
import Link from "next/link"
|
||||
import { usePathname } from "next/navigation"
|
||||
import { LanguageToggle } from "@/components/LanguageToggle"
|
||||
@@ -52,24 +52,23 @@ export default function DashboardLayout({ children }: { children: ReactNode }) {
|
||||
const userInitials = user ? user.name.split(" ").filter(n => /^[a-zA-Z]/.test(n)).slice(0, 2).map(n => n[0]).join("").toUpperCase() : "DS"
|
||||
const targetExamLabel = user?.target_exam === "JEE_Main" ? "JEE Main" : user?.target_exam === "JEE_Advanced" ? "JEE Advanced" : user?.target_exam === "NEET" ? "NEET" : "NEET/JEE"
|
||||
|
||||
let daysLeft: number | null = null;
|
||||
const trialDays = globalSettings?.trial_duration_days !== undefined ? globalSettings.trial_duration_days : 15;
|
||||
const trialEnabled = globalSettings?.enable_free_trial !== undefined ? globalSettings.enable_free_trial : true;
|
||||
|
||||
// For the demo, treat both Free tier and Neha as Trial users so the banner appears
|
||||
if (user && (user.tier === 'Free' || user.id === DEMO_USERS.NEHA.id)) {
|
||||
if (!trialEnabled) {
|
||||
daysLeft = 0;
|
||||
} else {
|
||||
// For demo purposes, we ignore created_at and just show the exact days set in the admin panel
|
||||
daysLeft = trialDays;
|
||||
}
|
||||
}
|
||||
// Trial logic removed as requested
|
||||
|
||||
if (maintenance) {
|
||||
return <MaintenanceScreen />;
|
||||
}
|
||||
|
||||
if (loading || !user) {
|
||||
return (
|
||||
<div className="h-screen w-screen flex flex-col items-center justify-center bg-background">
|
||||
<div className="animate-pulse flex flex-col items-center gap-4">
|
||||
<Logo height={56} className="mb-4" />
|
||||
<div className="h-6 w-6 border-4 border-[#00F0FF] border-t-transparent rounded-full animate-spin"></div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex h-screen bg-background overflow-hidden text-foreground selection:bg-primary/20">
|
||||
{/* Sidebar */}
|
||||
@@ -78,59 +77,64 @@ export default function DashboardLayout({ children }: { children: ReactNode }) {
|
||||
<Logo href="/dashboard" className="w-full" imageClassName="object-contain object-left scale-110 origin-left" height={56} />
|
||||
</div>
|
||||
<nav className="flex-1 px-4 space-y-1.5 mt-4 overflow-y-auto">
|
||||
<div className="pt-2 pb-2">
|
||||
{/* <div className="pt-2 pb-2">
|
||||
<p className="px-3 text-xs font-bold uppercase tracking-widest text-muted-foreground flex items-center gap-2">
|
||||
<span className="w-1.5 h-1.5 rounded-full bg-white"></span> Free Tier
|
||||
</p>
|
||||
</div>
|
||||
<NavItem href="/dashboard" icon={<LayoutDashboard size={18} />} label="Overview" active={pathname === "/dashboard"} />
|
||||
<NavItem href="/dashboard/dpp" icon={<FileText size={18} />} label="Target DPPs" active={pathname.startsWith("/dashboard/dpp")} />
|
||||
<NavItem href="/dashboard/leaderboard" icon={<Trophy size={18} />} label="Leaderboard" active={pathname.startsWith("/dashboard/leaderboard")} />
|
||||
</div> */}
|
||||
|
||||
<div className="pt-4 pb-2">
|
||||
<p className="px-3 text-xs font-bold uppercase tracking-widest text-muted-foreground flex items-center gap-2">
|
||||
<span className="w-1.5 h-1.5 rounded-full bg-yellow-500 shadow-[0_0_8px_-2px_var(--color-yellow-500)]"></span> Pro Tier
|
||||
<div className="pt-3 pb-1">
|
||||
<p className="px-3 text-[10px] font-black uppercase tracking-widest text-muted-foreground/60 flex items-center gap-2">
|
||||
<span className="flex-1 h-px bg-border/60"></span>
|
||||
<span className="flex items-center gap-1.5"><span className="w-1.5 h-1.5 rounded-full bg-yellow-500 shadow-[0_0_8px_-2px_rgba(234,179,8,0.8)]"></span>Pro Tier</span>
|
||||
<span className="flex-1 h-px bg-border/60"></span>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<NavItem href="/dashboard" icon={<LayoutDashboard size={17} />} label="Overview" active={pathname === "/dashboard"} />
|
||||
{/* <NavItem href="/dashboard/dpp" icon={<FileText size={17} />} label="Target DPPs" active={pathname.startsWith("/dashboard/dpp")} /> */}
|
||||
<NavItem href="/dashboard/leaderboard" icon={<Trophy size={17} />} label="Leaderboard" active={pathname.startsWith("/dashboard/leaderboard")} />
|
||||
<NavItem
|
||||
href="/dashboard/progress"
|
||||
icon={<Activity size={18} />}
|
||||
icon={<BarChart3 size={17} />}
|
||||
label="Syllabus Tracker"
|
||||
active={pathname.startsWith("/dashboard/progress")}
|
||||
isLocked={!hasAccessTo("chapter-wise-progress-tracking")}
|
||||
/>
|
||||
<NavItem
|
||||
href="/dashboard/cbt"
|
||||
icon={<Activity size={18} />}
|
||||
icon={<CheckSquare size={17} />}
|
||||
label="Mock Tests (CBT)"
|
||||
active={pathname.startsWith("/dashboard/cbt")}
|
||||
isLocked={!hasAccessTo("weekly-test-series")}
|
||||
/>
|
||||
<NavItem
|
||||
href="/dashboard/materials"
|
||||
icon={<BookOpen size={18} />}
|
||||
icon={<BookOpen size={17} />}
|
||||
label="Quick Notes"
|
||||
active={pathname.startsWith("/dashboard/materials")}
|
||||
isLocked={!hasAccessTo("detailed-performance-analytics")}
|
||||
/>
|
||||
<NavItem
|
||||
href="/dashboard/analytics"
|
||||
icon={<LineChart size={18} />}
|
||||
icon={<LineChart size={17} />}
|
||||
label="Performance Analytics"
|
||||
active={pathname.startsWith("/dashboard/analytics")}
|
||||
isLocked={!hasAccessTo("detailed-performance-analytics")}
|
||||
/>
|
||||
<NavItem
|
||||
href="/dashboard/community"
|
||||
icon={<BookOpen size={18} />}
|
||||
icon={<School size={17} />}
|
||||
label="Doubt Forum"
|
||||
active={pathname.startsWith("/dashboard/community")}
|
||||
isLocked={!hasAccessTo("doubt-resolution-community")}
|
||||
/>
|
||||
|
||||
<div className="pt-4 pb-2">
|
||||
<p className="px-3 text-xs font-bold uppercase tracking-widest text-muted-foreground flex items-center gap-2">
|
||||
<span className="w-1.5 h-1.5 rounded-full bg-[#00F0FF] shadow-[0_0_8px_-2px_#00F0FF]"></span> AI Coach
|
||||
<div className="pt-4 pb-1">
|
||||
<p className="px-3 text-[10px] font-black uppercase tracking-widest text-muted-foreground/60 flex items-center gap-2">
|
||||
<span className="flex-1 h-px bg-border/60"></span>
|
||||
<span className="flex items-center gap-1.5"><span className="w-1.5 h-1.5 rounded-full bg-[#00F0FF] shadow-[0_0_8px_-2px_#00F0FF]"></span>AI Coach</span>
|
||||
<span className="flex-1 h-px bg-border/60"></span>
|
||||
</p>
|
||||
</div>
|
||||
<NavItem
|
||||
@@ -142,63 +146,63 @@ export default function DashboardLayout({ children }: { children: ReactNode }) {
|
||||
/>
|
||||
<NavItem
|
||||
href="/dashboard/ai-coach/cbt-dpp"
|
||||
icon={<FileText size={18} />}
|
||||
icon={<Zap size={17} />}
|
||||
label="PrepPilot AI"
|
||||
active={pathname.startsWith("/dashboard/ai-coach/cbt-dpp")}
|
||||
isLocked={!hasAccessTo("ai-tutor-chat")}
|
||||
/>
|
||||
<NavItem
|
||||
href="/dashboard/ai-coach/readiness"
|
||||
icon={<Activity size={18} />}
|
||||
icon={<Gauge size={17} />}
|
||||
label="Exam Readiness"
|
||||
active={pathname.startsWith("/dashboard/ai-coach/readiness")}
|
||||
isLocked={!hasAccessTo("detailed-performance-analytics")}
|
||||
/>
|
||||
<NavItem
|
||||
href="/dashboard/ai-coach/college-predictor"
|
||||
icon={<Trophy size={18} />}
|
||||
icon={<School size={17} />}
|
||||
label="College Predictor"
|
||||
active={pathname.startsWith("/dashboard/ai-coach/college-predictor")}
|
||||
isLocked={!hasAccessTo("college-predictor")}
|
||||
/>
|
||||
<NavItem
|
||||
href="/dashboard/ai-coach/strategy"
|
||||
icon={<BookOpen size={18} />}
|
||||
icon={<Target size={17} />}
|
||||
label="Exam Strategy"
|
||||
active={pathname.startsWith("/dashboard/ai-coach/strategy")}
|
||||
isLocked={!hasAccessTo("exam-day-strategy")}
|
||||
/>
|
||||
<NavItem
|
||||
href="/dashboard/ai-coach/study-plan"
|
||||
icon={<LineChart size={18} />}
|
||||
icon={<Calendar size={17} />}
|
||||
label="AI Study Plan"
|
||||
active={pathname.startsWith("/dashboard/ai-coach/study-plan")}
|
||||
isLocked={!hasAccessTo("personalized-study-plan")}
|
||||
/>
|
||||
<NavItem
|
||||
href="/dashboard/ai-coach/time-optimization"
|
||||
icon={<Activity size={18} />}
|
||||
label="Study Time Optimization"
|
||||
icon={<Clock size={17} />}
|
||||
label="Time Optimization"
|
||||
active={pathname.startsWith("/dashboard/ai-coach/time-optimization")}
|
||||
isLocked={!hasAccessTo("personalized-study-plan")}
|
||||
/>
|
||||
<NavItem
|
||||
href="/dashboard/ai-coach/heatmap"
|
||||
icon={<Flame size={18} />}
|
||||
icon={<Flame size={17} />}
|
||||
label="Mistake Heatmap"
|
||||
active={pathname.startsWith("/dashboard/ai-coach/heatmap")}
|
||||
isLocked={!hasAccessTo("ai-mistake-tracking")}
|
||||
/>
|
||||
<NavItem
|
||||
href="/dashboard/ai-coach/weekly-progress"
|
||||
icon={<Target size={18} />}
|
||||
icon={<Activity size={17} />}
|
||||
label="Weekly Progress"
|
||||
active={pathname.startsWith("/dashboard/ai-coach/weekly-progress")}
|
||||
isLocked={!hasAccessTo("detailed-performance-analytics")}
|
||||
/>
|
||||
<NavItem
|
||||
href="/dashboard/ai-coach/mistakes"
|
||||
icon={<Activity size={18} />}
|
||||
icon={<BrainCircuit size={17} />}
|
||||
label="Mistake Analyzer"
|
||||
active={pathname.startsWith("/dashboard/ai-coach/mistakes")}
|
||||
isLocked={!hasAccessTo("ai-mistake-tracking")}
|
||||
@@ -272,27 +276,6 @@ export default function DashboardLayout({ children }: { children: ReactNode }) {
|
||||
{targetExamLabel} Target
|
||||
</Badge>
|
||||
)}
|
||||
{(user?.tier === 'Free' || user?.id === DEMO_USERS.NEHA.id) && daysLeft !== null && (
|
||||
daysLeft > 0 ? (
|
||||
<Link href="/upgrade" className="group">
|
||||
<Badge variant="outline" className="bg-yellow-500/10 text-yellow-500 border-yellow-500/30 font-bold px-3 py-1 cursor-pointer hover:bg-yellow-500/20 hover:scale-105 transition-all flex items-center gap-2 shadow-[0_0_12px_rgba(234,179,8,0.15)]">
|
||||
<span className="flex items-center gap-1.5"><ShieldAlert size={14} className="text-yellow-500" /> {daysLeft} Days Left in Trial</span>
|
||||
<span className="text-[11px] bg-yellow-500 text-black px-2 py-0.5 rounded-full font-black group-hover:bg-yellow-400 transition-colors shadow-sm">
|
||||
Upgrade Now →
|
||||
</span>
|
||||
</Badge>
|
||||
</Link>
|
||||
) : (
|
||||
<Link href="/upgrade" className="group">
|
||||
<Badge variant="outline" className="bg-red-500/15 text-red-400 border-red-500/40 font-black px-3.5 py-1 cursor-pointer hover:bg-red-500/30 hover:scale-105 transition-all flex items-center gap-2 shadow-[0_0_15px_rgba(239,68,68,0.25)] animate-pulse">
|
||||
<span className="flex items-center gap-1.5"><ShieldAlert size={14} className="text-red-400" /> Trial Expired</span>
|
||||
<span className="text-[11px] bg-red-500 text-white px-2 py-0.5 rounded-full font-black shadow-sm group-hover:bg-red-600 transition-colors">
|
||||
Upgrade Now →
|
||||
</span>
|
||||
</Badge>
|
||||
</Link>
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-4">
|
||||
@@ -314,7 +297,7 @@ export default function DashboardLayout({ children }: { children: ReactNode }) {
|
||||
<option value={DEMO_USERS.NEHA.id}>Neha Verma (AI Coach)</option>
|
||||
<option value={DEMO_USERS.AI_COACH.id}>Rahul (AI Coach)</option>
|
||||
<option value={DEMO_USERS.PRO.id}>Priya (Pro Plan)</option>
|
||||
<option value={DEMO_USERS.FREE.id}>Amit (Free Plan)</option>
|
||||
{/* <option value={DEMO_USERS.FREE.id}>Amit (Free Plan)</option> */}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
@@ -344,19 +327,24 @@ function NavItem({ href, icon, label, active, isLocked }: { href: string, icon:
|
||||
return (
|
||||
<Link
|
||||
href={isLocked ? "#" : href}
|
||||
className={`flex items-center justify-between px-3 py-2.5 rounded-xl transition-all duration-300 ${
|
||||
className={`group flex items-center justify-between px-3 py-2 rounded-xl transition-all duration-200 ${
|
||||
active
|
||||
? "bg-secondary text-foreground font-semibold border border-border"
|
||||
? "bg-[#00F0FF]/10 text-[#00F0FF] font-semibold border border-[#00F0FF]/20 shadow-[0_0_12px_-4px_rgba(0,240,255,0.3)]"
|
||||
: isLocked
|
||||
? "text-muted-foreground/60 cursor-not-allowed border border-transparent"
|
||||
: "text-muted-foreground hover:bg-secondary/50 hover:text-foreground border border-transparent"
|
||||
? "text-muted-foreground/40 cursor-not-allowed border border-transparent"
|
||||
: "text-muted-foreground/80 hover:bg-secondary/60 hover:text-foreground border border-transparent hover:border-border/40"
|
||||
}`}
|
||||
>
|
||||
<div className="flex items-center gap-3">
|
||||
{icon}
|
||||
<span className="text-sm">{label}</span>
|
||||
<div className="flex items-center gap-2.5">
|
||||
<span className={`transition-all duration-200 ${
|
||||
active ? "text-[#00F0FF]" : isLocked ? "text-muted-foreground/30" : "text-muted-foreground/60 group-hover:text-foreground/80"
|
||||
}`}>
|
||||
{icon}
|
||||
</span>
|
||||
<span className="text-[13px] font-medium tracking-[-0.01em]">{label}</span>
|
||||
</div>
|
||||
{isLocked && <Lock size={12} className="text-muted-foreground/50" />}
|
||||
{isLocked && <Lock size={11} className="text-muted-foreground/30 shrink-0" />}
|
||||
{active && <span className="w-1 h-4 rounded-full bg-[#00F0FF] shrink-0 shadow-[0_0_6px_rgba(0,240,255,0.8)]"></span>}
|
||||
</Link>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -25,14 +25,18 @@ export default function LeaderboardPage() {
|
||||
]
|
||||
|
||||
return (
|
||||
<div className="p-8 space-y-8 animate-in fade-in slide-in-from-bottom-4 duration-500 max-w-5xl mx-auto">
|
||||
<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 justify-between items-start md:items-end gap-4">
|
||||
<div>
|
||||
<h1 className="text-3xl font-bold tracking-tight text-foreground flex items-center gap-3">
|
||||
<Trophy className="text-yellow-500" size={32} />
|
||||
<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">
|
||||
<Trophy className="text-[#00F0FF]" size={28} />
|
||||
</span>
|
||||
Leaderboard
|
||||
</h1>
|
||||
<p className="text-muted-foreground mt-2 font-medium">Rankings are based on your Accuracy × Consistency (Streak Bonus).</p>
|
||||
<p className="text-muted-foreground text-base font-medium max-w-2xl pl-1">
|
||||
Rankings are based on your Accuracy × Consistency (Streak Bonus).
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-3 bg-secondary/50 p-1.5 rounded-xl border border-border">
|
||||
|
||||
@@ -1,7 +1,16 @@
|
||||
"use client"
|
||||
|
||||
import { useState } from "react"
|
||||
import { Card, CardContent } from "@/components/ui/card"
|
||||
import { Folder, FileText, Download, PlaySquare } from "lucide-react"
|
||||
import { Folder, FileText, PlaySquare, Share2, Maximize2, Minimize2, X, CheckCircle2, ShieldAlert, Sparkles, Eye } from "lucide-react"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import Link from "next/link"
|
||||
|
||||
export default function MaterialsPage() {
|
||||
const [selectedPdf, setSelectedPdf] = useState<any | null>(null)
|
||||
const [isFullScreen, setIsFullScreen] = useState(false)
|
||||
const [copiedShare, setCopiedShare] = useState(false)
|
||||
|
||||
const folders = [
|
||||
{ name: "Physics Formula Sheets", items: 12, color: "text-blue-500" },
|
||||
{ name: "Organic Chem Mind Maps", items: 8, color: "text-emerald-500" },
|
||||
@@ -10,16 +19,32 @@ export default function MaterialsPage() {
|
||||
]
|
||||
|
||||
const recentFiles = [
|
||||
{ name: "Kinematics 1D & 2D Rapid Revision.pdf", type: "PDF", size: "2.4 MB" },
|
||||
{ name: "S-Block Elements Trick Video.mp4", type: "Video", size: "45 MB" },
|
||||
{ name: "Human Reproduction Master Notes.pdf", type: "PDF", size: "8.1 MB" },
|
||||
{ name: "Kinematics 1D & 2D Rapid Revision.pdf", type: "PDF", size: "2.4 MB", url: "/dpps/dps-phy-ch4-20260707.pdf#toolbar=0&navpanes=0&scrollbar=0" },
|
||||
{ name: "S-Block Elements Trick Video.mp4", type: "Video", size: "45 MB", url: "" },
|
||||
{ name: "Human Reproduction Master Notes.pdf", type: "PDF", size: "8.1 MB", url: "/dpps/dps-bio-ch4-20260707.pdf#toolbar=0&navpanes=0&scrollbar=0" },
|
||||
]
|
||||
|
||||
const handleShare = (e: React.MouseEvent, fileName: string) => {
|
||||
e.stopPropagation();
|
||||
navigator.clipboard.writeText(window.location.origin + '/dashboard/materials');
|
||||
setCopiedShare(true);
|
||||
setTimeout(() => setCopiedShare(false), 2000);
|
||||
};
|
||||
|
||||
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>
|
||||
<h1 className="text-3xl font-bold tracking-tight">Study Materials</h1>
|
||||
<p className="text-muted-foreground mt-1 font-medium">High-yield notes, mind maps, and video lectures.</p>
|
||||
<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 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">
|
||||
<FileText className="text-[#00F0FF]" size={28} />
|
||||
</span>
|
||||
Study Materials & Notes
|
||||
</h1>
|
||||
<p className="text-muted-foreground text-base font-medium max-w-2xl pl-1">
|
||||
High-yield notes, formula sheets, and mind maps with protected read-only share access.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4">
|
||||
@@ -27,15 +52,18 @@ export default function MaterialsPage() {
|
||||
<Card key={i} className="hover:bg-secondary/50 cursor-pointer transition-colors border-border shadow-none">
|
||||
<CardContent className="p-6">
|
||||
<Folder size={40} className={`${folder.color} mb-4`} />
|
||||
<h3 className="font-bold">{folder.name}</h3>
|
||||
<p className="text-sm text-muted-foreground mt-1 font-medium">{folder.items} Files</p>
|
||||
<h3 className="font-bold text-base">{folder.name}</h3>
|
||||
<p className="text-sm text-muted-foreground mt-1 font-medium">{folder.items} Protected Files</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="pt-6">
|
||||
<h2 className="text-xl font-bold border-b border-border pb-2 mb-4">Recently Accessed</h2>
|
||||
<h2 className="text-xl font-bold border-b border-border pb-2 mb-4 flex items-center justify-between">
|
||||
<span>Recently Accessed Study Notes</span>
|
||||
<span className="text-xs font-normal text-muted-foreground">Click any PDF to launch fullscreen reader</span>
|
||||
</h2>
|
||||
<div className="bg-secondary/10 border border-border rounded-xl overflow-hidden">
|
||||
<table className="w-full text-left text-sm">
|
||||
<thead className="bg-secondary/30 text-muted-foreground border-b border-border">
|
||||
@@ -43,20 +71,44 @@ export default function MaterialsPage() {
|
||||
<th className="px-6 py-4 font-semibold">File Name</th>
|
||||
<th className="px-6 py-4 font-semibold">Type</th>
|
||||
<th className="px-6 py-4 font-semibold">Size</th>
|
||||
<th className="px-6 py-4 font-semibold text-right">Download</th>
|
||||
<th className="px-6 py-4 font-semibold text-right">Action (Share Link)</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="divide-y divide-border">
|
||||
{recentFiles.map((file, i) => (
|
||||
<tr key={i} className="hover:bg-secondary/20 transition-colors cursor-pointer group">
|
||||
<tr
|
||||
key={i}
|
||||
onClick={() => file.type === 'PDF' && setSelectedPdf(file)}
|
||||
className="hover:bg-secondary/20 transition-colors cursor-pointer group"
|
||||
>
|
||||
<td className="px-6 py-4 font-medium flex items-center gap-3">
|
||||
{file.type === 'PDF' ? <FileText size={18} className="text-red-400" /> : <PlaySquare size={18} className="text-blue-400" />}
|
||||
{file.name}
|
||||
<span className="group-hover:text-primary transition-colors font-bold">{file.name}</span>
|
||||
</td>
|
||||
<td className="px-6 py-4 text-muted-foreground">{file.type}</td>
|
||||
<td className="px-6 py-4 text-muted-foreground">{file.size}</td>
|
||||
<td className="px-6 py-4 text-muted-foreground font-medium">{file.type}</td>
|
||||
<td className="px-6 py-4 text-muted-foreground font-medium">{file.size}</td>
|
||||
<td className="px-6 py-4 text-right">
|
||||
<button className="p-2 hover:text-[#00F0FF] hover:bg-secondary rounded-lg transition-colors inline-block"><Download size={18} /></button>
|
||||
<div className="flex items-center justify-end gap-2" onClick={(e) => e.stopPropagation()}>
|
||||
{file.type === 'PDF' && (
|
||||
<Button
|
||||
size="sm"
|
||||
variant="secondary"
|
||||
onClick={() => setSelectedPdf(file)}
|
||||
className="h-8 px-3 rounded-lg text-xs font-bold gap-1.5 hover:bg-primary hover:text-primary-foreground"
|
||||
>
|
||||
<Eye size={14} /> View PDF
|
||||
</Button>
|
||||
)}
|
||||
<Button
|
||||
size="sm"
|
||||
variant="outline"
|
||||
onClick={(e) => handleShare(e, file.name)}
|
||||
className="h-8 px-3 rounded-lg text-xs font-bold gap-1.5"
|
||||
>
|
||||
{copiedShare ? <CheckCircle2 size={14} className="text-green-500" /> : <Share2 size={14} />}
|
||||
<span>{copiedShare ? "Link Copied" : "Share Link"}</span>
|
||||
</Button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
@@ -64,6 +116,106 @@ export default function MaterialsPage() {
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* ── PROTECTED STUDY MATERIAL PDF READER MODAL ── */}
|
||||
{selectedPdf && (
|
||||
<div className={`fixed inset-0 z-50 bg-black/90 backdrop-blur-md flex items-center justify-center ${isFullScreen ? 'p-0' : 'p-4'} animate-in fade-in duration-300`}>
|
||||
<div className={`bg-background border border-border ${isFullScreen ? 'w-screen h-screen max-w-none max-h-none rounded-none border-0' : 'rounded-3xl w-full max-w-7xl max-h-[95vh]'} flex flex-col overflow-hidden shadow-2xl transition-all duration-300`}>
|
||||
{/* Modal Header */}
|
||||
<div className="p-4 md:p-5 border-b border-border bg-secondary/30 flex items-center justify-between">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="p-2.5 bg-primary/10 rounded-xl border border-primary/20 text-primary hidden sm:flex">
|
||||
<FileText size={22} />
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="font-black text-base sm:text-lg flex items-center gap-2">
|
||||
{selectedPdf.name}
|
||||
<span className="text-[10px] bg-yellow-500/10 text-yellow-500 border border-yellow-500/20 px-2 py-0.5 rounded-full uppercase tracking-wider font-extrabold hidden md:inline-block">
|
||||
Protected In-App View
|
||||
</span>
|
||||
</h3>
|
||||
<p className="text-xs text-muted-foreground font-medium">
|
||||
Study Material • Read-Only Share Mode
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<Button
|
||||
size="sm"
|
||||
variant="outline"
|
||||
onClick={(e) => handleShare(e, selectedPdf.name)}
|
||||
className="rounded-xl font-bold text-xs gap-1.5 h-9 px-3"
|
||||
>
|
||||
{copiedShare ? <CheckCircle2 size={14} className="text-green-500" /> : <Share2 size={14} />}
|
||||
<span>{copiedShare ? "Copied" : "Share Link"}</span>
|
||||
</Button>
|
||||
<Button
|
||||
size="sm"
|
||||
variant="outline"
|
||||
onClick={() => setIsFullScreen(!isFullScreen)}
|
||||
className="rounded-xl font-bold text-xs gap-1.5 h-9 px-3 bg-primary/10 text-primary border-primary/20 hover:bg-primary/20"
|
||||
>
|
||||
{isFullScreen ? <Minimize2 size={14} /> : <Maximize2 size={14} />}
|
||||
<span>{isFullScreen ? "Exit Fullscreen" : "Fullscreen"}</span>
|
||||
</Button>
|
||||
<button
|
||||
onClick={() => { setSelectedPdf(null); setIsFullScreen(false); }}
|
||||
className="ml-1 flex-shrink-0 w-9 h-9 rounded-full border border-border bg-secondary flex items-center justify-center text-foreground hover:bg-destructive hover:border-destructive hover:text-white transition-all"
|
||||
>
|
||||
<X size={18} />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Security Notice Banner */}
|
||||
<div className="bg-amber-500/10 border-b border-amber-500/20 px-4 sm:px-6 py-2 flex items-center justify-between text-xs text-amber-400 font-bold">
|
||||
<span className="flex items-center gap-2 truncate">
|
||||
<ShieldAlert size={16} className="shrink-0" /> Read-Only Mode — Direct PDF download and offline exports are disabled for security.
|
||||
</span>
|
||||
<Link href="/upgrade" className="hidden sm:block shrink-0 ml-2">
|
||||
<Button size="sm" className="bg-amber-500 text-black hover:bg-amber-400 font-extrabold h-7 text-[11px] rounded-lg">
|
||||
Upgrade for Offline Vault Access →
|
||||
</Button>
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
{/* Protected PDF Document Sheet Content (select-none) */}
|
||||
<div
|
||||
onContextMenu={(e) => e.preventDefault()}
|
||||
className="flex-1 w-full select-none bg-[#525659] overflow-hidden"
|
||||
style={{minHeight: isFullScreen ? 'calc(100vh - 120px)' : '75vh'}}
|
||||
>
|
||||
<embed
|
||||
src={selectedPdf.url || '/dpps/dps-phy-ch4-20260707.pdf#toolbar=0&navpanes=0&scrollbar=0'}
|
||||
type="application/pdf"
|
||||
className="w-full h-full"
|
||||
style={{height: isFullScreen ? 'calc(100vh - 120px)' : '75vh'}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Modal Footer */}
|
||||
<div className="p-3.5 sm:p-4 border-t border-border bg-secondary/30 flex items-center justify-between flex-wrap gap-2">
|
||||
<span className="text-xs text-muted-foreground font-medium flex items-center gap-2">
|
||||
📄 PragatiVeer Protected Study Vault v2.0
|
||||
</span>
|
||||
<div className="flex gap-2 items-center">
|
||||
<Button onClick={() => setIsFullScreen(!isFullScreen)} variant="secondary" className="rounded-xl font-bold text-xs h-9 bg-primary/10 text-primary hover:bg-primary/20 flex items-center gap-1.5">
|
||||
{isFullScreen ? <Minimize2 size={14} /> : <Maximize2 size={14} />}
|
||||
<span>{isFullScreen ? "Exit Fullscreen" : "Fullscreen"}</span>
|
||||
</Button>
|
||||
<Button onClick={() => { setSelectedPdf(null); setIsFullScreen(false); }} variant="outline" className="rounded-xl font-bold text-xs h-9">
|
||||
Close Reader
|
||||
</Button>
|
||||
<Link href="/upgrade">
|
||||
<Button className="bg-[#00F0FF] text-black hover:bg-[#00F0FF]/90 font-extrabold rounded-xl shadow-[0_0_15px_rgba(0,240,255,0.2)] text-xs h-9">
|
||||
<Sparkles size={14} className="mr-1.5" /> Unlock All Premium Notes
|
||||
</Button>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -4,14 +4,14 @@ import { useRef } from "react"
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
|
||||
import { Badge } from "@/components/ui/badge"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { Activity, BrainCircuit, ArrowRight, Clock, Target, CheckCircle2, TrendingUp, Lock, Trophy, Share2 } from "lucide-react"
|
||||
import { BrainCircuit, Play, ChevronRight, Activity, Target, CheckCircle2, AlertTriangle, TrendingUp, Calendar, Zap, LayoutDashboard, Share2, Trophy, Lock, ArrowRight, Clock } from "lucide-react"
|
||||
import * as htmlToImage from 'html-to-image'
|
||||
import { MistakeHeatmap } from "@/components/AI/MistakeHeatmap"
|
||||
import { AdaptiveStudyPlan } from "@/components/AI/AdaptiveStudyPlan"
|
||||
import { useActiveUser } from "@/hooks/useActiveUser"
|
||||
|
||||
export default function DashboardPage() {
|
||||
const { user, hasAccessTo } = useActiveUser()
|
||||
const { user, hasAccessTo, loading } = useActiveUser()
|
||||
const progressRef = useRef<HTMLDivElement>(null)
|
||||
|
||||
const handleExport = async () => {
|
||||
@@ -28,37 +28,33 @@ export default function DashboardPage() {
|
||||
}
|
||||
}
|
||||
|
||||
if (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 dashboard...</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="p-8 max-w-6xl mx-auto space-y-10 animate-in fade-in duration-500 slide-in-from-bottom-4">
|
||||
|
||||
|
||||
{/* Free Tier Upgrade CTA Banner */}
|
||||
<Card className="bg-primary/5 border-primary/20 relative overflow-hidden">
|
||||
<div className="absolute top-0 right-0 p-8 opacity-10">
|
||||
<BrainCircuit size={120} />
|
||||
</div>
|
||||
<CardContent className="p-6 md:p-8 flex flex-col md:flex-row md:items-center justify-between gap-6 relative z-10">
|
||||
<div className="space-y-2">
|
||||
<Badge className="bg-primary text-primary-foreground font-bold tracking-widest uppercase mb-2">Upgrade to Pro</Badge>
|
||||
<h2 className="text-2xl font-bold tracking-tight">Unlock Unlimited DPS & Mocks</h2>
|
||||
<p className="text-muted-foreground max-w-xl font-medium">
|
||||
You're currently on the Free Tier. Upgrade to Pro for ₹3499/year to access unlimited Daily Practice Sheets, weekly CBT tests, and advanced analytics.
|
||||
</p>
|
||||
</div>
|
||||
<Button size="lg" className="shrink-0 rounded-xl shadow-[0_0_20px_-5px_var(--color-primary)] font-bold">
|
||||
Upgrade Now
|
||||
</Button>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<div className="p-8 max-w-7xl mx-auto space-y-10 animate-in fade-in duration-500 slide-in-from-bottom-4">
|
||||
|
||||
{/* Basic Performance Dashboard (F-05) */}
|
||||
<section>
|
||||
<div className="flex items-center justify-between mb-5">
|
||||
<h2 className="text-xl font-semibold tracking-tight flex items-center gap-2">
|
||||
Performance Overview
|
||||
<Badge variant="outline" className="border-white/30 text-muted-foreground bg-white/5 rounded-md text-[10px] uppercase font-bold">Free</Badge>
|
||||
</h2>
|
||||
<Button onClick={handleExport} variant="outline" size="sm" className="gap-2 font-medium">
|
||||
<div className="flex flex-col md:flex-row md:items-end justify-between gap-4 mb-8">
|
||||
<div>
|
||||
<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">
|
||||
<LayoutDashboard className="text-[#00F0FF]" size={28} />
|
||||
</span>
|
||||
Performance Overview
|
||||
</h1>
|
||||
<p className="text-muted-foreground text-base font-medium max-w-2xl pl-1">
|
||||
Your overall progress, scores, and active study plan at a glance.
|
||||
</p>
|
||||
</div>
|
||||
<Button onClick={handleExport} variant="outline" size="sm" className="gap-2 font-medium shrink-0">
|
||||
<Share2 size={16} /> Share Card
|
||||
</Button>
|
||||
</div>
|
||||
@@ -93,7 +89,6 @@ export default function DashboardPage() {
|
||||
<div className="p-3 bg-background border border-border shadow-sm rounded-full mb-3 text-foreground group-hover:scale-110 transition-transform"><Trophy size={24} /></div>
|
||||
<div className="flex items-center gap-2 mb-1">
|
||||
<p className="text-sm font-bold text-muted-foreground uppercase tracking-wider">City Rank</p>
|
||||
<Badge variant="outline" className="border-yellow-500/30 text-yellow-500 bg-yellow-500/5 rounded-md text-[10px] uppercase font-bold px-1.5 py-0">Pro</Badge>
|
||||
</div>
|
||||
<h3 className="text-2xl font-bold">#42 <span className="text-base text-muted-foreground font-medium">/ 1,200</span></h3>
|
||||
<p className="text-xs text-muted-foreground font-medium mt-2 text-[#00F0FF]">Top 5% in Jaipur</p>
|
||||
@@ -117,8 +112,6 @@ export default function DashboardPage() {
|
||||
<div className="flex items-center justify-between mb-5">
|
||||
<h2 className="text-xl font-semibold tracking-tight flex items-center gap-2">
|
||||
Today's DPPs
|
||||
<Badge variant="outline" className="border-white/30 text-muted-foreground bg-white/5 rounded-md text-[10px] uppercase font-bold">Free (1/Day)</Badge>
|
||||
<Badge variant="outline" className="border-yellow-500/30 text-yellow-500 bg-yellow-500/5 rounded-md text-[10px] uppercase font-bold">Pro (Unlimited)</Badge>
|
||||
</h2>
|
||||
<Button variant="ghost" size="sm" className="text-muted-foreground font-medium">View All</Button>
|
||||
</div>
|
||||
@@ -183,7 +176,6 @@ export default function DashboardPage() {
|
||||
<div className="flex items-center justify-between mb-5">
|
||||
<h2 className="text-xl font-semibold tracking-tight flex items-center gap-2">
|
||||
Upcoming CBTs
|
||||
<Badge variant="outline" className="border-yellow-500/30 text-yellow-500 bg-yellow-500/5 rounded-md text-[10px] uppercase font-bold">Pro</Badge>
|
||||
</h2>
|
||||
</div>
|
||||
<Card className="overflow-hidden">
|
||||
|
||||
@@ -104,13 +104,19 @@ export default function ProgressTrackerPage() {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="p-8 space-y-8 animate-in fade-in slide-in-from-bottom-4 duration-500 max-w-5xl mx-auto">
|
||||
<div>
|
||||
<h1 className="text-3xl font-bold tracking-tight text-foreground flex items-center gap-3">
|
||||
<Target className="text-[#00F0FF]" size={32} />
|
||||
Syllabus Tracker
|
||||
</h1>
|
||||
<p className="text-muted-foreground mt-2 font-medium">Track your syllabus coverage and mastery per chapter to ensure no gaps.</p>
|
||||
<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 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">
|
||||
<Target className="text-[#00F0FF]" size={28} />
|
||||
</span>
|
||||
Syllabus Tracker
|
||||
</h1>
|
||||
<p className="text-muted-foreground text-base font-medium max-w-2xl pl-1">
|
||||
Track your syllabus coverage and mastery per chapter to ensure no gaps.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-6 mb-8">
|
||||
|
||||
@@ -63,7 +63,7 @@ export default function RankPredictorPage() {
|
||||
]
|
||||
|
||||
return (
|
||||
<div className="p-6 md:p-8 space-y-8 animate-in fade-in duration-500 max-w-5xl mx-auto min-h-screen">
|
||||
<div className="p-6 md:p-8 space-y-8 animate-in fade-in duration-500 max-w-7xl mx-auto min-h-screen">
|
||||
|
||||
{/* Header */}
|
||||
<div className="text-center max-w-3xl mx-auto space-y-3">
|
||||
|
||||
@@ -16,7 +16,7 @@ export default function StreaksPage() {
|
||||
];
|
||||
|
||||
return (
|
||||
<div className="p-8 space-y-8 animate-in fade-in slide-in-from-bottom-4 duration-500 max-w-5xl mx-auto">
|
||||
<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 items-center gap-4">
|
||||
<div className="size-16 rounded-2xl bg-orange-500/10 text-orange-500 flex items-center justify-center">
|
||||
<Flame size={32} />
|
||||
|
||||
Reference in New Issue
Block a user