chore: Secure routes with auth middleware, update text and fix minor bugs
This commit is contained in:
@@ -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"
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
</>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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 & 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>
|
||||
)
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -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 & 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"
|
||||
|
||||
@@ -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'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>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user