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

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -1,9 +0,0 @@
import { unified } from "unified";
import remarkParse from "remark-parse";
import remarkMath from "remark-math";
const text = "C) $2 \\text{ m}$";
const tree = unified().use(remarkParse).use(remarkMath).parse(text);
console.log(JSON.stringify(tree, null, 2));

View File

@@ -1,18 +0,0 @@
import { unified } from "unified";
import remarkParse from "remark-parse";
import remarkMath from "remark-math";
import rehypeKatex from "rehype-katex";
import remarkRehype from "remark-rehype";
import rehypeStringify from "rehype-stringify";
const text = "The velocity of a particle moving along the x-axis is given by $v(t) = \\int_{0}^{t} (3\\tau^2 - 2\\tau) d\\tau$. What is the displacement of the particle at $t = 2$ seconds if it starts from the origin?";
const tree = unified()
.use(remarkParse)
.use(remarkMath)
.use(remarkRehype)
.use(rehypeKatex, { strict: "warn", throwOnError: false })
.use(rehypeStringify)
.processSync(text);
console.log(tree.toString());

File diff suppressed because it is too large Load Diff

View File

@@ -1,821 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>PragatiVeer — AI Coach Dashboard</title>
<style>
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800&display=swap');
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: 'Inter', -apple-system, sans-serif;
background: #0f0f1a;
color: #e0e0e0;
display: flex;
justify-content: center;
padding: 40px 20px;
min-height: 100vh;
}
.dashboard {
max-width: 1200px;
width: 100%;
}
/* Header */
.header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 30px;
padding-bottom: 20px;
border-bottom: 1px solid #2a2a3e;
}
.logo-area {
display: flex;
align-items: center;
gap: 12px;
}
.logo-icon {
width: 40px;
height: 40px;
background: linear-gradient(135deg, #7c3aed, #a855f7);
border-radius: 12px;
display: flex;
align-items: center;
justify-content: center;
font-weight: 800;
font-size: 18px;
color: white;
}
.logo-text {
font-size: 20px;
font-weight: 700;
color: #fff;
}
.logo-text span {
color: #a855f7;
}
.header-right {
display: flex;
align-items: center;
gap: 20px;
}
.tier-badge {
background: linear-gradient(135deg, #7c3aed, #a855f7);
padding: 6px 16px;
border-radius: 20px;
font-size: 12px;
font-weight: 600;
color: white;
letter-spacing: 0.5px;
}
.user-avatar {
width: 36px;
height: 36px;
background: #2a2a3e;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-weight: 600;
font-size: 14px;
color: #a855f7;
border: 2px solid #a855f7;
}
/* Greeting */
.greeting {
margin-bottom: 25px;
}
.greeting h1 {
font-size: 24px;
font-weight: 700;
color: #fff;
margin-bottom: 4px;
}
.greeting p {
font-size: 14px;
color: #888;
}
.greeting p span {
color: #a855f7;
font-weight: 500;
}
/* Stats Row */
.stats-row {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 16px;
margin-bottom: 24px;
}
.stat-card {
background: #1a1a2e;
border-radius: 16px;
padding: 20px;
border: 1px solid #2a2a3e;
transition: transform 0.2s;
}
.stat-card:hover {
transform: translateY(-2px);
border-color: #7c3aed40;
}
.stat-label {
font-size: 12px;
color: #888;
font-weight: 500;
text-transform: uppercase;
letter-spacing: 0.5px;
margin-bottom: 8px;
}
.stat-value {
font-size: 28px;
font-weight: 700;
color: #fff;
}
.stat-value .unit {
font-size: 14px;
font-weight: 400;
color: #888;
}
.stat-change {
font-size: 12px;
margin-top: 6px;
font-weight: 500;
}
.stat-change.up { color: #22c55e; }
.stat-change.down { color: #ef4444; }
/* Readiness Score - Special */
.readiness-ring {
position: relative;
display: inline-flex;
align-items: center;
justify-content: center;
}
.readiness-ring svg {
width: 60px;
height: 60px;
transform: rotate(-90deg);
}
.readiness-ring .bg { fill: none; stroke: #2a2a3e; stroke-width: 4; }
.readiness-ring .progress { fill: none; stroke: #a855f7; stroke-width: 4; stroke-linecap: round; }
.readiness-value {
position: absolute;
font-size: 18px;
font-weight: 700;
color: #fff;
}
.stat-card.readiness {
display: flex;
align-items: center;
gap: 16px;
}
.readiness-text h3 {
font-size: 16px;
font-weight: 600;
color: #fff;
margin-bottom: 2px;
}
.readiness-text p {
font-size: 12px;
color: #888;
}
/* Main Grid */
.main-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 20px;
margin-bottom: 24px;
}
.card {
background: #1a1a2e;
border-radius: 16px;
padding: 24px;
border: 1px solid #2a2a3e;
}
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 18px;
}
.card-title {
font-size: 16px;
font-weight: 600;
color: #fff;
}
.card-action {
font-size: 12px;
color: #a855f7;
cursor: pointer;
font-weight: 500;
}
.card-action:hover { text-decoration: underline; }
/* Mistake Heatmap */
.heatmap-grid {
display: grid;
grid-template-columns: 120px 1fr;
gap: 8px;
}
.heatmap-subjects {
display: flex;
flex-direction: column;
gap: 6px;
}
.heatmap-subject {
height: 28px;
display: flex;
align-items: center;
font-size: 12px;
font-weight: 500;
color: #888;
}
.heatmap-cells {
display: flex;
flex-direction: column;
gap: 6px;
}
.heatmap-row {
display: flex;
gap: 4px;
}
.heatmap-cell {
width: 28px;
height: 28px;
border-radius: 4px;
cursor: pointer;
transition: transform 0.15s;
}
.heatmap-cell:hover {
transform: scale(1.15);
}
.heatmap-cell.level-0 { background: #1a1a2e; border: 1px solid #2a2a3e; }
.heatmap-cell.level-1 { background: #2d1b4e; }
.heatmap-cell.level-2 { background: #4a1d6e; }
.heatmap-cell.level-3 { background: #7c3aed; }
.heatmap-cell.level-4 { background: #a855f7; }
.heatmap-cell.level-5 { background: #22c55e; }
.heatmap-legend {
display: flex;
justify-content: flex-end;
gap: 8px;
margin-top: 10px;
font-size: 10px;
color: #666;
align-items: center;
}
.heatmap-legend .dot {
width: 10px;
height: 10px;
border-radius: 2px;
}
/* Daily Plan */
.plan-item {
display: flex;
align-items: center;
gap: 12px;
padding: 10px 0;
border-bottom: 1px solid #2a2a3e;
}
.plan-item:last-child { border-bottom: none; }
.plan-check {
width: 20px;
height: 20px;
border-radius: 50%;
border: 2px solid #3a3a5e;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
cursor: pointer;
transition: all 0.2s;
}
.plan-check.done {
background: #22c55e;
border-color: #22c55e;
}
.plan-check.done::after {
content: '✓';
color: white;
font-size: 11px;
font-weight: 700;
}
.plan-info {
flex: 1;
}
.plan-name {
font-size: 13px;
font-weight: 500;
color: #e0e0e0;
}
.plan-name.done-text {
text-decoration: line-through;
color: #666;
}
.plan-meta {
font-size: 11px;
color: #666;
margin-top: 2px;
}
.plan-time {
font-size: 12px;
color: #888;
font-weight: 500;
white-space: nowrap;
}
.plan-tag {
font-size: 10px;
padding: 2px 8px;
border-radius: 10px;
font-weight: 500;
}
.plan-tag.physics { background: #7c3aed30; color: #a855f7; }
.plan-tag.chemistry { background: #22c55e30; color: #22c55e; }
.plan-tag.maths { background: #f59e0b30; color: #f59e0b; }
.plan-tag.review { background: #ef444430; color: #ef4444; }
/* Bottom Row */
.bottom-grid {
display: grid;
grid-template-columns: 1fr 1fr 1fr;
gap: 20px;
}
/* AI Insight */
.insight-card {
background: linear-gradient(135deg, #1a1a2e, #2d1b4e);
border: 1px solid #7c3aed30;
}
.insight-icon {
font-size: 24px;
margin-bottom: 12px;
}
.insight-text {
font-size: 13px;
line-height: 1.6;
color: #ccc;
}
.insight-text strong {
color: #a855f7;
}
/* Leaderboard Mini */
.lb-item {
display: flex;
align-items: center;
gap: 10px;
padding: 8px 0;
border-bottom: 1px solid #2a2a3e;
}
.lb-item:last-child { border-bottom: none; }
.lb-rank {
width: 24px;
height: 24px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 11px;
font-weight: 700;
flex-shrink: 0;
}
.lb-rank.gold { background: #f59e0b30; color: #f59e0b; }
.lb-rank.silver { background: #94a3b830; color: #94a3b8; }
.lb-rank.bronze { background: #b4530930; color: #b45309; }
.lb-rank.other { background: #2a2a3e; color: #666; }
.lb-name {
flex: 1;
font-size: 13px;
font-weight: 500;
}
.lb-score {
font-size: 12px;
color: #888;
font-weight: 500;
}
.lb-badge {
font-size: 10px;
padding: 2px 6px;
border-radius: 8px;
background: #7c3aed20;
color: #a855f7;
}
/* Streak */
.streak-display {
display: flex;
align-items: center;
gap: 16px;
}
.streak-fire {
font-size: 40px;
line-height: 1;
}
.streak-info h3 {
font-size: 28px;
font-weight: 700;
color: #f59e0b;
}
.streak-info p {
font-size: 12px;
color: #888;
}
.streak-days {
display: flex;
gap: 6px;
margin-top: 12px;
}
.streak-day {
width: 32px;
height: 32px;
border-radius: 8px;
display: flex;
align-items: center;
justify-content: center;
font-size: 11px;
font-weight: 600;
}
.streak-day.active {
background: #22c55e;
color: white;
}
.streak-day.inactive {
background: #2a2a3e;
color: #555;
}
.streak-day.today {
border: 2px solid #f59e0b;
background: #f59e0b20;
color: #f59e0b;
}
/* Responsive */
@media (max-width: 900px) {
.stats-row { grid-template-columns: repeat(2, 1fr); }
.main-grid { grid-template-columns: 1fr; }
.bottom-grid { grid-template-columns: 1fr; }
}
@media (max-width: 500px) {
.stats-row { grid-template-columns: 1fr; }
}
</style>
</head>
<body>
<div class="dashboard">
<!-- Header -->
<div class="header">
<div class="logo-area">
<div class="logo-icon">PV</div>
<div class="logo-text">Pragati<span>Veer</span></div>
</div>
<div class="header-right">
<div class="tier-badge">✦ AI COACH</div>
<div class="user-avatar">RK</div>
</div>
</div>
<!-- Greeting -->
<div class="greeting">
<h1>Good morning, Rohan! 👋</h1>
<p>Your AI Coach has prepared today's plan. <span>You're on track for NIT Surat.</span></p>
</div>
<!-- Stats Row -->
<div class="stats-row">
<div class="stat-card">
<div class="stat-label">Readiness Score</div>
<div class="stat-card readiness" style="padding:0; border:none; background:transparent;">
<div class="readiness-ring">
<svg viewBox="0 0 60 60">
<circle class="bg" cx="30" cy="30" r="24"/>
<circle class="progress" cx="30" cy="30" r="24" stroke-dasharray="150.8" stroke-dashoffset="45.2"/>
</svg>
<span class="readiness-value">72</span>
</div>
<div class="readiness-text">
<h3>Good Progress</h3>
<p>↑ 5 pts this week</p>
</div>
</div>
</div>
<div class="stat-card">
<div class="stat-label">Streak</div>
<div class="stat-value">23 <span class="unit">days</span></div>
<div class="stat-change up">🔥 Best: 23 days</div>
</div>
<div class="stat-card">
<div class="stat-label">Questions Solved</div>
<div class="stat-value">1,247 <span class="unit">total</span></div>
<div class="stat-change up">↑ 47 this week</div>
</div>
<div class="stat-card">
<div class="stat-label">Overall Accuracy</div>
<div class="stat-value">74<span class="unit">%</span></div>
<div class="stat-change up">↑ 3% this month</div>
</div>
</div>
<!-- Main Grid: Heatmap + Daily Plan -->
<div class="main-grid">
<!-- Mistake Heatmap -->
<div class="card">
<div class="card-header">
<div class="card-title">📊 Mistake Heatmap</div>
<div class="card-action">View Details →</div>
</div>
<div class="heatmap-grid">
<div class="heatmap-subjects">
<div class="heatmap-subject">Physics</div>
<div class="heatmap-subject">Chemistry</div>
<div class="heatmap-subject">Maths</div>
</div>
<div>
<div class="heatmap-row">
<div class="heatmap-cell level-5" title="Electrostatics - 92%"></div>
<div class="heatmap-cell level-4" title="Kinematics - 85%"></div>
<div class="heatmap-cell level-2" title="Thermodynamics - 55%"></div>
<div class="heatmap-cell level-3" title="Optics - 68%"></div>
<div class="heatmap-cell level-1" title="SHM - 40%"></div>
<div class="heatmap-cell level-4" title="Laws of Motion - 82%"></div>
<div class="heatmap-cell level-3" title="Work Energy - 65%"></div>
<div class="heatmap-cell level-5" title="Gravitation - 90%"></div>
<div class="heatmap-cell level-2" title="Fluids - 50%"></div>
<div class="heatmap-cell level-0" title="Waves - Not started"></div>
</div>
<div class="heatmap-row">
<div class="heatmap-cell level-3" title="Mole Concept - 70%"></div>
<div class="heatmap-cell level-5" title="Atomic Structure - 95%"></div>
<div class="heatmap-cell level-1" title="GOC - 38%"></div>
<div class="heatmap-cell level-2" title="Chemical Bonding - 52%"></div>
<div class="heatmap-cell level-4" title="Thermodynamics - 80%"></div>
<div class="heatmap-cell level-3" title="Equilibrium - 72%"></div>
<div class="heatmap-cell level-5" title="Redox - 88%"></div>
<div class="heatmap-cell level-2" title="Hydrocarbons - 48%"></div>
<div class="heatmap-cell level-1" title="Periodic Table - 35%"></div>
<div class="heatmap-cell level-0" title="Coordination - Not started"></div>
</div>
<div class="heatmap-row">
<div class="heatmap-cell level-4" title="Sets & Relations - 83%"></div>
<div class="heatmap-cell level-3" title="Functions - 67%"></div>
<div class="heatmap-cell level-5" title="Trigonometry - 91%"></div>
<div class="heatmap-cell level-2" title="Straight Lines - 45%"></div>
<div class="heatmap-cell level-1" title="Circles - 30%"></div>
<div class="heatmap-cell level-3" title="Limits - 62%"></div>
<div class="heatmap-cell level-4" title="Differentiation - 78%"></div>
<div class="heatmap-cell level-2" title="Integration - 55%"></div>
<div class="heatmap-cell level-0" title="Vectors - Not started"></div>
<div class="heatmap-cell level-0" title="3D Geometry - Not started"></div>
</div>
<div class="heatmap-legend">
<span>Weak</span>
<div class="dot" style="background:#2d1b4e"></div>
<div class="dot" style="background:#4a1d6e"></div>
<div class="dot" style="background:#7c3aed"></div>
<div class="dot" style="background:#a855f7"></div>
<div class="dot" style="background:#22c55e"></div>
<span>Strong</span>
</div>
</div>
</div>
</div>
<!-- Today's Plan -->
<div class="card">
<div class="card-header">
<div class="card-title">📋 Today's Study Plan</div>
<div class="card-action">Regenerate →</div>
</div>
<div class="plan-item">
<div class="plan-check done"></div>
<div class="plan-info">
<div class="plan-name done-text">DPS — Electrostatics + Kinematics</div>
<div class="plan-meta">10 questions • Mixed difficulty</div>
</div>
<span class="plan-tag physics">Physics</span>
<div class="plan-time">20 min</div>
</div>
<div class="plan-item">
<div class="plan-check done"></div>
<div class="plan-info">
<div class="plan-name done-text">Mistake Review — Thermodynamics</div>
<div class="plan-meta">5 re-attempts from spaced repetition queue</div>
</div>
<span class="plan-tag review">Review</span>
<div class="plan-time">10 min</div>
</div>
<div class="plan-item">
<div class="plan-check"></div>
<div class="plan-info">
<div class="plan-name">Micro-test — GOC (General Organic Chemistry)</div>
<div class="plan-meta">5 questions • Your accuracy here: 38% ⚠️</div>
</div>
<span class="plan-tag chemistry">Chemistry</span>
<div class="plan-time">5 min</div>
</div>
<div class="plan-item">
<div class="plan-check"></div>
<div class="plan-info">
<div class="plan-name">Weekly Test — Full Syllabus (JEE Main Pattern)</div>
<div class="plan-meta">90 questions • 3 hours • Sectional timing</div>
</div>
<span class="plan-tag maths">Test</span>
<div class="plan-time">3 hrs</div>
</div>
<div class="plan-item">
<div class="plan-check"></div>
<div class="plan-info">
<div class="plan-name">Revise — Circles + Straight Lines</div>
<div class="plan-meta">Formula sheet + 3 practice questions</div>
</div>
<span class="plan-tag maths">Maths</span>
<div class="plan-time">15 min</div>
</div>
</div>
</div>
<!-- Bottom Grid -->
<div class="bottom-grid">
<!-- AI Insight -->
<div class="card insight-card">
<div class="insight-icon">🤖</div>
<div class="card-title" style="margin-bottom:10px;">AI Coach Insight</div>
<div class="insight-text">
<strong>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.
</div>
<div style="margin-top:14px; padding:10px; background:#2a2a3e; border-radius:10px; font-size:12px; color:#888;">
💡 <strong style="color:#a855f7;">Tip:</strong> You perform best between 7-9 PM. Schedule difficult topics then.
</div>
</div>
<!-- Leaderboard -->
<div class="card">
<div class="card-header">
<div class="card-title">🏆 City Leaderboard — Jaipur</div>
<div class="card-action">This Week →</div>
</div>
<div class="lb-item">
<div class="lb-rank gold">1</div>
<div class="lb-name">Priya S.</div>
<div class="lb-score">94%</div>
<div class="lb-badge">🔥 45 day streak</div>
</div>
<div class="lb-item">
<div class="lb-rank silver">2</div>
<div class="lb-name">Arjun M.</div>
<div class="lb-score">91%</div>
<div class="lb-badge">30 day streak</div>
</div>
<div class="lb-item">
<div class="lb-rank bronze">3</div>
<div class="lb-name">Rohan K.</div>
<div class="lb-score">87%</div>
<div class="lb-badge" style="background:#f59e0b30; color:#f59e0b;">👈 You</div>
</div>
<div class="lb-item">
<div class="lb-rank other">4</div>
<div class="lb-name">Neha G.</div>
<div class="lb-score">85%</div>
</div>
<div class="lb-item">
<div class="lb-rank other">5</div>
<div class="lb-name">Vikram R.</div>
<div class="lb-score">82%</div>
</div>
</div>
<!-- Streak + Milestones -->
<div class="card">
<div class="card-header">
<div class="card-title">🔥 Streak & Milestones</div>
<div class="card-action">Share →</div>
</div>
<div class="streak-display">
<div class="streak-fire">🔥</div>
<div class="streak-info">
<h3>23 Days</h3>
<p>Longest streak: 23 days 🎉</p>
</div>
</div>
<div class="streak-days">
<div class="streak-day active">M</div>
<div class="streak-day active">T</div>
<div class="streak-day active">W</div>
<div class="streak-day active">T</div>
<div class="streak-day active">F</div>
<div class="streak-day active">S</div>
<div class="streak-day today">S</div>
</div>
<div style="margin-top:16px; display:flex; gap:8px; flex-wrap:wrap;">
<div style="padding:6px 12px; background:#2a2a3e; border-radius:20px; font-size:11px; color:#888;">
🏅 7-Day Streak ✅
</div>
<div style="padding:6px 12px; background:#2a2a3e; border-radius:20px; font-size:11px; color:#888;">
🏅 21-Day Streak ✅
</div>
<div style="padding:6px 12px; background:#7c3aed20; border-radius:20px; font-size:11px; color:#a855f7; border:1px solid #7c3aed40;">
🏆 30-Day Streak — 7 days away!
</div>
</div>
</div>
</div>
<!-- Footer -->
<div style="margin-top:30px; text-align:center; font-size:11px; color:#444; padding:20px 0; border-top:1px solid #2a2a3e;">
PragatiVeer — AI Coach Dashboard Mockup | Gyannetra Pvt Ltd | July 2026
</div>
</div>
</body>
</html>

File diff suppressed because one or more lines are too long

Binary file not shown.

Binary file not shown.

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,24 +0,0 @@
import os
import re
dir_path = r"c:\Users\naman\OneDrive\Desktop\pragativeer-v2\src\app\dashboard\ai-coach"
# Pattern to remove
pattern = re.compile(
r'\s*<Link href="/dashboard/ai-coach" className="inline-flex items-center gap-2 text-sm font-bold text-\[#00F0FF\] hover:underline mb-2">\s*<ArrowLeft size=\{16\} /> Back to AI Coach Overview\s*</Link>',
re.MULTILINE | re.DOTALL
)
for root, dirs, files in os.walk(dir_path):
for file in files:
if file == "page.tsx":
path = os.path.join(root, file)
with open(path, "r", encoding="utf-8") as f:
content = f.read()
new_content = pattern.sub('', content)
if new_content != content:
with open(path, "w", encoding="utf-8") as f:
f.write(new_content)
print(f"Updated {path}")

View File

@@ -1,24 +1,22 @@
import { Button } from "@/components/ui/button"
import Link from "next/link"
import { ArrowLeft } from "lucide-react"
import { PublicLayout } from "@/components/Landing/PublicLayout"
export default function AboutPage() {
return (
<div className="min-h-screen bg-background text-foreground flex flex-col">
<header className="border-b border-border bg-background px-6 py-4 flex items-center justify-between sticky top-0 z-10">
<Link href="/" className="text-muted-foreground hover:text-foreground flex items-center gap-2 transition-colors font-medium">
<ArrowLeft size={16} /> Back to Home
</Link>
</header>
<PublicLayout>
<div className="bg-background text-foreground flex flex-col pt-24 pb-16 min-h-[70vh]">
<main className="flex-1 flex flex-col items-center justify-center p-8 text-center animate-in fade-in duration-700">
<h1 className="text-5xl font-bold tracking-tighter mb-6">About PragatiVeer</h1>
<p className="text-xl text-muted-foreground max-w-2xl leading-relaxed mb-8">
We are a team of educators and engineers dedicated to re-engineering NEET/JEE preparation through the power of Artificial Intelligence.
<p className="text-xl text-muted-foreground max-w-4xl leading-relaxed mb-8 mx-auto text-left md:text-center">
PragatiVeer is India's first AI-powered daily practice platform for JEE and NEET aspirants — a product of Gyannetra Pvt Ltd, the first company to bring the power of artificial intelligence into Indian exam preparation. We combine daily practice sheets, exam-realistic test series, and an AI coach that personalises every student's learning journey, making high-quality preparation accessible and affordable for every Indian student.
</p>
<Link href="/signup">
<Button size="lg" className="rounded-full font-bold px-8 shadow-xl">Join the Revolution</Button>
</Link>
</main>
</div>
</PublicLayout>
)
}

View File

@@ -0,0 +1,43 @@
import { NextResponse } from 'next/server';
export async function POST(request: Request) {
try {
const body = await request.json();
const { email, password } = body;
let role = null;
if (email === "demo@pragativeer.com" && password === "Pv9#kL2$mQ8!vX4@") {
role = "user";
} else if (email === "admin@pragativeer.com" && password === "admin123") {
role = "admin";
}
if (role) {
const response = NextResponse.json({ success: true, role });
// Set HttpOnly cookie
response.cookies.set({
name: 'auth_token',
value: role,
httpOnly: true,
secure: process.env.NODE_ENV === 'production',
sameSite: 'lax',
path: '/',
maxAge: 60 * 60 * 24 * 7 // 1 week
});
return response;
}
return NextResponse.json(
{ success: false, error: 'Invalid email or password' },
{ status: 401 }
);
} catch (error) {
return NextResponse.json(
{ success: false, error: 'Bad request' },
{ status: 400 }
);
}
}

View File

@@ -461,7 +461,7 @@ export default function CBTExamPage({ params }: { params: Promise<{ examId: stri
</div>
</div>
<div className="prose prose-neutral dark:prose-invert max-w-none text-lg leading-relaxed text-foreground font-medium">
<div className="prose prose-neutral dark:prose-invert max-w-none text-xl md:text-2xl leading-relaxed text-foreground font-black">
<ReactMarkdown remarkPlugins={[remarkMath]} rehypePlugins={[rehypeKatex, rehypeRaw]}>
{currentQuestion.text_en}
</ReactMarkdown>
@@ -478,14 +478,14 @@ export default function CBTExamPage({ params }: { params: Promise<{ examId: stri
{currentQuestion.type === 'Numerical' ? (
<input
type="text"
className="w-full p-4 rounded-xl border border-border bg-background focus:ring-2 focus:ring-primary focus:border-transparent text-base"
className="w-full p-4 rounded-xl border border-border bg-background focus:ring-2 focus:ring-primary focus:border-transparent text-lg md:text-xl font-bold"
placeholder="Type your numerical answer here..."
value={currentState?.selected_answer || ""}
onChange={(e) => selectAnswer(currentQuestion.id, e.target.value)}
/>
) : currentQuestion.type === 'Subjective' ? (
<textarea
className="w-full min-h-[150px] p-4 rounded-xl border border-border bg-background focus:ring-2 focus:ring-primary focus:border-transparent resize-y text-base"
className="w-full min-h-[150px] p-4 rounded-xl border border-border bg-background focus:ring-2 focus:ring-primary focus:border-transparent resize-y text-lg md:text-xl font-bold"
placeholder="Type your answer or explanation here..."
value={currentState?.selected_answer || ""}
onChange={(e) => selectAnswer(currentQuestion.id, e.target.value)}
@@ -502,16 +502,16 @@ export default function CBTExamPage({ params }: { params: Promise<{ examId: stri
<button
key={idx}
onClick={() => handleOptionSelect(currentQuestion.id, letter, currentQuestion.type)}
className={`w-full text-left p-4 rounded-xl border transition-all duration-200 flex items-center gap-4 group focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent
className={`w-full text-left p-4.5 rounded-xl border transition-all duration-200 flex items-center gap-4 group focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent
${isSelected ? 'border-primary bg-primary/5 shadow-[0_0_15px_-5px_var(--color-primary)]' : 'border-border bg-background hover:border-primary/50 hover:bg-secondary/20'}
`}
>
<div className={`flex-shrink-0 size-8 ${isMultiple ? 'rounded-md' : 'rounded-full'} border flex items-center justify-center font-medium transition-colors
<div className={`flex-shrink-0 size-10 ${isMultiple ? 'rounded-md' : 'rounded-full'} border flex items-center justify-center font-black text-base transition-colors
${isSelected ? 'border-primary bg-primary text-primary-foreground' : 'border-border text-muted-foreground group-hover:border-primary/50 group-hover:text-primary group-hover:bg-primary/5'}
`}>
{letter}
</div>
<span className={`text-base font-semibold flex-1 ${isSelected ? 'text-primary' : ''}`}>
<span className={`text-lg md:text-xl font-bold flex-1 ${isSelected ? 'text-primary' : ''}`}>
<ReactMarkdown remarkPlugins={[remarkMath]} rehypePlugins={[rehypeKatex, rehypeRaw]}>
{renderLatex(option)}
</ReactMarkdown>
@@ -544,7 +544,7 @@ export default function CBTExamPage({ params }: { params: Promise<{ examId: stri
</Button>
</div>
</div>
<div className="prose prose-neutral dark:prose-invert max-w-none text-lg leading-relaxed text-foreground font-medium">
<div className="prose prose-neutral dark:prose-invert max-w-none text-xl md:text-2xl leading-relaxed text-foreground font-black">
<ReactMarkdown remarkPlugins={[remarkMath]} rehypePlugins={[rehypeKatex, rehypeRaw]}>
{currentQuestion.text_hi}
</ReactMarkdown>
@@ -561,14 +561,14 @@ export default function CBTExamPage({ params }: { params: Promise<{ examId: stri
{currentQuestion.type === 'Numerical' ? (
<input
type="text"
className="w-full p-4 rounded-xl border border-border bg-background focus:ring-2 focus:ring-primary focus:border-transparent text-base"
className="w-full p-4 rounded-xl border border-border bg-background focus:ring-2 focus:ring-primary focus:border-transparent text-lg md:text-xl font-bold"
placeholder="अपना संख्यात्मक उत्तर यहाँ टाइप करें..."
value={currentState?.selected_answer || ""}
onChange={(e) => selectAnswer(currentQuestion.id, e.target.value)}
/>
) : currentQuestion.type === 'Subjective' ? (
<textarea
className="w-full min-h-[150px] p-4 rounded-xl border border-border bg-background focus:ring-2 focus:ring-primary focus:border-transparent resize-y text-base"
className="w-full min-h-[150px] p-4 rounded-xl border border-border bg-background focus:ring-2 focus:ring-primary focus:border-transparent resize-y text-lg md:text-xl font-bold"
placeholder="अपना उत्तर या स्पष्टीकरण यहाँ टाइप करें..."
value={currentState?.selected_answer || ""}
onChange={(e) => selectAnswer(currentQuestion.id, e.target.value)}
@@ -585,16 +585,16 @@ export default function CBTExamPage({ params }: { params: Promise<{ examId: stri
<button
key={idx}
onClick={() => handleOptionSelect(currentQuestion.id, letter, currentQuestion.type)}
className={`w-full text-left p-4 rounded-xl border transition-all duration-200 flex items-center gap-4 group focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent
className={`w-full text-left p-4.5 rounded-xl border transition-all duration-200 flex items-center gap-4 group focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent
${isSelected ? 'border-primary bg-primary/5 shadow-[0_0_15px_-5px_var(--color-primary)]' : 'border-border bg-background hover:border-primary/50 hover:bg-secondary/20'}
`}
>
<div className={`flex-shrink-0 size-8 ${isMultiple ? 'rounded-md' : 'rounded-full'} border flex items-center justify-center font-medium transition-colors
<div className={`flex-shrink-0 size-10 ${isMultiple ? 'rounded-md' : 'rounded-full'} border flex items-center justify-center font-black text-base transition-colors
${isSelected ? 'border-primary bg-primary text-primary-foreground' : 'border-border text-muted-foreground group-hover:border-primary/50 group-hover:text-primary group-hover:bg-primary/5'}
`}>
{letter}
</div>
<span className={`text-base font-semibold flex-1 ${isSelected ? 'text-primary' : ''}`}>
<span className={`text-lg md:text-xl font-bold flex-1 ${isSelected ? 'text-primary' : ''}`}>
<ReactMarkdown remarkPlugins={[remarkMath]} rehypePlugins={[rehypeKatex, rehypeRaw]}>
{renderLatex(option)}
</ReactMarkdown>

View File

@@ -2,15 +2,12 @@ import { Button } from "@/components/ui/button"
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
import Link from "next/link"
import { ArrowLeft, Mail, MapPin, Phone } from "lucide-react"
import { PublicLayout } from "@/components/Landing/PublicLayout"
export default function ContactPage() {
return (
<div className="min-h-screen bg-background text-foreground flex flex-col">
<header className="border-b border-border bg-background px-6 py-4 flex items-center justify-between sticky top-0 z-10">
<Link href="/" className="text-muted-foreground hover:text-foreground flex items-center gap-2 transition-colors font-medium">
<ArrowLeft size={16} /> Back to Home
</Link>
</header>
<PublicLayout>
<div className="bg-background text-foreground flex flex-col pt-24 pb-16 min-h-[70vh]">
<main className="flex-1 flex flex-col items-center justify-center p-8 animate-in fade-in duration-700">
<h1 className="text-5xl font-bold tracking-tighter mb-4 text-center">Contact Us</h1>
<p className="text-lg text-muted-foreground max-w-2xl text-center mb-12">
@@ -36,13 +33,14 @@ export default function ContactPage() {
<div className="flex items-center gap-3 text-sm text-muted-foreground font-medium">
<Mail size={16} className="text-primary" /> support@pragativeer.com
</div>
<div className="flex items-center gap-3 text-sm text-muted-foreground font-medium">
<MapPin size={16} className="text-primary" /> Tech Hub, Bengaluru, India
<div className="flex items-center gap-3 text-sm text-muted-foreground font-medium mt-2">
<MapPin size={16} className="text-primary" /> Pune, Maharashtra
</div>
</div>
</CardContent>
</Card>
</main>
</div>
</PublicLayout>
)
}

View File

@@ -115,7 +115,7 @@ export default function AIComCBTDPPPage() {
<div>
<div className="flex items-center gap-2 mb-1">
<span className="text-xs font-black uppercase tracking-widest text-[#00F0FF] bg-[#00F0FF]/10 px-2.5 py-0.5 rounded-full border border-[#00F0FF]/30">
AI CBT DPP Simulator
PrepPilot AI
</span>
<span className="text-xs font-bold text-muted-foreground">• {user?.target_exam?.replace("_", " ") || "JEE/NEET"}</span>
</div>
@@ -180,7 +180,7 @@ export default function AIComCBTDPPPage() {
</div>
<div className="space-y-4">
<p className="text-lg font-bold leading-relaxed text-foreground">
<p className="text-xl md:text-2xl font-black leading-relaxed text-foreground">
{sampleQuestions[activeQuestion].question}
</p>
@@ -203,13 +203,13 @@ export default function AIComCBTDPPPage() {
<div
key={optIdx}
onClick={() => handleOptionSelect(activeQuestion, optIdx)}
className={`flex items-center gap-4 p-4 rounded-2xl border cursor-pointer transition-all ${btnStyle}`}
className={`flex items-center gap-4 p-4.5 rounded-2xl border cursor-pointer transition-all ${btnStyle}`}
>
<span className="w-8 h-8 rounded-xl bg-background/80 border border-border flex items-center justify-center font-bold text-sm shrink-0">
<span className="w-10 h-10 rounded-xl bg-background/80 border border-border flex items-center justify-center font-black text-base shrink-0">
{letter}
</span>
<span className="text-base font-medium flex-1">{opt}</span>
{submitted && isCorrect && <CheckCircle2 size={20} className="text-emerald-400" />}
<span className="text-lg md:text-xl font-bold flex-1">{opt}</span>
{submitted && isCorrect && <CheckCircle2 size={22} className="text-emerald-400" />}
</div>
)
})}

View File

@@ -0,0 +1,5 @@
import AttemptHistoryPage from "@/app/dashboard/cbt/history/[attemptId]/page";
export default function AIHistoryPage({ params }: { params: Promise<{ attemptId: string }> }) {
return <AttemptHistoryPage params={params} />;
}

View File

@@ -2,19 +2,21 @@
import { Button } from "@/components/ui/button"
import { Card, CardContent } from "@/components/ui/card"
import { BrainCircuit, Play, CheckCircle2, Download, Filter, Lock, FileText, Eye, ShieldAlert, X, Sparkles } from "lucide-react"
import { BrainCircuit, Play, CheckCircle2, Download, Filter, Lock, FileText, Eye, ShieldAlert, X, Sparkles, Share2, Maximize2, Minimize2 } from "lucide-react"
import { useEffect, useState } from "react"
import Link from "next/link"
import { useActiveUser } from "@/hooks/useActiveUser"
export default function PrepPilotAIPage() {
export default function AIDailyPracticePage() {
const { user, userId } = useActiveUser()
const [dpps, setDpps] = useState<any[]>([])
const [attempts, setAttempts] = useState<any[]>([])
const [loading, setLoading] = useState(true)
const [dateFilter, setDateFilter] = useState("")
const [selectedPdfDpp, setSelectedPdfDpp] = useState<any | null>(null)
const [isFullScreen, setIsFullScreen] = useState(false)
const [copiedShare, setCopiedShare] = useState(false)
useEffect(() => {
async function fetchDpps() {
@@ -90,14 +92,27 @@ export default function PrepPilotAIPage() {
const cleanTitle = (title: string) => title.replace(/\s*\(ExamEnum\.[^)]+\)/, '');
return (
<div className="p-8 space-y-8 animate-in fade-in slide-in-from-bottom-4 duration-500 max-w-5xl mx-auto">
<div className="flex flex-col md:flex-row md:items-center justify-between p-8 bg-gradient-to-r from-secondary/50 to-background border border-border rounded-2xl shadow-sm gap-6">
<div className="p-6 md:p-8 space-y-6 animate-in fade-in slide-in-from-bottom-4 duration-500 max-w-7xl mx-auto">
{/* Page Header */}
<div className="border-b border-border/50 pb-6">
<div className="flex items-start justify-between gap-4 flex-wrap">
<div>
<h1 className="text-3xl font-bold tracking-tight mb-2">PrepPilot AI <span className="text-[#00F0FF] text-xl">&</span> CBT Simulator</h1>
<p className="text-muted-foreground font-medium max-w-xl">
Select an interactive Target DPP to launch the AI-powered CBT examination simulator.
<h1 className="text-3xl md:text-4xl font-black tracking-tight flex items-center gap-3 mb-1">
<span className="p-2.5 rounded-xl bg-[#00F0FF]/10 border border-[#00F0FF]/20">
<Sparkles className="text-[#00F0FF]" size={28} />
</span>
AI Daily Practice (DPP)
</h1>
<p className="text-muted-foreground text-base font-medium max-w-2xl pl-1">
Launch AI-generated Daily Practice Problems (DPPs) tailored to your exam pattern and weak areas.
</p>
</div>
<div className="flex items-center gap-2 shrink-0">
<span className="text-xs font-bold uppercase tracking-wider text-[#00F0FF] bg-[#00F0FF]/10 border border-[#00F0FF]/20 px-3 py-1.5 rounded-full flex items-center gap-1.5">
<BrainCircuit size={12} /> AI Powered
</span>
</div>
</div>
</div>
<div className="flex flex-col sm:flex-row justify-between items-center gap-4 border-b border-border pb-6">
@@ -114,12 +129,9 @@ export default function PrepPilotAIPage() {
{diff}
</button>
))}
<div className="flex items-center gap-1 ml-2 text-xs text-muted-foreground bg-secondary/50 px-2 py-1 rounded-md">
<Lock size={12}/> Pro
</div>
</div>
<Button variant="outline" className="gap-2 rounded-xl text-sm font-bold">
<Download size={16} /> Export as PDF <span className="ml-1 text-[10px] uppercase tracking-wider bg-primary/10 text-primary px-1.5 py-0.5 rounded-md">Pro</span>
<Download size={16} /> Export as PDF
</Button>
</div>
@@ -200,7 +212,7 @@ export default function PrepPilotAIPage() {
{!isExpired && (
<Link href={`/cbt/${dpp.id}`}>
<Button variant="secondary" size="sm" className="font-bold bg-[#00F0FF]/10 text-[#00F0FF] hover:bg-[#00F0FF]/20 gap-2">
<Play size={14} className="fill-current" /> Retake CBT
<Play size={14} className="fill-current" /> Retake Test
</Button>
</Link>
)}
@@ -208,7 +220,7 @@ export default function PrepPilotAIPage() {
) : (
<Link href={`/cbt/${dpp.id}`}>
<Button variant="secondary" size="sm" className="rounded-xl bg-[#00F0FF] text-black hover:bg-[#00F0FF]/90 font-extrabold gap-2 text-xs shadow-[0_0_12px_rgba(0,240,255,0.2)]">
<Play size={14} className="fill-black" /> Launch CBT DPP
<Play size={14} className="fill-black" /> Launch Practice Test
</Button>
</Link>
)}
@@ -220,18 +232,18 @@ export default function PrepPilotAIPage() {
{/* ── PROTECTED PDF READER MODAL (FREE TIER) ── */}
{selectedPdfDpp && (
<div className="fixed inset-0 z-50 bg-black/80 backdrop-blur-md flex items-center justify-center p-4 animate-in fade-in duration-300">
<div className="bg-background border border-border rounded-3xl w-full max-w-4xl max-h-[90vh] flex flex-col overflow-hidden shadow-2xl">
<div className={`fixed inset-0 z-50 bg-black/90 backdrop-blur-md flex items-center justify-center ${isFullScreen ? 'p-0' : 'p-4'} animate-in fade-in duration-300`}>
<div className={`bg-background border border-border ${isFullScreen ? 'w-screen h-screen max-w-none max-h-none rounded-none border-0' : 'rounded-3xl w-full max-w-7xl max-h-[95vh]'} flex flex-col overflow-hidden shadow-2xl transition-all duration-300`}>
{/* Modal Header */}
<div className="p-5 border-b border-border bg-secondary/30 flex items-center justify-between">
<div className="p-4 md:p-5 border-b border-border bg-secondary/30 flex items-center justify-between">
<div className="flex items-center gap-3">
<div className="p-2.5 bg-primary/10 rounded-xl border border-primary/20 text-primary">
<div className="p-2.5 bg-primary/10 rounded-xl border border-primary/20 text-primary hidden sm:flex">
<FileText size={22} />
</div>
<div>
<h3 className="font-black text-lg flex items-center gap-2">
<h3 className="font-black text-base sm:text-lg flex items-center gap-2">
{selectedPdfDpp.title}
<span className="text-[10px] bg-yellow-500/10 text-yellow-500 border border-yellow-500/20 px-2 py-0.5 rounded-full uppercase tracking-wider font-extrabold">
<span className="text-[10px] bg-yellow-500/10 text-yellow-500 border border-yellow-500/20 px-2 py-0.5 rounded-full uppercase tracking-wider font-extrabold hidden md:inline-block">
Protected In-App PDF View
</span>
</h3>
@@ -240,24 +252,46 @@ export default function PrepPilotAIPage() {
</p>
</div>
</div>
<div className="flex items-center gap-2">
<Button
onClick={() => setSelectedPdfDpp(null)}
size="sm"
variant="outline"
size="icon"
className="rounded-full border-border hover:bg-secondary"
onClick={() => {
navigator.clipboard.writeText(window.location.origin + '/dashboard/ai-coach/cbt-dpp');
setCopiedShare(true);
setTimeout(() => setCopiedShare(false), 2000);
}}
className="rounded-xl font-bold text-xs gap-1.5 h-9 px-3"
>
{copiedShare ? <CheckCircle2 size={14} className="text-green-500" /> : <Share2 size={14} />}
<span>{copiedShare ? "Copied" : "Share PDF"}</span>
</Button>
<Button
size="sm"
variant="outline"
onClick={() => setIsFullScreen(!isFullScreen)}
className="rounded-xl font-bold text-xs gap-1.5 h-9 px-3 bg-primary/10 text-primary border-primary/20 hover:bg-primary/20"
>
{isFullScreen ? <Minimize2 size={14} /> : <Maximize2 size={14} />}
<span>{isFullScreen ? "Exit Fullscreen" : "Fullscreen"}</span>
</Button>
<button
onClick={() => { setSelectedPdfDpp(null); setIsFullScreen(false); }}
className="ml-1 flex-shrink-0 w-9 h-9 rounded-full border border-border bg-secondary flex items-center justify-center text-foreground hover:bg-destructive hover:border-destructive hover:text-white transition-all"
>
<X size={18} />
</Button>
</button>
</div>
</div>
{/* Security Notice Banner */}
<div className="bg-amber-500/10 border-b border-amber-500/20 px-6 py-2.5 flex items-center justify-between text-xs text-amber-400 font-bold">
<span className="flex items-center gap-2">
<ShieldAlert size={16} /> Read-Only Mode (Free Tier) — Downloads, text copying, and sharing are restricted.
<div className="bg-amber-500/10 border-b border-amber-500/20 px-4 sm:px-6 py-2 flex items-center justify-between text-xs text-amber-400 font-bold">
<span className="flex items-center gap-2 truncate">
<ShieldAlert size={16} className="shrink-0" /> Read-Only Mode (Free Tier) — Downloads & direct copying are restricted.
</span>
<Link href="/upgrade">
<Link href="/upgrade" className="hidden sm:block shrink-0 ml-2">
<Button size="sm" className="bg-amber-500 text-black hover:bg-amber-400 font-extrabold h-7 text-[11px] rounded-lg">
Upgrade for Interactive CBT Mode →
Upgrade for Interactive Practice Mode →
</Button>
</Link>
</div>
@@ -265,27 +299,33 @@ export default function PrepPilotAIPage() {
{/* Protected PDF Document Sheet Content (select-none) */}
<div
onContextMenu={(e) => e.preventDefault()}
className="flex-1 w-full h-full min-h-[60vh] select-none bg-[#525659]"
className="flex-1 w-full select-none bg-[#525659] overflow-hidden"
style={{minHeight: isFullScreen ? 'calc(100vh - 120px)' : '75vh'}}
>
<embed
src={getPdfUrl(selectedPdfDpp.title)}
type="application/pdf"
className="w-full h-full rounded-b-lg"
className="w-full h-full"
style={{height: isFullScreen ? 'calc(100vh - 120px)' : '75vh'}}
/>
</div>
{/* Modal Footer */}
<div className="p-4 border-t border-border bg-secondary/30 flex items-center justify-between">
<span className="text-xs text-muted-foreground font-medium">
<div className="p-3.5 sm:p-4 border-t border-border bg-secondary/30 flex items-center justify-between flex-wrap gap-2">
<span className="text-xs text-muted-foreground font-medium flex items-center gap-2">
📄 PragatiVeer Protected PDF Reader v2.0
</span>
<div className="flex gap-3">
<Button onClick={() => setSelectedPdfDpp(null)} variant="outline" className="rounded-xl font-bold">
Close PDF Reader
<div className="flex gap-2 items-center">
<Button onClick={() => setIsFullScreen(!isFullScreen)} variant="secondary" className="rounded-xl font-bold text-xs h-9 bg-primary/10 text-primary hover:bg-primary/20 flex items-center gap-1.5">
{isFullScreen ? <Minimize2 size={14} /> : <Maximize2 size={14} />}
<span>{isFullScreen ? "Exit Fullscreen" : "Fullscreen"}</span>
</Button>
<Button onClick={() => { setSelectedPdfDpp(null); setIsFullScreen(false); }} variant="outline" className="rounded-xl font-bold text-xs h-9">
Close Reader
</Button>
<Link href="/upgrade">
<Button className="bg-[#00F0FF] text-black hover:bg-[#00F0FF]/90 font-extrabold rounded-xl shadow-[0_0_15px_rgba(0,240,255,0.2)]">
<Sparkles size={16} className="mr-2" /> Upgrade to AI Coach for Interactive CBT
<Button className="bg-[#00F0FF] text-black hover:bg-[#00F0FF]/90 font-extrabold rounded-xl shadow-[0_0_15px_rgba(0,240,255,0.2)] text-xs h-9">
<Sparkles size={14} className="mr-1.5" /> Upgrade to AI Coach
</Button>
</Link>
</div>

View File

@@ -2,51 +2,131 @@
import { CollegePredictorCard } from "@/components/AI/CollegePredictorCard"
import { Card, CardContent } from "@/components/ui/card"
import { Button } from "@/components/ui/button"
import { GraduationCap, ArrowLeft, Building2, MapPin, Award, CheckCircle2 } from "lucide-react"
import { GraduationCap, Building2, Award, CheckCircle2, TrendingUp, MapPin, Users, BarChart2 } from "lucide-react"
import Link from "next/link"
const CUTOFF_TRENDS = [
{ year: "2021", rank: 3800 },
{ year: "2022", rank: 3500 },
{ year: "2023", rank: 3200 },
{ year: "2024", rank: 2950 },
{ year: "2025", rank: 2750 },
]
export default function CollegePredictorPage() {
const maxRank = Math.max(...CUTOFF_TRENDS.map(d => d.rank))
return (
<div className="p-6 md:p-8 max-w-7xl mx-auto space-y-8 animate-in fade-in duration-500 min-h-screen">
{/* Navigation Header */}
<div className="p-6 md:p-8 space-y-8 max-w-7xl mx-auto animate-in fade-in duration-500 min-h-screen">
{/* Page Header */}
<div className="border-b border-border/50 pb-6">
<h1 className="text-3xl md:text-5xl font-black tracking-tight flex items-center gap-3">
<GraduationCap className="text-[#00F0FF]" size={36} />
AI College & Cutoff Predictor
<div className="flex items-start justify-between gap-4 flex-wrap">
<div>
<h1 className="text-3xl md:text-4xl font-black tracking-tight flex items-center gap-3 mb-2">
<span className="p-2.5 rounded-xl bg-[#00F0FF]/10 border border-[#00F0FF]/20">
<GraduationCap className="text-[#00F0FF]" size={28} />
</span>
AI College &amp; Cutoff Predictor
</h1>
<p className="text-muted-foreground text-base md:text-lg mt-1 font-medium max-w-3xl">
Discover your probable target medical & engineering colleges based on current mock exam scores and historical cutoffs.
<p className="text-muted-foreground text-base font-medium max-w-2xl pl-1">
Discover your probable target medical &amp; engineering colleges based on current mock exam scores and historical cutoffs.
</p>
</div>
<span className="text-xs font-bold uppercase tracking-wider text-[#00F0FF] bg-[#00F0FF]/10 border border-[#00F0FF]/20 px-3 py-1.5 rounded-full flex items-center gap-1.5 shrink-0 self-start">
<GraduationCap size={12} /> AI Powered
</span>
</div>
</div>
{/* Summary KPI Strip */}
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
{[
{ label: "Predicted Rank", value: "~4,200", icon: <TrendingUp size={18} className="text-[#00F0FF]" />, color: "border-[#00F0FF]/20 bg-[#00F0FF]/5" },
{ label: "Target Colleges", value: "12", icon: <Building2 size={18} className="text-amber-400" />, color: "border-amber-400/20 bg-amber-400/5" },
{ label: "Reach Colleges", value: "5", icon: <MapPin size={18} className="text-purple-400" />, color: "border-purple-400/20 bg-purple-400/5" },
{ label: "Safe Colleges", value: "18", icon: <CheckCircle2 size={18} className="text-green-400" />, color: "border-green-400/20 bg-green-400/5" },
].map((kpi) => (
<Card key={kpi.label} className={`rounded-2xl border ${kpi.color} hover:scale-[1.02] transition-transform`}>
<CardContent className="p-5 flex flex-col gap-2">
{kpi.icon}
<div className="text-2xl font-black">{kpi.value}</div>
<div className="text-xs font-semibold text-muted-foreground uppercase tracking-wider">{kpi.label}</div>
</CardContent>
</Card>
))}
</div>
<div className="grid grid-cols-1 lg:grid-cols-3 gap-8">
<div className="lg:col-span-2 space-y-8">
<Card className="p-8 bg-secondary/10 border-border/60 rounded-3xl">
<h2 className="text-2xl font-extrabold tracking-tight mb-6 flex items-center gap-2">
<Building2 className="text-[#00F0FF]" size={24} /> Target College Analytics
</h2>
{/* College Predictor */}
<CollegePredictorCard />
{/* Cutoff Trend Chart */}
<Card className="p-6 bg-secondary/10 border-border/60 rounded-3xl">
<div className="flex items-center gap-2 mb-6">
<BarChart2 size={20} className="text-[#00F0FF]" />
<h3 className="text-lg font-extrabold tracking-tight">NIT Trichy CSE — Closing Rank Trend</h3>
<span className="ml-auto text-xs font-bold text-green-400 bg-green-400/10 border border-green-400/20 px-2.5 py-1 rounded-full flex items-center gap-1">
<TrendingUp size={11} /> Improving Cutoffs
</span>
</div>
<div className="flex items-end gap-4 h-32 px-2">
{CUTOFF_TRENDS.map((d, i) => (
<div key={d.year} className="flex flex-col items-center gap-1.5 flex-1">
<span className="text-xs font-black text-muted-foreground">{d.rank.toLocaleString()}</span>
<div
className={`w-full rounded-t-md transition-all duration-700 ${i === CUTOFF_TRENDS.length - 1 ? "bg-gradient-to-t from-[#00F0FF]/60 to-[#00F0FF]" : "bg-gradient-to-t from-white/10 to-white/25"}`}
style={{ height: `${(d.rank / maxRank) * 80}px` }}
/>
<span className="text-xs text-muted-foreground font-semibold">{d.year}</span>
</div>
))}
</div>
<p className="text-xs text-muted-foreground mt-4 pl-1">Closing rank trend: cutoffs are improving, meaning competition is easing. Your predicted rank of ~4,200 is within Target range.</p>
</Card>
</div>
<div className="space-y-6">
<Card className="p-6 bg-gradient-to-br from-[#00F0FF]/10 via-secondary/20 to-background border border-[#00F0FF]/30 rounded-3xl space-y-4">
<h3 className="text-xl font-black tracking-tight text-[#00F0FF] flex items-center gap-2">
<Award size={20} /> Cutoff Modeling Parameters
<h3 className="text-base font-black tracking-tight text-[#00F0FF] flex items-center gap-2">
<Award size={18} /> Cutoff Modelling Parameters
</h3>
<ul className="space-y-3 text-sm font-medium text-muted-foreground">
<li className="flex items-start gap-2">
<CheckCircle2 size={16} className="text-[#00F0FF] shrink-0 mt-0.5" />
<span>Includes 100,000+ candidate rank trends across 2021-2025 rounds.</span>
<span>Trained on <strong className="text-foreground">100,000+ candidate rank trends</strong> across 2021–2025 counselling rounds.</span>
</li>
<li className="flex items-start gap-2">
<CheckCircle2 size={16} className="text-[#00F0FF] shrink-0 mt-0.5" />
<span>Supports All India Quota (AIQ) & State Quota adjustments.</span>
<span>Supports <strong className="text-foreground">All India Quota (AIQ)</strong> &amp; State Quota seat adjustments.</span>
</li>
<li className="flex items-start gap-2">
<CheckCircle2 size={16} className="text-[#00F0FF] shrink-0 mt-0.5" />
<span>Simulates category reservation cutoffs (General, OBC, SC, ST, EWS).</span>
<span>Simulates <strong className="text-foreground">category reservation cutoffs</strong>: General, OBC, SC, ST, EWS.</span>
</li>
<li className="flex items-start gap-2">
<CheckCircle2 size={16} className="text-[#00F0FF] shrink-0 mt-0.5" />
<span>Score updated daily as you complete more mock tests.</span>
</li>
</ul>
</Card>
<Card className="p-6 bg-secondary/10 border-border/60 rounded-3xl space-y-3">
<h3 className="text-base font-black text-muted-foreground uppercase tracking-wider flex items-center gap-2">
<Users size={16} className="text-amber-400" /> Improve Your Rank
</h3>
<ul className="space-y-2.5 text-sm text-muted-foreground font-medium">
<li className="flex items-start gap-2">
<span className="text-amber-400 font-black shrink-0">+15 marks</span>
<span>in Maths &rarr; upgrades you from NIT Trichy to <strong className="text-foreground">IIT Roorkee</strong></span>
</li>
<li className="flex items-start gap-2">
<span className="text-[#00F0FF] font-black shrink-0">Fix Optics</span>
<span>errors to recover ~8 marks in Physics alone</span>
</li>
<li className="flex items-start gap-2">
<span className="text-green-400 font-black shrink-0">Boost Speed</span>
<span>from 45s → 38s/Q gives you ~12 extra attempt opportunities</span>
</li>
</ul>
</Card>

View File

@@ -1,25 +1,157 @@
"use client"
import { MistakeHeatmap } from "@/components/AI/MistakeHeatmap"
import { ArrowLeft, Flame } from "lucide-react"
import { Flame, AlertTriangle, TrendingDown, BookOpen, ArrowRight, BarChart2, Target } from "lucide-react"
import { Card, CardContent } from "@/components/ui/card"
import Link from "next/link"
const CRITICAL_TOPICS = [
{ name: "Rotational Dynamics", rate: "80%", rateNum: 80, subject: "Physics" },
{ name: "Optics", rate: "60%", rateNum: 60, subject: "Physics" },
{ name: "Electromagnetism", rate: "30%", rateNum: 30, subject: "Physics" },
{ name: "GOC — Reaction Mechanisms", rate: "55%", rateNum: 55, subject: "Chemistry" },
{ name: "Thermodynamics", rate: "25%", rateNum: 25, subject: "Physics" },
]
const SUMMARY_STATS = [
{ label: "Total Errors (Last 5 Tests)", value: "87", icon: <AlertTriangle size={18} className="text-red-400" />, color: "border-red-400/20 bg-red-400/5" },
{ label: "Critical Topics", value: "4", icon: <Flame size={18} className="text-orange-400" />, color: "border-orange-400/20 bg-orange-400/5" },
{ label: "Improving Topics", value: "3", icon: <TrendingDown size={18} className="text-green-400" />, color: "border-green-400/20 bg-green-400/5" },
{ label: "Avg Error Rate", value: "38%", icon: <Target size={18} className="text-amber-400" />, color: "border-amber-400/20 bg-amber-400/5" },
]
export default function HeatmapPage() {
return (
<div className="p-6 md:p-8 max-w-7xl mx-auto space-y-8 animate-in fade-in duration-500 min-h-screen">
{/* Page Header */}
<div className="border-b border-border/50 pb-6">
<h1 className="text-3xl md:text-5xl font-black tracking-tight flex items-center gap-3">
<Flame className="text-yellow-500" size={36} />
<div className="flex items-start justify-between gap-4 flex-wrap">
<div>
<h1 className="text-3xl md:text-4xl font-black tracking-tight flex items-center gap-3 mb-2">
<span className="p-2.5 rounded-xl bg-amber-500/10 border border-amber-500/20">
<Flame className="text-amber-500" size={28} />
</span>
Mistake Heatmap Vault
</h1>
<p className="text-muted-foreground text-base md:text-lg mt-1 font-medium max-w-3xl">
Visual representation of your error density across subjects and topics.
<p className="text-muted-foreground text-base font-medium max-w-2xl pl-1">
Visual density map of your errors across subjects and topics — pinpoint your weakest areas instantly.
</p>
</div>
<span className="text-xs font-bold uppercase tracking-wider text-amber-500 bg-amber-500/10 border border-amber-500/20 px-3 py-1.5 rounded-full flex items-center gap-1.5 shrink-0 self-start">
<AlertTriangle size={12} /> Error Density Analysis
</span>
</div>
</div>
<div className="max-w-4xl mx-auto">
{/* KPI Strip */}
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
{SUMMARY_STATS.map((kpi) => (
<Card key={kpi.label} className={`rounded-2xl border ${kpi.color} hover:scale-[1.02] transition-transform`}>
<CardContent className="p-5 flex flex-col gap-2">
{kpi.icon}
<div className="text-2xl font-black">{kpi.value}</div>
<div className="text-xs font-semibold text-muted-foreground uppercase tracking-wider">{kpi.label}</div>
</CardContent>
</Card>
))}
</div>
{/* Legend / Guidance Row */}
<div className="grid grid-cols-2 lg:grid-cols-4 gap-4">
{[
{ label: "Critical (> 50%)", color: "bg-red-500/20 border-red-500/30 text-red-400", dot: "bg-red-500", desc: "Immediate revision needed" },
{ label: "Warning (30–50%)", color: "bg-amber-500/20 border-amber-500/30 text-amber-400", dot: "bg-amber-500", desc: "Focus on these next" },
{ label: "Moderate (10–30%)", color: "bg-yellow-500/20 border-yellow-500/30 text-yellow-400", dot: "bg-yellow-500", desc: "Review periodically" },
{ label: "Good (< 10%)", color: "bg-green-500/20 border-green-500/30 text-green-400", dot: "bg-green-500", desc: "Mostly understood" },
].map((item) => (
<Card key={item.label} className={`rounded-2xl border ${item.color.split(" ").slice(0, 2).join(" ")}`}>
<CardContent className="p-4">
<div className={`inline-flex items-center gap-1.5 text-xs font-black uppercase tracking-wider mb-1 ${item.color.split(" ")[2]}`}>
<span className={`w-2.5 h-2.5 rounded-sm ${item.dot}`} />
{item.label}
</div>
<p className="text-xs text-muted-foreground font-medium">{item.desc}</p>
</CardContent>
</Card>
))}
</div>
{/* Main 2-col layout */}
<div className="grid grid-cols-1 lg:grid-cols-3 gap-8">
{/* Heatmap — takes 2/3 */}
<div className="lg:col-span-2 space-y-3">
<div className="flex items-center gap-2 text-sm font-black uppercase tracking-wider text-muted-foreground">
<TrendingDown size={16} className="text-red-400" />
Topic Error Rate — Last 5 Tests
</div>
<MistakeHeatmap />
</div>
{/* Side panel */}
<div className="space-y-6">
{/* Error frequency chart */}
<Card className="bg-secondary/10 border-border/60 rounded-2xl">
<CardContent className="p-5 space-y-4">
<h3 className="text-sm font-black uppercase tracking-wider text-foreground flex items-center gap-2">
<BarChart2 size={15} className="text-red-400" /> Priority Topics by Error Rate
</h3>
<div className="space-y-3">
{CRITICAL_TOPICS.map((t) => (
<div key={t.name}>
<div className="flex items-center justify-between mb-1">
<div>
<p className="text-xs font-bold leading-tight">{t.name}</p>
<p className="text-[11px] text-muted-foreground">{t.subject}</p>
</div>
<span className={`text-xs font-black px-2 py-0.5 rounded-md ${t.rateNum >= 50 ? "text-red-400 bg-red-400/10" : t.rateNum >= 30 ? "text-amber-400 bg-amber-400/10" : "text-yellow-400 bg-yellow-400/10"}`}>
{t.rate}
</span>
</div>
<div className="h-1.5 w-full bg-secondary rounded-full overflow-hidden">
<div
className={`h-full rounded-full ${t.rateNum >= 50 ? "bg-red-500" : t.rateNum >= 30 ? "bg-amber-500" : "bg-yellow-500"}`}
style={{ width: `${t.rateNum}%` }}
/>
</div>
</div>
))}
</div>
</CardContent>
</Card>
{/* Action card */}
<Card className="bg-gradient-to-br from-[#00F0FF]/10 via-secondary/20 to-background border border-[#00F0FF]/20 rounded-2xl">
<CardContent className="p-5 space-y-3">
<h3 className="text-sm font-black uppercase tracking-wider text-[#00F0FF]">What To Do Next</h3>
<div className="space-y-2">
{[
{ label: "Start Mistake Review Session", href: "/dashboard/ai-coach/mistakes" },
{ label: "Take a Physics DPP", href: "/dashboard/ai-coach/cbt-dpp" },
{ label: "Check Exam Readiness Score", href: "/dashboard/ai-coach/readiness" },
].map((link) => (
<Link key={link.href} href={link.href}
className="flex items-center justify-between text-sm font-bold text-muted-foreground hover:text-foreground py-2 border-b border-border/30 last:border-0 transition-colors group">
{link.label}
<ArrowRight size={14} className="group-hover:translate-x-1 transition-transform" />
</Link>
))}
</div>
</CardContent>
</Card>
{/* Tip */}
<Card className="bg-secondary/20 border-border/40 rounded-2xl">
<CardContent className="p-4">
<div className="flex items-start gap-2.5">
<BookOpen size={16} className="text-[#00F0FF] shrink-0 mt-0.5" />
<p className="text-xs text-muted-foreground font-medium">
<span className="font-black text-foreground">Pro Tip:</span> Focus the next 3 days entirely on Rotational Dynamics — it alone is worth 8–12 marks in JEE and shows an 80% error rate across your last 5 tests.
</p>
</div>
</CardContent>
</Card>
</div>
</div>
</div>
)
}

View File

@@ -5,9 +5,8 @@ import { MistakeHeatmap } from "@/components/AI/MistakeHeatmap"
import { MistakeReviewCard, MistakePayload } from "@/components/AI/MistakeReviewCard"
import { Card, CardContent } from "@/components/ui/card"
import { Button } from "@/components/ui/button"
import { BrainCircuit, RefreshCw, CheckCircle2, ArrowLeft, Flame, Sparkles, AlertTriangle } from "lucide-react"
import { BrainCircuit, RefreshCw, CheckCircle2, Flame, Sparkles, AlertTriangle, Target, Clock, TrendingDown } from "lucide-react"
import { useActiveUser } from "@/hooks/useActiveUser"
import Link from "next/link"
export default function MistakeTrackingPage() {
const [mistakes, setMistakes] = useState<MistakePayload[]>([])
@@ -23,8 +22,7 @@ export default function MistakeTrackingPage() {
if (!res.ok) throw new Error("Failed to fetch")
const data = await res.json()
setMistakes(data)
} catch (error) {
console.error(error)
} catch {
setMistakes([])
} finally {
setLoading(false)
@@ -32,9 +30,7 @@ export default function MistakeTrackingPage() {
}
useEffect(() => {
if (!userLoading && userId) {
fetchMistakes()
}
if (!userLoading && userId) fetchMistakes()
}, [userId, userLoading])
const submitReview = async (mistakeId: string, selectedAnswer: string) => {
@@ -55,51 +51,97 @@ export default function MistakeTrackingPage() {
}
}
const completedCount = Math.min(currentIndex, mistakes.length)
const progressPct = mistakes.length > 0 ? Math.round((completedCount / mistakes.length) * 100) : 0
return (
<div className="p-6 md:p-8 max-w-7xl mx-auto space-y-8 animate-in fade-in duration-500 min-h-screen">
{/* Navigation Header */}
<div className="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4 border-b border-border/50 pb-6">
<div className="p-6 md:p-8 space-y-8 max-w-7xl mx-auto animate-in fade-in duration-500 min-h-screen">
{/* Page Header */}
<div className="border-b border-border/50 pb-6">
<div className="flex items-start justify-between gap-4 flex-wrap">
<div>
<h1 className="text-3xl md:text-5xl font-black tracking-tight flex items-center gap-3">
<BrainCircuit className="text-[#00F0FF]" size={36} />
AI Mistake Vault & SM-2 Engine
<h1 className="text-3xl md:text-4xl font-black tracking-tight flex items-center gap-3 mb-2">
<span className="p-2.5 rounded-xl bg-[#00F0FF]/10 border border-[#00F0FF]/20">
<BrainCircuit className="text-[#00F0FF]" size={28} />
</span>
Mistake Review &amp; Spaced Repetition
</h1>
<p className="text-muted-foreground text-base md:text-lg mt-1 font-medium">
Review logged errors, practice spaced-repetition cards, and observe subject heatmap trends.
<p className="text-muted-foreground text-base font-medium max-w-2xl pl-1">
Review logged errors using our SM-2 spaced repetition engine. Questions resurface at optimal intervals to cement long-term retention.
</p>
</div>
<div className="flex items-center gap-3 shrink-0">
<div className="bg-[#00F0FF]/10 text-[#00F0FF] border border-[#00F0FF]/30 px-4 py-2 rounded-2xl font-bold text-sm flex items-center gap-2">
<Sparkles size={16} /> SM-2 Adaptive Algorithm Active
</div>
<span className="text-xs font-bold uppercase tracking-wider text-[#00F0FF] bg-[#00F0FF]/10 border border-[#00F0FF]/20 px-3 py-1.5 rounded-full flex items-center gap-1.5 shrink-0 self-start">
<Sparkles size={12} /> SM-2 Engine
</span>
</div>
</div>
{/* Main Review Workspace */}
<div className="grid grid-cols-1 lg:grid-cols-3 gap-8">
<div className="lg:col-span-2 space-y-6">
{/* KPI Strip */}
{!loading && (
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
{[
{ label: "In Today's Queue", value: mistakes.length.toString(), icon: <Flame size={18} className="text-amber-400" />, color: "border-amber-400/20 bg-amber-400/5" },
{ label: "Reviewed This Session", value: completedCount.toString(), icon: <CheckCircle2 size={18} className="text-green-400" />, color: "border-green-400/20 bg-green-400/5" },
{ label: "Session Progress", value: `${progressPct}%`, icon: <Target size={18} className="text-[#00F0FF]" />, color: "border-[#00F0FF]/20 bg-[#00F0FF]/5" },
{ label: "Avg Error Rate", value: "38%", icon: <TrendingDown size={18} className="text-red-400" />, color: "border-red-400/20 bg-red-400/5" },
].map(kpi => (
<Card key={kpi.label} className={`rounded-2xl border ${kpi.color}`}>
<CardContent className="p-4 flex flex-col gap-2">
{kpi.icon}
<div className="text-2xl font-black">{kpi.value}</div>
<div className="text-xs font-semibold text-muted-foreground uppercase tracking-wider">{kpi.label}</div>
</CardContent>
</Card>
))}
</div>
)}
{/* Progress bar */}
{!loading && mistakes.length > 0 && (
<div className="space-y-1.5">
<div className="flex justify-between text-xs font-bold text-muted-foreground">
<span>Session Progress</span>
<span className="text-[#00F0FF]">{completedCount} of {mistakes.length} reviewed</span>
</div>
<div className="h-2 w-full bg-secondary rounded-full overflow-hidden">
<div
className="h-full bg-gradient-to-r from-[#00F0FF] to-teal-400 rounded-full transition-all duration-500"
style={{ width: `${progressPct}%` }}
/>
</div>
</div>
)}
{/* Main 2-col layout */}
<div className="grid grid-cols-1 lg:grid-cols-12 gap-6 lg:gap-8 items-start">
<div className="lg:col-span-7 space-y-4">
<div className="flex items-center justify-between">
<h2 className="text-2xl font-extrabold tracking-tight flex items-center gap-2">
<Flame className="text-amber-500" size={24} /> Today's Active Review Queue
<h2 className="text-lg font-extrabold tracking-tight flex items-center gap-2 text-muted-foreground uppercase tracking-wider text-sm">
<Clock size={16} className="text-amber-400" />
Active Review Queue
</h2>
{mistakes.length > 0 && (
<span className="text-sm font-bold text-[#00F0FF] bg-[#00F0FF]/10 px-4 py-1.5 rounded-full border border-[#00F0FF]/20">
{currentIndex < mistakes.length ? `Question ${currentIndex + 1} of ${mistakes.length}` : "Completed"}
{currentIndex < mistakes.length ? `${currentIndex + 1} / ${mistakes.length}` : "Complete"}
</span>
)}
</div>
{loading ? (
<Card className="p-16 flex items-center justify-center border-dashed">
<RefreshCw className="animate-spin text-[#00F0FF] mr-3" size={24} />
<span className="text-lg font-bold">Loading your active error queue...</span>
<Card className="min-h-[480px] h-full flex flex-col items-center justify-center border-dashed p-8 gap-4">
<RefreshCw className="animate-spin text-[#00F0FF]" size={28} />
<span className="text-base font-semibold text-muted-foreground">Loading your review queue...</span>
</Card>
) : mistakes.length === 0 ? (
<Card className="p-16 text-center border-dashed bg-secondary/10 rounded-3xl">
<CheckCircle2 size={56} className="mx-auto text-green-400 mb-4 opacity-80" />
<h3 className="text-2xl font-extrabold mb-2">Zero Mistakes Pending!</h3>
<p className="text-muted-foreground text-base max-w-md mx-auto mb-6">
All scheduled errors have been reviewed. Solve more mock tests to automatically surface weak questions here.
<Card className="min-h-[480px] h-full flex flex-col items-center justify-center text-center border-dashed bg-secondary/10 rounded-3xl p-8 gap-4">
<CheckCircle2 size={56} className="text-green-400 opacity-80" />
<div>
<h3 className="text-xl font-extrabold mb-2">Queue Complete</h3>
<p className="text-muted-foreground text-sm max-w-md mx-auto font-medium">
All scheduled reviews for today are done. Solve more practice tests to surface new questions here.
</p>
<Button onClick={() => window.location.reload()} variant="outline" className="rounded-xl font-bold">
</div>
<Button onClick={() => window.location.reload()} variant="outline" className="rounded-xl font-bold h-10 px-6">
Refresh Queue
</Button>
</Card>
@@ -110,21 +152,27 @@ export default function MistakeTrackingPage() {
onSubmitReview={submitReview}
/>
) : (
<Card className="p-16 text-center border-dashed bg-[#00F0FF]/5 border-[#00F0FF]/30 rounded-3xl">
<BrainCircuit size={56} className="mx-auto text-[#00F0FF] mb-4" />
<h3 className="text-2xl font-extrabold mb-2 text-[#00F0FF]">Review Session Mastered 🎉</h3>
<p className="text-muted-foreground text-base max-w-md mx-auto mb-6">
Fantastic focus! Your memory decay intervals have been updated in the database.
<Card className="min-h-[480px] h-full flex flex-col items-center justify-center text-center border-dashed bg-[#00F0FF]/5 border-[#00F0FF]/30 rounded-3xl p-8 gap-4">
<BrainCircuit size={56} className="text-[#00F0FF]" />
<div>
<h3 className="text-xl font-extrabold mb-2 text-[#00F0FF]">Session Complete 🎉</h3>
<p className="text-muted-foreground text-sm max-w-md mx-auto font-medium">
All {mistakes.length} questions reviewed. The SM-2 engine has scheduled your next review intervals.
</p>
<Button onClick={() => window.location.reload()} className="bg-[#00F0FF] text-black font-bold rounded-xl px-6">
Check Additional Pending Queues
</div>
<Button onClick={() => window.location.reload()} className="bg-[#00F0FF] text-black font-bold rounded-xl px-6 h-10 text-sm">
Check for New Errors
</Button>
</Card>
)}
</div>
{/* Heatmap & Info Column */}
<div className="space-y-6">
{/* Heatmap Column */}
<div className="lg:col-span-5 space-y-4">
<div className="flex items-center gap-2 text-sm font-black uppercase tracking-wider text-muted-foreground">
<AlertTriangle size={16} className="text-amber-400" />
Error Density by Topic
</div>
<MistakeHeatmap />
</div>
</div>

View File

@@ -3,75 +3,169 @@
import { ReadinessGauge } from "@/components/AI/ReadinessGauge"
import { Card, CardContent } from "@/components/ui/card"
import { Button } from "@/components/ui/button"
import { Gauge, ArrowLeft, Target, Award, ShieldAlert, CheckCircle2 } from "lucide-react"
import { Gauge, TrendingUp, Target, Award, ShieldAlert, CheckCircle2, BookOpen, Zap, BarChart2, Clock } from "lucide-react"
import Link from "next/link"
const SCORE_HISTORY = [
{ week: "Wk 1", score: 54 },
{ week: "Wk 2", score: 60 },
{ week: "Wk 3", score: 63 },
{ week: "Wk 4", score: 68 },
{ week: "Wk 5", score: 72 },
{ week: "Wk 6", score: 78 },
]
export default function ReadinessPage() {
const maxScore = Math.max(...SCORE_HISTORY.map(d => d.score))
return (
<div className="p-6 md:p-8 max-w-7xl mx-auto space-y-8 animate-in fade-in duration-500 min-h-screen">
{/* Navigation Header */}
<div className="p-6 md:p-8 space-y-8 max-w-7xl mx-auto animate-in fade-in duration-500 min-h-screen">
{/* Page Header */}
<div className="border-b border-border/50 pb-6">
<h1 className="text-3xl md:text-5xl font-black tracking-tight flex items-center gap-3">
<Gauge className="text-[#00F0FF]" size={36} />
<div className="flex items-start justify-between gap-4 flex-wrap">
<div>
<h1 className="text-3xl md:text-4xl font-black tracking-tight flex items-center gap-3 mb-2">
<span className="p-2.5 rounded-xl bg-[#00F0FF]/10 border border-[#00F0FF]/20">
<Gauge className="text-[#00F0FF]" size={28} />
</span>
Exam Readiness Analytics
</h1>
<p className="text-muted-foreground text-base md:text-lg mt-1 font-medium max-w-3xl">
Real-time algorithmic prediction of your readiness percentage for JEE & NEET based on speed, accuracy, and test history.
<p className="text-muted-foreground text-base font-medium max-w-2xl pl-1">
Real-time algorithmic prediction of your exam readiness — based on speed, accuracy, syllabus coverage, and test history.
</p>
</div>
<span className="text-xs font-bold uppercase tracking-wider text-[#00F0FF] bg-[#00F0FF]/10 border border-[#00F0FF]/20 px-3 py-1.5 rounded-full flex items-center gap-1.5 shrink-0 self-start">
<Gauge size={12} /> AI Powered
</span>
</div>
</div>
{/* KPI Strip */}
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
{[
{ label: "Overall Score", value: "72 / 100", trend: "+5 this week", trendUp: true, icon: <Gauge size={20} className="text-[#00F0FF]" /> },
{ label: "Accuracy Rate", value: "88%", trend: "+3% vs last week", trendUp: true, icon: <Target size={20} className="text-amber-400" /> },
{ label: "Avg Speed / Q", value: "45 sec", trend: "↓ Needs work", trendUp: false, icon: <Clock size={20} className="text-red-400" /> },
{ label: "Syllabus Covered", value: "64%", trend: "+8% this month", trendUp: true, icon: <BookOpen size={20} className="text-emerald-400" /> },
].map((kpi) => (
<Card key={kpi.label} className="border-border/60 bg-secondary/10 rounded-2xl hover:border-[#00F0FF]/30 transition-colors">
<CardContent className="p-5">
<div className="flex items-center justify-between mb-3">
{kpi.icon}
<span className={`text-xs font-bold px-2 py-0.5 rounded-full ${kpi.trendUp ? "bg-green-500/10 text-green-400" : "bg-red-500/10 text-red-400"}`}>
{kpi.trend}
</span>
</div>
<div className="text-2xl font-black mb-0.5">{kpi.value}</div>
<div className="text-xs text-muted-foreground font-semibold uppercase tracking-wider">{kpi.label}</div>
</CardContent>
</Card>
))}
</div>
<div className="grid grid-cols-1 lg:grid-cols-3 gap-8">
<div className="lg:col-span-2 space-y-8">
<Card className="p-8 bg-secondary/10 border-border/60 rounded-3xl">
<h2 className="text-2xl font-extrabold tracking-tight mb-6 flex items-center gap-2">
<Award className="text-amber-400" size={24} /> Overall Readiness Index
</h2>
{/* Readiness Score Card */}
<Card className="p-6 bg-secondary/10 border-border/60 rounded-3xl">
<div className="flex items-center gap-2 mb-5">
<Award className="text-amber-400" size={22} />
<h2 className="text-xl font-extrabold tracking-tight">Composite Readiness Index</h2>
<span className="ml-auto text-xs font-bold text-[#00F0FF] bg-[#00F0FF]/10 border border-[#00F0FF]/20 px-2.5 py-1 rounded-full">Updated Daily</span>
</div>
<ReadinessGauge />
</Card>
{/* Subject Confidence Breakdown */}
<Card className="p-6 bg-secondary/10 border-border/60 rounded-3xl space-y-6">
<h3 className="text-xl font-extrabold tracking-tight">Subject Confidence Matrix</h3>
{/* Subject Confidence Matrix */}
<Card className="p-6 bg-secondary/10 border-border/60 rounded-3xl space-y-5">
<div className="flex items-center gap-2">
<BarChart2 size={20} className="text-[#00F0FF]" />
<h3 className="text-lg font-extrabold tracking-tight">Subject Confidence Matrix</h3>
</div>
<div className="space-y-4">
{[
{ subject: "Biology / Maths", score: 82, color: "bg-[#00F0FF]", status: "High Readiness" },
{ subject: "Chemistry", score: 74, color: "bg-amber-400", status: "Moderate Readiness" },
{ subject: "Physics", score: 61, color: "bg-red-400", status: "Needs Intensive Drill" },
{ subject: "Biology / Maths", score: 82, color: "bg-[#00F0FF]", status: "High Readiness", statusColor: "text-[#00F0FF]" },
{ subject: "Chemistry", score: 74, color: "bg-amber-400", status: "Moderate Readiness", statusColor: "text-amber-400" },
{ subject: "Physics", score: 61, color: "bg-red-400", status: "Needs Intensive Drill", statusColor: "text-red-400" },
].map((sub) => (
<div key={sub.subject} className="space-y-2">
<div className="flex justify-between items-center text-sm font-bold">
<span>{sub.subject}</span>
<span className="text-muted-foreground">{sub.score}% • {sub.status}</span>
<div className="flex justify-between items-center">
<span className="text-sm font-black">{sub.subject}</span>
<span className={`text-xs font-bold ${sub.statusColor}`}>{sub.score}% · {sub.status}</span>
</div>
<div className="h-3 w-full bg-secondary rounded-full overflow-hidden">
<div className={`h-full ${sub.color}`} style={{ width: `${sub.score}%` }} />
<div className="h-2.5 w-full bg-secondary rounded-full overflow-hidden">
<div className={`h-full ${sub.color} rounded-full transition-all duration-700`} style={{ width: `${sub.score}%` }} />
</div>
</div>
))}
</div>
</Card>
{/* Score History Chart */}
<Card className="p-6 bg-secondary/10 border-border/60 rounded-3xl">
<div className="flex items-center gap-2 mb-6">
<TrendingUp size={20} className="text-green-400" />
<h3 className="text-lg font-extrabold tracking-tight">6-Week Readiness Trend</h3>
</div>
<div className="flex items-end gap-3 h-28">
{SCORE_HISTORY.map((d, i) => (
<div key={d.week} className="flex flex-col items-center gap-1 flex-1">
<span className="text-xs font-black text-muted-foreground">{d.score}</span>
<div
className={`w-full rounded-t-md transition-all duration-700 ${i === SCORE_HISTORY.length - 1 ? "bg-gradient-to-t from-[#00F0FF]/60 to-[#00F0FF]" : "bg-gradient-to-t from-white/10 to-white/25"}`}
style={{ height: `${(d.score / maxScore) * 80}px` }}
/>
<span className="text-xs text-muted-foreground font-semibold">{d.week}</span>
</div>
))}
</div>
</Card>
</div>
{/* Sidebar */}
<div className="space-y-6">
<Card className="p-6 bg-gradient-to-br from-amber-500/10 via-secondary/20 to-background border border-amber-500/30 rounded-3xl space-y-4">
<h3 className="text-xl font-black tracking-tight text-amber-400 flex items-center gap-2">
<ShieldAlert size={20} /> Readiness Boosters
<h3 className="text-base font-black tracking-tight text-amber-400 flex items-center gap-2">
<ShieldAlert size={18} /> Readiness Boosters
</h3>
<ul className="space-y-3 text-sm font-medium text-muted-foreground">
<li className="flex items-start gap-2">
<ul className="space-y-3.5 text-sm font-semibold text-muted-foreground">
<li className="flex items-start gap-2.5">
<CheckCircle2 size={16} className="text-amber-400 shrink-0 mt-0.5" />
<span>Complete 2 Full-Length Mock Tests this week.</span>
<span>Complete 2 full-length mock tests this week to boost consistency score.</span>
</li>
<li className="flex items-start gap-2">
<CheckCircle2 size={16} className="text-amber-400 shrink-0 mt-0.5" />
<span>Clear 15 pending errors in Physics error queue.</span>
<span>Clear 15 pending errors in your Physics queue to gain ~4 readiness points.</span>
</li>
<li className="flex items-start gap-2">
<CheckCircle2 size={16} className="text-amber-400 shrink-0 mt-0.5" />
<span>Maintain a 7+ day study streak.</span>
<span>Maintain a 7+ day streak — consistency adds 6% to your readiness index.</span>
</li>
<li className="flex items-start gap-2">
<CheckCircle2 size={16} className="text-amber-400 shrink-0 mt-0.5" />
<span>Reduce avg time-per-question below 40s with daily timed DPP sessions.</span>
</li>
</ul>
</Card>
<Card className="p-6 bg-secondary/10 border-border/60 rounded-3xl space-y-4">
<h3 className="text-base font-black text-muted-foreground uppercase tracking-wider flex items-center gap-2">
<Zap size={16} className="text-[#00F0FF]" /> Quick Actions
</h3>
<div className="space-y-2.5">
{[
{ label: "Practice Physics DPP", href: "/dashboard/ai-coach/cbt-dpp", color: "text-[#00F0FF]" },
{ label: "Review Mistake Queue", href: "/dashboard/ai-coach/mistakes", color: "text-red-400" },
{ label: "View Exam Strategy", href: "/dashboard/ai-coach/strategy", color: "text-amber-400" },
{ label: "College Predictor", href: "/dashboard/ai-coach/college-predictor", color: "text-emerald-400" },
].map((a) => (
<Link key={a.href} href={a.href}
className="flex items-center justify-between p-2.5 rounded-xl border border-border/40 hover:border-[#00F0FF]/30 hover:bg-white/5 transition-all group text-sm font-bold">
<span className={a.color}>{a.label}</span>
<span className="text-muted-foreground group-hover:translate-x-1 transition-transform">→</span>
</Link>
))}
</div>
</Card>
</div>
</div>
</div>

View File

@@ -3,39 +3,133 @@
import { ExamStrategyCard } from "@/components/AI/ExamStrategyCard"
import { StudyTimeOptimizationCard } from "@/components/AI/StudyTimeOptimizationCard"
import { Card, CardContent } from "@/components/ui/card"
import { Button } from "@/components/ui/button"
import { Zap, ArrowLeft, Clock, Target, Lightbulb, CheckCircle2 } from "lucide-react"
import Link from "next/link"
import { Zap, Clock, Target, Lightbulb, CheckCircle2, AlertTriangle, TrendingUp } from "lucide-react"
const NEG_MARKING_DATA = [
{ subject: "Physics", attempts: 30, negMarks: 12, accuracy: 60 },
{ subject: "Chemistry", attempts: 35, negMarks: 7, accuracy: 80 },
{ subject: "Maths", attempts: 28, negMarks: 10, accuracy: 64 },
]
export default function StrategyPage() {
return (
<div className="p-6 md:p-8 max-w-7xl mx-auto space-y-8 animate-in fade-in duration-500 min-h-screen">
{/* Navigation Header */}
<div className="p-6 md:p-8 space-y-8 max-w-7xl mx-auto animate-in fade-in duration-500 min-h-screen">
{/* Page Header */}
<div className="border-b border-border/50 pb-6">
<h1 className="text-3xl md:text-5xl font-black tracking-tight flex items-center gap-3">
<Zap className="text-[#00F0FF]" size={36} />
Exam Strategy & Time Optimization
<div className="flex items-start justify-between gap-4 flex-wrap">
<div>
<h1 className="text-3xl md:text-4xl font-black tracking-tight flex items-center gap-3 mb-2">
<span className="p-2.5 rounded-xl bg-[#00F0FF]/10 border border-[#00F0FF]/20">
<Zap className="text-[#00F0FF]" size={28} />
</span>
Exam Strategy &amp; Time Optimization
</h1>
<p className="text-muted-foreground text-base md:text-lg mt-1 font-medium max-w-3xl">
<p className="text-muted-foreground text-base font-medium max-w-2xl pl-1">
Master time allocation per question, eliminate negative marking traps, and fine-tune your subject sequence for maximum score yield.
</p>
</div>
<div className="grid grid-cols-1 lg:grid-cols-2 gap-8">
<Card className="p-6 bg-secondary/10 border-border/60 rounded-3xl">
<h2 className="text-2xl font-extrabold tracking-tight mb-4 flex items-center gap-2">
<Target className="text-[#00F0FF]" size={24} /> Exam Day Time Strategy
</h2>
<ExamStrategyCard />
</Card>
<Card className="p-6 bg-secondary/10 border-border/60 rounded-3xl">
<h2 className="text-2xl font-extrabold tracking-tight mb-4 flex items-center gap-2">
<Clock className="text-amber-400" size={24} /> Study Time Optimization
</h2>
<StudyTimeOptimizationCard />
</Card>
<span className="text-xs font-bold uppercase tracking-wider text-[#00F0FF] bg-[#00F0FF]/10 border border-[#00F0FF]/20 px-3 py-1.5 rounded-full flex items-center gap-1.5 shrink-0 self-start">
<Zap size={12} /> AI Powered
</span>
</div>
</div>
{/* KPI Strip */}
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
{[
{ label: "Time Per Question", value: "45 sec", note: "Target: 38 sec", icon: <Clock size={18} className="text-red-400" />, color: "border-red-400/20 bg-red-400/5" },
{ label: "Negative Marks Lost", value: "−29", note: "Last mock test", icon: <AlertTriangle size={18} className="text-amber-400" />, color: "border-amber-400/20 bg-amber-400/5" },
{ label: "Attempted Questions", value: "93 / 120", note: "+7 vs last test", icon: <Target size={18} className="text-[#00F0FF]" />, color: "border-[#00F0FF]/20 bg-[#00F0FF]/5" },
{ label: "Score Yield", value: "214", note: "Predicted score", icon: <TrendingUp size={18} className="text-green-400" />, color: "border-green-400/20 bg-green-400/5" },
].map((kpi) => (
<Card key={kpi.label} className={`rounded-2xl border ${kpi.color} hover:scale-[1.02] transition-transform`}>
<CardContent className="p-5 flex flex-col gap-2">
{kpi.icon}
<div className="text-2xl font-black">{kpi.value}</div>
<div className="text-xs font-semibold text-muted-foreground">{kpi.label}</div>
<div className="text-[11px] font-bold text-muted-foreground/60">{kpi.note}</div>
</CardContent>
</Card>
))}
</div>
{/* Main Cards Row */}
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
<div className="space-y-3">
<div className="flex items-center gap-2 text-sm font-black uppercase tracking-wider text-muted-foreground">
<Target size={16} className="text-[#00F0FF]" />
Subject Sequence Strategy
</div>
<ExamStrategyCard />
</div>
<div className="space-y-3">
<div className="flex items-center gap-2 text-sm font-black uppercase tracking-wider text-muted-foreground">
<Clock size={16} className="text-amber-400" />
Daily Peak Hours Analysis
</div>
<StudyTimeOptimizationCard />
</div>
</div>
{/* Negative Marking Analysis */}
<Card className="p-6 bg-secondary/10 border-border/60 rounded-3xl">
<div className="flex items-center gap-2 mb-5">
<AlertTriangle size={20} className="text-amber-400" />
<h3 className="text-lg font-extrabold tracking-tight">Negative Marking Analysis</h3>
<span className="ml-auto text-xs text-muted-foreground font-semibold">Last Mock Test · Aug 1</span>
</div>
<div className="space-y-4">
{NEG_MARKING_DATA.map((row) => (
<div key={row.subject} className="grid grid-cols-4 gap-4 items-center p-3 rounded-xl bg-white/5 border border-border/40">
<div className="font-black text-sm">{row.subject}</div>
<div className="text-center">
<div className="text-sm font-black text-foreground">{row.attempts}</div>
<div className="text-[11px] text-muted-foreground font-semibold">Attempted</div>
</div>
<div className="text-center">
<div className="text-sm font-black text-red-400">−{row.negMarks}</div>
<div className="text-[11px] text-muted-foreground font-semibold">Neg. Marks</div>
</div>
<div>
<div className="flex justify-between text-xs font-bold mb-1">
<span className="text-muted-foreground">Accuracy</span>
<span className={row.accuracy >= 75 ? "text-green-400" : row.accuracy >= 60 ? "text-amber-400" : "text-red-400"}>{row.accuracy}%</span>
</div>
<div className="h-2 w-full bg-secondary rounded-full overflow-hidden">
<div
className={`h-full rounded-full ${row.accuracy >= 75 ? "bg-green-400" : row.accuracy >= 60 ? "bg-amber-400" : "bg-red-400"}`}
style={{ width: `${row.accuracy}%` }}
/>
</div>
</div>
</div>
))}
</div>
<p className="text-xs text-muted-foreground mt-4 font-medium pl-1">
<span className="font-black text-amber-400">Insight:</span> You&apos;re losing ~29 marks to wrong answers. Switching to &quot;skip and revisit&quot; for uncertain Physics questions could recover 15–20 marks.
</p>
</Card>
{/* Strategy Tips */}
<Card className="p-6 bg-gradient-to-br from-[#00F0FF]/5 via-secondary/10 to-background border border-[#00F0FF]/20 rounded-3xl">
<div className="flex items-center gap-2 mb-4">
<Lightbulb size={20} className="text-yellow-400" />
<h3 className="text-base font-black tracking-tight">AI Strategy Recommendations</h3>
</div>
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
{[
{ tip: "Start with Chemistry (highest accuracy) to build momentum and bank easy marks in the first 40 minutes.", color: "text-green-400", label: "Opening Strategy" },
{ tip: "Attempt Physics only if you are 80%+ confident per question. Otherwise, mark and skip — recover in last 10 min buffer.", color: "text-red-400", label: "Physics Guard" },
{ tip: "Reserve 10–15 minutes at the end to revisit starred questions. Your review accuracy is historically 20% higher than first-attempt.", color: "text-[#00F0FF]", label: "Review Window" },
].map((s) => (
<div key={s.label} className="p-4 rounded-xl bg-white/5 border border-border/30 space-y-2">
<div className={`text-xs font-black uppercase tracking-wider ${s.color}`}>{s.label}</div>
<p className="text-sm text-muted-foreground font-medium leading-relaxed">{s.tip}</p>
</div>
))}
</div>
</Card>
</div>
)
}

View File

@@ -3,62 +3,152 @@
import { StudyPlanCard } from "@/components/AI/StudyPlanCard"
import { WeeklyProgressReport } from "@/components/AI/WeeklyProgressReport"
import { Card, CardContent } from "@/components/ui/card"
import { Button } from "@/components/ui/button"
import { Calendar, ArrowLeft, BookOpen, Clock, Target, CheckCircle2 } from "lucide-react"
import Link from "next/link"
import { Calendar, BookOpen, Clock, Target, CheckCircle2, TrendingUp, Flame } from "lucide-react"
export default function StudyPlanPage() {
return (
<div className="p-6 md:p-8 max-w-7xl mx-auto space-y-8 animate-in fade-in duration-500 min-h-screen">
{/* Navigation Header */}
<div className="p-6 md:p-8 space-y-8 max-w-7xl mx-auto animate-in fade-in duration-500 min-h-screen">
{/* Page Header */}
<div className="border-b border-border/50 pb-6">
<h1 className="text-3xl md:text-5xl font-black tracking-tight flex items-center gap-3">
<Calendar className="text-[#00F0FF]" size={36} />
Personalized Daily & Weekly Study Plan
<div className="flex items-start justify-between gap-4 flex-wrap">
<div>
<h1 className="text-3xl md:text-4xl font-black tracking-tight flex items-center gap-3 mb-2">
<span className="p-2.5 rounded-xl bg-[#00F0FF]/10 border border-[#00F0FF]/20">
<Calendar className="text-[#00F0FF]" size={28} />
</span>
Personalized Daily &amp; Weekly Study Plan
</h1>
<p className="text-muted-foreground text-base md:text-lg mt-1 font-medium max-w-3xl">
<p className="text-muted-foreground text-base font-medium max-w-2xl pl-1">
AI-generated task schedule dynamically tailored to fix your weak areas and maintain optimal syllabus coverage.
</p>
</div>
<span className="text-xs font-bold uppercase tracking-wider text-[#00F0FF] bg-[#00F0FF]/10 border border-[#00F0FF]/20 px-3 py-1.5 rounded-full flex items-center gap-1.5 shrink-0 self-start">
<Calendar size={12} /> AI Powered
</span>
</div>
</div>
{/* KPI Strip */}
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
{[
{ label: "Tasks Today", value: "5", sub: "2 completed", icon: <CheckCircle2 size={18} className="text-green-400" />, color: "border-green-400/20 bg-green-400/5" },
{ label: "Study Hours Today", value: "3.8 hrs", sub: "Target: 5 hrs", icon: <Clock size={18} className="text-[#00F0FF]" />, color: "border-[#00F0FF]/20 bg-[#00F0FF]/5" },
{ label: "Syllabus Coverage", value: "64%", sub: "+8% this week", icon: <Target size={18} className="text-amber-400" />, color: "border-amber-400/20 bg-amber-400/5" },
{ label: "Current Streak", value: "12 Days", sub: "Keep it up!", icon: <Flame size={18} className="text-orange-400" />, color: "border-orange-400/20 bg-orange-400/5" },
].map((kpi) => (
<Card key={kpi.label} className={`rounded-2xl border ${kpi.color} hover:scale-[1.02] transition-transform`}>
<CardContent className="p-5 flex flex-col gap-2">
{kpi.icon}
<div className="text-2xl font-black">{kpi.value}</div>
<div className="text-xs font-semibold text-muted-foreground uppercase tracking-wider">{kpi.label}</div>
<div className="text-[11px] font-bold text-muted-foreground/60">{kpi.sub}</div>
</CardContent>
</Card>
))}
</div>
<div className="grid grid-cols-1 lg:grid-cols-3 gap-8">
<div className="lg:col-span-2 space-y-8">
<Card className="p-6 bg-secondary/10 border-border/60 rounded-3xl">
<h2 className="text-2xl font-extrabold tracking-tight mb-4 flex items-center gap-2">
<Clock className="text-[#00F0FF]" size={24} /> Today's Action Plan
</h2>
{/* Today's Schedule */}
<div className="space-y-3">
<div className="flex items-center justify-between">
<div className="flex items-center gap-2 text-sm font-black uppercase tracking-wider text-muted-foreground">
<Clock size={16} className="text-[#00F0FF]" />
Today&apos;s Schedule
</div>
<span className="text-xs font-bold text-[#00F0FF]">
{new Date().toLocaleDateString("en-IN", { weekday: "long", day: "numeric", month: "long" })}
</span>
</div>
<StudyPlanCard />
</Card>
</div>
<Card className="p-6 bg-secondary/10 border-border/60 rounded-3xl">
<h2 className="text-2xl font-extrabold tracking-tight mb-4 flex items-center gap-2">
<Target className="text-emerald-400" size={24} /> Weekly Syllabus Target
</h2>
{/* Weekly Overview */}
<div className="space-y-3">
<div className="flex items-center gap-2 text-sm font-black uppercase tracking-wider text-muted-foreground">
<TrendingUp size={16} className="text-emerald-400" />
Weekly Syllabus Progress
</div>
<WeeklyProgressReport />
</div>
{/* Syllabus Coverage */}
<Card className="p-6 bg-secondary/10 border-border/60 rounded-3xl">
<div className="flex items-center gap-2 mb-5">
<BookOpen size={20} className="text-[#00F0FF]" />
<h3 className="text-lg font-extrabold tracking-tight">Subject Coverage Breakdown</h3>
</div>
<div className="space-y-4">
{[
{ subject: "Physics", covered: 58, total: 100, chapters: "21 / 29 chapters" },
{ subject: "Chemistry", covered: 71, total: 100, chapters: "19 / 26 chapters" },
{ subject: "Biology / Maths", covered: 64, total: 100, chapters: "32 / 52 topics" },
].map((sub) => (
<div key={sub.subject}>
<div className="flex items-center justify-between mb-1.5">
<span className="text-sm font-black">{sub.subject}</span>
<div className="flex items-center gap-3 text-xs font-semibold text-muted-foreground">
<span>{sub.chapters}</span>
<span className={`font-black ${sub.covered >= 70 ? "text-green-400" : sub.covered >= 55 ? "text-amber-400" : "text-red-400"}`}>{sub.covered}%</span>
</div>
</div>
<div className="h-2.5 w-full bg-secondary rounded-full overflow-hidden">
<div
className={`h-full rounded-full transition-all duration-700 ${sub.covered >= 70 ? "bg-green-400" : sub.covered >= 55 ? "bg-amber-400" : "bg-red-400"}`}
style={{ width: `${sub.covered}%` }}
/>
</div>
</div>
))}
</div>
</Card>
</div>
{/* Sidebar Info */}
{/* Sidebar */}
<div className="space-y-6">
<Card className="p-6 bg-gradient-to-br from-[#00F0FF]/10 via-secondary/20 to-background border border-[#00F0FF]/30 rounded-3xl space-y-4">
<h3 className="text-xl font-black tracking-tight text-[#00F0FF] flex items-center gap-2">
<BookOpen size={20} /> AI Scheduler Rules
<h3 className="text-base font-black tracking-tight text-[#00F0FF] flex items-center gap-2">
<BookOpen size={18} /> AI Scheduler Rules
</h3>
<ul className="space-y-3 text-sm font-medium text-muted-foreground">
<li className="flex items-start gap-2">
<CheckCircle2 size={16} className="text-[#00F0FF] shrink-0 mt-0.5" />
<span><strong>High-Yield Prioritization:</strong> Focuses extra time on topics with heavy exam weightage.</span>
<span><strong className="text-foreground">High-Yield Prioritization:</strong> Extra time on topics with heavy exam weightage.</span>
</li>
<li className="flex items-start gap-2">
<CheckCircle2 size={16} className="text-[#00F0FF] shrink-0 mt-0.5" />
<span><strong>Error Interleaving:</strong> Integrates recent incorrect questions directly into study blocks.</span>
<span><strong className="text-foreground">Error Interleaving:</strong> Recent incorrect questions are integrated directly into study blocks.</span>
</li>
<li className="flex items-start gap-2">
<CheckCircle2 size={16} className="text-[#00F0FF] shrink-0 mt-0.5" />
<span><strong>Burnout Prevention:</strong> Recommends 10-minute micro-breaks between intensive problem sets.</span>
<span><strong className="text-foreground">Burnout Prevention:</strong> 10-minute micro-breaks recommended between intensive problem sets.</span>
</li>
<li className="flex items-start gap-2">
<CheckCircle2 size={16} className="text-[#00F0FF] shrink-0 mt-0.5" />
<span><strong className="text-foreground">Spaced Repetition:</strong> SM-2 algorithm reschedules mistake reviews at optimal intervals.</span>
</li>
</ul>
</Card>
<Card className="p-5 bg-secondary/10 border-border/60 rounded-3xl space-y-3">
<h3 className="text-sm font-black uppercase tracking-wider text-muted-foreground">Upcoming This Week</h3>
<div className="space-y-2">
{[
{ day: "Tue, Aug 5", task: "Full-Length Mock — JEE Mains Pattern", type: "Test", color: "text-purple-400 bg-purple-400/10" },
{ day: "Wed, Aug 6", task: "Rotational Dynamics Intensive Drill", type: "Practice", color: "text-[#00F0FF] bg-[#00F0FF]/10" },
{ day: "Thu, Aug 7", task: "Chemistry Organic — Mistake Review", type: "Review", color: "text-red-400 bg-red-400/10" },
{ day: "Fri, Aug 8", task: "Optics + Wave Optics Revision", type: "Revision", color: "text-yellow-400 bg-yellow-400/10" },
].map((item) => (
<div key={item.day} className="flex items-center gap-2 p-2.5 rounded-lg bg-white/5 border border-border/30">
<span className={`text-[10px] font-black px-2 py-0.5 rounded-md shrink-0 ${item.color}`}>{item.type}</span>
<div className="min-w-0">
<div className="text-xs font-bold truncate">{item.task}</div>
<div className="text-[11px] text-muted-foreground">{item.day}</div>
</div>
</div>
))}
</div>
</Card>
</div>
</div>
</div>

View File

@@ -1,25 +1,153 @@
"use client"
import { StudyTimeOptimizationCard } from "@/components/AI/StudyTimeOptimizationCard"
import { ArrowLeft, Clock } from "lucide-react"
import { Clock, Zap, Moon, Sun, AlertCircle, ArrowRight, BarChart2 } from "lucide-react"
import { Card, CardContent } from "@/components/ui/card"
import Link from "next/link"
const INSIGHTS = [
{
icon: <Sun size={16} className="text-yellow-400" />,
title: "Peak Cognitive Window",
desc: "2 PM – 5 PM is your highest-efficiency block. Reserve this for new concepts, hard problems, and mock tests.",
color: "border-yellow-400/20 bg-yellow-400/5",
},
{
icon: <Moon size={16} className="text-indigo-400" />,
title: "Evening Consolidation",
desc: "9–10 PM is ideal for light revision and flashcard review — this window solidifies short-term to long-term memory.",
color: "border-indigo-400/20 bg-indigo-400/5",
},
{
icon: <AlertCircle size={16} className="text-red-400" />,
title: "Avoid After 11 PM",
desc: "Cognitive fatigue significantly increases error rate. Solving new concepts late at night is counterproductive.",
color: "border-red-400/20 bg-red-400/5",
},
]
const HOURLY_EFFICIENCY = [
{ slot: "6–9 AM", pct: 60, label: "Morning" },
{ slot: "9–12 PM", pct: 72, label: "Mid-Morning" },
{ slot: "12–2 PM", pct: 55, label: "Post-Lunch dip" },
{ slot: "2–5 PM", pct: 95, label: "Peak ★", highlight: true },
{ slot: "5–7 PM", pct: 82, label: "Afternoon" },
{ slot: "7–9 PM", pct: 74, label: "Evening" },
{ slot: "9–11 PM", pct: 60, label: "Night Revision" },
{ slot: "11 PM+", pct: 32, label: "Avoid" },
]
export default function TimeOptimizationPage() {
return (
<div className="p-6 md:p-8 max-w-7xl mx-auto space-y-8 animate-in fade-in duration-500 min-h-screen">
{/* Page Header */}
<div className="border-b border-border/50 pb-6">
<h1 className="text-3xl md:text-5xl font-black tracking-tight flex items-center gap-3">
<Clock className="text-[#00F0FF]" size={36} />
<div className="flex items-start justify-between gap-4 flex-wrap">
<div>
<h1 className="text-3xl md:text-4xl font-black tracking-tight flex items-center gap-3 mb-2">
<span className="p-2.5 rounded-xl bg-[#00F0FF]/10 border border-[#00F0FF]/20">
<Clock className="text-[#00F0FF]" size={28} />
</span>
Study Time Optimization
</h1>
<p className="text-muted-foreground text-base md:text-lg mt-1 font-medium max-w-3xl">
Detailed metrics on your productive hours and stamina blocks.
<p className="text-muted-foreground text-base font-medium max-w-2xl pl-1">
Understand your peak productive hours and schedule your hardest topics in your highest-efficiency windows.
</p>
</div>
<span className="text-xs font-bold uppercase tracking-wider text-[#00F0FF] bg-[#00F0FF]/10 border border-[#00F0FF]/20 px-3 py-1.5 rounded-full flex items-center gap-1.5 shrink-0 self-start">
<Zap size={12} /> Chronobiology Analytics
</span>
</div>
</div>
<div className="max-w-4xl mx-auto">
{/* KPI Strip */}
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
{[
{ label: "Peak Efficiency", value: "95%", sub: "2–5 PM window", color: "border-yellow-400/20 bg-yellow-400/5", icon: <Sun size={18} className="text-yellow-400" /> },
{ label: "Study Hrs Today", value: "3.8 h", sub: "Target: 6 h", color: "border-[#00F0FF]/20 bg-[#00F0FF]/5", icon: <Clock size={18} className="text-[#00F0FF]" /> },
{ label: "Morning Block", value: "60%", sub: "6–9 AM", color: "border-indigo-400/20 bg-indigo-400/5", icon: <Moon size={18} className="text-indigo-400" /> },
{ label: "Evening Block", value: "74%", sub: "7–9 PM", color: "border-green-400/20 bg-green-400/5", icon: <BarChart2 size={18} className="text-green-400" /> },
].map(kpi => (
<Card key={kpi.label} className={`rounded-2xl border ${kpi.color}`}>
<CardContent className="p-4 flex flex-col gap-2">
{kpi.icon}
<div className="text-2xl font-black">{kpi.value}</div>
<div className="text-xs font-semibold text-muted-foreground uppercase tracking-wider">{kpi.label}</div>
<div className="text-[11px] font-bold text-muted-foreground/60">{kpi.sub}</div>
</CardContent>
</Card>
))}
</div>
<div className="grid grid-cols-1 lg:grid-cols-3 gap-8">
{/* Chart — 2/3 */}
<div className="lg:col-span-2 space-y-4">
<div className="flex items-center gap-2 text-sm font-black uppercase tracking-wider text-muted-foreground">
<BarChart2 size={16} className="text-[#00F0FF]" />
Hourly Efficiency Profile
</div>
<StudyTimeOptimizationCard />
</div>
{/* Right Panel */}
<div className="space-y-5">
{/* Slot breakdown table */}
<Card className="bg-secondary/10 border-border/60 rounded-2xl">
<CardContent className="p-5 space-y-3">
<h3 className="text-sm font-black uppercase tracking-wider text-muted-foreground">Time Slot Breakdown</h3>
<div className="space-y-2">
{HOURLY_EFFICIENCY.map(slot => (
<div key={slot.slot} className={`flex items-center gap-3 p-2 rounded-lg ${slot.highlight ? "bg-yellow-400/10 border border-yellow-400/20" : "bg-white/5"}`}>
<div className="w-16 text-xs font-bold text-muted-foreground shrink-0">{slot.slot}</div>
<div className="flex-1 h-1.5 bg-secondary rounded-full overflow-hidden">
<div
className={`h-full rounded-full ${slot.pct >= 80 ? (slot.highlight ? "bg-yellow-400" : "bg-green-400") : slot.pct >= 60 ? "bg-[#00F0FF]" : "bg-red-400"}`}
style={{ width: `${slot.pct}%` }}
/>
</div>
<div className={`text-xs font-black w-8 text-right shrink-0 ${slot.highlight ? "text-yellow-400" : "text-muted-foreground"}`}>{slot.pct}%</div>
{slot.highlight && <span className="text-[10px] font-black text-yellow-400 shrink-0">PEAK</span>}
</div>
))}
</div>
</CardContent>
</Card>
{/* AI Insights */}
<div className="space-y-3">
{INSIGHTS.map(insight => (
<Card key={insight.title} className={`rounded-2xl border ${insight.color}`}>
<CardContent className="p-4 flex items-start gap-3">
<div className="mt-0.5 shrink-0">{insight.icon}</div>
<div>
<p className="text-sm font-black">{insight.title}</p>
<p className="text-xs text-muted-foreground font-medium mt-0.5">{insight.desc}</p>
</div>
</CardContent>
</Card>
))}
</div>
<Card className="bg-gradient-to-br from-[#00F0FF]/10 via-secondary/20 to-background border border-[#00F0FF]/20 rounded-2xl">
<CardContent className="p-5 space-y-3">
<h3 className="text-sm font-black uppercase tracking-wider text-[#00F0FF]">Apply Insights</h3>
<div className="space-y-2">
{[
{ label: "Schedule Today's Study Plan", href: "/dashboard/ai-coach/study-plan" },
{ label: "Check Exam Readiness", href: "/dashboard/ai-coach/readiness" },
{ label: "Launch a Practice Test Now", href: "/dashboard/ai-coach/cbt-dpp" },
].map(link => (
<Link key={link.href} href={link.href}
className="flex items-center justify-between text-sm font-bold text-muted-foreground hover:text-foreground py-2 border-b border-border/30 last:border-0 transition-colors group">
{link.label}
<ArrowRight size={14} className="group-hover:translate-x-1 transition-transform" />
</Link>
))}
</div>
</CardContent>
</Card>
</div>
</div>
</div>
)
}

View File

@@ -1,11 +1,52 @@
"use client"
import { TutorChat } from "@/components/AI/TutorChat"
import { BrainCircuit, Sparkles, MessageSquare, Lightbulb } from "lucide-react"
import { Card, CardContent } from "@/components/ui/card"
const CAPABILITIES = [
{ icon: <Lightbulb size={16} className="text-yellow-400" />, label: "Concept Explanations", desc: "Step-by-step breakdown of Physics, Chemistry, Maths & Biology topics" },
{ icon: <Sparkles size={16} className="text-[#00F0FF]" />, label: "Problem Solving", desc: "Full worked solutions with formulas, derivations and shortcuts" },
{ icon: <MessageSquare size={16} className="text-green-400" />, label: "Doubt Clearing", desc: "Ask follow-up questions — the AI remembers context within the session" },
]
export default function TutorChatPage() {
return (
<div className="h-[calc(100vh-80px)] w-full flex flex-col p-4 md:p-6 animate-in fade-in duration-500">
<div className="flex-1 rounded-3xl overflow-hidden border border-border/50 shadow-xl bg-background">
<div className="p-6 md:p-8 space-y-6 max-w-7xl mx-auto animate-in fade-in duration-500 min-h-screen">
{/* Page Header */}
<div className="border-b border-border/50 pb-6">
<div className="flex items-start justify-between gap-4 flex-wrap">
<div>
<h1 className="text-3xl md:text-4xl font-black tracking-tight flex items-center gap-3 mb-2">
<span className="p-2.5 rounded-xl bg-[#00F0FF]/10 border border-[#00F0FF]/20">
<BrainCircuit className="text-[#00F0FF]" size={28} />
</span>
AI Tutor — 24/7 Doubt Solver
</h1>
<p className="text-muted-foreground text-base font-medium max-w-2xl pl-1">
Ask any JEE or NEET concept, get step-by-step explanations, formula derivations, and solved examples — instantly.
</p>
</div>
</div>
</div>
{/* Capabilities Strip */}
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
{CAPABILITIES.map(c => (
<Card key={c.label} className="border-border/50 bg-secondary/10 rounded-2xl">
<CardContent className="p-4 flex items-start gap-3">
<div className="mt-0.5 shrink-0">{c.icon}</div>
<div>
<div className="text-sm font-black">{c.label}</div>
<div className="text-xs text-muted-foreground font-medium mt-0.5">{c.desc}</div>
</div>
</CardContent>
</Card>
))}
</div>
{/* Chat — full height */}
<div className="flex-1 rounded-3xl overflow-hidden border border-border/50 shadow-xl bg-background" style={{ minHeight: "560px" }}>
<TutorChat />
</div>
</div>

View File

@@ -1,25 +1,130 @@
"use client"
import { WeeklyProgressReport } from "@/components/AI/WeeklyProgressReport"
import { ArrowLeft, Target } from "lucide-react"
import { Target, TrendingUp, Calendar, Zap, ArrowRight, BookOpen, CheckCircle2 } from "lucide-react"
import { Card, CardContent } from "@/components/ui/card"
import Link from "next/link"
const STATS = [
{ label: "Avg. Accuracy", value: "85%", delta: "+6%", positive: true },
{ label: "Days Active", value: "7 / 7", delta: "Perfect week", positive: true },
{ label: "Questions Solved", value: "214", delta: "+38 vs last week", positive: true },
{ label: "Topics Covered", value: "12", delta: "3 remaining", positive: false },
]
const SYLLABUS_PROGRESS = [
{ subject: "Physics", completed: 42, total: 60, percentage: 70, color: "bg-blue-500", text: "text-blue-500" },
{ subject: "Chemistry", completed: 35, total: 55, percentage: 63, color: "bg-amber-500", text: "text-amber-500" },
{ subject: "Mathematics", completed: 50, total: 65, percentage: 76, color: "bg-purple-500", text: "text-purple-500" },
{ subject: "Biology", completed: 20, total: 40, percentage: 50, color: "bg-emerald-500", text: "text-emerald-500" },
]
export default function WeeklyProgressPage() {
return (
<div className="p-6 md:p-8 max-w-7xl mx-auto space-y-8 animate-in fade-in duration-500 min-h-screen">
{/* Page Header */}
<div className="border-b border-border/50 pb-6">
<h1 className="text-3xl md:text-5xl font-black tracking-tight flex items-center gap-3">
<Target className="text-[#00F0FF]" size={36} />
<div className="flex items-start justify-between gap-4 flex-wrap">
<div>
<h1 className="text-3xl md:text-4xl font-black tracking-tight flex items-center gap-3 mb-1">
<span className="p-2.5 rounded-xl bg-[#00F0FF]/10 border border-[#00F0FF]/20">
<Target className="text-[#00F0FF]" size={28} />
</span>
Weekly Progress Overview
</h1>
<p className="text-muted-foreground text-base md:text-lg mt-1 font-medium max-w-3xl">
Track your syllabus completion goals and weekly milestones.
<p className="text-muted-foreground text-base font-medium max-w-2xl pl-1">
Track your syllabus completion goals, study streaks, and weekly milestones at a glance.
</p>
</div>
<div className="flex items-center gap-2 shrink-0">
<span className="text-xs font-bold uppercase tracking-wider text-[#00F0FF] bg-[#00F0FF]/10 border border-[#00F0FF]/20 px-3 py-1.5 rounded-full flex items-center gap-1.5">
<Zap size={12} /> Live Analytics
</span>
</div>
</div>
</div>
<div className="max-w-4xl mx-auto">
{/* KPI Stats Row */}
<div className="grid grid-cols-2 lg:grid-cols-4 gap-4">
{STATS.map((stat) => (
<Card key={stat.label} className="bg-secondary/20 border-border/60 rounded-2xl">
<CardContent className="p-4">
<p className="text-xs font-bold uppercase tracking-wider text-muted-foreground mb-1">{stat.label}</p>
<p className="text-2xl font-black tracking-tight">{stat.value}</p>
<p className={`text-xs font-bold mt-1 ${stat.positive ? "text-emerald-400" : "text-amber-400"}`}>
{stat.delta}
</p>
</CardContent>
</Card>
))}
</div>
{/* Main Content Grid */}
<div className="grid grid-cols-1 lg:grid-cols-3 gap-8">
{/* Weekly Bar Chart */}
<div className="lg:col-span-2 space-y-4">
<div className="flex items-center justify-between">
<h2 className="text-xl font-extrabold tracking-tight flex items-center gap-2">
<Calendar size={20} className="text-[#00F0FF]" /> This Week at a Glance
</h2>
</div>
<WeeklyProgressReport />
</div>
{/* Side Panel */}
<div className="space-y-6">
{/* Syllabus Progress */}
<div className="space-y-3">
<h2 className="text-xl font-extrabold tracking-tight flex items-center gap-2">
<BookOpen size={20} className="text-purple-400" /> Syllabus Progress
</h2>
<Card className="bg-secondary/10 border-border/60 rounded-2xl">
<CardContent className="p-5 space-y-4">
{SYLLABUS_PROGRESS.map((item) => (
<div key={item.subject} className="space-y-1.5">
<div className="flex items-center justify-between text-sm">
<span className="font-bold">{item.subject}</span>
<span className={`font-black ${item.text}`}>{item.percentage}%</span>
</div>
<div className="h-2 w-full bg-secondary rounded-full overflow-hidden">
<div
className={`h-full ${item.color} rounded-full transition-all duration-500`}
style={{ width: `${item.percentage}%` }}
/>
</div>
<div className="text-[10px] text-muted-foreground font-semibold text-right">
{item.completed} / {item.total} chapters
</div>
</div>
))}
</CardContent>
</Card>
</div>
{/* Weekly Goals */}
<Card className="bg-gradient-to-br from-[#00F0FF]/10 via-secondary/20 to-background border border-[#00F0FF]/20 rounded-2xl">
<CardContent className="p-5 space-y-3">
<h3 className="text-sm font-black uppercase tracking-wider text-[#00F0FF]">Weekly Goals</h3>
<div className="space-y-2">
<div className="flex items-start gap-2 p-2 rounded-lg bg-white/5">
<CheckCircle2 size={16} className="text-green-400 mt-0.5 shrink-0" />
<div>
<div className="text-xs font-bold">Complete 200 Questions</div>
<div className="text-[10px] text-muted-foreground">Goal Met (214/200)</div>
</div>
</div>
<div className="flex items-start gap-2 p-2 rounded-lg bg-white/5 opacity-70">
<div className="w-4 h-4 rounded-full border-2 border-muted-foreground mt-0.5 shrink-0" />
<div>
<div className="text-xs font-bold">Take 1 Full Mock Test</div>
<div className="text-[10px] text-muted-foreground">Scheduled for Sunday</div>
</div>
</div>
</div>
</CardContent>
</Card>
</div>
</div>
</div>
)
}

View File

@@ -31,10 +31,19 @@ export default function AnalyticsPage() {
}
return (
<div className="p-8 space-y-8 animate-in fade-in slide-in-from-bottom-4 duration-500 max-w-6xl mx-auto">
<div className="p-8 space-y-8 animate-in fade-in slide-in-from-bottom-4 duration-500 max-w-7xl mx-auto">
<div className="flex flex-col md:flex-row md:items-end justify-between gap-4">
<div>
<h1 className="text-3xl font-bold tracking-tight">Performance Analytics</h1>
<p className="text-muted-foreground mt-1 font-medium">Deep AI-driven insights into your study patterns and test trajectories.</p>
<h1 className="text-3xl md:text-4xl font-black tracking-tight flex items-center gap-3 mb-1">
<span className="p-2.5 rounded-xl bg-[#00F0FF]/10 border border-[#00F0FF]/20">
<LineChartIcon className="text-[#00F0FF]" size={28} />
</span>
Performance Analytics
</h1>
<p className="text-muted-foreground text-base font-medium max-w-2xl pl-1">
Deep AI-driven insights into your study patterns and test trajectories.
</p>
</div>
</div>
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">

View File

@@ -5,7 +5,7 @@ import Link from "next/link";
import { Button } from "@/components/ui/button";
import { Card, CardContent } from "@/components/ui/card";
import { Badge } from "@/components/ui/badge";
import { Activity, Clock, PlayCircle, Search, Lock } from "lucide-react";
import { Activity, Clock, PlayCircle, Search, Lock, MonitorPlay } from "lucide-react";
import { useActiveUser } from "@/hooks/useActiveUser";
interface TestData {
@@ -45,7 +45,7 @@ export default function CBTPage() {
// Only set the initial pattern if it hasn't been changed by the user
if (!selectedPattern) {
setSelectedPattern(pattern);
setSelectedPattern(pattern.startsWith("JEE") ? "JEE" : "NEET");
}
} catch (err) {
console.error("Error loading user:", err);
@@ -60,9 +60,16 @@ export default function CBTPage() {
async function loadTests() {
setLoading(true);
try {
const testRes = await fetch(`/api/tests?exam_pattern=${selectedPattern}&user_id=${userId}`);
let url = `/api/tests?user_id=${userId}`;
if (selectedPattern !== "JEE") {
url += `&exam_pattern=${selectedPattern}`;
}
const testRes = await fetch(url);
if (testRes.ok) {
const testsData = await testRes.json();
let testsData = await testRes.json();
if (selectedPattern === "JEE") {
testsData = testsData.filter((t: any) => t.exam_pattern === "JEE_Main" || t.exam_pattern === "JEE_Advanced");
}
setExams(testsData);
}
@@ -108,12 +115,26 @@ export default function CBTPage() {
return acc;
}, {} as Record<string, any>);
if (userLoading || loading) {
return (
<div className="p-8 space-y-8 animate-in fade-in slide-in-from-bottom-4 duration-500 max-w-6xl mx-auto">
<div className="flex justify-between items-end">
<div className="h-[70vh] flex flex-col items-center justify-center p-8">
<div className="h-8 w-8 border-4 border-[#00F0FF] border-t-transparent rounded-full animate-spin mb-4"></div>
<p className="text-muted-foreground font-medium animate-pulse">Loading exams...</p>
</div>
)
}
return (
<div className="p-8 space-y-8 animate-in fade-in slide-in-from-bottom-4 duration-500 max-w-7xl mx-auto">
<div className="flex flex-col md:flex-row md:items-end justify-between gap-4">
<div>
<h1 className="text-3xl font-bold tracking-tight">Computer Based Tests (CBT)</h1>
<p className="text-muted-foreground mt-1 font-medium">
<h1 className="text-3xl md:text-4xl font-black tracking-tight flex items-center gap-3 mb-1">
<span className="p-2.5 rounded-xl bg-[#00F0FF]/10 border border-[#00F0FF]/20">
<MonitorPlay className="text-[#00F0FF]" size={28} />
</span>
Computer Based Tests (CBT)
</h1>
<p className="text-muted-foreground text-base font-medium max-w-2xl pl-1">
Real exam environment simulations to conquer {targetExam || "NEET/JEE"}.
</p>
</div>
@@ -123,7 +144,7 @@ export default function CBTPage() {
type="date"
value={dateFilter}
onChange={(e) => setDateFilter(e.target.value)}
className="w-full px-4 py-2 bg-background border border-border rounded-xl text-sm focus:ring-2 focus:ring-[#00F0FF] outline-none text-muted-foreground"
className="w-full px-4 py-2 bg-background border border-border rounded-xl text-sm focus:ring-2 focus:ring-[#00F0FF] outline-none text-foreground [color-scheme:dark]"
/>
</div>
<div className="relative w-full sm:w-64">
@@ -140,17 +161,15 @@ export default function CBTPage() {
</div>
<div className="flex gap-3 overflow-x-auto pb-2 scrollbar-hide">
{["JEE_MAIN", "JEE_ADVANCED", "NEET"].map(p => {
// Normalize to match db/backend pattern
const dbPattern = p === "JEE_MAIN" ? "JEE_Main" : p === "JEE_ADVANCED" ? "JEE_Advanced" : "NEET";
{["JEE", "NEET"].map(p => {
return (
<Button
key={p}
variant={selectedPattern === dbPattern ? "default" : "outline"}
onClick={() => setSelectedPattern(dbPattern)}
className="rounded-full shadow-sm"
variant={selectedPattern === p ? "default" : "outline"}
onClick={() => setSelectedPattern(p)}
className="rounded-full shadow-sm px-6 font-bold"
>
{p === "JEE_MAIN" ? "JEE Main" : p === "JEE_ADVANCED" ? "JEE Advanced" : "NEET"}
{p}
</Button>
)
})}
@@ -386,11 +405,11 @@ export default function CBTPage() {
<Activity size={20} className="text-muted-foreground" /> Past Attempts
</h2>
<div className="space-y-4">
{attempts.length === 0 ? (
{attempts.filter(a => a.source_type === "Test" && exams.some(e => e.id === a.source_id)).length === 0 ? (
<div className="py-8 text-center text-muted-foreground bg-secondary/10 border border-dashed border-border rounded-2xl">
You haven't attempted any CBT exams yet.
</div>
) : attempts.map((attempt, i) => {
) : attempts.filter(a => a.source_type === "Test" && exams.some(e => e.id === a.source_id)).map((attempt, i) => {
const exam = exams.find(e => e.id === attempt.source_id);
const scoreCalc = (attempt.score * 4) - ((attempt.total_questions - attempt.score) * 1); // rough calc for display

View File

@@ -2,7 +2,7 @@
import { useState } from "react"
import { Card, CardContent, CardHeader, CardTitle, CardFooter } from "@/components/ui/card"
import { MessageSquare, ThumbsUp, ChevronUp, Search, Image as ImageIcon, CheckCircle, Lock } from "lucide-react"
import { MessageSquare, ThumbsUp, ChevronUp, Search, Image as ImageIcon, CheckCircle, Lock, X, Send } from "lucide-react"
import { useActiveUser } from "@/hooks/useActiveUser"
interface Doubt {
@@ -21,9 +21,11 @@ interface Doubt {
}
export default function CommunityPage() {
const { hasAccessTo } = useActiveUser()
const { hasAccessTo, loading } = useActiveUser()
const [activeFilter, setActiveFilter] = useState<'all' | 'my-doubts' | 'solved' | 'unsolved'>('all')
const [searchQuery, setSearchQuery] = useState('')
const [isAskModalOpen, setIsAskModalOpen] = useState(false)
const [expandedDoubtId, setExpandedDoubtId] = useState<string | null>(null)
const mockDoubts: Doubt[] = [
{
@@ -78,6 +80,15 @@ export default function CommunityPage() {
return true
})
if (loading) {
return (
<div className="h-[70vh] flex flex-col items-center justify-center p-8">
<div className="h-8 w-8 border-4 border-[#00F0FF] border-t-transparent rounded-full animate-spin mb-4"></div>
<p className="text-muted-foreground font-medium animate-pulse">Loading Doubt Forum...</p>
</div>
)
}
if (!hasAccessTo("doubt-resolution-community")) {
return (
<div className="p-8 h-full flex flex-col items-center justify-center text-center animate-in fade-in zoom-in duration-500 max-w-2xl mx-auto">
@@ -99,16 +110,23 @@ export default function CommunityPage() {
}
return (
<div className="p-8 space-y-6 animate-in fade-in slide-in-from-bottom-4 duration-500 max-w-5xl mx-auto">
<div className="p-8 space-y-6 animate-in fade-in slide-in-from-bottom-4 duration-500 max-w-7xl mx-auto">
<div className="flex flex-col md:flex-row justify-between items-start md:items-end gap-4">
<div>
<h1 className="text-3xl font-bold tracking-tight text-foreground flex items-center gap-3">
<MessageSquare className="text-[#00F0FF]" size={32} />
<h1 className="text-3xl md:text-4xl font-black tracking-tight flex items-center gap-3 mb-1">
<span className="p-2.5 rounded-xl bg-[#00F0FF]/10 border border-[#00F0FF]/20">
<MessageSquare className="text-[#00F0FF]" size={28} />
</span>
Doubt Forum
</h1>
<p className="text-muted-foreground mt-2 font-medium">Ask questions, share knowledge, and learn together with the community.</p>
<p className="text-muted-foreground text-base font-medium max-w-2xl pl-1">
Ask questions, share knowledge, and learn together with the community.
</p>
</div>
<button className="px-5 py-2.5 bg-[#00F0FF] text-black font-bold rounded-xl shadow-[0_0_15px_-3px_rgba(0,240,255,0.4)] hover:bg-[#00d0dd] transition-colors">
<button
onClick={() => setIsAskModalOpen(true)}
className="px-5 py-2.5 bg-[#00F0FF] text-black font-bold rounded-xl shadow-[0_0_15px_-3px_rgba(0,240,255,0.4)] hover:bg-[#00d0dd] transition-colors"
>
Ask a Doubt
</button>
</div>
@@ -179,12 +197,46 @@ export default function CommunityPage() {
<CheckCircle size={14} /> Solved
</span>
)}
<div className="flex items-center gap-1.5 text-muted-foreground hover:text-foreground transition-colors text-sm font-medium">
<button
onClick={() => setExpandedDoubtId(expandedDoubtId === doubt.id ? null : doubt.id)}
className="flex items-center gap-1.5 text-muted-foreground hover:text-foreground transition-colors text-sm font-medium"
>
<MessageSquare size={16} />
{doubt.replies} Replies
</button>
</div>
</div>
{/* Expanded Replies Section */}
{expandedDoubtId === doubt.id && (
<div className="mt-6 pt-4 border-t border-border space-y-4 animate-in fade-in duration-300">
<div className="flex gap-3">
<div className="h-8 w-8 shrink-0 rounded-full bg-[#00F0FF]/10 text-[#00F0FF] flex items-center justify-center text-xs font-bold border border-[#00F0FF]/30">
AI
</div>
<div className="bg-secondary/40 rounded-2xl rounded-tl-none p-4 text-sm flex-1">
<div className="font-bold mb-1 flex items-center gap-2">
PragatiVeer AI
<span className="text-[10px] text-emerald-500 bg-emerald-500/10 px-1.5 py-0.5 rounded uppercase tracking-wider">Expert</span>
</div>
<p className="text-muted-foreground">
This is a great question! When the car accelerates, the engine applies a torque to the wheel, making it want to spin faster. If there were no friction, the bottom of the tire would slip backward relative to the road. Therefore, static friction acts in the forward direction on the tire to oppose this backward slipping motion.
</p>
</div>
</div>
<div className="flex gap-3 mt-4 items-center">
<input
type="text"
placeholder="Add a reply..."
className="flex-1 bg-background border border-border rounded-xl px-4 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-[#00F0FF]/50"
/>
<button className="p-2 bg-[#00F0FF] text-black rounded-xl hover:bg-[#00d0dd] transition-colors">
<Send size={16} />
</button>
</div>
</div>
)}
</div>
</CardContent>
</Card>
@@ -196,6 +248,77 @@ export default function CommunityPage() {
</div>
)}
</div>
{/* Ask Doubt Modal */}
{isAskModalOpen && (
<div className="fixed inset-0 z-50 flex items-center justify-center p-4 bg-background/80 backdrop-blur-sm animate-in fade-in duration-200">
<div className="bg-background border border-border w-full max-w-2xl rounded-2xl shadow-2xl overflow-hidden animate-in zoom-in-95 duration-200">
<div className="flex justify-between items-center p-6 border-b border-border bg-secondary/30">
<h2 className="text-2xl font-bold flex items-center gap-2">
<MessageSquare className="text-[#00F0FF]" /> Ask a Doubt
</h2>
<button
onClick={() => setIsAskModalOpen(false)}
className="text-muted-foreground hover:text-foreground hover:bg-secondary p-2 rounded-lg transition-colors"
>
<X size={20} />
</button>
</div>
<div className="p-6 space-y-4">
<div className="grid grid-cols-2 gap-4">
<div className="space-y-1.5">
<label className="text-sm font-medium text-muted-foreground">Subject</label>
<select className="w-full bg-background border border-border rounded-xl px-4 py-2.5 text-sm focus:outline-none focus:ring-2 focus:ring-[#00F0FF]/50">
<option>Physics</option>
<option>Chemistry</option>
<option>Mathematics</option>
<option>Biology</option>
</select>
</div>
<div className="space-y-1.5">
<label className="text-sm font-medium text-muted-foreground">Topic</label>
<input
type="text"
placeholder="e.g. Rotational Mechanics"
className="w-full bg-background border border-border rounded-xl px-4 py-2.5 text-sm focus:outline-none focus:ring-2 focus:ring-[#00F0FF]/50"
/>
</div>
</div>
<div className="space-y-1.5">
<label className="text-sm font-medium text-muted-foreground">Question Title</label>
<input
type="text"
placeholder="What exactly are you stuck on?"
className="w-full bg-background border border-border rounded-xl px-4 py-2.5 text-sm focus:outline-none focus:ring-2 focus:ring-[#00F0FF]/50"
/>
</div>
<div className="space-y-1.5">
<label className="text-sm font-medium text-muted-foreground">Detailed Description</label>
<textarea
rows={4}
placeholder="Explain your approach, what you've tried, and where you're getting stuck..."
className="w-full bg-background border border-border rounded-xl px-4 py-3 text-sm focus:outline-none focus:ring-2 focus:ring-[#00F0FF]/50 resize-none"
/>
</div>
<div className="pt-2 border-t border-border flex justify-between items-center">
<button className="flex items-center gap-2 text-muted-foreground hover:text-[#00F0FF] transition-colors text-sm font-medium px-3 py-2 rounded-lg hover:bg-secondary">
<ImageIcon size={18} /> Attach Image
</button>
<button
onClick={() => setIsAskModalOpen(false)}
className="flex items-center gap-2 px-6 py-2.5 bg-[#00F0FF] text-black font-bold rounded-xl shadow-[0_0_15px_-3px_rgba(0,240,255,0.4)] hover:bg-[#00d0dd] transition-colors"
>
Post Doubt <Send size={16} />
</button>
</div>
</div>
</div>
</div>
)}
</div>
)
}

View File

@@ -2,7 +2,7 @@
import { Button } from "@/components/ui/button"
import { Card, CardContent } from "@/components/ui/card"
import { BrainCircuit, Play, CheckCircle2, Download, Filter, Lock, FileText, Eye, ShieldAlert, X, Sparkles } from "lucide-react"
import { BrainCircuit, Play, CheckCircle2, Download, Filter, Lock, FileText, Eye, ShieldAlert, X, Sparkles, Share2, Maximize2, Minimize2 } from "lucide-react"
import { useEffect, useState } from "react"
import Link from "next/link"
@@ -15,6 +15,8 @@ export default function DPPPage() {
const [loading, setLoading] = useState(true)
const [dateFilter, setDateFilter] = useState("")
const [selectedPdfDpp, setSelectedPdfDpp] = useState<any | null>(null)
const [isFullScreen, setIsFullScreen] = useState(false)
const [copiedShare, setCopiedShare] = useState(false)
useEffect(() => {
async function fetchDpps() {
@@ -90,7 +92,7 @@ export default function DPPPage() {
const cleanTitle = (title: string) => title.replace(/\s*\(ExamEnum\.[^)]+\)/, '');
return (
<div className="p-8 space-y-8 animate-in fade-in slide-in-from-bottom-4 duration-500 max-w-5xl mx-auto">
<div className="p-8 space-y-8 animate-in fade-in slide-in-from-bottom-4 duration-500 max-w-7xl mx-auto">
<div className="flex flex-col md:flex-row md:items-center justify-between p-8 bg-gradient-to-r from-secondary/50 to-background border border-border rounded-2xl shadow-sm gap-6">
<div>
<h1 className="text-3xl font-bold tracking-tight mb-2">Daily Practice Problems (DPP)</h1>
@@ -118,8 +120,17 @@ export default function DPPPage() {
<Lock size={12}/> Pro
</div>
</div>
<Button variant="outline" className="gap-2 rounded-xl text-sm font-bold">
<Download size={16} /> Export as PDF <span className="ml-1 text-[10px] uppercase tracking-wider bg-primary/10 text-primary px-1.5 py-0.5 rounded-md">Pro</span>
<Button
variant="outline"
onClick={() => {
navigator.clipboard.writeText(window.location.origin + '/dashboard/dpp');
setCopiedShare(true);
setTimeout(() => setCopiedShare(false), 2000);
}}
className="gap-2 rounded-xl text-sm font-bold border-primary/30 hover:bg-primary/10 text-primary transition-all shadow-sm"
>
{copiedShare ? <CheckCircle2 size={16} className="text-green-500" /> : <Share2 size={16} />}
{copiedShare ? "Share Link Copied!" : "Share DPPs Link"}
</Button>
</div>
@@ -216,18 +227,18 @@ export default function DPPPage() {
{/* ── PROTECTED PDF READER MODAL (FREE TIER) ── */}
{selectedPdfDpp && (
<div className="fixed inset-0 z-50 bg-black/80 backdrop-blur-md flex items-center justify-center p-4 animate-in fade-in duration-300">
<div className="bg-background border border-border rounded-3xl w-full max-w-4xl max-h-[90vh] flex flex-col overflow-hidden shadow-2xl">
<div className={`fixed inset-0 z-50 bg-black/90 backdrop-blur-md flex items-center justify-center ${isFullScreen ? 'p-0' : 'p-4'} animate-in fade-in duration-300`}>
<div className={`bg-background border border-border ${isFullScreen ? 'w-screen h-screen max-w-none max-h-none rounded-none border-0' : 'rounded-3xl w-full max-w-7xl max-h-[95vh]'} flex flex-col overflow-hidden shadow-2xl transition-all duration-300`}>
{/* Modal Header */}
<div className="p-5 border-b border-border bg-secondary/30 flex items-center justify-between">
<div className="p-4 md:p-5 border-b border-border bg-secondary/30 flex items-center justify-between">
<div className="flex items-center gap-3">
<div className="p-2.5 bg-primary/10 rounded-xl border border-primary/20 text-primary">
<div className="p-2.5 bg-primary/10 rounded-xl border border-primary/20 text-primary hidden sm:flex">
<FileText size={22} />
</div>
<div>
<h3 className="font-black text-lg flex items-center gap-2">
<h3 className="font-black text-base sm:text-lg flex items-center gap-2">
{cleanTitle(selectedPdfDpp.title)}
<span className="text-[10px] bg-yellow-500/10 text-yellow-500 border border-yellow-500/20 px-2 py-0.5 rounded-full uppercase tracking-wider font-extrabold">
<span className="text-[10px] bg-yellow-500/10 text-yellow-500 border border-yellow-500/20 px-2 py-0.5 rounded-full uppercase tracking-wider font-extrabold hidden md:inline-block">
Protected In-App PDF View
</span>
</h3>
@@ -236,20 +247,44 @@ export default function DPPPage() {
</p>
</div>
</div>
<div className="flex items-center gap-2">
<Button
size="sm"
variant="outline"
onClick={() => {
navigator.clipboard.writeText(window.location.origin + '/dashboard/dpp');
setCopiedShare(true);
setTimeout(() => setCopiedShare(false), 2000);
}}
className="rounded-xl font-bold text-xs gap-1.5 h-9 px-3"
>
{copiedShare ? <CheckCircle2 size={14} className="text-green-500" /> : <Share2 size={14} />}
<span>{copiedShare ? "Copied" : "Share PDF"}</span>
</Button>
<Button
size="sm"
variant="outline"
onClick={() => setIsFullScreen(!isFullScreen)}
className="rounded-xl font-bold text-xs gap-1.5 h-9 px-3 bg-primary/10 text-primary border-primary/20 hover:bg-primary/20"
>
{isFullScreen ? <Minimize2 size={14} /> : <Maximize2 size={14} />}
<span>{isFullScreen ? "Exit Fullscreen" : "Fullscreen"}</span>
</Button>
<button
onClick={() => setSelectedPdfDpp(null)}
className="ml-4 flex-shrink-0 w-9 h-9 rounded-full border border-border bg-secondary flex items-center justify-center text-foreground hover:bg-destructive hover:border-destructive hover:text-white transition-all"
onClick={() => { setSelectedPdfDpp(null); setIsFullScreen(false); }}
className="ml-1 flex-shrink-0 w-9 h-9 rounded-full border border-border bg-secondary flex items-center justify-center text-foreground hover:bg-destructive hover:border-destructive hover:text-white transition-all"
>
<X size={18} />
</button>
</div>
</div>
{/* Security Notice Banner */}
<div className="bg-amber-500/10 border-b border-amber-500/20 px-6 py-2.5 flex items-center justify-between text-xs text-amber-400 font-bold">
<span className="flex items-center gap-2">
<ShieldAlert size={16} /> Read-Only Mode (Free Tier) — Downloads, text copying, and sharing are restricted.
<div className="bg-amber-500/10 border-b border-amber-500/20 px-4 sm:px-6 py-2 flex items-center justify-between text-xs text-amber-400 font-bold">
<span className="flex items-center gap-2 truncate">
<ShieldAlert size={16} className="shrink-0" /> Read-Only Mode (Free Tier) — Downloads & direct copying are restricted.
</span>
<Link href="/upgrade">
<Link href="/upgrade" className="hidden sm:block shrink-0 ml-2">
<Button size="sm" className="bg-amber-500 text-black hover:bg-amber-400 font-extrabold h-7 text-[11px] rounded-lg">
Upgrade for Interactive CBT Mode →
</Button>
@@ -259,28 +294,33 @@ export default function DPPPage() {
{/* Protected PDF Document Sheet Content (select-none) */}
<div
onContextMenu={(e) => e.preventDefault()}
className="flex-1 w-full select-none bg-[#525659] overflow-hidden" style={{minHeight: '55vh'}}
className="flex-1 w-full select-none bg-[#525659] overflow-hidden"
style={{minHeight: isFullScreen ? 'calc(100vh - 120px)' : '75vh'}}
>
<embed
src={getPdfUrl(selectedPdfDpp.title)}
type="application/pdf"
className="w-full h-full"
style={{height: '55vh'}}
style={{height: isFullScreen ? 'calc(100vh - 120px)' : '75vh'}}
/>
</div>
{/* Modal Footer */}
<div className="p-4 border-t border-border bg-secondary/30 flex items-center justify-between">
<span className="text-xs text-muted-foreground font-medium">
<div className="p-3.5 sm:p-4 border-t border-border bg-secondary/30 flex items-center justify-between flex-wrap gap-2">
<span className="text-xs text-muted-foreground font-medium flex items-center gap-2">
📄 PragatiVeer Protected PDF Reader v2.0
</span>
<div className="flex gap-3">
<Button onClick={() => setSelectedPdfDpp(null)} variant="outline" className="rounded-xl font-bold">
Close PDF Reader
<div className="flex gap-2 items-center">
<Button onClick={() => setIsFullScreen(!isFullScreen)} variant="secondary" className="rounded-xl font-bold text-xs h-9 bg-primary/10 text-primary hover:bg-primary/20 flex items-center gap-1.5">
{isFullScreen ? <Minimize2 size={14} /> : <Maximize2 size={14} />}
<span>{isFullScreen ? "Exit Fullscreen" : "Fullscreen"}</span>
</Button>
<Button onClick={() => { setSelectedPdfDpp(null); setIsFullScreen(false); }} variant="outline" className="rounded-xl font-bold text-xs h-9">
Close Reader
</Button>
<Link href="/upgrade">
<Button className="bg-[#00F0FF] text-black hover:bg-[#00F0FF]/90 font-extrabold rounded-xl shadow-[0_0_15px_rgba(0,240,255,0.2)]">
<Sparkles size={16} className="mr-2" /> Upgrade to AI Coach for Interactive CBT
<Button className="bg-[#00F0FF] text-black hover:bg-[#00F0FF]/90 font-extrabold rounded-xl shadow-[0_0_15px_rgba(0,240,255,0.2)] text-xs h-9">
<Sparkles size={14} className="mr-1.5" /> Upgrade to AI Coach for CBT
</Button>
</Link>
</div>

View File

@@ -1,7 +1,7 @@
"use client"
import { ReactNode, useState, useEffect, useRef } from "react"
import { BookOpen, LayoutDashboard, Settings, FileText, Activity, LineChart, Trophy, LogOut, User, ChevronUp, Lock, ShieldAlert, Flame, Target } from "lucide-react"
import { BookOpen, LayoutDashboard, Settings, FileText, Activity, LineChart, Trophy, User, ChevronUp, Lock, ShieldAlert, Flame, Target, Gauge, Zap, Calendar, Clock, BrainCircuit, BarChart3, School, CheckSquare } from "lucide-react"
import Link from "next/link"
import { usePathname } from "next/navigation"
import { LanguageToggle } from "@/components/LanguageToggle"
@@ -52,24 +52,23 @@ export default function DashboardLayout({ children }: { children: ReactNode }) {
const userInitials = user ? user.name.split(" ").filter(n => /^[a-zA-Z]/.test(n)).slice(0, 2).map(n => n[0]).join("").toUpperCase() : "DS"
const targetExamLabel = user?.target_exam === "JEE_Main" ? "JEE Main" : user?.target_exam === "JEE_Advanced" ? "JEE Advanced" : user?.target_exam === "NEET" ? "NEET" : "NEET/JEE"
let daysLeft: number | null = null;
const trialDays = globalSettings?.trial_duration_days !== undefined ? globalSettings.trial_duration_days : 15;
const trialEnabled = globalSettings?.enable_free_trial !== undefined ? globalSettings.enable_free_trial : true;
// For the demo, treat both Free tier and Neha as Trial users so the banner appears
if (user && (user.tier === 'Free' || user.id === DEMO_USERS.NEHA.id)) {
if (!trialEnabled) {
daysLeft = 0;
} else {
// For demo purposes, we ignore created_at and just show the exact days set in the admin panel
daysLeft = trialDays;
}
}
// Trial logic removed as requested
if (maintenance) {
return <MaintenanceScreen />;
}
if (loading || !user) {
return (
<div className="h-screen w-screen flex flex-col items-center justify-center bg-background">
<div className="animate-pulse flex flex-col items-center gap-4">
<Logo height={56} className="mb-4" />
<div className="h-6 w-6 border-4 border-[#00F0FF] border-t-transparent rounded-full animate-spin"></div>
</div>
</div>
);
}
return (
<div className="flex h-screen bg-background overflow-hidden text-foreground selection:bg-primary/20">
{/* Sidebar */}
@@ -78,59 +77,64 @@ export default function DashboardLayout({ children }: { children: ReactNode }) {
<Logo href="/dashboard" className="w-full" imageClassName="object-contain object-left scale-110 origin-left" height={56} />
</div>
<nav className="flex-1 px-4 space-y-1.5 mt-4 overflow-y-auto">
<div className="pt-2 pb-2">
{/* <div className="pt-2 pb-2">
<p className="px-3 text-xs font-bold uppercase tracking-widest text-muted-foreground flex items-center gap-2">
<span className="w-1.5 h-1.5 rounded-full bg-white"></span> Free Tier
</p>
</div>
<NavItem href="/dashboard" icon={<LayoutDashboard size={18} />} label="Overview" active={pathname === "/dashboard"} />
<NavItem href="/dashboard/dpp" icon={<FileText size={18} />} label="Target DPPs" active={pathname.startsWith("/dashboard/dpp")} />
<NavItem href="/dashboard/leaderboard" icon={<Trophy size={18} />} label="Leaderboard" active={pathname.startsWith("/dashboard/leaderboard")} />
</div> */}
<div className="pt-4 pb-2">
<p className="px-3 text-xs font-bold uppercase tracking-widest text-muted-foreground flex items-center gap-2">
<span className="w-1.5 h-1.5 rounded-full bg-yellow-500 shadow-[0_0_8px_-2px_var(--color-yellow-500)]"></span> Pro Tier
<div className="pt-3 pb-1">
<p className="px-3 text-[10px] font-black uppercase tracking-widest text-muted-foreground/60 flex items-center gap-2">
<span className="flex-1 h-px bg-border/60"></span>
<span className="flex items-center gap-1.5"><span className="w-1.5 h-1.5 rounded-full bg-yellow-500 shadow-[0_0_8px_-2px_rgba(234,179,8,0.8)]"></span>Pro Tier</span>
<span className="flex-1 h-px bg-border/60"></span>
</p>
</div>
<NavItem href="/dashboard" icon={<LayoutDashboard size={17} />} label="Overview" active={pathname === "/dashboard"} />
{/* <NavItem href="/dashboard/dpp" icon={<FileText size={17} />} label="Target DPPs" active={pathname.startsWith("/dashboard/dpp")} /> */}
<NavItem href="/dashboard/leaderboard" icon={<Trophy size={17} />} label="Leaderboard" active={pathname.startsWith("/dashboard/leaderboard")} />
<NavItem
href="/dashboard/progress"
icon={<Activity size={18} />}
icon={<BarChart3 size={17} />}
label="Syllabus Tracker"
active={pathname.startsWith("/dashboard/progress")}
isLocked={!hasAccessTo("chapter-wise-progress-tracking")}
/>
<NavItem
href="/dashboard/cbt"
icon={<Activity size={18} />}
icon={<CheckSquare size={17} />}
label="Mock Tests (CBT)"
active={pathname.startsWith("/dashboard/cbt")}
isLocked={!hasAccessTo("weekly-test-series")}
/>
<NavItem
href="/dashboard/materials"
icon={<BookOpen size={18} />}
icon={<BookOpen size={17} />}
label="Quick Notes"
active={pathname.startsWith("/dashboard/materials")}
isLocked={!hasAccessTo("detailed-performance-analytics")}
/>
<NavItem
href="/dashboard/analytics"
icon={<LineChart size={18} />}
icon={<LineChart size={17} />}
label="Performance Analytics"
active={pathname.startsWith("/dashboard/analytics")}
isLocked={!hasAccessTo("detailed-performance-analytics")}
/>
<NavItem
href="/dashboard/community"
icon={<BookOpen size={18} />}
icon={<School size={17} />}
label="Doubt Forum"
active={pathname.startsWith("/dashboard/community")}
isLocked={!hasAccessTo("doubt-resolution-community")}
/>
<div className="pt-4 pb-2">
<p className="px-3 text-xs font-bold uppercase tracking-widest text-muted-foreground flex items-center gap-2">
<span className="w-1.5 h-1.5 rounded-full bg-[#00F0FF] shadow-[0_0_8px_-2px_#00F0FF]"></span> AI Coach
<div className="pt-4 pb-1">
<p className="px-3 text-[10px] font-black uppercase tracking-widest text-muted-foreground/60 flex items-center gap-2">
<span className="flex-1 h-px bg-border/60"></span>
<span className="flex items-center gap-1.5"><span className="w-1.5 h-1.5 rounded-full bg-[#00F0FF] shadow-[0_0_8px_-2px_#00F0FF]"></span>AI Coach</span>
<span className="flex-1 h-px bg-border/60"></span>
</p>
</div>
<NavItem
@@ -142,63 +146,63 @@ export default function DashboardLayout({ children }: { children: ReactNode }) {
/>
<NavItem
href="/dashboard/ai-coach/cbt-dpp"
icon={<FileText size={18} />}
icon={<Zap size={17} />}
label="PrepPilot AI"
active={pathname.startsWith("/dashboard/ai-coach/cbt-dpp")}
isLocked={!hasAccessTo("ai-tutor-chat")}
/>
<NavItem
href="/dashboard/ai-coach/readiness"
icon={<Activity size={18} />}
icon={<Gauge size={17} />}
label="Exam Readiness"
active={pathname.startsWith("/dashboard/ai-coach/readiness")}
isLocked={!hasAccessTo("detailed-performance-analytics")}
/>
<NavItem
href="/dashboard/ai-coach/college-predictor"
icon={<Trophy size={18} />}
icon={<School size={17} />}
label="College Predictor"
active={pathname.startsWith("/dashboard/ai-coach/college-predictor")}
isLocked={!hasAccessTo("college-predictor")}
/>
<NavItem
href="/dashboard/ai-coach/strategy"
icon={<BookOpen size={18} />}
icon={<Target size={17} />}
label="Exam Strategy"
active={pathname.startsWith("/dashboard/ai-coach/strategy")}
isLocked={!hasAccessTo("exam-day-strategy")}
/>
<NavItem
href="/dashboard/ai-coach/study-plan"
icon={<LineChart size={18} />}
icon={<Calendar size={17} />}
label="AI Study Plan"
active={pathname.startsWith("/dashboard/ai-coach/study-plan")}
isLocked={!hasAccessTo("personalized-study-plan")}
/>
<NavItem
href="/dashboard/ai-coach/time-optimization"
icon={<Activity size={18} />}
label="Study Time Optimization"
icon={<Clock size={17} />}
label="Time Optimization"
active={pathname.startsWith("/dashboard/ai-coach/time-optimization")}
isLocked={!hasAccessTo("personalized-study-plan")}
/>
<NavItem
href="/dashboard/ai-coach/heatmap"
icon={<Flame size={18} />}
icon={<Flame size={17} />}
label="Mistake Heatmap"
active={pathname.startsWith("/dashboard/ai-coach/heatmap")}
isLocked={!hasAccessTo("ai-mistake-tracking")}
/>
<NavItem
href="/dashboard/ai-coach/weekly-progress"
icon={<Target size={18} />}
icon={<Activity size={17} />}
label="Weekly Progress"
active={pathname.startsWith("/dashboard/ai-coach/weekly-progress")}
isLocked={!hasAccessTo("detailed-performance-analytics")}
/>
<NavItem
href="/dashboard/ai-coach/mistakes"
icon={<Activity size={18} />}
icon={<BrainCircuit size={17} />}
label="Mistake Analyzer"
active={pathname.startsWith("/dashboard/ai-coach/mistakes")}
isLocked={!hasAccessTo("ai-mistake-tracking")}
@@ -272,27 +276,6 @@ export default function DashboardLayout({ children }: { children: ReactNode }) {
{targetExamLabel} Target
</Badge>
)}
{(user?.tier === 'Free' || user?.id === DEMO_USERS.NEHA.id) && daysLeft !== null && (
daysLeft > 0 ? (
<Link href="/upgrade" className="group">
<Badge variant="outline" className="bg-yellow-500/10 text-yellow-500 border-yellow-500/30 font-bold px-3 py-1 cursor-pointer hover:bg-yellow-500/20 hover:scale-105 transition-all flex items-center gap-2 shadow-[0_0_12px_rgba(234,179,8,0.15)]">
<span className="flex items-center gap-1.5"><ShieldAlert size={14} className="text-yellow-500" /> {daysLeft} Days Left in Trial</span>
<span className="text-[11px] bg-yellow-500 text-black px-2 py-0.5 rounded-full font-black group-hover:bg-yellow-400 transition-colors shadow-sm">
Upgrade Now →
</span>
</Badge>
</Link>
) : (
<Link href="/upgrade" className="group">
<Badge variant="outline" className="bg-red-500/15 text-red-400 border-red-500/40 font-black px-3.5 py-1 cursor-pointer hover:bg-red-500/30 hover:scale-105 transition-all flex items-center gap-2 shadow-[0_0_15px_rgba(239,68,68,0.25)] animate-pulse">
<span className="flex items-center gap-1.5"><ShieldAlert size={14} className="text-red-400" /> Trial Expired</span>
<span className="text-[11px] bg-red-500 text-white px-2 py-0.5 rounded-full font-black shadow-sm group-hover:bg-red-600 transition-colors">
Upgrade Now →
</span>
</Badge>
</Link>
)
)}
</div>
<div className="flex items-center gap-4">
@@ -314,7 +297,7 @@ export default function DashboardLayout({ children }: { children: ReactNode }) {
<option value={DEMO_USERS.NEHA.id}>Neha Verma (AI Coach)</option>
<option value={DEMO_USERS.AI_COACH.id}>Rahul (AI Coach)</option>
<option value={DEMO_USERS.PRO.id}>Priya (Pro Plan)</option>
<option value={DEMO_USERS.FREE.id}>Amit (Free Plan)</option>
{/* <option value={DEMO_USERS.FREE.id}>Amit (Free Plan)</option> */}
</select>
</div>
@@ -344,19 +327,24 @@ function NavItem({ href, icon, label, active, isLocked }: { href: string, icon:
return (
<Link
href={isLocked ? "#" : href}
className={`flex items-center justify-between px-3 py-2.5 rounded-xl transition-all duration-300 ${
className={`group flex items-center justify-between px-3 py-2 rounded-xl transition-all duration-200 ${
active
? "bg-secondary text-foreground font-semibold border border-border"
? "bg-[#00F0FF]/10 text-[#00F0FF] font-semibold border border-[#00F0FF]/20 shadow-[0_0_12px_-4px_rgba(0,240,255,0.3)]"
: isLocked
? "text-muted-foreground/60 cursor-not-allowed border border-transparent"
: "text-muted-foreground hover:bg-secondary/50 hover:text-foreground border border-transparent"
? "text-muted-foreground/40 cursor-not-allowed border border-transparent"
: "text-muted-foreground/80 hover:bg-secondary/60 hover:text-foreground border border-transparent hover:border-border/40"
}`}
>
<div className="flex items-center gap-3">
<div className="flex items-center gap-2.5">
<span className={`transition-all duration-200 ${
active ? "text-[#00F0FF]" : isLocked ? "text-muted-foreground/30" : "text-muted-foreground/60 group-hover:text-foreground/80"
}`}>
{icon}
<span className="text-sm">{label}</span>
</span>
<span className="text-[13px] font-medium tracking-[-0.01em]">{label}</span>
</div>
{isLocked && <Lock size={12} className="text-muted-foreground/50" />}
{isLocked && <Lock size={11} className="text-muted-foreground/30 shrink-0" />}
{active && <span className="w-1 h-4 rounded-full bg-[#00F0FF] shrink-0 shadow-[0_0_6px_rgba(0,240,255,0.8)]"></span>}
</Link>
)
}

View File

@@ -25,14 +25,18 @@ export default function LeaderboardPage() {
]
return (
<div className="p-8 space-y-8 animate-in fade-in slide-in-from-bottom-4 duration-500 max-w-5xl mx-auto">
<div className="p-8 space-y-8 animate-in fade-in slide-in-from-bottom-4 duration-500 max-w-7xl mx-auto">
<div className="flex flex-col md:flex-row justify-between items-start md:items-end gap-4">
<div>
<h1 className="text-3xl font-bold tracking-tight text-foreground flex items-center gap-3">
<Trophy className="text-yellow-500" size={32} />
<h1 className="text-3xl md:text-4xl font-black tracking-tight flex items-center gap-3 mb-1">
<span className="p-2.5 rounded-xl bg-[#00F0FF]/10 border border-[#00F0FF]/20">
<Trophy className="text-[#00F0FF]" size={28} />
</span>
Leaderboard
</h1>
<p className="text-muted-foreground mt-2 font-medium">Rankings are based on your Accuracy × Consistency (Streak Bonus).</p>
<p className="text-muted-foreground text-base font-medium max-w-2xl pl-1">
Rankings are based on your Accuracy × Consistency (Streak Bonus).
</p>
</div>
<div className="flex items-center gap-3 bg-secondary/50 p-1.5 rounded-xl border border-border">

View File

@@ -1,7 +1,16 @@
"use client"
import { useState } from "react"
import { Card, CardContent } from "@/components/ui/card"
import { Folder, FileText, Download, PlaySquare } from "lucide-react"
import { Folder, FileText, PlaySquare, Share2, Maximize2, Minimize2, X, CheckCircle2, ShieldAlert, Sparkles, Eye } from "lucide-react"
import { Button } from "@/components/ui/button"
import Link from "next/link"
export default function MaterialsPage() {
const [selectedPdf, setSelectedPdf] = useState<any | null>(null)
const [isFullScreen, setIsFullScreen] = useState(false)
const [copiedShare, setCopiedShare] = useState(false)
const folders = [
{ name: "Physics Formula Sheets", items: 12, color: "text-blue-500" },
{ name: "Organic Chem Mind Maps", items: 8, color: "text-emerald-500" },
@@ -10,16 +19,32 @@ export default function MaterialsPage() {
]
const recentFiles = [
{ name: "Kinematics 1D & 2D Rapid Revision.pdf", type: "PDF", size: "2.4 MB" },
{ name: "S-Block Elements Trick Video.mp4", type: "Video", size: "45 MB" },
{ name: "Human Reproduction Master Notes.pdf", type: "PDF", size: "8.1 MB" },
{ name: "Kinematics 1D & 2D Rapid Revision.pdf", type: "PDF", size: "2.4 MB", url: "/dpps/dps-phy-ch4-20260707.pdf#toolbar=0&navpanes=0&scrollbar=0" },
{ name: "S-Block Elements Trick Video.mp4", type: "Video", size: "45 MB", url: "" },
{ name: "Human Reproduction Master Notes.pdf", type: "PDF", size: "8.1 MB", url: "/dpps/dps-bio-ch4-20260707.pdf#toolbar=0&navpanes=0&scrollbar=0" },
]
const handleShare = (e: React.MouseEvent, fileName: string) => {
e.stopPropagation();
navigator.clipboard.writeText(window.location.origin + '/dashboard/materials');
setCopiedShare(true);
setTimeout(() => setCopiedShare(false), 2000);
};
return (
<div className="p-8 space-y-8 animate-in fade-in slide-in-from-bottom-4 duration-500 max-w-6xl mx-auto">
<div className="p-8 space-y-8 animate-in fade-in slide-in-from-bottom-4 duration-500 max-w-7xl mx-auto">
<div className="flex flex-col md:flex-row md:items-end justify-between gap-4">
<div>
<h1 className="text-3xl font-bold tracking-tight">Study Materials</h1>
<p className="text-muted-foreground mt-1 font-medium">High-yield notes, mind maps, and video lectures.</p>
<h1 className="text-3xl md:text-4xl font-black tracking-tight flex items-center gap-3 mb-1">
<span className="p-2.5 rounded-xl bg-[#00F0FF]/10 border border-[#00F0FF]/20">
<FileText className="text-[#00F0FF]" size={28} />
</span>
Study Materials & Notes
</h1>
<p className="text-muted-foreground text-base font-medium max-w-2xl pl-1">
High-yield notes, formula sheets, and mind maps with protected read-only share access.
</p>
</div>
</div>
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4">
@@ -27,15 +52,18 @@ export default function MaterialsPage() {
<Card key={i} className="hover:bg-secondary/50 cursor-pointer transition-colors border-border shadow-none">
<CardContent className="p-6">
<Folder size={40} className={`${folder.color} mb-4`} />
<h3 className="font-bold">{folder.name}</h3>
<p className="text-sm text-muted-foreground mt-1 font-medium">{folder.items} Files</p>
<h3 className="font-bold text-base">{folder.name}</h3>
<p className="text-sm text-muted-foreground mt-1 font-medium">{folder.items} Protected Files</p>
</CardContent>
</Card>
))}
</div>
<div className="pt-6">
<h2 className="text-xl font-bold border-b border-border pb-2 mb-4">Recently Accessed</h2>
<h2 className="text-xl font-bold border-b border-border pb-2 mb-4 flex items-center justify-between">
<span>Recently Accessed Study Notes</span>
<span className="text-xs font-normal text-muted-foreground">Click any PDF to launch fullscreen reader</span>
</h2>
<div className="bg-secondary/10 border border-border rounded-xl overflow-hidden">
<table className="w-full text-left text-sm">
<thead className="bg-secondary/30 text-muted-foreground border-b border-border">
@@ -43,20 +71,44 @@ export default function MaterialsPage() {
<th className="px-6 py-4 font-semibold">File Name</th>
<th className="px-6 py-4 font-semibold">Type</th>
<th className="px-6 py-4 font-semibold">Size</th>
<th className="px-6 py-4 font-semibold text-right">Download</th>
<th className="px-6 py-4 font-semibold text-right">Action (Share Link)</th>
</tr>
</thead>
<tbody className="divide-y divide-border">
{recentFiles.map((file, i) => (
<tr key={i} className="hover:bg-secondary/20 transition-colors cursor-pointer group">
<tr
key={i}
onClick={() => file.type === 'PDF' && setSelectedPdf(file)}
className="hover:bg-secondary/20 transition-colors cursor-pointer group"
>
<td className="px-6 py-4 font-medium flex items-center gap-3">
{file.type === 'PDF' ? <FileText size={18} className="text-red-400" /> : <PlaySquare size={18} className="text-blue-400" />}
{file.name}
<span className="group-hover:text-primary transition-colors font-bold">{file.name}</span>
</td>
<td className="px-6 py-4 text-muted-foreground">{file.type}</td>
<td className="px-6 py-4 text-muted-foreground">{file.size}</td>
<td className="px-6 py-4 text-muted-foreground font-medium">{file.type}</td>
<td className="px-6 py-4 text-muted-foreground font-medium">{file.size}</td>
<td className="px-6 py-4 text-right">
<button className="p-2 hover:text-[#00F0FF] hover:bg-secondary rounded-lg transition-colors inline-block"><Download size={18} /></button>
<div className="flex items-center justify-end gap-2" onClick={(e) => e.stopPropagation()}>
{file.type === 'PDF' && (
<Button
size="sm"
variant="secondary"
onClick={() => setSelectedPdf(file)}
className="h-8 px-3 rounded-lg text-xs font-bold gap-1.5 hover:bg-primary hover:text-primary-foreground"
>
<Eye size={14} /> View PDF
</Button>
)}
<Button
size="sm"
variant="outline"
onClick={(e) => handleShare(e, file.name)}
className="h-8 px-3 rounded-lg text-xs font-bold gap-1.5"
>
{copiedShare ? <CheckCircle2 size={14} className="text-green-500" /> : <Share2 size={14} />}
<span>{copiedShare ? "Link Copied" : "Share Link"}</span>
</Button>
</div>
</td>
</tr>
))}
@@ -64,6 +116,106 @@ export default function MaterialsPage() {
</table>
</div>
</div>
{/* ── PROTECTED STUDY MATERIAL PDF READER MODAL ── */}
{selectedPdf && (
<div className={`fixed inset-0 z-50 bg-black/90 backdrop-blur-md flex items-center justify-center ${isFullScreen ? 'p-0' : 'p-4'} animate-in fade-in duration-300`}>
<div className={`bg-background border border-border ${isFullScreen ? 'w-screen h-screen max-w-none max-h-none rounded-none border-0' : 'rounded-3xl w-full max-w-7xl max-h-[95vh]'} flex flex-col overflow-hidden shadow-2xl transition-all duration-300`}>
{/* Modal Header */}
<div className="p-4 md:p-5 border-b border-border bg-secondary/30 flex items-center justify-between">
<div className="flex items-center gap-3">
<div className="p-2.5 bg-primary/10 rounded-xl border border-primary/20 text-primary hidden sm:flex">
<FileText size={22} />
</div>
<div>
<h3 className="font-black text-base sm:text-lg flex items-center gap-2">
{selectedPdf.name}
<span className="text-[10px] bg-yellow-500/10 text-yellow-500 border border-yellow-500/20 px-2 py-0.5 rounded-full uppercase tracking-wider font-extrabold hidden md:inline-block">
Protected In-App View
</span>
</h3>
<p className="text-xs text-muted-foreground font-medium">
Study Material • Read-Only Share Mode
</p>
</div>
</div>
<div className="flex items-center gap-2">
<Button
size="sm"
variant="outline"
onClick={(e) => handleShare(e, selectedPdf.name)}
className="rounded-xl font-bold text-xs gap-1.5 h-9 px-3"
>
{copiedShare ? <CheckCircle2 size={14} className="text-green-500" /> : <Share2 size={14} />}
<span>{copiedShare ? "Copied" : "Share Link"}</span>
</Button>
<Button
size="sm"
variant="outline"
onClick={() => setIsFullScreen(!isFullScreen)}
className="rounded-xl font-bold text-xs gap-1.5 h-9 px-3 bg-primary/10 text-primary border-primary/20 hover:bg-primary/20"
>
{isFullScreen ? <Minimize2 size={14} /> : <Maximize2 size={14} />}
<span>{isFullScreen ? "Exit Fullscreen" : "Fullscreen"}</span>
</Button>
<button
onClick={() => { setSelectedPdf(null); setIsFullScreen(false); }}
className="ml-1 flex-shrink-0 w-9 h-9 rounded-full border border-border bg-secondary flex items-center justify-center text-foreground hover:bg-destructive hover:border-destructive hover:text-white transition-all"
>
<X size={18} />
</button>
</div>
</div>
{/* Security Notice Banner */}
<div className="bg-amber-500/10 border-b border-amber-500/20 px-4 sm:px-6 py-2 flex items-center justify-between text-xs text-amber-400 font-bold">
<span className="flex items-center gap-2 truncate">
<ShieldAlert size={16} className="shrink-0" /> Read-Only Mode — Direct PDF download and offline exports are disabled for security.
</span>
<Link href="/upgrade" className="hidden sm:block shrink-0 ml-2">
<Button size="sm" className="bg-amber-500 text-black hover:bg-amber-400 font-extrabold h-7 text-[11px] rounded-lg">
Upgrade for Offline Vault Access →
</Button>
</Link>
</div>
{/* Protected PDF Document Sheet Content (select-none) */}
<div
onContextMenu={(e) => e.preventDefault()}
className="flex-1 w-full select-none bg-[#525659] overflow-hidden"
style={{minHeight: isFullScreen ? 'calc(100vh - 120px)' : '75vh'}}
>
<embed
src={selectedPdf.url || '/dpps/dps-phy-ch4-20260707.pdf#toolbar=0&navpanes=0&scrollbar=0'}
type="application/pdf"
className="w-full h-full"
style={{height: isFullScreen ? 'calc(100vh - 120px)' : '75vh'}}
/>
</div>
{/* Modal Footer */}
<div className="p-3.5 sm:p-4 border-t border-border bg-secondary/30 flex items-center justify-between flex-wrap gap-2">
<span className="text-xs text-muted-foreground font-medium flex items-center gap-2">
📄 PragatiVeer Protected Study Vault v2.0
</span>
<div className="flex gap-2 items-center">
<Button onClick={() => setIsFullScreen(!isFullScreen)} variant="secondary" className="rounded-xl font-bold text-xs h-9 bg-primary/10 text-primary hover:bg-primary/20 flex items-center gap-1.5">
{isFullScreen ? <Minimize2 size={14} /> : <Maximize2 size={14} />}
<span>{isFullScreen ? "Exit Fullscreen" : "Fullscreen"}</span>
</Button>
<Button onClick={() => { setSelectedPdf(null); setIsFullScreen(false); }} variant="outline" className="rounded-xl font-bold text-xs h-9">
Close Reader
</Button>
<Link href="/upgrade">
<Button className="bg-[#00F0FF] text-black hover:bg-[#00F0FF]/90 font-extrabold rounded-xl shadow-[0_0_15px_rgba(0,240,255,0.2)] text-xs h-9">
<Sparkles size={14} className="mr-1.5" /> Unlock All Premium Notes
</Button>
</Link>
</div>
</div>
</div>
</div>
)}
</div>
)
}

View File

@@ -4,14 +4,14 @@ import { useRef } from "react"
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
import { Badge } from "@/components/ui/badge"
import { Button } from "@/components/ui/button"
import { Activity, BrainCircuit, ArrowRight, Clock, Target, CheckCircle2, TrendingUp, Lock, Trophy, Share2 } from "lucide-react"
import { BrainCircuit, Play, ChevronRight, Activity, Target, CheckCircle2, AlertTriangle, TrendingUp, Calendar, Zap, LayoutDashboard, Share2, Trophy, Lock, ArrowRight, Clock } from "lucide-react"
import * as htmlToImage from 'html-to-image'
import { MistakeHeatmap } from "@/components/AI/MistakeHeatmap"
import { AdaptiveStudyPlan } from "@/components/AI/AdaptiveStudyPlan"
import { useActiveUser } from "@/hooks/useActiveUser"
export default function DashboardPage() {
const { user, hasAccessTo } = useActiveUser()
const { user, hasAccessTo, loading } = useActiveUser()
const progressRef = useRef<HTMLDivElement>(null)
const handleExport = async () => {
@@ -28,37 +28,33 @@ export default function DashboardPage() {
}
}
if (loading) {
return (
<div className="p-8 max-w-6xl mx-auto space-y-10 animate-in fade-in duration-500 slide-in-from-bottom-4">
{/* Free Tier Upgrade CTA Banner */}
<Card className="bg-primary/5 border-primary/20 relative overflow-hidden">
<div className="absolute top-0 right-0 p-8 opacity-10">
<BrainCircuit size={120} />
<div className="h-[70vh] flex flex-col items-center justify-center p-8">
<div className="h-8 w-8 border-4 border-[#00F0FF] border-t-transparent rounded-full animate-spin mb-4"></div>
<p className="text-muted-foreground font-medium animate-pulse">Loading dashboard...</p>
</div>
<CardContent className="p-6 md:p-8 flex flex-col md:flex-row md:items-center justify-between gap-6 relative z-10">
<div className="space-y-2">
<Badge className="bg-primary text-primary-foreground font-bold tracking-widest uppercase mb-2">Upgrade to Pro</Badge>
<h2 className="text-2xl font-bold tracking-tight">Unlock Unlimited DPS & Mocks</h2>
<p className="text-muted-foreground max-w-xl font-medium">
You're currently on the Free Tier. Upgrade to Pro for ₹3499/year to access unlimited Daily Practice Sheets, weekly CBT tests, and advanced analytics.
</p>
</div>
<Button size="lg" className="shrink-0 rounded-xl shadow-[0_0_20px_-5px_var(--color-primary)] font-bold">
Upgrade Now
</Button>
</CardContent>
</Card>
)
}
return (
<div className="p-8 max-w-7xl mx-auto space-y-10 animate-in fade-in duration-500 slide-in-from-bottom-4">
{/* Basic Performance Dashboard (F-05) */}
<section>
<div className="flex items-center justify-between mb-5">
<h2 className="text-xl font-semibold tracking-tight flex items-center gap-2">
<div className="flex flex-col md:flex-row md:items-end justify-between gap-4 mb-8">
<div>
<h1 className="text-3xl md:text-4xl font-black tracking-tight flex items-center gap-3 mb-1">
<span className="p-2.5 rounded-xl bg-[#00F0FF]/10 border border-[#00F0FF]/20">
<LayoutDashboard className="text-[#00F0FF]" size={28} />
</span>
Performance Overview
<Badge variant="outline" className="border-white/30 text-muted-foreground bg-white/5 rounded-md text-[10px] uppercase font-bold">Free</Badge>
</h2>
<Button onClick={handleExport} variant="outline" size="sm" className="gap-2 font-medium">
</h1>
<p className="text-muted-foreground text-base font-medium max-w-2xl pl-1">
Your overall progress, scores, and active study plan at a glance.
</p>
</div>
<Button onClick={handleExport} variant="outline" size="sm" className="gap-2 font-medium shrink-0">
<Share2 size={16} /> Share Card
</Button>
</div>
@@ -93,7 +89,6 @@ export default function DashboardPage() {
<div className="p-3 bg-background border border-border shadow-sm rounded-full mb-3 text-foreground group-hover:scale-110 transition-transform"><Trophy size={24} /></div>
<div className="flex items-center gap-2 mb-1">
<p className="text-sm font-bold text-muted-foreground uppercase tracking-wider">City Rank</p>
<Badge variant="outline" className="border-yellow-500/30 text-yellow-500 bg-yellow-500/5 rounded-md text-[10px] uppercase font-bold px-1.5 py-0">Pro</Badge>
</div>
<h3 className="text-2xl font-bold">#42 <span className="text-base text-muted-foreground font-medium">/ 1,200</span></h3>
<p className="text-xs text-muted-foreground font-medium mt-2 text-[#00F0FF]">Top 5% in Jaipur</p>
@@ -117,8 +112,6 @@ export default function DashboardPage() {
<div className="flex items-center justify-between mb-5">
<h2 className="text-xl font-semibold tracking-tight flex items-center gap-2">
Today's DPPs
<Badge variant="outline" className="border-white/30 text-muted-foreground bg-white/5 rounded-md text-[10px] uppercase font-bold">Free (1/Day)</Badge>
<Badge variant="outline" className="border-yellow-500/30 text-yellow-500 bg-yellow-500/5 rounded-md text-[10px] uppercase font-bold">Pro (Unlimited)</Badge>
</h2>
<Button variant="ghost" size="sm" className="text-muted-foreground font-medium">View All</Button>
</div>
@@ -183,7 +176,6 @@ export default function DashboardPage() {
<div className="flex items-center justify-between mb-5">
<h2 className="text-xl font-semibold tracking-tight flex items-center gap-2">
Upcoming CBTs
<Badge variant="outline" className="border-yellow-500/30 text-yellow-500 bg-yellow-500/5 rounded-md text-[10px] uppercase font-bold">Pro</Badge>
</h2>
</div>
<Card className="overflow-hidden">

View File

@@ -104,13 +104,19 @@ export default function ProgressTrackerPage() {
}
return (
<div className="p-8 space-y-8 animate-in fade-in slide-in-from-bottom-4 duration-500 max-w-5xl mx-auto">
<div className="p-8 space-y-8 animate-in fade-in slide-in-from-bottom-4 duration-500 max-w-7xl mx-auto">
<div className="flex flex-col md:flex-row md:items-end justify-between gap-4">
<div>
<h1 className="text-3xl font-bold tracking-tight text-foreground flex items-center gap-3">
<Target className="text-[#00F0FF]" size={32} />
<h1 className="text-3xl md:text-4xl font-black tracking-tight flex items-center gap-3 mb-1">
<span className="p-2.5 rounded-xl bg-[#00F0FF]/10 border border-[#00F0FF]/20">
<Target className="text-[#00F0FF]" size={28} />
</span>
Syllabus Tracker
</h1>
<p className="text-muted-foreground mt-2 font-medium">Track your syllabus coverage and mastery per chapter to ensure no gaps.</p>
<p className="text-muted-foreground text-base font-medium max-w-2xl pl-1">
Track your syllabus coverage and mastery per chapter to ensure no gaps.
</p>
</div>
</div>
<div className="grid grid-cols-1 md:grid-cols-3 gap-6 mb-8">

View File

@@ -63,7 +63,7 @@ export default function RankPredictorPage() {
]
return (
<div className="p-6 md:p-8 space-y-8 animate-in fade-in duration-500 max-w-5xl mx-auto min-h-screen">
<div className="p-6 md:p-8 space-y-8 animate-in fade-in duration-500 max-w-7xl mx-auto min-h-screen">
{/* Header */}
<div className="text-center max-w-3xl mx-auto space-y-3">

View File

@@ -16,7 +16,7 @@ export default function StreaksPage() {
];
return (
<div className="p-8 space-y-8 animate-in fade-in slide-in-from-bottom-4 duration-500 max-w-5xl mx-auto">
<div className="p-8 space-y-8 animate-in fade-in slide-in-from-bottom-4 duration-500 max-w-7xl mx-auto">
<div className="flex items-center gap-4">
<div className="size-16 rounded-2xl bg-orange-500/10 text-orange-500 flex items-center justify-center">
<Flame size={32} />

View File

@@ -15,7 +15,7 @@ const geistMono = Geist_Mono({
export const metadata: Metadata = {
title: "PragatiVeer - AI Powered NEET/JEE Preparation",
description: "Experience the most accurate Computer-Based Test simulator combined with adaptive Daily Practice Problems generated specifically for your weak areas in real-time.",
description: "Experience the most accurate Computer-Based Test Exam Engine combined with adaptive Daily Practice Problems generated specifically for your weak areas in real-time.",
keywords: "NEET, JEE, Prep, AI, CBT, DPP, Online Test, Study",
};

View File

@@ -10,17 +10,35 @@ import { Logo } from "@/components/Logo"
export default function LoginPage() {
const [showPassword, setShowPassword] = useState(false)
const [email, setEmail] = useState("demo@pragativeer.com")
const [password, setPassword] = useState("PragatiVeer@Secure2026!")
const [email, setEmail] = useState("")
const [password, setPassword] = useState("")
const [error, setError] = useState("")
const router = useRouter()
const handleLogin = (e: React.FormEvent) => {
const handleLogin = async (e: React.FormEvent) => {
e.preventDefault()
if (email === "demo@pragativeer.com" && password === "PragatiVeer@Secure2026!") {
router.push("/dashboard")
setError("")
try {
const res = await fetch("/api/auth/login", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ email, password })
})
const data = await res.json()
if (data.success) {
if (data.role === "admin") {
router.push("/admin")
} else {
setError("Invalid credentials. Please use the demo account.")
router.push("/dashboard")
}
} else {
setError(data.error || "Invalid email or password.")
}
} catch (err) {
setError("An error occurred. Please try again.")
}
}
return (
@@ -55,7 +73,9 @@ export default function LoginPage() {
</div>
<div className="space-y-2">
<div className="flex items-center justify-between">
<label className="text-sm font-bold text-foreground">Password</label>
<label className="text-sm font-bold text-foreground">
Password
</label>
<Link href="#" className="text-xs font-bold text-[#00F0FF] hover:text-[#00F0FF]/80 transition-colors">Forgot password?</Link>
</div>
<div className="relative">

View File

@@ -79,7 +79,7 @@ export default function MarketingPage() {
<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>
@@ -119,7 +119,7 @@ export default function MarketingPage() {
transition={{ duration: 0.8, delay: 0.2 }}
className="text-xl md:text-2xl text-muted-foreground max-w-2xl mx-auto leading-relaxed font-medium"
>
Experience the most accurate CBT simulator combined with adaptive DPPs generated specifically for your weak areas in real-time.
Experience the most accurate CBT Exam Engine combined with adaptive DPPs generated specifically for your weak areas in real-time.
</motion.p>
<motion.div
@@ -409,8 +409,8 @@ export default function MarketingPage() {
<p className="text-muted-foreground text-xl max-w-2xl mx-auto">Unlock the ultimate unfair advantage for your NEET/JEE preparation.</p>
</motion.div>
<div className="grid grid-cols-1 lg:grid-cols-3 gap-8 max-w-6xl mx-auto">
{/* Free Plan */}
<div className="grid grid-cols-1 lg:grid-cols-2 gap-8 max-w-4xl mx-auto items-stretch">
{/* Free Plan
<motion.div
initial={{ opacity: 0, x: -50 }}
whileInView={{ opacity: 1, x: 0 }}
@@ -431,6 +431,7 @@ export default function MarketingPage() {
</Link>
</Card>
</motion.div>
*/}
{/* Pro Plan */}
<motion.div
@@ -495,10 +496,10 @@ export default function MarketingPage() {
<table className="w-full text-left border-collapse min-w-[700px]">
<thead>
<tr className="border-b border-border/50 bg-background/50">
<th className="p-4 md:p-6 font-bold text-lg w-2/5 border-l border-t border-border/50">Feature</th>
<th className="p-4 md:p-6 font-bold text-lg text-center w-1/5 border-x border-t border-white">Free</th>
<th className="p-4 md:p-6 font-bold text-lg text-center w-1/5 text-yellow-500 border-x border-t border-yellow-500">Pro</th>
<th className="p-4 md:p-6 font-bold text-lg text-center w-1/5 text-[#00F0FF] bg-[#00F0FF]/5 border-x border-t border-[#00F0FF]">AI Coach</th>
<th className="p-4 md:p-6 font-bold text-lg w-1/2 border-l border-t border-border/50">Feature</th>
{/* <th className="p-4 md:p-6 font-bold text-lg text-center w-1/5 border-x border-t border-white">Free</th> */}
<th className="p-4 md:p-6 font-bold text-lg text-center w-1/4 text-yellow-500 border-x border-t border-yellow-500">Pro</th>
<th className="p-4 md:p-6 font-bold text-lg text-center w-1/4 text-[#00F0FF] bg-[#00F0FF]/5 border-x border-t border-[#00F0FF]">AI Coach</th>
</tr>
</thead>
<tbody className="divide-y divide-border/30 text-sm md:text-base">
@@ -528,9 +529,9 @@ export default function MarketingPage() {
].map((feature, idx) => (
<tr key={idx} className="hover:bg-background/40 transition-colors">
<td className="p-4 md:p-6 font-medium border-y border-l border-border/30">{feature.name}</td>
<td className="p-4 md:p-6 text-center text-muted-foreground border border-white">
{/* <td className="p-4 md:p-6 text-center text-muted-foreground border border-white">
{typeof feature.free === 'boolean' ? (feature.free ? <CheckCircle2 className="mx-auto text-white" size={20} /> : <Minus className="mx-auto text-white/30" size={20} />) : <span className="font-semibold text-white">{feature.free}</span>}
</td>
</td> */}
<td className="p-4 md:p-6 text-center border border-yellow-500">
{typeof feature.pro === 'boolean' ? (feature.pro ? <CheckCircle2 className="mx-auto text-yellow-500" size={20} /> : <Minus className="mx-auto text-yellow-500/30" size={20} />) : <span className="font-semibold text-yellow-500">{feature.pro}</span>}
</td>
@@ -561,7 +562,7 @@ export default function MarketingPage() {
/>
</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 className="flex items-center gap-4 pt-2">
{/*
@@ -583,8 +584,6 @@ export default function MarketingPage() {
<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>
@@ -593,7 +592,6 @@ export default function MarketingPage() {
<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>
@@ -611,9 +609,6 @@ export default function MarketingPage() {
<div className="border-t border-border pt-8 flex flex-col md:flex-row items-center justify-between gap-4">
<p className="text-sm text-muted-foreground font-medium">© {new Date().getFullYear()} PragatiVeer. All rights reserved.</p>
<div className="flex items-center gap-2 text-sm text-muted-foreground font-medium">
Made with <div className="size-2 rounded-full bg-primary animate-pulse"></div> by AI Agents
</div>
</div>
</div>
</footer>

View File

@@ -12,7 +12,15 @@ export default function SignupPage() {
const [step, setStep] = useState(1)
const [showPassword, setShowPassword] = useState(false)
const [showOTP, setShowOTP] = useState(false)
const [password, setPassword] = useState("")
const [isSubmitted, setIsSubmitted] = useState(false)
const [firstName, setFirstName] = useState("")
const [lastName, setLastName] = useState("")
const [phone, setPhone] = useState("")
const [termsAccepted, setTermsAccepted] = useState(false)
const isStep1Valid = firstName.trim() !== "" && lastName.trim() !== "" && phone.length === 10 && termsAccepted
const router = useRouter()
const handleNextStep = () => {
@@ -51,8 +59,7 @@ export default function SignupPage() {
<CheckCircle2 size={64} className="text-primary mb-4" />
<h2 className="text-3xl font-black tracking-tight text-foreground">Thanks for registering!</h2>
<p className="text-muted-foreground text-sm font-medium px-4 leading-relaxed">
Our team will get back to you shortly. <br/><br/>
<span className="font-bold text-foreground">Note:</span> We are not live currently. Please use the demo credentials provided at the login screen to explore the platform.
Thank you for the registration. Our team will contact you shortly.
</p>
<Link href="/login" className="w-full mt-4">
<Button className="w-full rounded-xl h-12 font-bold text-base bg-primary text-primary-foreground hover:bg-primary/90 transition-all shadow-[0_0_20px_-5px_var(--color-primary)]">
@@ -78,11 +85,11 @@ export default function SignupPage() {
<div className="grid grid-cols-2 gap-4">
<div className="space-y-2">
<label className="text-sm font-bold text-foreground">First Name</label>
<input type="text" placeholder="John" className="w-full bg-background border border-border rounded-xl px-4 py-3 text-sm focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent transition-all"/>
<input type="text" placeholder="John" value={firstName} onChange={(e) => setFirstName(e.target.value)} className="w-full bg-background border border-border rounded-xl px-4 py-3 text-sm focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent transition-all"/>
</div>
<div className="space-y-2">
<label className="text-sm font-bold text-foreground">Last Name</label>
<input type="text" placeholder="Doe" className="w-full bg-background border border-border rounded-xl px-4 py-3 text-sm focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent transition-all"/>
<input type="text" placeholder="Doe" value={lastName} onChange={(e) => setLastName(e.target.value)} className="w-full bg-background border border-border rounded-xl px-4 py-3 text-sm focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent transition-all"/>
</div>
</div>
@@ -96,9 +103,11 @@ export default function SignupPage() {
type="tel"
maxLength={10}
pattern="[0-9]*"
value={phone}
onInput={(e) => {
e.currentTarget.value = e.currentTarget.value.replace(/[^0-9]/g, '');
}}
onChange={(e) => setPhone(e.target.value.replace(/[^0-9]/g, ''))}
placeholder="9876543210"
className="flex-1 bg-background border border-border rounded-xl px-4 py-3 text-sm focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent transition-all"
/>
@@ -123,40 +132,54 @@ export default function SignupPage() {
)}
<div className="space-y-2">
<div className="flex items-center justify-between">
<label className="text-sm font-bold text-foreground">Password (Optional if using OTP)</label>
</div>
<div className="relative">
<input type={showPassword ? "text" : "password"} placeholder="••••••••" className="w-full bg-background border border-border rounded-xl px-4 py-3 text-sm focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent transition-all pr-10"/>
<input
type={showPassword ? "text" : "password"}
value={password}
onChange={(e) => setPassword(e.target.value)}
placeholder="••••••••"
className="w-full bg-background border border-border rounded-xl px-4 py-3 text-sm focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent transition-all pr-10"
/>
<button type="button" onClick={() => setShowPassword(!showPassword)} className="absolute right-3 top-1/2 -translate-y-1/2 text-muted-foreground hover:text-foreground transition-colors">
{showPassword ? <EyeOff size={16} /> : <Eye size={16} />}
</button>
</div>
</div>
<div className="flex items-start gap-2 pt-2">
<input type="checkbox" id="terms" checked={termsAccepted} onChange={(e) => setTermsAccepted(e.target.checked)} className="mt-1 w-4 h-4 rounded border-border bg-background focus:ring-primary focus:ring-2 cursor-pointer" />
<label htmlFor="terms" className="text-xs text-muted-foreground leading-relaxed">
I agree to the <Link href="/terms-and-conditions" className="text-primary hover:underline">Terms & Conditions</Link> and <Link href="/privacy-policy" className="text-primary hover:underline">Privacy Policy</Link>.
</label>
</div>
</div>
) : (
<div className="space-y-4 animate-in fade-in slide-in-from-right-8">
<div className="space-y-2">
<label className="text-sm font-bold text-foreground">Target Exam</label>
<select className="w-full bg-background border border-border rounded-xl px-4 py-3 text-sm focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent transition-all appearance-none cursor-pointer">
<option value="jee_main">JEE Main</option>
<option value="jee_adv">JEE Advanced</option>
<option value="neet">NEET</option>
<option value="jee_neet">JEE + NEET</option>
<select className="w-full bg-background border border-border rounded-xl px-4 py-3 text-sm focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent transition-all cursor-pointer">
<option value="jee_main" className="bg-background text-foreground font-medium">JEE Main</option>
<option value="jee_adv" className="bg-background text-foreground font-medium">JEE Advanced</option>
<option value="neet" className="bg-background text-foreground font-medium">NEET</option>
<option value="jee_neet" className="bg-background text-foreground font-medium">JEE + NEET</option>
</select>
</div>
<div className="space-y-2">
<label className="text-sm font-bold text-foreground">Current Class</label>
<select className="w-full bg-background border border-border rounded-xl px-4 py-3 text-sm focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent transition-all appearance-none cursor-pointer">
<option value="11">Class 11th</option>
<option value="12">Class 12th</option>
<option value="dropper">Dropper</option>
<select className="w-full bg-background border border-border rounded-xl px-4 py-3 text-sm focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent transition-all cursor-pointer">
<option value="11" className="bg-background text-foreground font-medium">Class 11th</option>
<option value="12" className="bg-background text-foreground font-medium">Class 12th</option>
<option value="dropper" className="bg-background text-foreground font-medium">Dropper</option>
</select>
</div>
<div className="space-y-2">
<label className="text-sm font-bold text-foreground">Study Medium</label>
<select className="w-full bg-background border border-border rounded-xl px-4 py-3 text-sm focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent transition-all appearance-none cursor-pointer">
<option value="en">English</option>
<option value="hi">Hindi (हिंदी)</option>
<option value="bi">Bilingual (English + Hindi)</option>
<select className="w-full bg-background border border-border rounded-xl px-4 py-3 text-sm focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent transition-all cursor-pointer">
<option value="en" className="bg-background text-foreground font-medium">English</option>
<option value="hi" className="bg-background text-foreground font-medium">Hindi (हिंदी)</option>
<option value="bi" className="bg-background text-foreground font-medium">Bilingual (English + Hindi)</option>
</select>
</div>
<div className="space-y-2">
@@ -166,7 +189,11 @@ export default function SignupPage() {
</div>
)}
<Button onClick={handleNextStep} className="w-full rounded-xl h-12 font-bold text-base bg-primary text-primary-foreground hover:bg-primary/90 transition-all shadow-[0_0_20px_-5px_var(--color-primary)]">
<Button
onClick={handleNextStep}
disabled={step === 1 && !isStep1Valid}
className="w-full rounded-xl h-12 font-bold text-base bg-primary text-primary-foreground hover:bg-primary/90 transition-all shadow-[0_0_20px_-5px_var(--color-primary)] disabled:opacity-50 disabled:cursor-not-allowed disabled:shadow-none"
>
{step === 1 ? "Continue" : "Complete Setup"}
</Button>
</CardContent>

View File

@@ -103,46 +103,40 @@ export default function UpgradePage() {
<p className="text-xl text-muted-foreground">Unlock the full power of PragatiVeer to master JEE & NEET.</p>
</div>
<div className="grid grid-cols-1 md:grid-cols-3 gap-8 items-stretch">
{/* Free Tier */}
<div className="grid grid-cols-1 lg:grid-cols-2 gap-8 max-w-5xl mx-auto items-stretch">
{/* Free Tier
<Card className="bg-background/80 backdrop-blur-md border border-border p-6 rounded-[2rem] flex flex-col justify-between h-full z-10 relative shadow-xl hover:border-border/80 transition-colors">
<div>
<CardHeader className="p-0 mb-6">
<CardTitle className="text-3xl font-bold mb-1 text-foreground">Free</CardTitle>
<CardDescription className="text-muted-foreground mb-6 text-base">Perfect to get started.</CardDescription>
<h3 className="text-3xl font-bold mb-2 text-foreground">Free</h3>
<p className="text-muted-foreground mb-6 text-base">Perfect to get started.</p>
<div className="text-6xl font-bold tracking-tighter mb-6 text-foreground">
₹0<span className="text-xl text-white/60 font-medium tracking-normal ml-1">/year</span>
</div>
</CardHeader>
<CardContent className="p-0 space-y-3 mb-6 font-medium text-lg">
<div className="flex items-center gap-4"><CheckCircle2 className="text-foreground shrink-0" size={24}/> 1 Daily Practice Sheet</div>
<div className="flex items-center gap-4"><CheckCircle2 className="text-foreground shrink-0" size={24}/> Basic Performance Analytics</div>
<div className="flex items-center gap-4"><CheckCircle2 className="text-foreground shrink-0" size={24}/> Community Access</div>
</CardContent>
<ul className="space-y-3 mb-6 font-medium text-lg">
<li className="flex items-center gap-4"><CheckCircle2 className="text-foreground shrink-0" size={24}/> 1 Daily Practice Sheet</li>
<li className="flex items-center gap-4"><CheckCircle2 className="text-foreground shrink-0" size={24}/> Basic Performance Analytics</li>
<li className="flex items-center gap-4"><CheckCircle2 className="text-foreground shrink-0" size={24}/> Community Access</li>
</ul>
</div>
<CardFooter className="p-0">
<Button variant="outline" className="w-full rounded-2xl h-12 border-border text-foreground font-bold text-lg hover:bg-secondary" disabled>Get Started Free</Button>
</CardFooter>
</Card>
*/}
{/* Pro Tier */}
<Card className="bg-background/80 backdrop-blur-md border border-border p-6 rounded-[2rem] flex flex-col justify-between h-full z-10 relative shadow-xl hover:border-border/80 transition-colors">
<div>
<CardHeader className="p-0 mb-6">
<CardTitle className="text-3xl font-bold mb-1 text-yellow-500">Pro</CardTitle>
<CardDescription className="text-muted-foreground mb-6 text-base">Perfect for self-paced practice.</CardDescription>
<h3 className="text-3xl font-bold mb-2 text-yellow-500">Pro</h3>
<p className="text-muted-foreground mb-6 text-base">Perfect for self-paced practice.</p>
<div className="text-6xl font-bold tracking-tighter mb-6 text-yellow-500">
₹3,499<span className="text-xl text-yellow-500/60 font-medium tracking-normal ml-1">/year</span>
</div>
</CardHeader>
<CardContent className="p-0 space-y-3 mb-6 font-medium text-lg">
<div className="flex items-center gap-4"><CheckCircle2 className="text-yellow-500 shrink-0" size={24}/> Unlimited Daily Practice Sheets</div>
<div className="flex items-center gap-4"><CheckCircle2 className="text-yellow-500 shrink-0" size={24}/> Weekly Test Series & Mock Tests</div>
<div className="flex items-center gap-4"><CheckCircle2 className="text-yellow-500 shrink-0" size={24}/> Detailed Performance Analytics</div>
<div className="flex items-center gap-4"><CheckCircle2 className="text-yellow-500 shrink-0" size={24}/> Parent Dashboard Access</div>
</CardContent>
<ul className="space-y-3 mb-6 font-medium text-lg">
<li className="flex items-center gap-4"><CheckCircle2 className="text-yellow-500 shrink-0" size={24}/> Unlimited Daily Practice Sheets</li>
<li className="flex items-center gap-4"><CheckCircle2 className="text-yellow-500 shrink-0" size={24}/> Weekly Test Series & Mock Tests</li>
<li className="flex items-center gap-4"><CheckCircle2 className="text-yellow-500 shrink-0" size={24}/> Detailed Performance Analytics</li>
<li className="flex items-center gap-4"><CheckCircle2 className="text-yellow-500 shrink-0" size={24}/> Parent Dashboard Access</li>
</ul>
</div>
<CardFooter className="p-0">
<Button
className="w-full bg-yellow-500 text-black hover:bg-yellow-400 font-bold text-lg rounded-2xl h-12"
onClick={() => handleUpgrade("Pro")}
@@ -150,27 +144,23 @@ export default function UpgradePage() {
>
{loadingTier === "Pro" ? <Loader2 className="animate-spin" /> : "Get Pro"}
</Button>
</CardFooter>
</Card>
{/* AI Coach Tier */}
<Card className="bg-background/80 backdrop-blur-md border border-border p-6 rounded-[2rem] flex flex-col justify-between h-full z-10 relative shadow-xl hover:border-border/80 transition-colors">
<div>
<CardHeader className="p-0 mb-6">
<CardTitle className="text-3xl font-bold mb-2 text-[#00F0FF]">AI Coach</CardTitle>
<CardDescription className="text-muted-foreground mb-6 text-base">The complete AI-driven system.</CardDescription>
<h3 className="text-3xl font-bold mb-2 text-[#00F0FF]">AI Coach</h3>
<p className="text-muted-foreground mb-6 text-base">The complete AI-driven system.</p>
<div className="text-6xl font-bold tracking-tighter mb-6 text-[#00F0FF]">
₹11,999<span className="text-xl text-[#00F0FF]/60 font-medium tracking-normal ml-1">/year</span>
</div>
</CardHeader>
<CardContent className="p-0 space-y-3 mb-6 font-medium text-lg">
<div className="flex items-center gap-4"><CheckCircle2 className="text-[#00F0FF] shrink-0" size={24}/> Everything in Pro</div>
<div className="flex items-center gap-4"><CheckCircle2 className="text-[#00F0FF] shrink-0" size={24}/> AI Spaced Repetition (Mistakes)</div>
<div className="flex items-center gap-4"><CheckCircle2 className="text-[#00F0FF] shrink-0" size={24}/> 24/7 Streaming AI Tutor Chat</div>
<div className="flex items-center gap-4"><CheckCircle2 className="text-[#00F0FF] shrink-0" size={24}/> Predictive Rank & College Predictor</div>
</CardContent>
<ul className="space-y-3 mb-6 font-medium text-lg">
<li className="flex items-center gap-4"><CheckCircle2 className="text-[#00F0FF] shrink-0" size={24}/> Everything in Pro</li>
<li className="flex items-center gap-4"><CheckCircle2 className="text-[#00F0FF] shrink-0" size={24}/> AI Spaced Repetition (Mistakes)</li>
<li className="flex items-center gap-4"><CheckCircle2 className="text-[#00F0FF] shrink-0" size={24}/> 24/7 Streaming AI Tutor Chat</li>
<li className="flex items-center gap-4"><CheckCircle2 className="text-[#00F0FF] shrink-0" size={24}/> Predictive Rank & College Predictor</li>
</ul>
</div>
<CardFooter className="p-0">
<Button
className="w-full font-bold text-lg rounded-2xl h-12 bg-[#00F0FF] text-black hover:bg-[#00F0FF]/90 transition-all"
onClick={() => handleUpgrade("Al_Coach")}
@@ -178,7 +168,6 @@ export default function UpgradePage() {
>
{loadingTier === "Al_Coach" ? <Loader2 className="animate-spin" /> : "Get AI Coach"}
</Button>
</CardFooter>
</Card>
</div>
@@ -199,10 +188,10 @@ export default function UpgradePage() {
<table className="w-full text-left border-collapse min-w-[700px]">
<thead>
<tr className="border-b border-border/50 bg-background/50">
<th className="p-4 md:p-6 font-bold text-lg w-2/5 border-l border-t border-border/50">Feature</th>
<th className="p-4 md:p-6 font-bold text-lg text-center w-1/5 border-x border-t border-white">Free</th>
<th className="p-4 md:p-6 font-bold text-lg text-center w-1/5 text-yellow-500 border-x border-t border-yellow-500">Pro</th>
<th className="p-4 md:p-6 font-bold text-lg text-center w-1/5 text-[#00F0FF] bg-[#00F0FF]/5 border-x border-t border-[#00F0FF]">AI Coach</th>
<th className="p-4 md:p-6 font-bold text-lg w-1/2 border-l border-t border-border/50">Feature</th>
{/* <th className="p-4 md:p-6 font-bold text-lg text-center w-1/5 border-x border-t border-white">Free</th> */}
<th className="p-4 md:p-6 font-bold text-lg text-center w-1/4 text-yellow-500 border-x border-t border-yellow-500">Pro</th>
<th className="p-4 md:p-6 font-bold text-lg text-center w-1/4 text-[#00F0FF] bg-[#00F0FF]/5 border-x border-t border-[#00F0FF]">AI Coach</th>
</tr>
</thead>
<tbody className="divide-y divide-border/30 text-sm md:text-base">
@@ -232,9 +221,9 @@ export default function UpgradePage() {
].map((feature, idx) => (
<tr key={idx} className="hover:bg-background/40 transition-colors">
<td className="p-4 md:p-6 font-medium border-y border-l border-border/30">{feature.name}</td>
<td className="p-4 md:p-6 text-center text-muted-foreground border border-white">
{/* <td className="p-4 md:p-6 text-center text-muted-foreground border border-white">
{typeof feature.free === 'boolean' ? (feature.free ? <CheckCircle2 className="mx-auto text-white" size={20} /> : <Minus className="mx-auto text-white/30" size={20} />) : <span className="font-semibold text-white">{feature.free}</span>}
</td>
</td> */}
<td className="p-4 md:p-6 text-center border border-yellow-500">
{typeof feature.pro === 'boolean' ? (feature.pro ? <CheckCircle2 className="mx-auto text-yellow-500" size={20} /> : <Minus className="mx-auto text-yellow-500/30" size={20} />) : <span className="font-semibold text-yellow-500">{feature.pro}</span>}
</td>

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)] 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
<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 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"
}`}
className={`flex-1 py-2 text-xs font-bold rounded-lg border transition-all ${activeTab === tab ? TAB_COLORS[tab] : INACTIVE}`}
>
{tab}
</button>
))}
</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>
{/* 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="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-xs font-black px-2.5 py-1 rounded-md shrink-0 ${MATCH_COLOR(col.match)}`}>
{col.match}%
</div>
</div>
))}
</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
</div>
<div className="flex-1 min-w-0">
<div className="text-[13px] font-bold text-primary truncate">NIT Trichy</div>
<div className="text-[11px] text-muted-foreground truncate">Computer Science</div>
</div>
<div className="text-[12px] font-bold px-2 py-0.5 rounded-md shrink-0 text-green-500 bg-green-500/10">
85%
</div>
</div>
<div className="flex items-center gap-3 p-2.5 rounded-md bg-white/5 border border-border hover:bg-white/10 transition-colors">
<div className="w-7 h-7 rounded-full flex items-center justify-center text-[11px] font-bold shrink-0 bg-[#00F0FF]/10 text-[#00F0FF]">
#2
</div>
<div className="flex-1 min-w-0">
<div className="text-[13px] font-bold text-primary truncate">NIT Surathkal</div>
<div className="text-[11px] text-muted-foreground truncate">Information Tech</div>
</div>
<div className="text-[12px] font-bold px-2 py-0.5 rounded-md shrink-0 text-yellow-500 bg-yellow-500/10">
72%
</div>
</div>
</div>
<div className="mt-4 p-2.5 bg-yellow-500/10 border border-yellow-500/20 rounded-md text-xs text-yellow-500 font-medium text-center">
Just 15 more marks in Maths can upgrade you to IIT Roorkee!
{/* 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>
</div>
)
}

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">
<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} />
Mastering this will boost your rank by an estimated{" "}
<span className="font-bold text-white/70">+120 positions</span>
{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>
<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>
<Card className="border-border bg-card shadow-[0_4px_24px_rgba(0,0,0,0.3)] flex-1 overflow-hidden group hover:border-[#00F0FF]/30 transition-colors">
<CardContent className="p-4 flex flex-col h-full justify-between">
<div className="grid grid-cols-2 gap-2">
<div className="p-3 rounded-md bg-white/5 border border-border text-center">
<div className="text-xl mb-1">🧪</div>
<div className="text-[11px] font-bold text-primary">Chemistry First</div>
<div className="text-[10px] text-muted-foreground mt-0.5">Finish in 40 mins</div>
<div 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">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">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">Maths Last</div>
<div className="text-[10px] text-muted-foreground mt-0.5">Reserve 75 mins</div>
</div>
<div className="p-3 rounded-md bg-white/5 border border-border text-center">
<div className="text-xl mb-1">⏱️</div>
<div className="text-[11px] font-bold text-primary">Buffer Time</div>
<div className="text-[10px] text-muted-foreground mt-0.5">10 mins for review</div>
<div 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,13 +30,12 @@ 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]">
<CardContent className="flex-1 p-4 md:p-6 flex flex-col justify-between">
<div className="w-full">
{/* 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">
<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>
@@ -49,17 +48,17 @@ export function MistakeHeatmap() {
<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">
<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 className="flex-1 flex justify-between gap-1">
<div className="flex-1 grid grid-cols-5 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)}`}
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] font-bold text-foreground/80">{errRate}%</span>
<span className="text-[10px] sm:text-xs font-black text-foreground/90">{errRate}%</span>
</div>
))}
</div>
@@ -67,10 +66,9 @@ export function MistakeHeatmap() {
))}
</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>
{[
{ 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>
<div className="relative z-10 flex flex-col items-center gap-1.5 opacity-60">
<div className="w-3.5 h-3.5 rounded-full bg-[#00F0FF] border-2 border-background" />
<span className="text-[10px] font-bold text-[#00F0FF]">Attempt 2</span>
<span className="text-[9px] text-muted-foreground font-semibold">14 Jul</span>
</div>
<div className="relative z-10 flex flex-col items-center gap-1.5 scale-110">
<div className="w-4 h-4 rounded-full border-[3px] border-[#00F0FF] bg-background shadow-[0_0_10px_rgba(0,240,255,0.5)]" />
<span className="text-[11px] font-bold text-[#00F0FF]">Today</span>
<span className="text-[9px] text-muted-foreground font-semibold">19 Jul</span>
</div>
<div className="relative z-10 flex flex-col items-center gap-1.5 opacity-50">
<div className="w-3.5 h-3.5 rounded-full bg-secondary border-2 border-background" />
<span className="text-[10px] font-bold text-muted-foreground">Mastery</span>
<span className="text-[9px] text-muted-foreground font-semibold">26 Jul</span>
</div>
</div>
{/* 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)]"
@@ -144,35 +159,35 @@ export function MistakeReviewCard({ data, onNext, onSubmitReview }: MistakeRevie
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}
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
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
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>
<Button onClick={handleDownload} variant="outline" className="w-full" disabled={downloading}>
<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 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">
<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
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">
<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">{link}</code>
<Button variant="secondary" size="sm" onClick={copyToClipboard} className="shrink-0 rounded-md">
<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">
<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>
</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">
<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">
{/* 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 gap-0.5 cursor-pointer"
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/60 to-yellow-400 shadow-[0_0_10px_rgba(234,179,8,0.4)]"
? "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]/50 to-[#00F0FF]"
: "bg-gradient-to-t from-[#00F0FF]/20 to-[#00F0FF]/60"
? "bg-gradient-to-t from-[#00F0FF]/70 to-[#00F0FF]"
: "bg-gradient-to-t from-[#00F0FF]/20 to-[#00F0FF]/50"
}`}
style={{ height: `${slot.h}%` }}
style={{ height: `${(slot.h / maxH) * 100}%` }}
/>
<span className="text-[8px] text-muted-foreground font-medium whitespace-nowrap mt-1">
{slot.label}
</span>
</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,9 +15,16 @@ 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)
@@ -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 ${
<div className={`p-3.5 text-sm 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]"
? "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,25 +131,36 @@ 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
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

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">
{/* 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 flex-col items-center gap-1 flex-1">
<div className="relative w-full flex items-end justify-center" style={{ height: "80px" }}>
<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]"
: "bg-gradient-to-t from-white/10 to-white/20"
: hovered === i
? "bg-gradient-to-t from-[#00F0FF]/40 to-[#00F0FF]/80"
: "bg-gradient-to-t from-white/10 to-white/25"
}`}
style={{ height: `${(BAR_HEIGHTS[i] / maxBar) * 80}px` }}
title={`${ACCURACY[i]}% accuracy`}
style={{ height: `${(DATA[i] / maxVal) * 100}%` }}
/>
</div>
<span className="text-[9px] text-muted-foreground font-semibold">{day}</span>
))}
</div>
</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>

27
src/proxy.ts Normal file
View File

@@ -0,0 +1,27 @@
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
export function proxy(request: NextRequest) {
const token = request.cookies.get('auth_token')?.value;
const path = request.nextUrl.pathname;
// Protect /admin routes
if (path.startsWith('/admin')) {
if (token !== 'admin') {
return NextResponse.redirect(new URL('/login', request.url));
}
}
// Protect /dashboard routes
if (path.startsWith('/dashboard')) {
if (!token) {
return NextResponse.redirect(new URL('/login', request.url));
}
}
return NextResponse.next();
}
export const config = {
matcher: ['/dashboard/:path*', '/admin/:path*']
};

18
test.js
View File

@@ -1,18 +0,0 @@
import("react-markdown").then(rm => {
import("remark-math").then(math => {
import("rehype-katex").then(katex => {
import("react-dom/server").then(rds => {
import("react").then(react => {
const el = react.createElement(rm.default, {
remarkPlugins: [math.default],
rehypePlugins: [katex.default],
children: "Testing math $$a=b$$ inline"
});
console.log(rds.renderToString(el));
});
});
});
});
})

View File

@@ -1,19 +0,0 @@
import("react-markdown").then(rm => {
import("remark-math").then(math => {
import("rehype-katex").then(katex => {
import("react-dom/server").then(rds => {
import("react").then(react => {
const text = "The velocity of a particle moving along the x-axis is given by $v(t) = \\int_{0}^{t} (3\\tau^2 - 2\\tau) d\\tau$. What is the displacement of the particle at $t = 2$ seconds if it starts from the origin?";
const el = react.createElement(rm.default, {
remarkPlugins: [math.default],
rehypePlugins: [katex.default],
children: text
});
console.log(rds.renderToString(el));
});
});
});
});
})

View File

@@ -1,19 +0,0 @@
import("react-markdown").then(rm => {
import("remark-math").then(math => {
import("rehype-katex").then(katex => {
import("react-dom/server").then(rds => {
import("react").then(react => {
const text = "The velocity of a particle moving along the x-axis is given by $\\v(t) = \\int_{0}^{t} (3\\tau^2 - 2\\tau) d\\tau$. What is the displacement of the particle at $t = 2$ seconds if it starts from the origin?";
const el = react.createElement(rm.default, {
remarkPlugins: [math.default],
rehypePlugins: [katex.default],
children: text
});
console.log(rds.renderToString(el));
});
});
});
});
})

View File

@@ -1,20 +0,0 @@
import("react-markdown").then(rm => {
import("remark-math").then(math => {
import("rehype-katex").then(katex => {
import("react-dom/server").then(rds => {
import("react").then(react => {
// This simulates string with actual backslash (e.g. from JSON parse)
const text = "Testing math $\\v(t) = \\int$ and $$\\alpha$$";
const el = react.createElement(rm.default, {
remarkPlugins: [math.default],
rehypePlugins: [katex.default],
children: text
});
console.log(rds.renderToString(el));
});
});
});
});
})