feat: setup admin JSON generation and pdf viewer for free plan DPPs

This commit is contained in:
2026-07-27 14:56:25 +05:30
parent c214ca41b6
commit a5bb2d867c
74 changed files with 8546 additions and 915 deletions

View File

@@ -1,14 +1,16 @@
"use client"
import { useEffect, useState } from "react"
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/card"
import { GraduationCap, MapPin, RefreshCw } from "lucide-react"
import { Card, CardContent } from "@/components/ui/card"
import { RefreshCw } from "lucide-react"
import Link from "next/link"
import { useActiveUser } from "@/hooks/useActiveUser"
export function CollegePredictorCard() {
const [data, setData] = useState<any>(null)
const [loading, setLoading] = useState(true)
const [activeTab, setActiveTab] = useState("Target")
const { userId, loading: userLoading } = useActiveUser();
useEffect(() => {
@@ -54,34 +56,65 @@ export function CollegePredictorCard() {
}
return (
<Card className="border-border shadow-sm h-full flex flex-col bg-gradient-to-br from-background to-secondary/30 relative overflow-hidden">
<div className="absolute -right-12 -top-12 opacity-5 pointer-events-none">
<GraduationCap size={150} />
<div className="flex flex-col h-full">
<div className="flex justify-between items-center pb-3">
<h2 className="text-base font-bold text-primary flex items-center gap-2">
🎓 College Predictor
</h2>
<Link href="/dashboard/ai-coach/college-predictor" className="text-xs text-[#00F0FF] font-semibold hover:underline transition-colors">Expand View ↗</Link>
</div>
<CardHeader className="pb-2">
<CardTitle className="text-xl flex items-center justify-between">
<div className="flex items-center gap-2">
<MapPin className="text-primary" size={24} />
Admissions Radar
<Card className="border-border bg-card shadow-[0_4px_24px_rgba(0,0,0,0.3)] flex-1 overflow-hidden group hover:border-[#00F0FF]/30 transition-colors">
<CardContent className="p-4 flex flex-col h-full">
<div className="flex gap-1.5 mb-3.5">
{["Reach", "Target", "Safety"].map(tab => (
<div
key={tab}
onClick={() => setActiveTab(tab)}
className={`flex-1 p-2 text-center text-[11px] font-semibold rounded-md cursor-pointer transition-colors border ${
activeTab === tab
? "bg-[#00F0FF]/10 border-[#00F0FF]/20 text-[#00F0FF]"
: "bg-white/5 border-transparent text-muted-foreground hover:bg-white/10"
}`}
>
{tab}
</div>
))}
</div>
<span className={`px-3 py-1 rounded-full text-[10px] font-black uppercase tracking-widest border ${getCategoryColor(category)}`}>
{category}
</span>
</CardTitle>
<CardDescription>AI-projected admission trajectory.</CardDescription>
</CardHeader>
<CardContent className="flex-1 flex flex-col justify-center">
<div className="p-5 bg-background rounded-xl border border-border shadow-sm mt-4">
<p className="font-semibold text-lg leading-snug">
"{prediction}"
</p>
</div>
<p className="text-xs text-muted-foreground mt-4 font-medium flex items-center gap-1 opacity-70">
<RefreshCw size={10} /> Prediction updates dynamically after every test.
</p>
</CardContent>
</Card>
<div className="flex flex-col gap-2 flex-1">
<div className="flex items-center gap-3 p-2.5 rounded-md bg-white/5 border border-border hover:bg-white/10 transition-colors">
<div className="w-7 h-7 rounded-full flex items-center justify-center text-[11px] font-bold shrink-0 bg-[#00F0FF]/10 text-[#00F0FF]">
#1
</div>
<div className="flex-1 min-w-0">
<div className="text-[13px] font-bold text-primary truncate">NIT Trichy</div>
<div className="text-[11px] text-muted-foreground truncate">Computer Science</div>
</div>
<div className="text-[12px] font-bold px-2 py-0.5 rounded-md shrink-0 text-green-500 bg-green-500/10">
85%
</div>
</div>
<div className="flex items-center gap-3 p-2.5 rounded-md bg-white/5 border border-border hover:bg-white/10 transition-colors">
<div className="w-7 h-7 rounded-full flex items-center justify-center text-[11px] font-bold shrink-0 bg-[#00F0FF]/10 text-[#00F0FF]">
#2
</div>
<div className="flex-1 min-w-0">
<div className="text-[13px] font-bold text-primary truncate">NIT Surathkal</div>
<div className="text-[11px] text-muted-foreground truncate">Information Tech</div>
</div>
<div className="text-[12px] font-bold px-2 py-0.5 rounded-md shrink-0 text-yellow-500 bg-yellow-500/10">
72%
</div>
</div>
</div>
<div className="mt-4 p-2.5 bg-yellow-500/10 border border-yellow-500/20 rounded-md text-xs text-yellow-500 font-medium text-center">
Just 15 more marks in Maths can upgrade you to IIT Roorkee!
</div>
</CardContent>
</Card>
</div>
)
}

View File

@@ -0,0 +1,87 @@
"use client"
import { useState, useEffect } from "react"
import { Flame, ArrowRight, Timer, BookOpen } from "lucide-react"
const FOCUS_TOPICS = [
{ subject: "Physics", topic: "Rotational Dynamics", color: "from-blue-600/30 to-[#00F0FF]/20", accent: "#00F0FF", icon: "⚛️" },
{ subject: "Chemistry", topic: "Organic Reactions — Mechanism", color: "from-amber-600/30 to-yellow-500/20", accent: "#F59E0B", icon: "🧪" },
{ subject: "Biology", topic: "Human Nervous System", color: "from-green-600/30 to-emerald-400/20", accent: "#10B981", icon: "🧬" },
{ subject: "Maths", topic: "Integral Calculus", color: "from-purple-600/30 to-violet-400/20", accent: "#A78BFA", icon: "∫" },
]
export function DailyChallengeBanner() {
const [todayFocus] = useState(() => FOCUS_TOPICS[new Date().getDay() % FOCUS_TOPICS.length])
const [timeLeft, setTimeLeft] = useState("")
useEffect(() => {
const tick = () => {
const now = new Date()
const end = new Date()
end.setHours(23, 59, 59, 0)
const diff = Math.max(0, end.getTime() - now.getTime())
const h = Math.floor(diff / 3600000)
const m = Math.floor((diff % 3600000) / 60000)
const s = Math.floor((diff % 60000) / 1000)
setTimeLeft(`${h}h ${m}m ${s}s`)
}
tick()
const id = setInterval(tick, 1000)
return () => clearInterval(id)
}, [])
return (
<div
className={`relative overflow-hidden rounded-2xl border border-white/10 bg-gradient-to-r ${todayFocus.color} p-5 flex flex-col sm:flex-row items-start sm:items-center justify-between gap-4 shadow-[0_4px_30px_rgba(0,0,0,0.3)]`}
>
<div
className="absolute -right-10 -top-10 w-48 h-48 rounded-full blur-3xl opacity-20 pointer-events-none"
style={{ background: todayFocus.accent }}
/>
<div className="flex items-center gap-4 relative z-10">
<div
className="w-12 h-12 rounded-xl flex items-center justify-center text-2xl font-bold border border-white/10 shrink-0"
style={{ background: `${todayFocus.accent}20` }}
>
{todayFocus.icon}
</div>
<div>
<div className="flex items-center gap-2 mb-1">
<span className="text-[10px] font-black uppercase tracking-widest text-white/50">
AI-Recommended Focus · Today
</span>
<span className="inline-flex items-center gap-1 text-[10px] font-bold px-2 py-0.5 rounded-full bg-white/10 text-white/70">
<Flame size={9} />
DAILY CHALLENGE
</span>
</div>
<h3 className="text-lg font-extrabold text-white tracking-tight leading-tight">
{todayFocus.subject}:{" "}
<span style={{ color: todayFocus.accent }}>{todayFocus.topic}</span>
</h3>
<p className="text-xs text-white/50 mt-0.5 flex items-center gap-1.5">
<BookOpen size={11} />
Mastering this will boost your rank by an estimated{" "}
<span className="font-bold text-white/70">+120 positions</span>
</p>
</div>
</div>
<div className="flex items-center gap-3 relative z-10 shrink-0">
<div className="text-right hidden sm:block">
<div className="text-[10px] text-white/40 font-semibold uppercase flex items-center gap-1 justify-end">
<Timer size={10} /> Expires In
</div>
<div className="text-sm font-black text-white/80 font-mono tracking-wider">{timeLeft}</div>
</div>
<button
className="flex items-center gap-2 px-4 py-2 rounded-xl font-bold text-sm text-background transition-all hover:scale-105 active:scale-95"
style={{ background: todayFocus.accent }}
>
Start Session <ArrowRight size={15} />
</button>
</div>
</div>
)
}

View File

@@ -0,0 +1,48 @@
import { Card, CardContent } from "@/components/ui/card"
import { Printer } from "lucide-react"
import Link from "next/link"
export function ExamStrategyCard() {
return (
<div className="flex flex-col h-full">
<div className="flex justify-between items-center pb-3">
<h2 className="text-base font-bold text-primary flex items-center gap-2">
🧠 Exam Day Strategy
</h2>
<Link href="/dashboard/ai-coach/strategy" className="text-xs text-[#00F0FF] font-semibold hover:underline transition-colors">Expand View ↗</Link>
</div>
<Card className="border-border bg-card shadow-[0_4px_24px_rgba(0,0,0,0.3)] flex-1 overflow-hidden group hover:border-[#00F0FF]/30 transition-colors">
<CardContent className="p-4 flex flex-col h-full justify-between">
<div className="grid grid-cols-2 gap-2">
<div className="p-3 rounded-md bg-white/5 border border-border text-center">
<div className="text-xl mb-1">🧪</div>
<div className="text-[11px] font-bold text-primary">Chemistry First</div>
<div className="text-[10px] text-muted-foreground mt-0.5">Finish in 40 mins</div>
</div>
<div className="p-3 rounded-md bg-white/5 border border-border text-center">
<div className="text-xl mb-1">🧲</div>
<div className="text-[11px] font-bold text-primary">Physics Next</div>
<div className="text-[10px] text-muted-foreground mt-0.5">Target 55 mins</div>
</div>
<div className="p-3 rounded-md bg-white/5 border border-border text-center">
<div className="text-xl mb-1">📐</div>
<div className="text-[11px] font-bold text-primary">Maths Last</div>
<div className="text-[10px] text-muted-foreground mt-0.5">Reserve 75 mins</div>
</div>
<div className="p-3 rounded-md bg-white/5 border border-border text-center">
<div className="text-xl mb-1">⏱️</div>
<div className="text-[11px] font-bold text-primary">Buffer Time</div>
<div className="text-[10px] text-muted-foreground mt-0.5">10 mins for review</div>
</div>
</div>
<div className="mt-3 flex items-center justify-center gap-1.5 p-2.5 rounded-md bg-[#00F0FF]/10 border border-dashed border-[#00F0FF]/20 text-xs text-[#00F0FF] font-bold cursor-pointer hover:bg-[#00F0FF]/20 transition-colors">
<Printer size={16} className="stroke-[#00F0FF]" />
Print Exam Day Blueprint
</div>
</CardContent>
</Card>
</div>
)
}

View File

@@ -0,0 +1,33 @@
import Link from "next/link"
const features = [
{ name: "Mistake Tracking", href: "/dashboard/ai-coach/mistakes" },
{ name: "Spaced Repetition", href: "/dashboard/ai-coach/mistakes" },
{ name: "Study Plan", href: "/dashboard/ai-coach/study-plan" },
{ name: "AI Tutor", href: "/dashboard/ai-coach/tutor" },
{ name: "Readiness Score", href: "/dashboard/ai-coach/readiness" },
{ name: "Exam Strategy", href: "/dashboard/ai-coach/strategy" },
{ name: "College Predictor", href: "/dashboard/ai-coach/college-predictor" },
{ name: "Time Optimization", href: "/dashboard/ai-coach/strategy" },
]
export function FeatureIndex() {
return (
<div className="py-2 mb-4">
<div className="flex flex-wrap gap-2.5">
{features.map((f) => (
<Link
key={f.name}
href={f.href}
className="inline-flex items-center gap-2 px-3.5 py-1.5 rounded-full text-xs font-extrabold bg-[#00F0FF]/10 border border-[#00F0FF]/30 text-[#00F0FF] hover:bg-[#00F0FF] hover:text-black transition-all shadow-[0_0_12px_rgba(0,240,255,0.1)] group"
>
<span className="size-2 rounded-full bg-[#00F0FF] group-hover:bg-black transition-colors" />
<span>{f.name}</span>
<span className="text-[10px] opacity-75">→</span>
</Link>
))}
</div>
</div>
)
}

View File

@@ -2,9 +2,8 @@
import { useState } from "react"
import { Card, CardContent } from "@/components/ui/card"
import { Button } from "@/components/ui/button"
import { Badge } from "@/components/ui/badge"
import { CheckCircle2, XCircle, BrainCircuit } from "lucide-react"
import { Flame } from "lucide-react"
import Link from "next/link"
import ReactMarkdown from 'react-markdown'
import remarkMath from 'remark-math'
import rehypeKatex from 'rehype-katex'
@@ -69,101 +68,132 @@ export function MistakeReviewCard({ data, onNext, onSubmitReview }: MistakeRevie
const { mistake, question } = data;
return (
<Card className="border-border shadow-sm overflow-hidden flex flex-col">
<div className="p-4 border-b border-border bg-secondary/20 flex items-center justify-between">
<div className="flex items-center gap-3">
<Badge variant="outline" className="text-muted-foreground border-primary/20 bg-primary/5 text-primary">
Attempt {mistake.review_count + 1}
</Badge>
<span className="text-xs font-bold text-muted-foreground uppercase tracking-wider">{mistake.topic} • {mistake.subtopic}</span>
<div className="flex flex-col gap-4">
<div className="flex justify-between items-center pb-1">
<h2 className="text-base font-bold text-primary flex items-center gap-2">
♻️ Spaced Repetition Queue
</h2>
<Link href="/dashboard/ai-coach/mistakes" className="text-xs text-[#00F0FF] font-semibold hover:underline transition-colors">Expand View ↗</Link>
</div>
{/* Spaced Repetition Timeline */}
<div className="flex justify-between items-center relative py-4 px-2">
<div className="absolute top-1/2 left-0 right-0 h-0.5 bg-secondary -translate-y-1/2 z-0" />
<div className="absolute top-1/2 left-0 w-1/2 h-0.5 bg-[#00F0FF] -translate-y-1/2 z-0" />
<div className="relative z-10 flex flex-col items-center gap-1.5 opacity-60">
<div className="w-3.5 h-3.5 rounded-full bg-[#00F0FF] border-2 border-background" />
<span className="text-[10px] font-bold text-[#00F0FF]">Attempt 1</span>
<span className="text-[9px] text-muted-foreground font-semibold">10 Jul</span>
</div>
<div className="flex items-center gap-2 text-xs font-medium text-muted-foreground">
<BrainCircuit size={14} className={mistake.consecutive_correct > 0 ? "text-amber-500" : ""} />
Consecutive Correct: <span className="font-bold text-foreground">{mistake.consecutive_correct}/3</span> before mastery
<div className="relative z-10 flex flex-col items-center gap-1.5 opacity-60">
<div className="w-3.5 h-3.5 rounded-full bg-[#00F0FF] border-2 border-background" />
<span className="text-[10px] font-bold text-[#00F0FF]">Attempt 2</span>
<span className="text-[9px] text-muted-foreground font-semibold">14 Jul</span>
</div>
<div className="relative z-10 flex flex-col items-center gap-1.5 scale-110">
<div className="w-4 h-4 rounded-full border-[3px] border-[#00F0FF] bg-background shadow-[0_0_10px_rgba(0,240,255,0.5)]" />
<span className="text-[11px] font-bold text-[#00F0FF]">Today</span>
<span className="text-[9px] text-muted-foreground font-semibold">19 Jul</span>
</div>
<div className="relative z-10 flex flex-col items-center gap-1.5 opacity-50">
<div className="w-3.5 h-3.5 rounded-full bg-secondary border-2 border-background" />
<span className="text-[10px] font-bold text-muted-foreground">Mastery</span>
<span className="text-[9px] text-muted-foreground font-semibold">26 Jul</span>
</div>
</div>
<CardContent className="p-6 flex-1 flex flex-col">
<div className="prose prose-neutral dark:prose-invert max-w-none text-base md:text-lg mb-8 font-medium">
<ReactMarkdown remarkPlugins={[remarkMath]} rehypePlugins={[rehypeKatex]}>
{question.question_text}
</ReactMarkdown>
</div>
<div className="space-y-3 mt-auto">
{Object.entries(question.options).map(([letter, text]) => {
const isSelected = selected === letter
let btnClass = "border-border bg-background hover:border-primary/50 hover:bg-secondary/20"
let iconClass = "border-border text-muted-foreground group-hover:border-primary/50 group-hover:text-primary"
if (result) {
// Post-check styling
if (letter === correctAnswer) {
btnClass = "border-green-500 bg-green-500/10 shadow-[0_0_15px_-5px_#22c55e]"
iconClass = "border-green-500 bg-green-500 text-white"
} else if (isSelected && letter !== correctAnswer) {
btnClass = "border-red-500 bg-red-500/10"
iconClass = "border-red-500 bg-red-500 text-white"
}
} else if (isSelected) {
btnClass = "border-primary bg-primary/5 shadow-[0_0_15px_-5px_var(--color-primary)]"
iconClass = "border-primary bg-primary text-primary-foreground"
}
return (
<button
key={letter}
onClick={() => !result && setSelected(letter)}
disabled={!!result}
className={`w-full text-left p-4 rounded-xl border transition-all duration-200 flex items-center gap-4 group ${btnClass}`}
>
<div className={`flex-shrink-0 size-8 rounded-lg border flex items-center justify-center font-medium transition-colors ${iconClass}`}>
{letter}
</div>
<span className="text-base font-medium flex-1">
<ReactMarkdown remarkPlugins={[remarkMath]} rehypePlugins={[rehypeKatex]}>{text}</ReactMarkdown>
</span>
{result && letter === correctAnswer && <CheckCircle2 className="text-green-500 ml-2" size={20} />}
{result && isSelected && letter !== correctAnswer && <XCircle className="text-red-500 ml-2" size={20} />}
</button>
)
})}
</div>
<div className="mt-8 flex justify-end">
{!result ? (
<Button
size="lg"
onClick={handleCheck}
disabled={!selected || isSubmitting}
className="rounded-xl px-8 font-bold"
>
Check Answer
</Button>
) : (
<Button
size="lg"
onClick={handleNext}
className="rounded-xl px-8 font-bold bg-secondary text-foreground hover:bg-secondary/80 border border-border"
>
Continue to Next
</Button>
)}
</div>
{/* Solution Block */}
{result && (
<div className="mt-6 p-4 rounded-xl border border-primary/20 bg-primary/5 animate-in slide-in-from-top-2 fade-in">
<h4 className="text-sm font-bold text-primary mb-2 flex items-center gap-2"><BrainCircuit size={16}/> AI Coach Explanation</h4>
<div className="prose prose-sm dark:prose-invert max-w-none text-muted-foreground">
<ReactMarkdown remarkPlugins={[remarkMath]} rehypePlugins={[rehypeKatex]}>
{question.solution_text}
</ReactMarkdown>
</div>
{/* Mistake Card */}
<Card className="border-border bg-card shadow-[0_4px_24px_rgba(0,0,0,0.3)] relative overflow-hidden flex flex-col group hover:border-[#00F0FF]/30 transition-colors">
<CardContent className="p-5 flex-1 flex flex-col">
<div className="flex items-center gap-2 mb-4">
<span className="text-[10px] font-bold px-2.5 py-1 rounded-md bg-white/5 border border-border text-muted-foreground uppercase tracking-widest">
{mistake.topic}
</span>
<span className="text-[11px] font-bold text-red-500 bg-red-500/10 px-2 py-0.5 rounded-md">
{mistake.error_type}
</span>
</div>
)}
</CardContent>
</Card>
<div className="prose prose-sm dark:prose-invert max-w-none text-base md:text-lg mb-6 font-medium text-foreground leading-relaxed">
<ReactMarkdown remarkPlugins={[remarkMath]} rehypePlugins={[rehypeKatex]}>
{question.question_text}
</ReactMarkdown>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-3 mt-auto">
{Object.entries(question.options).map(([letter, text]) => {
const isSelected = selected === letter
let btnClass = "border-border bg-white/5 hover:bg-white/10 hover:border-border text-muted-foreground"
if (result) {
if (letter === correctAnswer) {
btnClass = "border-green-500 bg-green-500/10 text-green-500 shadow-[0_0_15px_rgba(34,197,94,0.15)]"
} else if (isSelected && letter !== correctAnswer) {
btnClass = "border-red-500 bg-red-500/10 text-red-500"
}
} else if (isSelected) {
btnClass = "border-[#00F0FF] bg-[#00F0FF]/10 text-[#00F0FF] shadow-[0_0_15px_rgba(0,240,255,0.15)]"
}
return (
<button
key={letter}
onClick={() => !result && setSelected(letter)}
disabled={!!result || isSubmitting}
className={`text-left p-3.5 rounded-lg border-[1.5px] transition-all font-medium flex gap-3 ${btnClass}`}
>
<span className="font-bold opacity-70">{letter})</span>
<span className="flex-1">
<ReactMarkdown remarkPlugins={[remarkMath]} rehypePlugins={[rehypeKatex]}>{text}</ReactMarkdown>
</span>
</button>
)
})}
</div>
{/* Action Button */}
{!result ? (
<div className="mt-5 flex justify-end">
<button
onClick={handleCheck}
disabled={!selected || isSubmitting}
className="px-6 py-2.5 rounded-md font-bold text-sm bg-gradient-to-r from-[#00F0FF] to-teal-400 text-background hover:opacity-90 transition-opacity disabled:opacity-50"
>
{isSubmitting ? "Checking..." : "Submit Answer"}
</button>
</div>
) : (
<>
{/* Solution Block */}
<div className="mt-5 p-4 rounded-xl border border-[#00F0FF]/20 bg-[#00F0FF]/5">
<h4 className="text-[13px] font-bold text-[#00F0FF] mb-2 flex items-center gap-2">
<Flame size={16} className="fill-[#00F0FF]/20" />
AI Coach Explanation
</h4>
<div className="prose prose-sm dark:prose-invert max-w-none text-[13px] text-muted-foreground leading-relaxed [&>p]:m-0">
<ReactMarkdown remarkPlugins={[remarkMath]} rehypePlugins={[rehypeKatex]}>
{question.solution_text}
</ReactMarkdown>
</div>
</div>
<div className="mt-5 flex justify-end">
<button
onClick={handleNext}
className="px-6 py-2.5 rounded-md font-bold text-sm bg-gradient-to-r from-[#00F0FF] to-teal-400 text-background hover:opacity-90 transition-opacity"
>
Got It, Next Question →
</button>
</div>
</>
)}
</CardContent>
</Card>
</div>
)
}

View File

@@ -1,8 +1,9 @@
"use client"
import { useEffect, useState } from "react"
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/card"
import { Activity, RefreshCw } from "lucide-react"
import { Card, CardContent } from "@/components/ui/card"
import { TrendingUp, Flame, RefreshCw } from "lucide-react"
import Link from "next/link"
import { useActiveUser } from "@/hooks/useActiveUser"
@@ -59,65 +60,108 @@ export function ReadinessGauge() {
}
return (
<Card className="border-border shadow-sm h-full flex flex-col">
<CardHeader className="pb-2">
<CardTitle className="text-xl flex items-center gap-2">
<Activity className="text-primary" size={24} />
Exam Readiness
</CardTitle>
<CardDescription>Real-time AI composite score.</CardDescription>
</CardHeader>
<div className="flex flex-col h-full">
<div className="flex justify-between items-center pb-3">
<h2 className="text-base font-bold text-primary flex items-center gap-2">
🎯 Exam Readiness
</h2>
<Link href="/dashboard/ai-coach/readiness" className="text-xs text-[#00F0FF] font-semibold hover:underline transition-colors">Expand View ↗</Link>
</div>
<CardContent className="flex flex-col items-center flex-1 justify-center p-6 space-y-6">
<Card className="border-border bg-card shadow-[0_4px_24px_rgba(0,0,0,0.3)] relative overflow-hidden flex-1 flex flex-col justify-between p-4 group hover:border-[#00F0FF]/30 transition-colors">
{/* Background glow decoration */}
<div className="absolute -top-1/2 -right-1/4 w-48 h-48 bg-[radial-gradient(circle,rgba(94,196,184,0.06)_0%,transparent_70%)] rounded-full pointer-events-none" />
{/* SVG Circular Gauge */}
<div className="relative w-32 h-32 flex items-center justify-center">
<svg className="w-full h-full transform -rotate-90" viewBox="0 0 100 100">
{/* Background Ring */}
<circle cx="50" cy="50" r="45" stroke="currentColor" strokeWidth="8" fill="transparent" className="text-secondary" />
{/* Progress Ring */}
<circle
cx="50" cy="50" r="45"
stroke="currentColor"
strokeWidth="8"
fill="transparent"
strokeDasharray={circumference}
strokeDashoffset={strokeDashoffset}
strokeLinecap="round"
className={`transition-all duration-1000 ease-out ${getColor(score)}`}
/>
</svg>
<div className="absolute flex flex-col items-center justify-center">
<span className="text-3xl font-black">{score}</span>
<span className="text-[10px] text-muted-foreground uppercase tracking-wider font-bold">/ 100</span>
<div className="flex justify-between items-start mb-4 relative z-10">
<span className="text-[11px] font-bold text-muted-foreground uppercase tracking-widest">Updated daily • Today</span>
<span className="flex items-center gap-1 text-xs font-bold text-green-500 bg-green-500/10 px-2.5 py-0.5 rounded-full">
<TrendingUp size={14} className="stroke-[2.5px]" />
+5 this week
</span>
</div>
<div className="flex flex-col md:flex-row items-center gap-6 mb-4 relative z-10">
{/* Circular Score */}
<div className="relative w-[110px] h-[110px] shrink-0">
<svg className="w-full h-full transform -rotate-90" viewBox="0 0 100 100">
<defs>
<linearGradient id="scoreGradient" x1="0%" y1="0%" x2="100%" y2="0%">
<stop offset="0%" stopColor="#00F0FF" />
<stop offset="100%" stopColor="#3B82F6" />
</linearGradient>
</defs>
<circle cx="50" cy="50" r="45" stroke="currentColor" strokeWidth="6" fill="transparent" className="text-[#00F0FF]/10" />
<circle
cx="50" cy="50" r="45"
stroke="url(#scoreGradient)"
strokeWidth="6"
fill="transparent"
strokeDasharray={circumference}
strokeDashoffset={strokeDashoffset}
strokeLinecap="round"
className="transition-all duration-1500 ease-out"
/>
</svg>
<div className="absolute inset-0 flex flex-col items-center justify-center text-center mt-1">
<span className="text-[32px] font-extrabold leading-none">{score}</span>
<span className="text-[9px] text-muted-foreground font-semibold mt-0.5">SCORE</span>
</div>
</div>
{/* Quick metric grid beside ring */}
<div className="flex-1 grid grid-cols-2 gap-2 w-full">
<div className="bg-white/5 rounded-md p-2">
<div className="text-sm font-bold flex items-baseline gap-1">88% <span className="text-[10px] text-green-500">↑</span></div>
<div className="text-[10px] font-medium text-muted-foreground">Accuracy</div>
</div>
<div className="bg-white/5 rounded-md p-2">
<div className="text-sm font-bold flex items-baseline gap-1">45s <span className="text-[10px] text-red-500">↓</span></div>
<div className="text-[10px] font-medium text-muted-foreground">Speed/Q</div>
</div>
<div className="bg-white/5 rounded-md p-2">
<div className="text-sm font-bold flex items-baseline gap-1">High <span className="text-[10px] text-green-500">↑</span></div>
<div className="text-[10px] font-medium text-muted-foreground">Consistency</div>
</div>
<div className="bg-white/5 rounded-md p-2">
<div className="text-sm font-bold flex items-baseline gap-1">64% <span className="text-[10px] text-green-500">↑</span></div>
<div className="text-[10px] font-medium text-muted-foreground">Syllabus</div>
</div>
</div>
</div>
{/* Breakdown Bars */}
<div className="w-full space-y-3">
{/* Subject Bars */}
<div className="space-y-1.5 mb-3 relative z-10">
{[
{ label: "Accuracy", value: breakdown.accuracy, max: 40 },
{ label: "Speed", value: breakdown.speed, max: 20 },
{ label: "Coverage", value: breakdown.coverage, max: 20 },
{ label: "Consistency", value: breakdown.consistency, max: 10 },
{ label: "Recovery", value: breakdown.recovery, max: 10 },
].map((item, idx) => (
<div key={idx} className="space-y-1">
<div className="flex justify-between text-xs font-semibold">
<span className="text-muted-foreground">{item.label}</span>
<span>{item.value} <span className="opacity-50">/ {item.max}</span></span>
</div>
<div className="h-2 w-full bg-secondary rounded-full overflow-hidden">
<div
className="h-full bg-primary transition-all duration-1000 ease-out"
style={{ width: `${(item.value / item.max) * 100}%` }}
{ name: "Physics", val: 68, color: "#00F0FF" },
{ name: "Chemistry", val: 74, color: "#F59E0B" },
{ name: "Biology", val: 82, color: "#10B981" },
].map(sub => (
<div key={sub.name} className="flex items-center gap-2">
<span className="text-[9px] font-semibold text-muted-foreground w-14 shrink-0">{sub.name}</span>
<div className="flex-1 h-1.5 rounded-full bg-white/10 overflow-hidden">
<div
className="h-full rounded-full transition-all duration-1000"
style={{ width: `${sub.val}%`, background: sub.color }}
/>
</div>
<span className="text-[9px] font-bold w-7 text-right" style={{ color: sub.color }}>{sub.val}%</span>
</div>
))}
</div>
</CardContent>
</Card>
{/* National Avg */}
<div className="flex items-center justify-between px-2 py-1.5 bg-white/5 rounded-md mb-3 relative z-10">
<span className="text-[10px] text-muted-foreground font-semibold">National Avg</span>
<span className="text-[10px] font-bold text-muted-foreground">58 / 100</span>
<span className="text-[10px] font-bold text-green-500">You: +{score - 58} above avg</span>
</div>
<div className="mt-auto px-3 py-2.5 bg-[#00F0FF]/10 border border-[#00F0FF]/20 rounded-md text-xs text-[#00F0FF] font-semibold flex items-center gap-2 relative z-10">
<Flame size={16} className="fill-[#00F0FF]/20 shrink-0" />
Your speed has dropped slightly. Focus on time-bound practice today.
</div>
</Card>
</div>
)
}

View File

@@ -1,9 +1,9 @@
"use client"
import { useEffect, useState } from "react"
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/card"
import { Badge } from "@/components/ui/badge"
import { CalendarClock, CheckCircle2, Circle, Clock, Loader2, PlayCircle, RefreshCw, XCircle } from "lucide-react"
import { Card, CardContent } from "@/components/ui/card"
import { Check, RefreshCw } from "lucide-react"
import Link from "next/link"
import { useActiveUser } from "@/hooks/useActiveUser"
@@ -30,9 +30,11 @@ export function StudyPlanCard() {
}
// Mock fallback if celery hasn't seeded yet
setTasks([
{ type: "Revision", description: "Revise Thermodynamics", duration_minutes: 15, status: "Pending" },
{ type: "Mistake_Review", description: "Daily Mistake Review Session", duration_minutes: 10, status: "Pending" },
{ type: "DPS", description: "Attempt Thermodynamics Practice Sheet", duration_minutes: 20, status: "Pending" }
{ type: "DPS", description: "DPS — Electrostatics + Kinematics", duration_minutes: 20, status: "Done" },
{ type: "Mistake_Review", description: "Mistake Review — Thermodynamics", duration_minutes: 10, status: "Done" },
{ type: "Micro_test", description: "Micro-test — GOC (General Organic Chemistry)", duration_minutes: 5, status: "Pending" },
{ type: "Test", description: "Weekly Test — Full Syllabus", duration_minutes: 180, status: "Pending" },
{ type: "Revision", description: "Revise — Circles + Straight Lines", duration_minutes: 15, status: "Pending" }
])
} finally {
setLoading(false)
@@ -49,91 +51,112 @@ export function StudyPlanCard() {
})
}
return (
<Card className="border-border shadow-sm h-full flex flex-col">
<CardHeader className="pb-4">
<div className="flex items-center justify-between">
<CardTitle className="text-xl flex items-center gap-2">
<CalendarClock className="text-primary" size={24} />
Today's Plan
</CardTitle>
<Badge variant="outline" className="bg-primary/5 text-primary border-primary/20">
{tasks.reduce((acc, curr) => acc + curr.duration_minutes, 0)} min total
</Badge>
</div>
<CardDescription>AI-generated curriculum targeting your weak spots.</CardDescription>
</CardHeader>
<CardContent className="flex-1 flex flex-col">
{loading ? (
<div className="flex-1 flex items-center justify-center p-8">
<RefreshCw className="animate-spin text-muted-foreground mr-3" /> Loading schedule...
</div>
) : (
<div className="space-y-3">
{tasks.map((task, idx) => {
const isDone = task.status === "Done"
const isSkipped = task.status === "Skipped"
const isPending = task.status === "Pending"
const completedTasks = tasks.filter(t => t.status === "Done").length
const totalTasks = tasks.length
const progressPercent = totalTasks === 0 ? 0 : Math.round((completedTasks / totalTasks) * 100)
return (
<div
key={idx}
className={`flex items-center p-4 rounded-xl border transition-all duration-300 ${
isDone ? "bg-green-500/5 border-green-500/20" :
isSkipped ? "bg-secondary/50 border-transparent opacity-60" :
"bg-background border-border hover:border-primary/50"
}`}
>
<div className="flex flex-col flex-1 mr-4">
<div className="flex items-center gap-2 mb-1">
<Badge variant="secondary" className="text-[10px] uppercase tracking-wider">{task.type}</Badge>
<span className="text-xs font-semibold text-muted-foreground flex items-center gap-1">
<Clock size={12}/> {task.duration_minutes} min
</span>
</div>
<p className={`font-semibold text-sm transition-all duration-300 ${
isDone || isSkipped ? "line-through text-muted-foreground" : "text-foreground"
}`}>
{task.description}
</p>
</div>
{/* Controls */}
<div className="flex items-center gap-2">
{isPending && (
<>
<button
onClick={() => toggleTask(idx, "Skipped")}
className="p-2 rounded-full hover:bg-secondary text-muted-foreground hover:text-foreground transition-colors"
title="Skip Task"
>
<XCircle size={20} />
</button>
<button
onClick={() => toggleTask(idx, "Done")}
className="p-2 rounded-full hover:bg-green-500/10 text-muted-foreground hover:text-green-500 transition-colors"
title="Complete Task"
>
<Circle size={20} />
</button>
</>
)}
{(isDone || isSkipped) && (
<button
onClick={() => toggleTask(idx, "Pending")}
className={`flex items-center gap-1 text-xs font-bold px-3 py-1.5 rounded-full ${isDone ? "bg-green-500 text-white" : "bg-secondary text-muted-foreground"}`}
>
{isDone ? <CheckCircle2 size={14}/> : <PlayCircle size={14}/>}
{isDone ? "Completed" : "Resume"}
</button>
)}
</div>
</div>
)
})}
const getTagStyle = (type: string) => {
switch(type) {
case "Revision": return "bg-yellow-500/10 text-yellow-500"
case "Mistake_Review": return "bg-red-500/10 text-red-500"
case "DPS": return "bg-[#00F0FF]/10 text-[#00F0FF]"
case "Micro_test": return "bg-green-500/10 text-green-500"
case "Test": return "bg-purple-500/10 text-purple-500"
default: return "bg-secondary text-muted-foreground"
}
}
const getDisplayType = (type: string) => {
switch(type) {
case "Revision": return "Revision"
case "Mistake_Review": return "Review"
case "DPS": return "Practice"
case "Micro_test": return "Micro-test"
case "Test": return "Test"
default: return "Task"
}
}
return (
<div className="flex flex-col h-full">
<div className="flex justify-between items-center pb-3">
<h2 className="text-base font-bold text-primary flex items-center gap-2">
📅 Today's Study Plan
</h2>
<Link href="/dashboard/ai-coach/study-plan" className="text-xs text-[#00F0FF] font-semibold hover:underline transition-colors">Expand View ↗</Link>
</div>
<Card className="border-border bg-card shadow-[0_4px_24px_rgba(0,0,0,0.3)] flex-1 overflow-hidden group hover:border-[#00F0FF]/30 transition-colors">
<div className="px-4 py-3.5 flex justify-between items-center border-b border-border">
<div className="text-[13px] font-bold text-muted-foreground">
{new Date().toLocaleDateString('en-US', { weekday: 'long', day: 'numeric', month: 'short' })}
</div>
)}
</CardContent>
</Card>
<div className="text-[12px] text-[#00F0FF] font-bold">{progressPercent}% Completed</div>
</div>
{/* Progress bar */}
<div className="h-[3px] bg-[#00F0FF]/10 w-full">
<div
className="h-full bg-gradient-to-r from-[#00F0FF] to-teal-400 rounded-r-[2px] transition-all duration-500"
style={{ width: `${progressPercent}%` }}
/>
</div>
<CardContent className="p-0 flex-1 flex flex-col">
{loading ? (
<div className="flex-1 flex items-center justify-center p-8 min-h-[200px]">
<RefreshCw className="animate-spin text-muted-foreground mr-3" /> Loading schedule...
</div>
) : (
<div className="py-1">
{tasks.map((task, idx) => {
const isDone = task.status === "Done"
const isCurrent = idx === completedTasks && !isDone // First pending task is current
return (
<div
key={idx}
onClick={() => toggleTask(idx, isDone ? "Pending" : "Done")}
className={`flex items-center gap-3 px-4 py-2.5 transition-colors cursor-pointer border-l-[3px] ${
isDone ? "opacity-60 border-green-500 hover:bg-white/5" :
isCurrent ? "border-[#00F0FF] bg-[#00F0FF]/5 hover:bg-[#00F0FF]/10" :
"border-transparent hover:bg-white/5"
}`}
>
{/* Custom Checkbox */}
<div className={`w-5 h-5 rounded-full border-2 flex items-center justify-center shrink-0 transition-all ${
isDone ? "bg-green-500 border-green-500" :
isCurrent ? "border-[#00F0FF] shadow-[0_0_0_0_rgba(0,240,255,0.3)] animate-[pulse-border_2s_infinite]" :
"border-muted-foreground"
}`}>
{isDone && <Check size={12} className="stroke-white stroke-[3px]" />}
</div>
<div className="flex-1 min-w-0">
<div className="text-[13px] font-bold text-primary truncate">{task.description}</div>
<div className="text-[11px] text-muted-foreground mt-0.5">{task.type.replace('_', ' ')} • Active Session</div>
</div>
<div className={`text-[10px] font-bold px-2 py-0.5 rounded-md shrink-0 ${getTagStyle(task.type)}`}>
{getDisplayType(task.type)}
</div>
<div className="text-[12px] font-bold text-muted-foreground bg-white/5 px-2 py-0.5 rounded-md shrink-0">
{task.duration_minutes}m
</div>
</div>
)
})}
</div>
)}
</CardContent>
</Card>
<style dangerouslySetInnerHTML={{__html: `
@keyframes pulse-border {
0%, 100% { box-shadow: 0 0 0 0 rgba(0, 240, 255, 0.3); }
50% { box-shadow: 0 0 0 6px rgba(0, 240, 255, 0); }
}
`}} />
</div>
)
}

View File

@@ -0,0 +1,74 @@
"use client"
import { Card, CardContent } from "@/components/ui/card"
import { Flame, Star } from "lucide-react"
import { useState } from "react"
import Link from "next/link"
const SLOTS = [
{ label: "6 AM", h: 30, peak: false },
{ label: "9 AM", h: 50, peak: false },
{ label: "12 PM", h: 65, peak: false },
{ label: "3 PM", h: 100, peak: true },
{ label: "6 PM", h: 80, peak: false },
{ label: "9 PM", h: 60, peak: false },
{ label: "12 AM", h: 38, peak: false },
]
export function StudyTimeOptimizationCard() {
const [hovered, setHovered] = useState<number | null>(null)
return (
<div>
<div className="flex justify-between items-center pb-3">
<h2 className="text-base font-bold text-primary flex items-center gap-2">
📈 Study Time Optimization
</h2>
<Link href="/dashboard/ai-coach/strategy" className="text-xs text-[#00F0FF] font-semibold hover:underline transition-colors">Expand View ↗</Link>
</div>
<Card className="border-border bg-card shadow-[0_4px_24px_rgba(0,0,0,0.3)] group hover:border-[#00F0FF]/30 transition-colors">
<CardContent className="p-4">
{/* Peak window badge */}
<div className="flex items-center gap-2 mb-4">
<Star size={13} className="fill-yellow-500 text-yellow-500" />
<span className="text-xs font-bold text-yellow-500">Peak Productivity:</span>
<span className="text-xs font-semibold text-foreground bg-yellow-500/10 border border-yellow-500/20 px-2 py-0.5 rounded-full">
2 PM – 5 PM
</span>
</div>
{/* Bar chart */}
<div className="flex items-end gap-1.5 h-20 px-1 mb-6">
{SLOTS.map((slot, i) => (
<div
key={slot.label}
className="flex-1 flex flex-col items-center gap-0.5 cursor-pointer"
onMouseEnter={() => setHovered(i)}
onMouseLeave={() => setHovered(null)}
>
<div
className={`w-full rounded-t-md transition-all duration-300 ${
slot.peak
? "bg-gradient-to-t from-yellow-600/60 to-yellow-400 shadow-[0_0_10px_rgba(234,179,8,0.4)]"
: hovered === i
? "bg-gradient-to-t from-[#00F0FF]/50 to-[#00F0FF]"
: "bg-gradient-to-t from-[#00F0FF]/20 to-[#00F0FF]/60"
}`}
style={{ height: `${slot.h}%` }}
/>
<span className="text-[8px] text-muted-foreground font-medium whitespace-nowrap mt-1">
{slot.label}
</span>
</div>
))}
</div>
<div className="mt-1 px-3 py-2.5 bg-yellow-500/10 border border-yellow-500/20 rounded-md text-xs text-yellow-500 font-medium flex items-center gap-2">
<Flame size={16} className="fill-yellow-500 shrink-0" />
You perform best 2 PM – 5 PM. Schedule your hardest mock tests in this window.
</div>
</CardContent>
</Card>
</div>
)
}

View File

@@ -1,10 +1,9 @@
"use client"
import { useState, useRef, useEffect } from "react"
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input"
import { Send, Bot, User, Loader2 } from "lucide-react"
import { Card, CardContent } from "@/components/ui/card"
import { Send, Loader2 } from "lucide-react"
import Link from "next/link"
import ReactMarkdown from "react-markdown"
import remarkMath from "remark-math"
import rehypeKatex from "rehype-katex"
@@ -93,53 +92,81 @@ export function TutorChat() {
}
return (
<Card className="border-border shadow-sm flex flex-col h-[600px]">
<CardHeader className="border-b bg-secondary/10 pb-4">
<CardTitle className="text-xl flex items-center gap-2">
<Bot className="text-primary" size={24} />
AI Tutor Chat
</CardTitle>
</CardHeader>
<CardContent className="flex-1 overflow-y-auto p-4 space-y-4">
{messages.map((msg, idx) => (
<div key={idx} className={`flex ${msg.role === "user" ? "justify-end" : "justify-start"}`}>
<div className={`max-w-[80%] rounded-2xl p-4 flex gap-3 ${
msg.role === "user" ? "bg-primary text-primary-foreground rounded-tr-sm" : "bg-secondary rounded-tl-sm"
}`}>
{msg.role === "assistant" && <Bot size={20} className="mt-1 shrink-0" />}
<div className="prose prose-sm dark:prose-invert max-w-none overflow-hidden">
{msg.role === "assistant" ? (
<ReactMarkdown
remarkPlugins={[remarkMath]}
rehypePlugins={[rehypeKatex]}
>
{msg.content || "..."}
</ReactMarkdown>
) : (
msg.content
)}
</div>
<div className="flex flex-col h-full min-h-[500px]">
<div className="flex justify-between items-center pb-3">
<h2 className="text-base font-bold text-primary flex items-center gap-2">
🤖 Ask AI Tutor
</h2>
</div>
<Card className="border-border bg-card shadow-[0_4px_24px_rgba(0,0,0,0.3)] flex-1 overflow-hidden flex flex-col group hover:border-[#00F0FF]/30 transition-colors">
<div className="px-4 py-3 bg-[#00F0FF]/5 border-b border-border flex items-center gap-2.5">
<div className="w-8 h-8 rounded-full bg-gradient-to-br from-[#00F0FF] to-blue-500 flex items-center justify-center text-sm font-bold text-background shrink-0">
AI
</div>
<div>
<div className="text-[13px] font-bold text-primary">PragatiVeer Tutor</div>
<div className="text-[11px] text-green-500 flex items-center gap-1 font-medium">
<span className="w-1.5 h-1.5 rounded-full bg-green-500"></span> Online
</div>
</div>
))}
<div ref={messagesEndRef} />
</CardContent>
<div className="ml-auto text-[11px] text-muted-foreground">
<span className="text-yellow-500 font-bold">14</span> msgs left
</div>
</div>
<CardContent className="flex-1 overflow-y-auto p-4 space-y-4">
{messages.map((msg, idx) => (
<div key={idx} className={`flex ${msg.role === "user" ? "justify-end" : "justify-start"}`}>
<div className="max-w-[85%] flex flex-col gap-1">
<div className={`p-3 text-[13px] leading-relaxed ${
msg.role === "user"
? "bg-[#00F0FF] text-background rounded-[12px] rounded-br-[4px] font-medium"
: "bg-[#00F0FF]/10 border border-[#00F0FF]/20 text-primary rounded-[12px] rounded-bl-[4px]"
}`}>
<div className="prose prose-sm dark:prose-invert max-w-none [&>p]:m-0">
{msg.role === "assistant" ? (
<ReactMarkdown
remarkPlugins={[remarkMath]}
rehypePlugins={[rehypeKatex]}
>
{msg.content || "..."}
</ReactMarkdown>
) : (
msg.content
)}
</div>
</div>
<div className={`text-[10px] text-muted-foreground ${msg.role === "user" ? "text-right" : "text-left"}`}>
Now
</div>
</div>
</div>
))}
<div ref={messagesEndRef} />
</CardContent>
<div className="p-4 border-t bg-background">
<form onSubmit={handleSend} className="flex gap-2">
<Input
<div className="p-2.5 border-t border-border bg-black/20 flex items-center gap-2">
<input
type="text"
value={input}
onChange={(e) => setInput(e.target.value)}
placeholder="Ask me to explain a concept..."
className="flex-1 bg-secondary/50 border-transparent focus-visible:ring-primary"
onKeyDown={(e) => {
if (e.key === 'Enter') handleSend();
}}
placeholder="Message AI Tutor..."
className="flex-1 bg-white/5 border border-border rounded-full px-4 py-2 text-[13px] text-primary focus:outline-none focus:border-[#00F0FF] focus:bg-[#00F0FF]/5 transition-colors"
disabled={streaming}
/>
<Button type="submit" size="icon" disabled={!input.trim() || streaming}>
{streaming ? <Loader2 className="animate-spin" size={18} /> : <Send size={18} />}
</Button>
</form>
</div>
</Card>
<button
onClick={handleSend}
disabled={!input.trim() || streaming}
className="w-9 h-9 rounded-full bg-[#00F0FF] flex items-center justify-center shrink-0 hover:bg-[#00F0FF]/90 transition-transform hover:scale-105 disabled:opacity-50 disabled:hover:scale-100"
>
{streaming ? <Loader2 className="animate-spin text-background" size={16} /> : <Send size={16} className="text-background" />}
</button>
</div>
</Card>
</div>
)
}

View File

@@ -0,0 +1,77 @@
"use client"
import { TrendingUp, TrendingDown, Brain, Zap } from "lucide-react"
import { Card, CardContent } from "@/components/ui/card"
import Link from "next/link"
const DAYS = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"]
const BAR_HEIGHTS = [55, 70, 45, 90, 80, 60, 75] // % heights
const ACCURACY = [82, 78, 91, 88, 85, 79, 93]
export function WeeklyProgressReport() {
const avgAccuracy = Math.round(ACCURACY.reduce((a, b) => a + b, 0) / ACCURACY.length)
const trend = ACCURACY[ACCURACY.length - 1] - ACCURACY[0]
const maxBar = Math.max(...BAR_HEIGHTS)
return (
<div className="flex flex-col h-full">
<div className="flex justify-between items-center pb-3">
<h2 className="text-base font-bold text-primary flex items-center gap-2">
📊 This Week at a Glance
</h2>
<Link href="/dashboard/ai-coach/study-plan" className="text-xs text-[#00F0FF] font-semibold hover:underline transition-colors">
Expand View ↗
</Link>
</div>
<Card className="border-border bg-card shadow-[0_4px_24px_rgba(0,0,0,0.3)] flex-1 flex flex-col group hover:border-[#00F0FF]/30 transition-colors">
<CardContent className="p-4 flex flex-col h-full gap-4">
{/* Summary chips */}
<div className="flex items-center gap-2 flex-wrap">
<div className="flex items-center gap-1.5 text-xs font-bold px-2.5 py-1 rounded-full bg-green-500/10 text-green-500 border border-green-500/20">
<TrendingUp size={12} />
{avgAccuracy}% avg accuracy
</div>
<div className={`flex items-center gap-1.5 text-xs font-bold px-2.5 py-1 rounded-full border ${trend >= 0 ? "bg-[#00F0FF]/10 text-[#00F0FF] border-[#00F0FF]/20" : "bg-red-500/10 text-red-500 border-red-500/20"}`}>
{trend >= 0 ? <TrendingUp size={12} /> : <TrendingDown size={12} />}
{trend >= 0 ? "+" : ""}{trend}% vs Mon
</div>
<div className="flex items-center gap-1.5 text-xs font-bold px-2.5 py-1 rounded-full bg-yellow-500/10 text-yellow-500 border border-yellow-500/20">
<Zap size={12} />
7/7 days active
</div>
</div>
{/* Bar Chart */}
<div className="flex items-end gap-2 h-24 flex-1">
{DAYS.map((day, i) => (
<div key={day} className="flex flex-col items-center gap-1 flex-1">
<div className="relative w-full flex items-end justify-center" style={{ height: "80px" }}>
<div
className={`w-full rounded-t-md transition-all duration-700 ${
i === 6
? "bg-gradient-to-t from-[#00F0FF]/60 to-[#00F0FF]"
: "bg-gradient-to-t from-white/10 to-white/20"
}`}
style={{ height: `${(BAR_HEIGHTS[i] / maxBar) * 80}px` }}
title={`${ACCURACY[i]}% accuracy`}
/>
</div>
<span className="text-[9px] text-muted-foreground font-semibold">{day}</span>
</div>
))}
</div>
{/* AI insight */}
<div className="px-3 py-2.5 bg-[#00F0FF]/10 border border-[#00F0FF]/20 rounded-md text-xs text-[#00F0FF] font-medium flex items-start gap-3">
<Brain size={16} className="shrink-0 mt-0.5" />
<div className="flex flex-col gap-1.5">
<span><strong className="text-white">You keep making sign errors in Thermodynamics.</strong> In your last 3 attempts, you've used the wrong sign for work done (W = -PΔV). Review the First Law of Thermodynamics — we've queued 3 practice questions for tomorrow.</span>
<span className="text-[#00F0FF]/80">💡 <strong className="text-white">Tip:</strong> You perform best between 7-9 PM. Schedule difficult topics then.</span>
</div>
</div>
</CardContent>
</Card>
</div>
)
}

View File

@@ -2,7 +2,7 @@ import React from 'react';
import { useRouter } from 'next/navigation';
import { Button } from "@/components/ui/button";
import { Card, CardContent } from "@/components/ui/card";
import { CheckCircle2, XCircle, AlertCircle, Clock, Award, ArrowRight, RotateCcw } from "lucide-react";
import { CheckCircle2, XCircle, AlertCircle, Clock, Award, ArrowRight, RotateCcw, Share2, Mail } from "lucide-react";
interface ExamAttemptPayload {
score: number;
@@ -153,6 +153,26 @@ export function ExamResult({ payload, attemptId, onRetake }: ExamResultProps) {
</Button>
</div>
<div className="flex flex-col items-center pt-8 border-t border-border mt-8 space-y-4">
<h3 className="text-xl font-bold tracking-tight">Challenge a Friend</h3>
<p className="text-muted-foreground text-center max-w-md">Share this test with your friends and see if they can beat your score of {finalScore}!</p>
<div className="flex gap-4">
<Button variant="outline" className="gap-2 font-bold rounded-xl" onClick={() => {
const subject = encodeURIComponent("Can you beat my PragatiVeer Mock Test Score?");
const body = encodeURIComponent(`I just scored ${finalScore}/${maxPossibleScore} with ${Math.round(payload.accuracy)}% accuracy in a PragatiVeer Mock Test!\n\nTake the challenge here: https://pragativeer.com/challenge`);
window.open(`mailto:?subject=${subject}&body=${body}`);
}}>
<Mail className="size-4" /> Share via Email
</Button>
<Button className="gap-2 font-bold rounded-xl bg-green-500 hover:bg-green-600 text-white shadow-lg" onClick={() => {
const text = encodeURIComponent(`I just scored ${finalScore}/${maxPossibleScore} in a PragatiVeer Mock Test! Can you beat me? \nTake the challenge here: https://pragativeer.com/challenge`);
window.open(`https://wa.me/?text=${text}`, '_blank');
}}>
<Share2 className="size-4" /> Share on WhatsApp
</Button>
</div>
</div>
</div>
</div>
);

View File

@@ -72,7 +72,6 @@ export function StreakOnboardingModal() {
src={`/streak-animation.html?streak=${streak}&theme=${activeTheme === 'light' ? 'light' : 'dark'}`}
className="w-full h-full border-none"
title="Streak Celebration"
allowtransparency="true"
/>
</div>

View File

@@ -0,0 +1,101 @@
import Link from "next/link"
import Image from "next/image"
import { Button } from "@/components/ui/button"
export function PublicLayout({ children }: { children: React.ReactNode }) {
return (
<div className="min-h-screen bg-background text-foreground overflow-x-hidden selection:bg-primary/20 relative">
{/* Navigation */}
<nav className="fixed top-0 w-full z-50 bg-background/50 backdrop-blur-xl border-b border-border/50">
<div className="max-w-7xl mx-auto px-6 h-20 flex items-center justify-between">
<Link href="/" className="relative h-14 w-60 hover:opacity-90 transition-opacity block">
<Image
src="/PragatiVeer_Logo/SVG_Logos/Rectangular_light_background.svg"
alt="PragatiVeer" fill className="object-contain object-left scale-135 origin-left dark:hidden" priority
/>
<Image
src="/PragatiVeer_Logo/SVG_Logos/Rectangular_dark_background.svg"
alt="PragatiVeer" fill className="object-contain object-left scale-135 origin-left hidden dark:block" priority
/>
</Link>
<div className="hidden md:flex items-center gap-8 text-sm font-medium text-muted-foreground">
<Link href="/#features" className="hover:text-foreground transition-colors">Features</Link>
<Link href="/#pricing" className="hover:text-foreground transition-colors">Pricing</Link>
<Link href="/about" className="hover:text-foreground transition-colors">About</Link>
</div>
<div className="flex items-center gap-4">
<Link href="/login" className="text-sm font-bold hover:text-primary transition-colors hidden sm:block">Log in</Link>
<Link href="/signup">
<Button className="rounded-full font-bold px-6 shadow-[0_0_15px_-5px_var(--color-primary)] hover:shadow-[0_0_25px_-5px_var(--color-primary)] transition-all">Get Started</Button>
</Link>
</div>
</div>
</nav>
{/* Main Content */}
<main className="relative z-10 min-h-screen flex flex-col">
{children}
</main>
{/* Global Footer (Static) */}
<footer className="relative z-10 bg-background/90 backdrop-blur-md border-t border-border pt-16 pb-8">
<div className="max-w-7xl mx-auto px-6">
<div className="grid grid-cols-1 md:grid-cols-4 gap-12 mb-16">
<div className="md:col-span-1 space-y-4">
<div className="relative h-12 w-64 mb-6">
<Image
src="/PragatiVeer_Logo/SVG_Logos/Rectangular_light_background.svg"
alt="PragatiVeer" fill className="object-contain object-left scale-135 origin-left dark:hidden"
/>
<Image
src="/PragatiVeer_Logo/SVG_Logos/Rectangular_dark_background.svg"
alt="PragatiVeer" fill className="object-contain object-left scale-135 origin-left hidden dark:block"
/>
</div>
<p className="text-sm text-muted-foreground font-medium leading-relaxed">
Re-engineering NEET & JEE preparation with adaptive AI models. Learn faster, rank higher.
</p>
</div>
<div>
<h4 className="font-bold mb-6 tracking-tight text-foreground">Product</h4>
<ul className="space-y-3 text-sm font-medium text-muted-foreground">
<li><Link href="/#features" className="hover:text-primary transition-colors">Features</Link></li>
<li><Link href="/#pricing" className="hover:text-primary transition-colors">Pricing</Link></li>
<li><Link href="/dashboard" className="hover:text-primary transition-colors">Student Dashboard</Link></li>
<li><Link href="/admin" className="hover:text-[#00F0FF] transition-colors">Admin Panel</Link></li>
</ul>
</div>
<div>
<h4 className="font-bold mb-6 tracking-tight text-foreground">Company</h4>
<ul className="space-y-3 text-sm font-medium text-muted-foreground">
<li><Link href="/about" className="hover:text-primary transition-colors">About Us</Link></li>
<li><Link href="/contact" className="hover:text-primary transition-colors">Contact</Link></li>
<li><Link href="#" className="hover:text-primary transition-colors">Careers</Link></li>
</ul>
</div>
<div>
<h4 className="font-bold mb-6 tracking-tight text-foreground">Legal</h4>
<ul className="space-y-3 text-sm font-medium text-muted-foreground">
<li><Link href="/terms" className="hover:text-primary transition-colors">Terms & Conditions</Link></li>
<li><Link href="/privacy" className="hover:text-primary transition-colors">Privacy Policy</Link></li>
<li><Link href="/refund" className="hover:text-primary transition-colors">Refund Policy</Link></li>
<li><Link href="/cancellation" className="hover:text-primary transition-colors">Cancellation Policy</Link></li>
<li><Link href="/disclaimer" className="hover:text-primary transition-colors">Disclaimer</Link></li>
</ul>
</div>
</div>
<div className="border-t border-border pt-8 flex flex-col md:flex-row items-center justify-between gap-4">
<p className="text-sm text-muted-foreground font-medium">© {new Date().getFullYear()} PragatiVeer. All rights reserved.</p>
<div className="flex items-center gap-2 text-sm text-muted-foreground font-medium">
Made with <div className="size-2 rounded-full bg-primary animate-pulse"></div> by AI Agents
</div>
</div>
</div>
</footer>
</div>
)
}

View File

@@ -0,0 +1,32 @@
"use client";
import { Wrench } from "lucide-react";
import { motion } from "framer-motion";
export default function MaintenanceScreen() {
return (
<div className="min-h-screen bg-background flex flex-col items-center justify-center p-4">
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
className="max-w-md w-full bg-secondary/20 border border-border rounded-2xl p-8 text-center space-y-6"
>
<div className="mx-auto w-16 h-16 bg-yellow-500/10 text-yellow-500 rounded-full flex items-center justify-center mb-6">
<Wrench size={32} />
</div>
<h1 className="text-3xl font-bold tracking-tight text-foreground">We're Upgrading Your Learning Experience</h1>
<p className="text-muted-foreground">
Our platform is currently undergoing maintenance to bring you faster performance and exciting new features.
</p>
<div className="pt-4 border-t border-border/50">
<p className="text-sm font-medium text-foreground">
We'll be back shortly. Thank you for your patience!
</p>
</div>
</motion.div>
</div>
);
}

118
src/components/ui/table.tsx Normal file
View File

@@ -0,0 +1,118 @@
import * as React from "react"
import { cn } from "@/lib/utils"
const Table = React.forwardRef<
HTMLTableElement,
React.HTMLAttributes<HTMLTableElement>
>(({ className, ...props }, ref) => (
<div className="relative w-full overflow-auto">
<table
ref={ref}
className={cn("w-full caption-bottom text-sm", className)}
{...props}
/>
</div>
))
Table.displayName = "Table"
const TableHeader = React.forwardRef<
HTMLTableSectionElement,
React.HTMLAttributes<HTMLTableSectionElement>
>(({ className, ...props }, ref) => (
<thead
ref={ref}
className={cn("[&_tr]:border-b", className)}
{...props}
/>
))
TableHeader.displayName = "TableHeader"
const TableBody = React.forwardRef<
HTMLTableSectionElement,
React.HTMLAttributes<HTMLTableSectionElement>
>(({ className, ...props }, ref) => (
<tbody
ref={ref}
className={cn("[&_tr:last-child]:border-0", className)}
{...props}
/>
))
TableBody.displayName = "TableBody"
const TableFooter = React.forwardRef<
HTMLTableSectionElement,
React.HTMLAttributes<HTMLTableSectionElement>
>(({ className, ...props }, ref) => (
<tfoot
ref={ref}
className={cn("bg-primary font-medium text-primary-foreground", className)}
{...props}
/>
))
TableFooter.displayName = "TableFooter"
const TableRow = React.forwardRef<
HTMLTableRowElement,
React.HTMLAttributes<HTMLTableRowElement>
>(({ className, ...props }, ref) => (
<tr
ref={ref}
className={cn(
"border-b transition-colors hover:bg-muted/50 data-[state=selected]:bg-muted",
className
)}
{...props}
/>
))
TableRow.displayName = "TableRow"
const TableHead = React.forwardRef<
HTMLTableCellElement,
React.ThHTMLAttributes<HTMLTableCellElement>
>(({ className, ...props }, ref) => (
<th
ref={ref}
className={cn(
"h-12 px-4 text-left align-middle font-medium text-muted-foreground [&:has([role=checkbox])]:pr-0",
className
)}
{...props}
/>
))
TableHead.displayName = "TableHead"
const TableCell = React.forwardRef<
HTMLTableCellElement,
React.TdHTMLAttributes<HTMLTableCellElement>
>(({ className, ...props }, ref) => (
<td
ref={ref}
className={cn("p-4 align-middle [&:has([role=checkbox])]:pr-0", className)}
{...props}
/>
))
TableCell.displayName = "TableCell"
const TableCaption = React.forwardRef<
HTMLTableCaptionElement,
React.HTMLAttributes<HTMLTableCaptionElement>
>(({ className, ...props }, ref) => (
<caption
ref={ref}
className={cn("mt-4 text-sm text-muted-foreground", className)}
{...props}
/>
))
TableCaption.displayName = "TableCaption"
export {
Table,
TableHeader,
TableBody,
TableFooter,
TableHead,
TableRow,
TableCell,
TableCaption,
}