chore: Secure routes with auth middleware, update text and fix minor bugs

This commit is contained in:
2026-08-03 17:55:20 +05:30
parent 62d15c85f6
commit c2fab50749
68 changed files with 2515 additions and 6808 deletions

View File

@@ -77,7 +77,7 @@ export function AITutorChat() {
<Bot size={20} />
</div>
<div>
<CardTitle className="text-base font-bold text-foreground">AI Coach</CardTitle>
<CardTitle className="text-base font-bold text-foreground">AI Tutor</CardTitle>
<p className="text-xs font-medium text-green-500 flex items-center gap-1">
<span className="size-2 bg-green-500 rounded-full animate-pulse"></span> Online
</p>
@@ -123,7 +123,7 @@ export function AITutorChat() {
<CardFooter className="p-4 border-t border-border bg-background shrink-0">
<form onSubmit={(e) => { e.preventDefault(); handleSend(); }} className="flex w-full items-center gap-2">
<Input
placeholder="Ask your AI Coach..."
placeholder="Ask your AI Tutor..."
value={input}
onChange={(e) => setInput(e.target.value)}
className="flex-1 bg-secondary/30 border-border focus-visible:ring-primary h-12 rounded-xl"

View File

@@ -2,37 +2,57 @@
import { useEffect, useState } from "react"
import { Card, CardContent } from "@/components/ui/card"
import { RefreshCw } from "lucide-react"
import Link from "next/link"
import { RefreshCw, TrendingUp, MapPin, Star } from "lucide-react"
import { useActiveUser } from "@/hooks/useActiveUser"
const MOCK_COLLEGES: Record<string, Array<{ rank: number; name: string; branch: string; match: number; location: string; type: string; starred?: boolean }>> = {
Reach: [
{ rank: 1, name: "IIT Bombay", branch: "Computer Science", match: 48, location: "Mumbai", type: "IIT", starred: true },
{ rank: 2, name: "IIT Delhi", branch: "Electrical Engineering", match: 52, location: "Delhi", type: "IIT" },
{ rank: 3, name: "IIT Madras", branch: "Mech. Engineering", match: 55, location: "Chennai", type: "IIT" },
{ rank: 4, name: "IIT Roorkee", branch: "Computer Science", match: 60, location: "Roorkee", type: "IIT", starred: true },
{ rank: 5, name: "IIT Kharagpur", branch: "Aerospace Engg.", match: 63, location: "Kharagpur", type: "IIT" },
],
Target: [
{ rank: 1, name: "NIT Trichy", branch: "Computer Science", match: 85, location: "Trichy", type: "NIT", starred: true },
{ rank: 2, name: "NIT Surathkal", branch: "Information Tech.", match: 78, location: "Surathkal", type: "NIT" },
{ rank: 3, name: "NIT Warangal", branch: "Electronics & Comm.", match: 74, location: "Warangal", type: "NIT" },
{ rank: 4, name: "BITS Pilani", branch: "Computer Science", match: 70, location: "Pilani", type: "BITS", starred: true },
{ rank: 5, name: "NIT Calicut", branch: "Computer Science", match: 68, location: "Calicut", type: "NIT" },
],
Safety: [
{ rank: 1, name: "IIIT Hyderabad", branch: "Computer Science", match: 94, location: "Hyderabad", type: "IIIT", starred: true },
{ rank: 2, name: "IIIT Allahabad", branch: "Information Tech.", match: 91, location: "Allahabad", type: "IIIT" },
{ rank: 3, name: "DTU Delhi", branch: "Software Engineering", match: 89, location: "Delhi", type: "State" },
{ rank: 4, name: "NSIT Delhi", branch: "Computer Engineering", match: 87, location: "Delhi", type: "State" },
{ rank: 5, name: "NIT Jamshedpur", branch: "Mech. Engineering", match: 84, location: "Jamshedpur", type: "NIT" },
],
}
const MATCH_COLOR = (match: number) => {
if (match >= 80) return "text-green-500 bg-green-500/10"
if (match >= 60) return "text-amber-500 bg-amber-500/10"
return "text-red-400 bg-red-400/10"
}
export function CollegePredictorCard() {
const [data, setData] = useState<any>(null)
const [loading, setLoading] = useState(true)
const [activeTab, setActiveTab] = useState("Target")
const { userId, loading: userLoading } = useActiveUser();
const { userId, loading: userLoading } = useActiveUser()
useEffect(() => {
if (userLoading || !userId) return;
async function fetchReadiness() {
if (userLoading || !userId) return
async function fetchData() {
try {
const res = await fetch(`/api/ai-coach/readiness/${userId}`)
if (!res.ok) throw new Error("Failed to fetch")
const json = await res.json()
setData(json)
} catch (err) {
console.warn("Using mock fallback data for College Predictor.")
setData({
college_prediction: "NIT Trichy / NIT Surathkal on track.",
prediction_category: "Target"
})
if (!res.ok) throw new Error("fallback")
} catch {
// Use mock data — no-op
} finally {
setLoading(false)
}
}
fetchReadiness()
fetchData()
}, [userId, userLoading])
if (loading) {
@@ -43,77 +63,76 @@ export function CollegePredictorCard() {
)
}
const prediction = data?.college_prediction || "Keep Practicing"
const category = data?.prediction_category || "State Level"
const colleges = MOCK_COLLEGES[activeTab] || []
const getCategoryColor = (cat: string) => {
switch(cat.toUpperCase()) {
case "REACH": return "bg-purple-500/10 text-purple-600 border-purple-500/20"
case "TARGET": return "bg-blue-500/10 text-blue-600 border-blue-500/20"
case "SAFETY": return "bg-green-500/10 text-green-600 border-green-500/20"
default: return "bg-secondary text-muted-foreground border-transparent"
}
const TAB_COLORS: Record<string, string> = {
Reach: "bg-purple-500/10 border-purple-500/20 text-purple-400",
Target: "bg-[#00F0FF]/10 border-[#00F0FF]/20 text-[#00F0FF]",
Safety: "bg-green-500/10 border-green-500/20 text-green-400",
}
const INACTIVE = "bg-white/5 border-transparent text-muted-foreground hover:bg-white/10"
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">
🎓 College Predictor
</h2>
</div>
<Card className="border-border bg-card shadow-[0_4px_24px_rgba(0,0,0,0.3)] overflow-hidden group hover:border-[#00F0FF]/30 transition-colors">
<CardContent className="p-5 flex flex-col gap-4">
{/* Tabs */}
<div className="flex gap-1.5">
{["Reach", "Target", "Safety"].map((tab) => (
<button
key={tab}
onClick={() => setActiveTab(tab)}
className={`flex-1 py-2 text-xs font-bold rounded-lg border transition-all ${activeTab === tab ? TAB_COLORS[tab] : INACTIVE}`}
>
{tab}
</button>
))}
</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">
<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>
{/* Tab description */}
<div className="text-xs text-muted-foreground font-medium px-0.5">
{activeTab === "Reach" && "Colleges where your current score gives you a ~50–65% admission probability. Requires improvement."}
{activeTab === "Target" && "Colleges within strong reach. Your current trajectory suggests 68–90% match probability."}
{activeTab === "Safety" && "Colleges highly likely with your current score. Aim to convert all safety options."}
</div>
<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
{/* College List */}
<div className="flex flex-col gap-2">
{colleges.map((col) => (
<div key={col.name} className="flex items-center gap-3 p-3 rounded-xl bg-white/5 border border-border hover:bg-white/10 transition-colors group/item">
<div className={`w-8 h-8 rounded-full flex items-center justify-center text-xs font-black shrink-0 ${activeTab === "Reach" ? "bg-purple-500/10 text-purple-400" : activeTab === "Target" ? "bg-[#00F0FF]/10 text-[#00F0FF]" : "bg-green-500/10 text-green-400"}`}>
#{col.rank}
</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 className="flex items-center gap-1.5">
<span className="text-sm font-black text-primary truncate">{col.name}</span>
{col.starred && <Star size={11} className="text-yellow-400 fill-yellow-400 shrink-0" />}
</div>
<div className="flex items-center gap-2 text-xs text-muted-foreground font-semibold">
<span>{col.branch}</span>
<span className="opacity-40">·</span>
<span className="flex items-center gap-0.5">
<MapPin size={10} />{col.location}
</span>
<span className="opacity-40">·</span>
<span className="opacity-60">{col.type}</span>
</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 className={`text-xs font-black px-2.5 py-1 rounded-md shrink-0 ${MATCH_COLOR(col.match)}`}>
{col.match}%
</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>
<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>
{/* Bottom Tip */}
<div className="mt-1 p-3 bg-yellow-500/10 border border-yellow-500/20 rounded-xl text-xs text-yellow-500 font-bold flex items-center gap-2">
<TrendingUp size={14} className="shrink-0" />
{activeTab === "Reach" && "Score +20 marks in Maths to convert IIT Roorkee from Reach to Target!"}
{activeTab === "Target" && "Just 15 more marks in Maths can upgrade you from NIT Trichy to IIT Roorkee!"}
{activeTab === "Safety" && "You're on track for all safety colleges. Focus your energy on converting Target colleges."}
</div>
</CardContent>
</Card>
)
}

View File

@@ -1,13 +1,13 @@
"use client"
import { useState, useEffect } from "react"
import { Flame, ArrowRight, Timer, BookOpen } from "lucide-react"
import { Flame, ArrowRight, Timer, BookOpen, TrendingUp } 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: "∫" },
{ subject: "Physics", topic: "Rotational Dynamics", color: "from-blue-600/30 to-[#00F0FF]/20", accent: "#00F0FF", icon: "⚛️", questions: 12, weightage: "8–12 marks" },
{ subject: "Chemistry", topic: "Organic Reaction Mechanisms", color: "from-amber-600/30 to-yellow-500/20", accent: "#F59E0B", icon: "🧪", questions: 10, weightage: "6–10 marks" },
{ subject: "Biology", topic: "Human Nervous System", color: "from-green-600/30 to-emerald-400/20", accent: "#10B981", icon: "🧬", questions: 14, weightage: "10–14 marks" },
{ subject: "Maths", topic: "Integral Calculus", color: "from-purple-600/30 to-violet-400/20", accent: "#A78BFA", icon: "∫", questions: 15, weightage: "12–15 marks" },
]
export function DailyChallengeBanner() {
@@ -53,25 +53,30 @@ export function DailyChallengeBanner() {
</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
Daily Focus
</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 className="flex items-center gap-3 mt-1">
<p className="text-xs text-white/50 flex items-center gap-1.5">
<BookOpen size={11} />
{todayFocus.questions} practice questions
</p>
<p className="text-xs text-white/50 flex items-center gap-1.5">
<TrendingUp size={11} />
Worth <span className="font-bold text-white/70 ml-0.5">{todayFocus.weightage}</span> in exam
</p>
</div>
</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
<Timer size={10} /> Resets In
</div>
<div className="text-sm font-black text-white/80 font-mono tracking-wider">{timeLeft}</div>
</div>
@@ -79,7 +84,7 @@ export function DailyChallengeBanner() {
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} />
Start Practice <ArrowRight size={15} />
</button>
</div>
</div>

View File

@@ -5,39 +5,33 @@ 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>
</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>
<Card className="border-border bg-card shadow-[0_4px_24px_rgba(0,0,0,0.3)] flex-1 min-h-[440px] overflow-hidden group hover:border-[#00F0FF]/30 transition-colors">
<CardContent className="p-5 flex flex-col h-full justify-between">
<div className="grid grid-cols-2 gap-3">
<div className="p-3.5 rounded-lg bg-white/5 border border-border text-center flex flex-col justify-center">
<div className="text-2xl mb-1.5">🧪</div>
<div className="text-sm font-black text-primary">Chemistry First</div>
<div className="text-xs font-semibold text-muted-foreground mt-1">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 className="p-3.5 rounded-lg bg-white/5 border border-border text-center flex flex-col justify-center">
<div className="text-2xl mb-1.5">🧲</div>
<div className="text-sm font-black text-primary">Physics Next</div>
<div className="text-xs font-semibold text-muted-foreground mt-1">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 className="p-3.5 rounded-lg bg-white/5 border border-border text-center flex flex-col justify-center">
<div className="text-2xl mb-1.5">📐</div>
<div className="text-sm font-black text-primary">Maths Last</div>
<div className="text-xs font-semibold text-muted-foreground mt-1">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 className="p-3.5 rounded-lg bg-white/5 border border-border text-center flex flex-col justify-center">
<div className="text-2xl mb-1.5">⏱️</div>
<div className="text-sm font-black text-primary">Buffer Time</div>
<div className="text-xs font-semibold text-muted-foreground mt-1">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]" />
<div className="mt-4 flex items-center justify-center gap-2 p-3 rounded-lg bg-[#00F0FF]/10 border border-dashed border-[#00F0FF]/20 text-xs md:text-sm text-[#00F0FF] font-bold cursor-pointer hover:bg-[#00F0FF]/20 transition-colors">
<Printer size={18} className="stroke-[#00F0FF]" />
Print Exam Day Blueprint
</div>
</CardContent>

View File

@@ -19,7 +19,7 @@ export function MistakeHeatmap() {
};
return (
<Card className="border-border bg-background shadow-sm h-full flex flex-col">
<Card className="border-border bg-background shadow-sm h-full min-h-[540px] flex flex-col justify-between">
<CardHeader className="pb-4">
<div className="flex items-center justify-between">
<CardTitle className="text-xl flex items-center gap-2">
@@ -30,47 +30,45 @@ export function MistakeHeatmap() {
</div>
<p className="text-sm text-muted-foreground">Topic-wise error rate across last 5 tests.</p>
</CardHeader>
<CardContent className="flex-1">
<div className="overflow-x-auto">
<div className="min-w-[400px]">
{/* Header row */}
<div className="flex mb-2">
<div className="w-40 shrink-0 text-xs font-bold text-muted-foreground uppercase tracking-wider">Topic</div>
<div className="flex-1 flex justify-between text-xs font-bold text-muted-foreground uppercase tracking-wider text-center">
<span>T-4</span>
<span>T-3</span>
<span>T-2</span>
<span>T-1</span>
<span>Latest</span>
</div>
<CardContent className="flex-1 p-4 md:p-6 flex flex-col justify-between">
<div className="w-full">
{/* Header row */}
<div className="flex items-center mb-3 text-xs sm:text-sm font-black text-muted-foreground uppercase tracking-wider">
<div className="w-32 sm:w-36 md:w-40 shrink-0 pr-2">Topic</div>
<div className="flex-1 grid grid-cols-5 gap-1 text-center">
<span>T-4</span>
<span>T-3</span>
<span>T-2</span>
<span>T-1</span>
<span>Latest</span>
</div>
{/* Data rows */}
<div className="space-y-3">
{HEATMAP_DATA.map((row, idx) => (
<div key={idx} className="flex items-center group">
<div className="w-40 shrink-0 text-sm font-medium truncate pr-2 group-hover:text-primary transition-colors">
{row.topic}
</div>
<div className="flex-1 flex justify-between gap-1">
{row.attempts.map((errRate, i) => (
<div
key={i}
className={`flex-1 h-8 rounded-md border transition-all duration-300 group-hover:scale-105 flex items-center justify-center cursor-help ${getColor(errRate)}`}
title={`Error Rate: ${errRate}%`}
>
<span className="text-[10px] font-bold text-foreground/80">{errRate}%</span>
</div>
))}
</div>
</div>
{/* Data rows */}
<div className="space-y-3">
{HEATMAP_DATA.map((row, idx) => (
<div key={idx} className="flex items-center group">
<div className="w-32 sm:w-36 md:w-40 shrink-0 text-xs sm:text-sm md:text-base font-bold truncate pr-2 group-hover:text-primary transition-colors" title={row.topic}>
{row.topic}
</div>
))}
</div>
<div className="flex-1 grid grid-cols-5 gap-1">
{row.attempts.map((errRate, i) => (
<div
key={i}
className={`h-8 sm:h-9 rounded-md border transition-all duration-300 group-hover:scale-105 flex items-center justify-center cursor-help ${getColor(errRate)}`}
title={`Error Rate: ${errRate}%`}
>
<span className="text-[10px] sm:text-xs font-black text-foreground/90">{errRate}%</span>
</div>
))}
</div>
</div>
))}
</div>
</div>
{/* Legend */}
<div className="mt-6 flex items-center justify-center gap-4 text-xs font-medium text-muted-foreground">
<div className="mt-6 pt-4 border-t border-border/50 flex flex-wrap items-center justify-center gap-x-4 gap-y-2 text-xs font-bold text-muted-foreground">
<div className="flex items-center gap-1.5"><div className="size-3 rounded-sm bg-green-500/20 border border-green-500/30"></div> {"< 10%"}</div>
<div className="flex items-center gap-1.5"><div className="size-3 rounded-sm bg-yellow-500/30 border border-yellow-500/50"></div> {"10-30%"}</div>
<div className="flex items-center gap-1.5"><div className="size-3 rounded-sm bg-amber-500/60 border border-amber-500"></div> {"30-50%"}</div>

View File

@@ -2,8 +2,7 @@
import { useState } from "react"
import { Card, CardContent } from "@/components/ui/card"
import { Flame } from "lucide-react"
import Link from "next/link"
import { Flame, CheckCircle2, Clock } from "lucide-react"
import ReactMarkdown from 'react-markdown'
import remarkMath from 'remark-math'
import rehypeKatex from 'rehype-katex'
@@ -37,6 +36,22 @@ interface MistakeReviewCardProps {
onSubmitReview: (mistakeId: string, selectedAnswer: string) => Promise<{is_correct: boolean, correct_answer: string}>
}
// Generate dynamic SM-2 review dates relative to today
function getReviewDates(reviewCount: number) {
const today = new Date()
const fmt = (d: Date) => d.toLocaleDateString("en-IN", { day: "numeric", month: "short" })
const daysBefore = (n: number) => { const d = new Date(today); d.setDate(d.getDate() - n); return d }
const daysAfter = (n: number) => { const d = new Date(today); d.setDate(d.getDate() + n); return d }
// SM-2 intervals: 1d, 4d, 7d, then mastery
return {
first: fmt(daysBefore(reviewCount >= 2 ? 7 : 1)),
second: fmt(daysBefore(reviewCount >= 2 ? 3 : 0)),
today: fmt(today),
mastery: fmt(daysAfter(7)),
}
}
export function MistakeReviewCard({ data, onNext, onSubmitReview }: MistakeReviewCardProps) {
const [selected, setSelected] = useState<string | null>(null)
const [result, setResult] = useState<'success' | 'error' | null>(null)
@@ -46,7 +61,6 @@ export function MistakeReviewCard({ data, onNext, onSubmitReview }: MistakeRevie
const handleCheck = async () => {
if (!selected) return;
setIsSubmitting(true)
try {
const res = await onSubmitReview(data.mistake.id, selected)
setResult(res.is_correct ? 'success' : 'error')
@@ -66,69 +80,70 @@ export function MistakeReviewCard({ data, onNext, onSubmitReview }: MistakeRevie
}
const { mistake, question } = data;
const dates = getReviewDates(mistake.review_count)
const reviewLabel = mistake.review_count === 0 ? "1st Review" : mistake.review_count === 1 ? "2nd Review" : "3rd Review"
return (
<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>
</div>
{/* Spaced Repetition Timeline */}
{/* SM-2 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="absolute top-1/2 left-0 h-0.5 bg-[#00F0FF] -translate-y-1/2 z-0 transition-all"
style={{ width: mistake.review_count >= 1 ? (mistake.review_count >= 2 ? "67%" : "33%") : "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="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>
{[
{ label: "1st Attempt", date: dates.first, done: mistake.review_count >= 1 },
{ label: "2nd Review", date: dates.second, done: mistake.review_count >= 2 },
{ label: reviewLabel, date: dates.today, active: true },
{ label: "Mastered", date: dates.mastery, done: mistake.is_mastered },
].map((step, i) => (
<div key={i} className={`relative z-10 flex flex-col items-center gap-1.5 ${step.active ? "scale-110" : step.done ? "opacity-70" : "opacity-40"}`}>
<div className={`rounded-full border-2 border-background ${
step.done ? "w-3.5 h-3.5 bg-green-500" :
step.active ? "w-4 h-4 border-[3px] border-[#00F0FF] bg-background shadow-[0_0_10px_rgba(0,240,255,0.5)]" :
"w-3.5 h-3.5 bg-secondary"
}`} />
<span className={`text-xs font-bold ${step.active ? "text-[#00F0FF]" : step.done ? "text-green-400" : "text-muted-foreground"}`}>
{step.label}
</span>
<span className="text-[11px] text-muted-foreground font-semibold">{step.date}</span>
</div>
))}
</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">
{/* Question Card */}
<Card className="border-border bg-card shadow-[0_4px_24px_rgba(0,0,0,0.3)] min-h-[540px] h-full relative overflow-hidden flex flex-col justify-between group hover:border-[#00F0FF]/30 transition-colors">
<CardContent className="p-6 flex-1 flex flex-col justify-between">
{/* Meta Tags */}
<div className="flex items-center gap-2 mb-4 flex-wrap">
<span className="text-xs font-bold px-3 py-1 rounded-md bg-white/5 border border-border text-muted-foreground uppercase tracking-widest">
{question.subject}
</span>
<span className="text-xs font-bold px-2.5 py-1 rounded-md bg-white/5 border border-border text-muted-foreground">
{mistake.topic}
</span>
<span className="text-[11px] font-bold text-red-500 bg-red-500/10 px-2 py-0.5 rounded-md">
<span className="text-xs font-bold text-red-400 bg-red-400/10 border border-red-400/20 px-2.5 py-1 rounded-md">
{mistake.error_type}
</span>
<span className="ml-auto text-xs font-bold text-muted-foreground flex items-center gap-1">
<Clock size={11} /> Review #{mistake.review_count + 1}
</span>
</div>
{/* Question */}
<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>
{/* Options */}
<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)]"
@@ -140,39 +155,39 @@ export function MistakeReviewCard({ data, onNext, onSubmitReview }: MistakeRevie
}
return (
<button
<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}`}
className={`text-left p-4 rounded-xl border-[1.5px] transition-all text-base md:text-lg font-bold flex gap-3.5 items-center ${btnClass}`}
>
<span className="font-bold opacity-70">{letter})</span>
<span className="flex-1">
<ReactMarkdown remarkPlugins={[remarkMath]} rehypePlugins={[rehypeKatex]}>{text}</ReactMarkdown>
</span>
{result && letter === correctAnswer && <CheckCircle2 size={16} className="text-green-500 shrink-0" />}
</button>
)
})}
</div>
{/* Action Button */}
{/* Actions */}
{!result ? (
<div className="mt-5 flex justify-end">
<button
onClick={handleCheck}
<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"
className="px-6 py-2.5 rounded-xl 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
<Flame size={16} className="fill-[#00F0FF]/20" />
AI 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]}>
@@ -180,13 +195,12 @@ export function MistakeReviewCard({ data, onNext, onSubmitReview }: MistakeRevie
</ReactMarkdown>
</div>
</div>
<div className="mt-5 flex justify-end">
<button
<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"
className="px-6 py-2.5 rounded-xl font-bold text-sm bg-gradient-to-r from-[#00F0FF] to-teal-400 text-background hover:opacity-90 transition-opacity"
>
Got It, Next Question →
Next Question →
</button>
</div>
</>

View File

@@ -2,17 +2,21 @@
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/card"
import { Button } from "@/components/ui/button"
import { Download, Image as ImageIcon, Loader2 } from "lucide-react"
import { Download, Image as ImageIcon, Loader2, Sparkles, TrendingUp, Target } from "lucide-react"
import { useState } from "react"
import { useActiveUser } from "@/hooks/useActiveUser"
export function ProgressShare() {
const { user } = useActiveUser();
const [downloading, setDownloading] = useState(false)
// Example dynamic params
const streak = "12"
const accuracy = "85"
const name = "Student"
const imageUrl = `/api/og/progress?streak=${streak}&accuracy=${accuracy}&name=${name}`
const name = user?.name || "Student"
// Use a fallback image if API endpoint is not yet ready, else use real endpoint
const imageUrl = `/api/og/progress?streak=${streak}&accuracy=${accuracy}&name=${encodeURIComponent(name)}`
const handleDownload = async () => {
try {
@@ -35,28 +39,61 @@ export function ProgressShare() {
}
return (
<Card className="border-border shadow-sm h-full flex flex-col">
<CardHeader>
<CardTitle className="text-xl flex items-center gap-2">
<ImageIcon className="text-primary" size={24} />
Share Progress
<Card className="border-border shadow-sm h-full flex flex-col overflow-hidden group">
<CardHeader className="pb-2">
<CardTitle className="text-xl flex items-center justify-between">
<div className="flex items-center gap-2">
<ImageIcon className="text-[#00F0FF]" size={24} />
Share Your Wins
</div>
</CardTitle>
<CardDescription>Auto-generated dynamic scorecard.</CardDescription>
<CardDescription>Inspire your friends with your weekly stats.</CardDescription>
</CardHeader>
<CardContent className="flex-1 flex flex-col items-center justify-center gap-6">
<div className="w-full max-w-sm rounded-xl overflow-hidden border border-border shadow-sm bg-secondary/20 aspect-[1200/630] relative">
<img
src={imageUrl}
alt="Progress Scorecard"
className="w-full h-full object-cover"
loading="lazy"
/>
<CardContent className="flex-1 flex flex-col justify-between gap-6 pt-2">
{/* Mock Preview of the OG Image */}
<div className="w-full rounded-xl overflow-hidden border border-border/50 shadow-md bg-gradient-to-br from-indigo-900/40 to-black relative aspect-[1200/630] flex flex-col items-center justify-center p-4 transition-transform group-hover:scale-[1.02]">
<div className="absolute inset-0 bg-[radial-gradient(ellipse_at_top_right,_var(--tw-gradient-stops))] from-[#00F0FF]/20 via-transparent to-transparent opacity-50" />
<div className="relative z-10 w-full flex flex-col h-full">
<div className="flex justify-between items-start w-full">
<div className="flex items-center gap-2">
<div className="w-8 h-8 rounded-lg bg-[#00F0FF] flex items-center justify-center">
<Sparkles size={16} className="text-black" />
</div>
<span className="text-sm font-black text-white tracking-wider">PRAGATIVEER</span>
</div>
<div className="text-[10px] font-bold text-[#00F0FF] bg-[#00F0FF]/10 px-2 py-1 rounded-full border border-[#00F0FF]/20">
Weekly Report
</div>
</div>
<div className="mt-auto mb-auto text-center">
<h3 className="text-2xl font-black text-white mb-4">You crushed it this week!</h3>
<div className="flex justify-center gap-4">
<div className="bg-white/10 backdrop-blur-sm border border-white/20 rounded-xl p-3 w-28">
<TrendingUp className="text-amber-400 mx-auto mb-1" size={20} />
<div className="text-2xl font-black text-white">{streak}</div>
<div className="text-[9px] text-white/70 uppercase font-bold tracking-wider">Day Streak</div>
</div>
<div className="bg-white/10 backdrop-blur-sm border border-white/20 rounded-xl p-3 w-28">
<Target className="text-green-400 mx-auto mb-1" size={20} />
<div className="text-2xl font-black text-white">{accuracy}%</div>
<div className="text-[9px] text-white/70 uppercase font-bold tracking-wider">Accuracy</div>
</div>
</div>
</div>
<div className="text-left mt-auto">
<div className="text-sm font-bold text-white">{name}</div>
<div className="text-[10px] text-white/50">JEE Aspirant</div>
</div>
</div>
</div>
<Button onClick={handleDownload} variant="outline" className="w-full" disabled={downloading}>
<Button onClick={handleDownload} variant="outline" className="w-full h-11 font-bold text-sm hover:bg-[#00F0FF]/10 hover:text-[#00F0FF] hover:border-[#00F0FF]/30 transition-colors" disabled={downloading}>
{downloading ? <Loader2 size={18} className="mr-2 animate-spin" /> : <Download size={18} className="mr-2" />}
Download as PNG
Download Scorecard PNG
</Button>
</CardContent>
</Card>

View File

@@ -61,13 +61,8 @@ export function ReadinessGauge() {
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 Readiness
</h2>
</div>
<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">
<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-5 min-h-[440px] 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" />
@@ -108,55 +103,55 @@ export function ReadinessGauge() {
</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 className="flex-1 grid grid-cols-2 gap-2.5 w-full">
<div className="bg-white/5 rounded-lg p-2.5">
<div className="text-base font-black flex items-baseline gap-1">88% <span className="text-xs text-green-500">↑</span></div>
<div className="text-xs font-bold 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 className="bg-white/5 rounded-lg p-2.5">
<div className="text-base font-black flex items-baseline gap-1">45s <span className="text-xs text-red-500">↓</span></div>
<div className="text-xs font-bold 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 className="bg-white/5 rounded-lg p-2.5">
<div className="text-base font-black flex items-baseline gap-1">High <span className="text-xs text-green-500">↑</span></div>
<div className="text-xs font-bold 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 className="bg-white/5 rounded-lg p-2.5">
<div className="text-base font-black flex items-baseline gap-1">64% <span className="text-xs text-green-500">↑</span></div>
<div className="text-xs font-bold text-muted-foreground">Syllabus</div>
</div>
</div>
</div>
{/* Subject Bars */}
<div className="space-y-1.5 mb-3 relative z-10">
<div className="space-y-2 mb-4 relative z-10">
{[
{ 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 key={sub.name} className="flex items-center gap-3">
<span className="text-xs font-bold text-muted-foreground w-20 shrink-0">{sub.name}</span>
<div className="flex-1 h-2 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>
<span className="text-xs font-black w-10 text-right" style={{ color: sub.color }}>{sub.val}%</span>
</div>
))}
</div>
{/* 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 className="flex items-center justify-between px-3 py-2 bg-white/5 rounded-lg mb-4 relative z-10 text-xs font-bold">
<span className="text-muted-foreground">National Avg</span>
<span className="text-muted-foreground">58 / 100</span>
<span className="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" />
<div className="mt-auto px-3.5 py-3 bg-[#00F0FF]/10 border border-[#00F0FF]/20 rounded-lg text-xs md:text-sm text-[#00F0FF] font-bold flex items-center gap-2 relative z-10">
<Flame size={18} className="fill-[#00F0FF]/20 shrink-0" />
Your speed has dropped slightly. Focus on time-bound practice today.
</div>
</Card>

View File

@@ -3,7 +3,7 @@
import { useEffect, useState } from "react"
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/card"
import { Button } from "@/components/ui/button"
import { Link as LinkIcon, Share2, Check, Gift } from "lucide-react"
import { Link as LinkIcon, Share2, Check, Gift, Users, Trophy } from "lucide-react"
import { useActiveUser } from "@/hooks/useActiveUser"
@@ -30,6 +30,8 @@ export function ReferralCard() {
const link = data?.referral_link || `https://pragativeer.com/join/${userId || "demo"}`
const message = data?.share_message || "Join me on PragatiVeer! Use my link to get 7 days of the AI Coach for free."
const referralCount = data?.referral_count || 2 // mock 2 referrals
const nextTier = 5
const copyToClipboard = () => {
navigator.clipboard.writeText(link)
@@ -44,30 +46,56 @@ export function ReferralCard() {
return (
<Card className="border-border shadow-sm h-full flex flex-col relative overflow-hidden bg-gradient-to-br from-background to-primary/5">
<div className="absolute -right-6 -bottom-6 opacity-10 pointer-events-none">
<Gift size={120} />
<div className="absolute -right-6 -bottom-6 opacity-[0.03] pointer-events-none">
<Gift size={150} />
</div>
<CardHeader>
<CardTitle className="text-xl flex items-center gap-2">
<Gift className="text-primary" size={24} />
Invite & Earn
<CardHeader className="pb-2">
<CardTitle className="text-xl flex items-center justify-between">
<div className="flex items-center gap-2">
<Gift className="text-primary" size={24} />
Invite &amp; Earn
</div>
<div className="flex items-center gap-1.5 text-xs font-bold text-muted-foreground bg-secondary/30 px-2 py-1 rounded-md">
<Users size={14} /> {referralCount} Joined
</div>
</CardTitle>
<CardDescription>Give 7 days of AI Coach, get 7 days free.</CardDescription>
</CardHeader>
<CardContent className="flex-1 flex flex-col justify-end gap-4 z-10">
<div className="bg-secondary/50 border border-border p-3 rounded-lg flex items-center justify-between">
<code className="text-sm font-semibold truncate mr-4">{link}</code>
<Button variant="secondary" size="sm" onClick={copyToClipboard} className="shrink-0 rounded-md">
{copied ? <Check size={16} className="text-green-500" /> : <LinkIcon size={16} />}
<span className="ml-2 hidden sm:inline">{copied ? "Copied" : "Copy"}</span>
<CardContent className="flex-1 flex flex-col gap-4 z-10 pt-2">
{/* Progress Bar for Rewards */}
<div className="bg-white/5 border border-border/50 rounded-xl p-3 space-y-2">
<div className="flex justify-between items-center text-xs font-bold">
<span className="text-muted-foreground flex items-center gap-1">
<Trophy size={14} className="text-yellow-500" /> Free Month Tier
</span>
<span>{referralCount} / {nextTier}</span>
</div>
<div className="h-2 w-full bg-secondary rounded-full overflow-hidden">
<div
className="h-full bg-gradient-to-r from-primary to-teal-400 rounded-full transition-all duration-500"
style={{ width: `${Math.min((referralCount / nextTier) * 100, 100)}%` }}
/>
</div>
<p className="text-[10px] text-muted-foreground font-medium text-center">
Invite {nextTier - referralCount} more friends to unlock 1 free month!
</p>
</div>
<div className="mt-auto space-y-3">
<div className="bg-secondary/50 border border-border p-3 rounded-lg flex items-center justify-between">
<code className="text-sm font-semibold truncate mr-4 text-muted-foreground">{link}</code>
<Button variant="secondary" size="sm" onClick={copyToClipboard} className="shrink-0 rounded-md bg-white/10 hover:bg-white/20">
{copied ? <Check size={16} className="text-green-500" /> : <LinkIcon size={16} />}
<span className="ml-2 hidden sm:inline">{copied ? "Copied" : "Copy"}</span>
</Button>
</div>
<Button onClick={shareToWhatsApp} className="w-full bg-[#25D366] hover:bg-[#1ebd5a] text-white font-bold h-11">
<Share2 size={18} className="mr-2" /> Share via WhatsApp
</Button>
</div>
<Button onClick={shareToWhatsApp} className="w-full bg-[#25D366] hover:bg-[#1ebd5a] text-white">
<Share2 size={18} className="mr-2" /> Share via WhatsApp
</Button>
</CardContent>
</Card>
)

View File

@@ -30,11 +30,11 @@ export function StudyPlanCard() {
}
// Mock fallback if celery hasn't seeded yet
setTasks([
{ 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" }
{ type: "DPS", description: "Electrostatics + Kinematics", duration_minutes: 20, status: "Done" },
{ type: "Mistake_Review", description: "Thermodynamics error queue", duration_minutes: 10, status: "Done" },
{ type: "Micro_test", description: "GOC — General Organic Chemistry", duration_minutes: 5, status: "Pending" },
{ type: "Test", description: "Weekly Full-Syllabus Mock Test", duration_minutes: 180, status: "Pending" },
{ type: "Revision", description: "Circles + Straight Lines (Maths)", duration_minutes: 15, status: "Pending" }
])
} finally {
setLoading(false)
@@ -79,18 +79,13 @@ export function StudyPlanCard() {
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>
</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">
<div className="px-5 py-4 flex justify-between items-center border-b border-border">
<div className="text-sm font-bold text-muted-foreground">
{new Date().toLocaleDateString('en-US', { weekday: 'long', day: 'numeric', month: 'short' })}
</div>
<div className="text-[12px] text-[#00F0FF] font-bold">{progressPercent}% Completed</div>
<div className="text-xs text-[#00F0FF] font-bold">{progressPercent}% Completed</div>
</div>
{/* Progress bar */}
@@ -132,15 +127,15 @@ export function StudyPlanCard() {
</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 className="text-sm font-bold text-primary truncate">{task.description}</div>
<div className="text-xs text-muted-foreground mt-0.5 font-medium">{getDisplayType(task.type)} · {task.duration_minutes} min</div>
</div>
<div className={`text-[10px] font-bold px-2 py-0.5 rounded-md shrink-0 ${getTagStyle(task.type)}`}>
<div className={`text-xs font-bold px-2.5 py-1 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">
<div className="text-xs font-bold text-muted-foreground bg-white/5 px-2.5 py-1 rounded-md shrink-0">
{task.duration_minutes}m
</div>
</div>

View File

@@ -1,73 +1,123 @@
"use client"
import { Card, CardContent } from "@/components/ui/card"
import { Flame, Star } from "lucide-react"
import { Flame, Star, Clock, TrendingUp } 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 },
{ label: "6 AM", h: 30, score: 52, peak: false },
{ label: "9 AM", h: 55, score: 68, peak: false },
{ label: "12 PM", h: 65, score: 74, peak: false },
{ label: "2 PM", h: 90, score: 91, peak: false },
{ label: "3 PM", h: 100, score: 95, peak: true },
{ label: "5 PM", h: 82, score: 85, peak: false },
{ label: "7 PM", h: 70, score: 78, peak: false },
{ label: "9 PM", h: 55, score: 67, peak: false },
{ label: "11 PM", h: 32, score: 48, peak: false },
]
export function StudyTimeOptimizationCard() {
const [hovered, setHovered] = useState<number | null>(null)
const maxH = Math.max(...SLOTS.map(s => s.h))
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>
</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>
<Card className="border-border bg-card shadow-[0_4px_24px_rgba(0,0,0,0.3)] min-h-[440px] flex flex-col justify-between group hover:border-[#00F0FF]/30 transition-colors">
<CardContent className="p-5 flex-1 flex flex-col gap-4">
{/* Header stats */}
<div className="flex items-center gap-3 flex-wrap">
<div className="flex items-center gap-2">
<Star size={15} className="fill-yellow-500 text-yellow-500" />
<span className="text-sm font-bold text-yellow-500">Peak Window:</span>
<span className="text-sm font-bold bg-yellow-500/10 border border-yellow-500/20 px-2.5 py-0.5 rounded-full">
2 PM – 5 PM
</span>
</div>
<div className="ml-auto flex items-center gap-1.5 text-xs font-bold text-green-400 bg-green-400/10 border border-green-400/20 px-2.5 py-1 rounded-full">
<TrendingUp size={11} /> 95% efficiency at peak
</div>
</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>
{/* Bar chart with Y-axis */}
<div className="flex gap-2 flex-1">
{/* Y-axis labels */}
<div className="flex flex-col justify-between pb-6 text-right pr-1.5">
{[100, 75, 50, 25].map(v => (
<span key={v} className="text-[10px] text-muted-foreground font-semibold leading-none">{v}%</span>
))}
</div>
{/* Bars */}
<div className="flex-1 flex flex-col gap-1">
{/* Grid lines */}
<div className="relative flex-1 min-h-[120px]">
{[0, 25, 50, 75].map(pct => (
<div
key={pct}
className="absolute w-full border-t border-border/20"
style={{ bottom: `${pct}%` }}
/>
))}
<div className="flex items-end gap-1 h-full absolute inset-0">
{SLOTS.map((slot, i) => (
<div
key={slot.label}
className="flex-1 flex flex-col items-center justify-end h-full cursor-pointer relative group/bar"
onMouseEnter={() => setHovered(i)}
onMouseLeave={() => setHovered(null)}
>
{/* Tooltip */}
{hovered === i && (
<div className="absolute bottom-full mb-1.5 left-1/2 -translate-x-1/2 bg-background border border-border rounded-lg px-2.5 py-1.5 text-xs font-bold whitespace-nowrap shadow-lg z-10 pointer-events-none">
<div className="text-foreground">{slot.label}</div>
<div className={slot.peak ? "text-yellow-400" : "text-[#00F0FF]"}>Score: {slot.score}%</div>
</div>
)}
<div
className={`w-full rounded-t-md transition-all duration-300 ${
slot.peak
? "bg-gradient-to-t from-yellow-600/80 to-yellow-400 shadow-[0_0_14px_rgba(234,179,8,0.5)]"
: hovered === i
? "bg-gradient-to-t from-[#00F0FF]/70 to-[#00F0FF]"
: "bg-gradient-to-t from-[#00F0FF]/20 to-[#00F0FF]/50"
}`}
style={{ height: `${(slot.h / maxH) * 100}%` }}
/>
</div>
))}
</div>
</div>
{/* X-axis labels */}
<div className="flex gap-1 pt-1">
{SLOTS.map((slot) => (
<div key={slot.label} className="flex-1 text-center">
<span className="text-[10px] text-muted-foreground font-bold whitespace-nowrap">{slot.label}</span>
</div>
))}
</div>
</div>
</div>
{/* Efficiency breakdown */}
<div className="grid grid-cols-3 gap-2 text-center">
{[
{ label: "Morning", time: "6–9 AM", eff: 60, color: "text-muted-foreground" },
{ label: "Peak", time: "2–5 PM", eff: 95, color: "text-yellow-400" },
{ label: "Evening", time: "7–9 PM", eff: 73, color: "text-[#00F0FF]" },
].map(slot => (
<div key={slot.label} className="p-2.5 rounded-xl bg-white/5 border border-border/30">
<div className={`text-base font-black ${slot.color}`}>{slot.eff}%</div>
<div className="text-[11px] font-bold text-muted-foreground">{slot.label}</div>
<div className="text-[10px] text-muted-foreground/60">{slot.time}</div>
</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 className="px-3.5 py-3 bg-yellow-500/10 border border-yellow-500/20 rounded-xl text-xs md:text-sm text-yellow-500 font-bold flex items-center gap-2">
<Flame size={18} className="fill-yellow-500 shrink-0" />
You perform best 2–5 PM. Schedule your hardest mock tests and DPPs in this window.
</div>
</CardContent>
</Card>
</div>
)
}

View File

@@ -2,8 +2,7 @@
import { useState, useRef, useEffect } from "react"
import { Card, CardContent } from "@/components/ui/card"
import { Send, Loader2 } from "lucide-react"
import Link from "next/link"
import { Send, Loader2, BrainCircuit, Sparkles } from "lucide-react"
import ReactMarkdown from "react-markdown"
import remarkMath from "remark-math"
import rehypeKatex from "rehype-katex"
@@ -16,14 +15,21 @@ interface Message {
content: string
}
const QUICK_PROMPTS = [
"Explain Newton's laws with an example",
"What is the difference between speed and velocity?",
"How does organic chemistry reaction mechanism work?",
"Solve: Integration of x² from 0 to 3",
]
export function TutorChat() {
const [messages, setMessages] = useState<Message[]>([
{ role: "assistant", content: "Hello! I am your PragatiVeer AI Coach. What topic would you like to review today?" }
{ role: "assistant", content: "Hello! I'm your PragatiVeer AI Tutor. Ask me anything — from concept explanations to step-by-step problem solving for JEE & NEET topics." }
])
const [input, setInput] = useState("")
const [streaming, setStreaming] = useState(false)
const messagesEndRef = useRef<HTMLDivElement>(null)
const { userId, loading: userLoading } = useActiveUser();
const scrollToBottom = () => {
@@ -34,15 +40,11 @@ export function TutorChat() {
scrollToBottom()
}, [messages])
const handleSend = async (e?: React.FormEvent) => {
e?.preventDefault()
if (!input.trim() || streaming || userLoading || !userId) return
const sendMessage = async (text: string) => {
if (!text.trim() || streaming || userLoading || !userId) return
const userMsg = input.trim()
setInput("")
setMessages(prev => [...prev, { role: "user", content: userMsg }])
// Add empty assistant message placeholder
setMessages(prev => [...prev, { role: "user", content: text.trim() }])
setMessages(prev => [...prev, { role: "assistant", content: "" }])
setStreaming(true)
@@ -50,14 +52,10 @@ export function TutorChat() {
const res = await fetch("/api/ai-coach/chat/stream", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ user_id: userId, message: userMsg })
body: JSON.stringify({ user_id: userId, message: text.trim() })
})
if (!res.ok) {
throw new Error("Backend streaming failed. Using mock fallback.")
}
if (!res.body) throw new Error("No readable stream")
if (!res.ok || !res.body) throw new Error("stream_unavailable")
const reader = res.body.getReader()
const decoder = new TextDecoder()
@@ -67,23 +65,21 @@ export function TutorChat() {
if (done) break
const chunk = decoder.decode(value, { stream: true })
setMessages(prev => {
const newMessages = [...prev]
newMessages[newMessages.length - 1].content += chunk
return newMessages
const msgs = [...prev]
msgs[msgs.length - 1].content += chunk
return msgs
})
}
} catch (err) {
console.warn("Falling back to mock stream due to error:", err)
// Mock Fallback Streaming logic
const mockResponse = "It looks like my backend connection is restricted right now. However, I can still render math for you! Here is the quadratic formula:\n\n$$ x = \\frac{-b \\pm \\sqrt{b^2 - 4ac}}{2a} $$\n\nAnd inline math: $E = mc^2$."
const words = mockResponse.split(" ")
} catch {
// Graceful fallback — don't expose internal errors
const fallback = "I'm having trouble connecting right now. Please try again in a moment. In the meantime, you can browse your **Study Plan** or review your **Mistake Queue** for offline practice."
const words = fallback.split(" ")
for (let i = 0; i < words.length; i++) {
await new Promise(resolve => setTimeout(resolve, 50)) // Simulate network delay
await new Promise(r => setTimeout(r, 40))
setMessages(prev => {
const newMessages = [...prev]
newMessages[newMessages.length - 1].content += (i === 0 ? "" : " ") + words[i]
return newMessages
const msgs = [...prev]
msgs[msgs.length - 1].content += (i === 0 ? "" : " ") + words[i]
return msgs
})
}
} finally {
@@ -91,45 +87,43 @@ export function TutorChat() {
}
}
const handleSend = (e?: React.FormEvent) => {
e?.preventDefault()
sendMessage(input)
}
return (
<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
{/* Chat Header */}
<div className="px-4 py-3 bg-[#00F0FF]/5 border-b border-border flex items-center gap-3">
<div className="w-9 h-9 rounded-full bg-gradient-to-br from-[#00F0FF] to-blue-500 flex items-center justify-center shrink-0">
<BrainCircuit size={18} className="text-background" />
</div>
<div>
<div className="text-[13px] font-bold text-primary">PragatiVeer Tutor</div>
<div className="flex-1">
<div className="text-[13px] font-bold text-primary">PragatiVeer AI 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
<span className="w-1.5 h-1.5 rounded-full bg-green-500" /> Online · JEE &amp; NEET specialist
</div>
</div>
<div className="ml-auto text-[11px] text-muted-foreground">
<span className="text-yellow-500 font-bold">14</span> msgs left
<div className="flex items-center gap-1.5 text-[11px] font-bold text-[#00F0FF] bg-[#00F0FF]/10 px-2.5 py-1 rounded-full border border-[#00F0FF]/20">
<Sparkles size={10} /> AI Powered
</div>
</div>
{/* Messages */}
<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={`p-3.5 text-sm leading-relaxed ${
msg.role === "user"
? "bg-[#00F0FF] text-background rounded-[12px] rounded-br-[4px] font-bold"
: "bg-[#00F0FF]/10 border border-[#00F0FF]/20 text-primary rounded-[12px] rounded-bl-[4px] font-medium"
}`}>
<div className="prose prose-sm dark:prose-invert max-w-none [&>p]:m-0">
{msg.role === "assistant" ? (
<ReactMarkdown
remarkPlugins={[remarkMath]}
rehypePlugins={[rehypeKatex]}
>
<ReactMarkdown remarkPlugins={[remarkMath]} rehypePlugins={[rehypeKatex]}>
{msg.content || "..."}
</ReactMarkdown>
) : (
@@ -137,28 +131,39 @@ export function TutorChat() {
)}
</div>
</div>
<div className={`text-[10px] text-muted-foreground ${msg.role === "user" ? "text-right" : "text-left"}`}>
Now
</div>
</div>
</div>
))}
<div ref={messagesEndRef} />
</CardContent>
{/* Quick Prompts — shown only when only 1 message (initial) */}
{messages.length === 1 && (
<div className="px-4 pb-2 flex flex-wrap gap-2">
{QUICK_PROMPTS.map(p => (
<button
key={p}
onClick={() => sendMessage(p)}
className="text-xs font-bold px-3 py-1.5 rounded-full border border-border/50 bg-white/5 text-muted-foreground hover:border-[#00F0FF]/40 hover:text-[#00F0FF] hover:bg-[#00F0FF]/5 transition-all"
>
{p}
</button>
))}
</div>
)}
{/* Input */}
<div className="p-2.5 border-t border-border bg-black/20 flex items-center gap-2">
<input
<input
type="text"
value={input}
onChange={(e) => setInput(e.target.value)}
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"
onChange={e => setInput(e.target.value)}
onKeyDown={e => { if (e.key === "Enter") handleSend() }}
placeholder="Ask about any JEE or NEET concept..."
className="flex-1 bg-white/5 border border-border rounded-full px-4 py-2.5 text-sm text-primary font-medium focus:outline-none focus:border-[#00F0FF] focus:bg-[#00F0FF]/5 transition-colors"
disabled={streaming}
/>
<button
<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"

View File

@@ -1,37 +1,40 @@
"use client"
import { TrendingUp, TrendingDown, Brain, Zap } from "lucide-react"
import { TrendingUp, TrendingDown, Brain, Zap, Target, Clock } from "lucide-react"
import { Card, CardContent } from "@/components/ui/card"
import Link from "next/link"
import { useState } from "react"
const DAYS = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"]
const BAR_HEIGHTS = [55, 70, 45, 90, 80, 60, 75] // % heights
const QUESTIONS = [42, 58, 35, 71, 65, 48, 60]
const ACCURACY = [82, 78, 91, 88, 85, 79, 93]
const HOURS = [2.5, 3.5, 2, 4.5, 4, 3, 3.8]
export function WeeklyProgressReport() {
const [metric, setMetric] = useState<"questions" | "accuracy" | "hours">("accuracy")
const [hovered, setHovered] = useState<number | null>(null)
const DATA = metric === "questions" ? QUESTIONS : metric === "accuracy" ? ACCURACY : HOURS
const maxVal = Math.max(...DATA)
const avgVal = Math.round((DATA.reduce((a, b) => a + b, 0) / DATA.length) * 10) / 10
const trend = DATA[DATA.length - 1] - DATA[0]
const trendUp = trend >= 0
const UNIT = metric === "accuracy" ? "%" : metric === "hours" ? "h" : ""
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>
</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">
<CardContent className="p-5 flex flex-col gap-4 h-full">
{/* 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 className={`flex items-center gap-1.5 text-xs font-bold px-2.5 py-1 rounded-full border ${trendUp ? "bg-[#00F0FF]/10 text-[#00F0FF] border-[#00F0FF]/20" : "bg-red-500/10 text-red-500 border-red-500/20"}`}>
{trendUp ? <TrendingUp size={12} /> : <TrendingDown size={12} />}
{trendUp ? "+" : ""}{trend}{UNIT} 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} />
@@ -39,32 +42,97 @@ export function WeeklyProgressReport() {
</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`}
/>
{/* Metric Toggle */}
<div className="flex gap-1.5">
{(["accuracy", "questions", "hours"] as const).map((m) => (
<button
key={m}
onClick={() => setMetric(m)}
className={`flex-1 py-1.5 text-xs font-bold rounded-lg border transition-all capitalize ${metric === m ? "bg-[#00F0FF]/10 border-[#00F0FF]/20 text-[#00F0FF]" : "bg-white/5 border-transparent text-muted-foreground hover:bg-white/10"}`}
>
{m === "accuracy" ? "Accuracy" : m === "questions" ? "Questions" : "Study Hrs"}
</button>
))}
</div>
{/* Bar Chart with Y-axis */}
<div className="flex gap-2 flex-1 min-h-[120px]">
{/* Y-axis */}
<div className="flex flex-col justify-between pb-5 text-right pr-1">
{[100, 75, 50, 25, 0].map(v => (
<span key={v} className="text-[10px] text-muted-foreground font-semibold leading-none">
{metric === "hours" ? (v / 100 * 5).toFixed(1) : `${v}${metric === "accuracy" ? "%" : ""}`}
</span>
))}
</div>
<div className="flex-1 flex flex-col gap-0.5">
{/* Grid + Bars */}
<div className="relative flex-1 min-h-[120px]">
{[25, 50, 75].map(pct => (
<div key={pct} className="absolute w-full border-t border-border/15" style={{ bottom: `${pct}%` }} />
))}
<div className="absolute inset-0 flex items-end gap-1.5">
{DAYS.map((day, i) => (
<div
key={day}
className="flex-1 flex flex-col justify-end items-center h-full relative cursor-pointer"
onMouseEnter={() => setHovered(i)}
onMouseLeave={() => setHovered(null)}
>
{/* Tooltip */}
{hovered === i && (
<div className="absolute bottom-full mb-1.5 left-1/2 -translate-x-1/2 bg-background border border-border rounded-lg px-2.5 py-1.5 text-xs font-bold whitespace-nowrap shadow-lg z-10 pointer-events-none">
<div>{day}</div>
<div className="text-[#00F0FF]">{DATA[i]}{UNIT}</div>
</div>
)}
<div
className={`w-full rounded-t-md transition-all duration-700 ${
i === 6
? "bg-gradient-to-t from-[#00F0FF]/60 to-[#00F0FF]"
: hovered === i
? "bg-gradient-to-t from-[#00F0FF]/40 to-[#00F0FF]/80"
: "bg-gradient-to-t from-white/10 to-white/25"
}`}
style={{ height: `${(DATA[i] / maxVal) * 100}%` }}
/>
</div>
))}
</div>
<span className="text-[9px] text-muted-foreground font-semibold">{day}</span>
</div>
{/* X labels */}
<div className="flex gap-1.5 pt-1">
{DAYS.map((day) => (
<div key={day} className="flex-1 text-center">
<span className="text-[10px] text-muted-foreground font-bold">{day}</span>
</div>
))}
</div>
</div>
</div>
{/* Quick stats row */}
<div className="grid grid-cols-3 gap-2">
{[
{ label: "Avg Accuracy", value: `${avgAccuracy}%`, icon: <Target size={12} className="text-green-400" /> },
{ label: "Questions / Day", value: `${Math.round(QUESTIONS.reduce((a, b) => a + b, 0) / 7)}`, icon: <Zap size={12} className="text-[#00F0FF]" /> },
{ label: "Study Hrs / Day", value: `${(HOURS.reduce((a, b) => a + b, 0) / 7).toFixed(1)}h`, icon: <Clock size={12} className="text-amber-400" /> },
].map(stat => (
<div key={stat.label} className="p-2.5 rounded-xl bg-white/5 border border-border/30 text-center">
<div className="flex items-center justify-center gap-1 mb-0.5">{stat.icon}</div>
<div className="text-base font-black">{stat.value}</div>
<div className="text-[10px] text-muted-foreground font-semibold">{stat.label}</div>
</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 className="px-3 py-2.5 bg-[#00F0FF]/10 border border-[#00F0FF]/20 rounded-xl text-xs text-[#00F0FF] font-medium flex items-start gap-2.5">
<Brain size={15} className="shrink-0 mt-0.5" />
<div>
<span className="font-black text-white">Sign errors in Thermodynamics</span> — detected in your last 3 attempts (W = −PΔV misapplied). We&apos;ve queued 3 targeted practice questions for tomorrow.{" "}
<span className="text-[#00F0FF]/80"><strong className="text-white">Tip:</strong> You peak at 7–9 PM. Schedule the hardest topics then.</span>
</div>
</div>
</CardContent>

View File

@@ -133,7 +133,7 @@ export function ExamResult({ payload, attemptId, examType, onRetake }: ExamResul
} catch (err) {
console.error("Error exiting fullscreen", err);
}
router.push(`/dashboard/cbt/history/${attemptId}`);
router.push(isDPP ? `/dashboard/ai-coach/cbt-dpp/history/${attemptId}` : `/dashboard/cbt/history/${attemptId}`);
}}
>
View Solutions

View File

@@ -21,7 +21,7 @@ export function PublicLayout({ children }: { children: React.ReactNode }) {
<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>
<Link href="/about" className="hover:text-foreground transition-colors">About Us</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>
@@ -53,7 +53,7 @@ export function PublicLayout({ children }: { children: React.ReactNode }) {
/>
</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.
PragatiVeer is India's AI-powered daily practice platform for JEE and NEET aspirants. It is a product of Gyannetra Pvt Ltd.
</p>
</div>
@@ -62,8 +62,6 @@ export function PublicLayout({ children }: { children: React.ReactNode }) {
<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>
@@ -72,7 +70,6 @@ export function PublicLayout({ children }: { children: React.ReactNode }) {
<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>
@@ -90,9 +87,9 @@ export function PublicLayout({ children }: { children: React.ReactNode }) {
<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">
{/* <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>
</div>
</footer>