chore: Secure routes with auth middleware, update text and fix minor bugs
This commit is contained in:
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
cbt_test.html
BIN
cbt_test.html
Binary file not shown.
9
parse.js
9
parse.js
@@ -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));
|
|
||||||
|
|
||||||
18
parse2.js
18
parse2.js
@@ -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
@@ -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.
BIN
prd_dump.txt
BIN
prd_dump.txt
Binary file not shown.
1385
prd_dump_utf8.txt
1385
prd_dump_utf8.txt
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -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}")
|
|
||||||
@@ -1,24 +1,22 @@
|
|||||||
import { Button } from "@/components/ui/button"
|
import { Button } from "@/components/ui/button"
|
||||||
import Link from "next/link"
|
import Link from "next/link"
|
||||||
import { ArrowLeft } from "lucide-react"
|
import { ArrowLeft } from "lucide-react"
|
||||||
|
import { PublicLayout } from "@/components/Landing/PublicLayout"
|
||||||
|
|
||||||
export default function AboutPage() {
|
export default function AboutPage() {
|
||||||
return (
|
return (
|
||||||
<div className="min-h-screen bg-background text-foreground flex flex-col">
|
<PublicLayout>
|
||||||
<header className="border-b border-border bg-background px-6 py-4 flex items-center justify-between sticky top-0 z-10">
|
<div className="bg-background text-foreground flex flex-col pt-24 pb-16 min-h-[70vh]">
|
||||||
<Link href="/" className="text-muted-foreground hover:text-foreground flex items-center gap-2 transition-colors font-medium">
|
<main className="flex-1 flex flex-col items-center justify-center p-8 text-center animate-in fade-in duration-700">
|
||||||
<ArrowLeft size={16} /> Back to Home
|
|
||||||
</Link>
|
|
||||||
</header>
|
|
||||||
<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>
|
<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">
|
<p className="text-xl text-muted-foreground max-w-4xl leading-relaxed mb-8 mx-auto text-left md:text-center">
|
||||||
We are a team of educators and engineers dedicated to re-engineering NEET/JEE preparation through the power of Artificial Intelligence.
|
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>
|
</p>
|
||||||
<Link href="/signup">
|
<Link href="/signup">
|
||||||
<Button size="lg" className="rounded-full font-bold px-8 shadow-xl">Join the Revolution</Button>
|
<Button size="lg" className="rounded-full font-bold px-8 shadow-xl">Join the Revolution</Button>
|
||||||
</Link>
|
</Link>
|
||||||
</main>
|
</main>
|
||||||
</div>
|
</div>
|
||||||
|
</PublicLayout>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
43
src/app/api/auth/login/route.ts
Normal file
43
src/app/api/auth/login/route.ts
Normal 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 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -461,7 +461,7 @@ export default function CBTExamPage({ params }: { params: Promise<{ examId: stri
|
|||||||
</div>
|
</div>
|
||||||
</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]}>
|
<ReactMarkdown remarkPlugins={[remarkMath]} rehypePlugins={[rehypeKatex, rehypeRaw]}>
|
||||||
{currentQuestion.text_en}
|
{currentQuestion.text_en}
|
||||||
</ReactMarkdown>
|
</ReactMarkdown>
|
||||||
@@ -478,14 +478,14 @@ export default function CBTExamPage({ params }: { params: Promise<{ examId: stri
|
|||||||
{currentQuestion.type === 'Numerical' ? (
|
{currentQuestion.type === 'Numerical' ? (
|
||||||
<input
|
<input
|
||||||
type="text"
|
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..."
|
placeholder="Type your numerical answer here..."
|
||||||
value={currentState?.selected_answer || ""}
|
value={currentState?.selected_answer || ""}
|
||||||
onChange={(e) => selectAnswer(currentQuestion.id, e.target.value)}
|
onChange={(e) => selectAnswer(currentQuestion.id, e.target.value)}
|
||||||
/>
|
/>
|
||||||
) : currentQuestion.type === 'Subjective' ? (
|
) : currentQuestion.type === 'Subjective' ? (
|
||||||
<textarea
|
<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..."
|
placeholder="Type your answer or explanation here..."
|
||||||
value={currentState?.selected_answer || ""}
|
value={currentState?.selected_answer || ""}
|
||||||
onChange={(e) => selectAnswer(currentQuestion.id, e.target.value)}
|
onChange={(e) => selectAnswer(currentQuestion.id, e.target.value)}
|
||||||
@@ -502,16 +502,16 @@ export default function CBTExamPage({ params }: { params: Promise<{ examId: stri
|
|||||||
<button
|
<button
|
||||||
key={idx}
|
key={idx}
|
||||||
onClick={() => handleOptionSelect(currentQuestion.id, letter, currentQuestion.type)}
|
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'}
|
${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'}
|
${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}
|
{letter}
|
||||||
</div>
|
</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]}>
|
<ReactMarkdown remarkPlugins={[remarkMath]} rehypePlugins={[rehypeKatex, rehypeRaw]}>
|
||||||
{renderLatex(option)}
|
{renderLatex(option)}
|
||||||
</ReactMarkdown>
|
</ReactMarkdown>
|
||||||
@@ -544,7 +544,7 @@ export default function CBTExamPage({ params }: { params: Promise<{ examId: stri
|
|||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</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]}>
|
<ReactMarkdown remarkPlugins={[remarkMath]} rehypePlugins={[rehypeKatex, rehypeRaw]}>
|
||||||
{currentQuestion.text_hi}
|
{currentQuestion.text_hi}
|
||||||
</ReactMarkdown>
|
</ReactMarkdown>
|
||||||
@@ -561,14 +561,14 @@ export default function CBTExamPage({ params }: { params: Promise<{ examId: stri
|
|||||||
{currentQuestion.type === 'Numerical' ? (
|
{currentQuestion.type === 'Numerical' ? (
|
||||||
<input
|
<input
|
||||||
type="text"
|
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="अपना संख्यात्मक उत्तर यहाँ टाइप करें..."
|
placeholder="अपना संख्यात्मक उत्तर यहाँ टाइप करें..."
|
||||||
value={currentState?.selected_answer || ""}
|
value={currentState?.selected_answer || ""}
|
||||||
onChange={(e) => selectAnswer(currentQuestion.id, e.target.value)}
|
onChange={(e) => selectAnswer(currentQuestion.id, e.target.value)}
|
||||||
/>
|
/>
|
||||||
) : currentQuestion.type === 'Subjective' ? (
|
) : currentQuestion.type === 'Subjective' ? (
|
||||||
<textarea
|
<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="अपना उत्तर या स्पष्टीकरण यहाँ टाइप करें..."
|
placeholder="अपना उत्तर या स्पष्टीकरण यहाँ टाइप करें..."
|
||||||
value={currentState?.selected_answer || ""}
|
value={currentState?.selected_answer || ""}
|
||||||
onChange={(e) => selectAnswer(currentQuestion.id, e.target.value)}
|
onChange={(e) => selectAnswer(currentQuestion.id, e.target.value)}
|
||||||
@@ -585,16 +585,16 @@ export default function CBTExamPage({ params }: { params: Promise<{ examId: stri
|
|||||||
<button
|
<button
|
||||||
key={idx}
|
key={idx}
|
||||||
onClick={() => handleOptionSelect(currentQuestion.id, letter, currentQuestion.type)}
|
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'}
|
${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'}
|
${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}
|
{letter}
|
||||||
</div>
|
</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]}>
|
<ReactMarkdown remarkPlugins={[remarkMath]} rehypePlugins={[rehypeKatex, rehypeRaw]}>
|
||||||
{renderLatex(option)}
|
{renderLatex(option)}
|
||||||
</ReactMarkdown>
|
</ReactMarkdown>
|
||||||
|
|||||||
@@ -2,16 +2,13 @@ import { Button } from "@/components/ui/button"
|
|||||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
|
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
|
||||||
import Link from "next/link"
|
import Link from "next/link"
|
||||||
import { ArrowLeft, Mail, MapPin, Phone } from "lucide-react"
|
import { ArrowLeft, Mail, MapPin, Phone } from "lucide-react"
|
||||||
|
import { PublicLayout } from "@/components/Landing/PublicLayout"
|
||||||
|
|
||||||
export default function ContactPage() {
|
export default function ContactPage() {
|
||||||
return (
|
return (
|
||||||
<div className="min-h-screen bg-background text-foreground flex flex-col">
|
<PublicLayout>
|
||||||
<header className="border-b border-border bg-background px-6 py-4 flex items-center justify-between sticky top-0 z-10">
|
<div className="bg-background text-foreground flex flex-col pt-24 pb-16 min-h-[70vh]">
|
||||||
<Link href="/" className="text-muted-foreground hover:text-foreground flex items-center gap-2 transition-colors font-medium">
|
<main className="flex-1 flex flex-col items-center justify-center p-8 animate-in fade-in duration-700">
|
||||||
<ArrowLeft size={16} /> Back to Home
|
|
||||||
</Link>
|
|
||||||
</header>
|
|
||||||
<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>
|
<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">
|
<p className="text-lg text-muted-foreground max-w-2xl text-center mb-12">
|
||||||
Have a question or need support? We're here to help you ace your preparation.
|
Have a question or need support? We're here to help you ace your preparation.
|
||||||
@@ -36,13 +33,14 @@ export default function ContactPage() {
|
|||||||
<div className="flex items-center gap-3 text-sm text-muted-foreground font-medium">
|
<div className="flex items-center gap-3 text-sm text-muted-foreground font-medium">
|
||||||
<Mail size={16} className="text-primary" /> support@pragativeer.com
|
<Mail size={16} className="text-primary" /> support@pragativeer.com
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-3 text-sm text-muted-foreground font-medium">
|
<div className="flex items-center gap-3 text-sm text-muted-foreground font-medium mt-2">
|
||||||
<MapPin size={16} className="text-primary" /> Tech Hub, Bengaluru, India
|
<MapPin size={16} className="text-primary" /> Pune, Maharashtra
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
</main>
|
</main>
|
||||||
</div>
|
</div>
|
||||||
|
</PublicLayout>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -115,7 +115,7 @@ export default function AIComCBTDPPPage() {
|
|||||||
<div>
|
<div>
|
||||||
<div className="flex items-center gap-2 mb-1">
|
<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">
|
<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>
|
||||||
<span className="text-xs font-bold text-muted-foreground">• {user?.target_exam?.replace("_", " ") || "JEE/NEET"}</span>
|
<span className="text-xs font-bold text-muted-foreground">• {user?.target_exam?.replace("_", " ") || "JEE/NEET"}</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -180,7 +180,7 @@ export default function AIComCBTDPPPage() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="space-y-4">
|
<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}
|
{sampleQuestions[activeQuestion].question}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
@@ -203,13 +203,13 @@ export default function AIComCBTDPPPage() {
|
|||||||
<div
|
<div
|
||||||
key={optIdx}
|
key={optIdx}
|
||||||
onClick={() => handleOptionSelect(activeQuestion, 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}
|
{letter}
|
||||||
</span>
|
</span>
|
||||||
<span className="text-base font-medium flex-1">{opt}</span>
|
<span className="text-lg md:text-xl font-bold flex-1">{opt}</span>
|
||||||
{submitted && isCorrect && <CheckCircle2 size={20} className="text-emerald-400" />}
|
{submitted && isCorrect && <CheckCircle2 size={22} className="text-emerald-400" />}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
})}
|
})}
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
import AttemptHistoryPage from "@/app/dashboard/cbt/history/[attemptId]/page";
|
||||||
|
|
||||||
|
export default function AIHistoryPage({ params }: { params: Promise<{ attemptId: string }> }) {
|
||||||
|
return <AttemptHistoryPage params={params} />;
|
||||||
|
}
|
||||||
@@ -2,19 +2,21 @@
|
|||||||
|
|
||||||
import { Button } from "@/components/ui/button"
|
import { Button } from "@/components/ui/button"
|
||||||
import { Card, CardContent } from "@/components/ui/card"
|
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 { useEffect, useState } from "react"
|
||||||
import Link from "next/link"
|
import Link from "next/link"
|
||||||
import { useActiveUser } from "@/hooks/useActiveUser"
|
import { useActiveUser } from "@/hooks/useActiveUser"
|
||||||
|
|
||||||
export default function PrepPilotAIPage() {
|
export default function AIDailyPracticePage() {
|
||||||
const { user, userId } = useActiveUser()
|
const { user, userId } = useActiveUser()
|
||||||
const [dpps, setDpps] = useState<any[]>([])
|
const [dpps, setDpps] = useState<any[]>([])
|
||||||
const [attempts, setAttempts] = useState<any[]>([])
|
const [attempts, setAttempts] = useState<any[]>([])
|
||||||
const [loading, setLoading] = useState(true)
|
const [loading, setLoading] = useState(true)
|
||||||
const [dateFilter, setDateFilter] = useState("")
|
const [dateFilter, setDateFilter] = useState("")
|
||||||
const [selectedPdfDpp, setSelectedPdfDpp] = useState<any | null>(null)
|
const [selectedPdfDpp, setSelectedPdfDpp] = useState<any | null>(null)
|
||||||
|
const [isFullScreen, setIsFullScreen] = useState(false)
|
||||||
|
const [copiedShare, setCopiedShare] = useState(false)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
async function fetchDpps() {
|
async function fetchDpps() {
|
||||||
@@ -90,13 +92,26 @@ export default function PrepPilotAIPage() {
|
|||||||
const cleanTitle = (title: string) => title.replace(/\s*\(ExamEnum\.[^)]+\)/, '');
|
const cleanTitle = (title: string) => title.replace(/\s*\(ExamEnum\.[^)]+\)/, '');
|
||||||
|
|
||||||
return (
|
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-6 md: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 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">
|
{/* Page Header */}
|
||||||
<div>
|
<div className="border-b border-border/50 pb-6">
|
||||||
<h1 className="text-3xl font-bold tracking-tight mb-2">PrepPilot AI <span className="text-[#00F0FF] text-xl">&</span> CBT Simulator</h1>
|
<div className="flex items-start justify-between gap-4 flex-wrap">
|
||||||
<p className="text-muted-foreground font-medium max-w-xl">
|
<div>
|
||||||
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">
|
||||||
</p>
|
<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>
|
</div>
|
||||||
|
|
||||||
@@ -114,12 +129,9 @@ export default function PrepPilotAIPage() {
|
|||||||
{diff}
|
{diff}
|
||||||
</button>
|
</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>
|
</div>
|
||||||
<Button variant="outline" className="gap-2 rounded-xl text-sm font-bold">
|
<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>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -200,7 +212,7 @@ export default function PrepPilotAIPage() {
|
|||||||
{!isExpired && (
|
{!isExpired && (
|
||||||
<Link href={`/cbt/${dpp.id}`}>
|
<Link href={`/cbt/${dpp.id}`}>
|
||||||
<Button variant="secondary" size="sm" className="font-bold bg-[#00F0FF]/10 text-[#00F0FF] hover:bg-[#00F0FF]/20 gap-2">
|
<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>
|
</Button>
|
||||||
</Link>
|
</Link>
|
||||||
)}
|
)}
|
||||||
@@ -208,7 +220,7 @@ export default function PrepPilotAIPage() {
|
|||||||
) : (
|
) : (
|
||||||
<Link href={`/cbt/${dpp.id}`}>
|
<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)]">
|
<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>
|
</Button>
|
||||||
</Link>
|
</Link>
|
||||||
)}
|
)}
|
||||||
@@ -220,18 +232,18 @@ export default function PrepPilotAIPage() {
|
|||||||
|
|
||||||
{/* ── PROTECTED PDF READER MODAL (FREE TIER) ── */}
|
{/* ── PROTECTED PDF READER MODAL (FREE TIER) ── */}
|
||||||
{selectedPdfDpp && (
|
{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={`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 rounded-3xl w-full max-w-4xl max-h-[90vh] flex flex-col overflow-hidden shadow-2xl">
|
<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 */}
|
{/* 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="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} />
|
<FileText size={22} />
|
||||||
</div>
|
</div>
|
||||||
<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}
|
{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
|
Protected In-App PDF View
|
||||||
</span>
|
</span>
|
||||||
</h3>
|
</h3>
|
||||||
@@ -240,24 +252,46 @@ export default function PrepPilotAIPage() {
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<Button
|
<div className="flex items-center gap-2">
|
||||||
onClick={() => setSelectedPdfDpp(null)}
|
<Button
|
||||||
variant="outline"
|
size="sm"
|
||||||
size="icon"
|
variant="outline"
|
||||||
className="rounded-full border-border hover:bg-secondary"
|
onClick={() => {
|
||||||
>
|
navigator.clipboard.writeText(window.location.origin + '/dashboard/ai-coach/cbt-dpp');
|
||||||
<X size={18} />
|
setCopiedShare(true);
|
||||||
</Button>
|
setTimeout(() => setCopiedShare(false), 2000);
|
||||||
|
}}
|
||||||
|
className="rounded-xl font-bold text-xs gap-1.5 h-9 px-3"
|
||||||
|
>
|
||||||
|
{copiedShare ? <CheckCircle2 size={14} className="text-green-500" /> : <Share2 size={14} />}
|
||||||
|
<span>{copiedShare ? "Copied" : "Share PDF"}</span>
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
size="sm"
|
||||||
|
variant="outline"
|
||||||
|
onClick={() => setIsFullScreen(!isFullScreen)}
|
||||||
|
className="rounded-xl font-bold text-xs gap-1.5 h-9 px-3 bg-primary/10 text-primary border-primary/20 hover:bg-primary/20"
|
||||||
|
>
|
||||||
|
{isFullScreen ? <Minimize2 size={14} /> : <Maximize2 size={14} />}
|
||||||
|
<span>{isFullScreen ? "Exit Fullscreen" : "Fullscreen"}</span>
|
||||||
|
</Button>
|
||||||
|
<button
|
||||||
|
onClick={() => { setSelectedPdfDpp(null); setIsFullScreen(false); }}
|
||||||
|
className="ml-1 flex-shrink-0 w-9 h-9 rounded-full border border-border bg-secondary flex items-center justify-center text-foreground hover:bg-destructive hover:border-destructive hover:text-white transition-all"
|
||||||
|
>
|
||||||
|
<X size={18} />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Security Notice Banner */}
|
{/* 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">
|
<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">
|
<span className="flex items-center gap-2 truncate">
|
||||||
<ShieldAlert size={16} /> Read-Only Mode (Free Tier) — Downloads, text copying, and sharing are restricted.
|
<ShieldAlert size={16} className="shrink-0" /> Read-Only Mode (Free Tier) — Downloads & direct copying are restricted.
|
||||||
</span>
|
</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">
|
<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>
|
</Button>
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
@@ -265,27 +299,33 @@ export default function PrepPilotAIPage() {
|
|||||||
{/* Protected PDF Document Sheet Content (select-none) */}
|
{/* Protected PDF Document Sheet Content (select-none) */}
|
||||||
<div
|
<div
|
||||||
onContextMenu={(e) => e.preventDefault()}
|
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
|
<embed
|
||||||
src={getPdfUrl(selectedPdfDpp.title)}
|
src={getPdfUrl(selectedPdfDpp.title)}
|
||||||
type="application/pdf"
|
type="application/pdf"
|
||||||
className="w-full h-full rounded-b-lg"
|
className="w-full h-full"
|
||||||
|
style={{height: isFullScreen ? 'calc(100vh - 120px)' : '75vh'}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Modal Footer */}
|
{/* Modal Footer */}
|
||||||
<div className="p-4 border-t border-border bg-secondary/30 flex items-center justify-between">
|
<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">
|
<span className="text-xs text-muted-foreground font-medium flex items-center gap-2">
|
||||||
📄 PragatiVeer Protected PDF Reader v2.0
|
📄 PragatiVeer Protected PDF Reader v2.0
|
||||||
</span>
|
</span>
|
||||||
<div className="flex gap-3">
|
<div className="flex gap-2 items-center">
|
||||||
<Button onClick={() => setSelectedPdfDpp(null)} variant="outline" className="rounded-xl font-bold">
|
<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">
|
||||||
Close PDF Reader
|
{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>
|
</Button>
|
||||||
<Link href="/upgrade">
|
<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)]">
|
<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={16} className="mr-2" /> Upgrade to AI Coach for Interactive CBT
|
<Sparkles size={14} className="mr-1.5" /> Upgrade to AI Coach
|
||||||
</Button>
|
</Button>
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -2,51 +2,131 @@
|
|||||||
|
|
||||||
import { CollegePredictorCard } from "@/components/AI/CollegePredictorCard"
|
import { CollegePredictorCard } from "@/components/AI/CollegePredictorCard"
|
||||||
import { Card, CardContent } from "@/components/ui/card"
|
import { Card, CardContent } from "@/components/ui/card"
|
||||||
import { Button } from "@/components/ui/button"
|
import { GraduationCap, Building2, Award, CheckCircle2, TrendingUp, MapPin, Users, BarChart2 } from "lucide-react"
|
||||||
import { GraduationCap, ArrowLeft, Building2, MapPin, Award, CheckCircle2 } from "lucide-react"
|
|
||||||
import Link from "next/link"
|
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() {
|
export default function CollegePredictorPage() {
|
||||||
|
const maxRank = Math.max(...CUTOFF_TRENDS.map(d => d.rank))
|
||||||
|
|
||||||
return (
|
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">
|
<div className="p-6 md:p-8 space-y-8 max-w-7xl mx-auto animate-in fade-in duration-500 min-h-screen">
|
||||||
{/* Navigation Header */}
|
{/* Page Header */}
|
||||||
<div className="border-b border-border/50 pb-6">
|
<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">
|
<div className="flex items-start justify-between gap-4 flex-wrap">
|
||||||
<GraduationCap className="text-[#00F0FF]" size={36} />
|
<div>
|
||||||
AI College & Cutoff Predictor
|
<h1 className="text-3xl md:text-4xl font-black tracking-tight flex items-center gap-3 mb-2">
|
||||||
</h1>
|
<span className="p-2.5 rounded-xl bg-[#00F0FF]/10 border border-[#00F0FF]/20">
|
||||||
<p className="text-muted-foreground text-base md:text-lg mt-1 font-medium max-w-3xl">
|
<GraduationCap className="text-[#00F0FF]" size={28} />
|
||||||
Discover your probable target medical & engineering colleges based on current mock exam scores and historical cutoffs.
|
</span>
|
||||||
</p>
|
AI College & Cutoff Predictor
|
||||||
|
</h1>
|
||||||
|
<p className="text-muted-foreground text-base font-medium max-w-2xl pl-1">
|
||||||
|
Discover your probable target medical & engineering colleges based on current mock exam scores and historical cutoffs.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<span className="text-xs font-bold uppercase tracking-wider text-[#00F0FF] bg-[#00F0FF]/10 border border-[#00F0FF]/20 px-3 py-1.5 rounded-full flex items-center gap-1.5 shrink-0 self-start">
|
||||||
|
<GraduationCap size={12} /> AI Powered
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Summary KPI Strip */}
|
||||||
|
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
|
||||||
|
{[
|
||||||
|
{ label: "Predicted Rank", value: "~4,200", icon: <TrendingUp size={18} className="text-[#00F0FF]" />, color: "border-[#00F0FF]/20 bg-[#00F0FF]/5" },
|
||||||
|
{ label: "Target Colleges", value: "12", icon: <Building2 size={18} className="text-amber-400" />, color: "border-amber-400/20 bg-amber-400/5" },
|
||||||
|
{ label: "Reach Colleges", value: "5", icon: <MapPin size={18} className="text-purple-400" />, color: "border-purple-400/20 bg-purple-400/5" },
|
||||||
|
{ label: "Safe Colleges", value: "18", icon: <CheckCircle2 size={18} className="text-green-400" />, color: "border-green-400/20 bg-green-400/5" },
|
||||||
|
].map((kpi) => (
|
||||||
|
<Card key={kpi.label} className={`rounded-2xl border ${kpi.color} hover:scale-[1.02] transition-transform`}>
|
||||||
|
<CardContent className="p-5 flex flex-col gap-2">
|
||||||
|
{kpi.icon}
|
||||||
|
<div className="text-2xl font-black">{kpi.value}</div>
|
||||||
|
<div className="text-xs font-semibold text-muted-foreground uppercase tracking-wider">{kpi.label}</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="grid grid-cols-1 lg:grid-cols-3 gap-8">
|
<div className="grid grid-cols-1 lg:grid-cols-3 gap-8">
|
||||||
<div className="lg:col-span-2 space-y-8">
|
<div className="lg:col-span-2 space-y-8">
|
||||||
<Card className="p-8 bg-secondary/10 border-border/60 rounded-3xl">
|
{/* College Predictor */}
|
||||||
<h2 className="text-2xl font-extrabold tracking-tight mb-6 flex items-center gap-2">
|
<CollegePredictorCard />
|
||||||
<Building2 className="text-[#00F0FF]" size={24} /> Target College Analytics
|
|
||||||
</h2>
|
{/* Cutoff Trend Chart */}
|
||||||
<CollegePredictorCard />
|
<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>
|
</Card>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="space-y-6">
|
<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">
|
<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">
|
<h3 className="text-base font-black tracking-tight text-[#00F0FF] flex items-center gap-2">
|
||||||
<Award size={20} /> Cutoff Modeling Parameters
|
<Award size={18} /> Cutoff Modelling Parameters
|
||||||
</h3>
|
</h3>
|
||||||
<ul className="space-y-3 text-sm font-medium text-muted-foreground">
|
<ul className="space-y-3 text-sm font-medium text-muted-foreground">
|
||||||
<li className="flex items-start gap-2">
|
<li className="flex items-start gap-2">
|
||||||
<CheckCircle2 size={16} className="text-[#00F0FF] shrink-0 mt-0.5" />
|
<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>
|
||||||
<li className="flex items-start gap-2">
|
<li className="flex items-start gap-2">
|
||||||
<CheckCircle2 size={16} className="text-[#00F0FF] shrink-0 mt-0.5" />
|
<CheckCircle2 size={16} className="text-[#00F0FF] shrink-0 mt-0.5" />
|
||||||
<span>Supports All India Quota (AIQ) & State Quota adjustments.</span>
|
<span>Supports <strong className="text-foreground">All India Quota (AIQ)</strong> & State Quota seat adjustments.</span>
|
||||||
</li>
|
</li>
|
||||||
<li className="flex items-start gap-2">
|
<li className="flex items-start gap-2">
|
||||||
<CheckCircle2 size={16} className="text-[#00F0FF] shrink-0 mt-0.5" />
|
<CheckCircle2 size={16} className="text-[#00F0FF] shrink-0 mt-0.5" />
|
||||||
<span>Simulates category reservation cutoffs (General, OBC, SC, ST, EWS).</span>
|
<span>Simulates <strong className="text-foreground">category reservation cutoffs</strong>: General, OBC, SC, ST, EWS.</span>
|
||||||
|
</li>
|
||||||
|
<li className="flex items-start gap-2">
|
||||||
|
<CheckCircle2 size={16} className="text-[#00F0FF] shrink-0 mt-0.5" />
|
||||||
|
<span>Score updated daily as you complete more mock tests.</span>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
<Card className="p-6 bg-secondary/10 border-border/60 rounded-3xl space-y-3">
|
||||||
|
<h3 className="text-base font-black text-muted-foreground uppercase tracking-wider flex items-center gap-2">
|
||||||
|
<Users size={16} className="text-amber-400" /> Improve Your Rank
|
||||||
|
</h3>
|
||||||
|
<ul className="space-y-2.5 text-sm text-muted-foreground font-medium">
|
||||||
|
<li className="flex items-start gap-2">
|
||||||
|
<span className="text-amber-400 font-black shrink-0">+15 marks</span>
|
||||||
|
<span>in Maths → upgrades you from NIT Trichy to <strong className="text-foreground">IIT Roorkee</strong></span>
|
||||||
|
</li>
|
||||||
|
<li className="flex items-start gap-2">
|
||||||
|
<span className="text-[#00F0FF] font-black shrink-0">Fix Optics</span>
|
||||||
|
<span>errors to recover ~8 marks in Physics alone</span>
|
||||||
|
</li>
|
||||||
|
<li className="flex items-start gap-2">
|
||||||
|
<span className="text-green-400 font-black shrink-0">Boost Speed</span>
|
||||||
|
<span>from 45s → 38s/Q gives you ~12 extra attempt opportunities</span>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</Card>
|
</Card>
|
||||||
|
|||||||
@@ -1,24 +1,156 @@
|
|||||||
"use client"
|
"use client"
|
||||||
|
|
||||||
import { MistakeHeatmap } from "@/components/AI/MistakeHeatmap"
|
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"
|
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() {
|
export default function HeatmapPage() {
|
||||||
return (
|
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">
|
<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">
|
<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">
|
<div className="flex items-start justify-between gap-4 flex-wrap">
|
||||||
<Flame className="text-yellow-500" size={36} />
|
<div>
|
||||||
Mistake Heatmap Vault
|
<h1 className="text-3xl md:text-4xl font-black tracking-tight flex items-center gap-3 mb-2">
|
||||||
</h1>
|
<span className="p-2.5 rounded-xl bg-amber-500/10 border border-amber-500/20">
|
||||||
<p className="text-muted-foreground text-base md:text-lg mt-1 font-medium max-w-3xl">
|
<Flame className="text-amber-500" size={28} />
|
||||||
Visual representation of your error density across subjects and topics.
|
</span>
|
||||||
</p>
|
Mistake Heatmap Vault
|
||||||
|
</h1>
|
||||||
|
<p className="text-muted-foreground text-base font-medium max-w-2xl pl-1">
|
||||||
|
Visual density map of your errors across subjects and topics — pinpoint your weakest areas instantly.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<span className="text-xs font-bold uppercase tracking-wider text-amber-500 bg-amber-500/10 border border-amber-500/20 px-3 py-1.5 rounded-full flex items-center gap-1.5 shrink-0 self-start">
|
||||||
|
<AlertTriangle size={12} /> Error Density Analysis
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="max-w-4xl mx-auto">
|
{/* KPI Strip */}
|
||||||
<MistakeHeatmap />
|
<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>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -5,9 +5,8 @@ import { MistakeHeatmap } from "@/components/AI/MistakeHeatmap"
|
|||||||
import { MistakeReviewCard, MistakePayload } from "@/components/AI/MistakeReviewCard"
|
import { MistakeReviewCard, MistakePayload } from "@/components/AI/MistakeReviewCard"
|
||||||
import { Card, CardContent } from "@/components/ui/card"
|
import { Card, CardContent } from "@/components/ui/card"
|
||||||
import { Button } from "@/components/ui/button"
|
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 { useActiveUser } from "@/hooks/useActiveUser"
|
||||||
import Link from "next/link"
|
|
||||||
|
|
||||||
export default function MistakeTrackingPage() {
|
export default function MistakeTrackingPage() {
|
||||||
const [mistakes, setMistakes] = useState<MistakePayload[]>([])
|
const [mistakes, setMistakes] = useState<MistakePayload[]>([])
|
||||||
@@ -23,8 +22,7 @@ export default function MistakeTrackingPage() {
|
|||||||
if (!res.ok) throw new Error("Failed to fetch")
|
if (!res.ok) throw new Error("Failed to fetch")
|
||||||
const data = await res.json()
|
const data = await res.json()
|
||||||
setMistakes(data)
|
setMistakes(data)
|
||||||
} catch (error) {
|
} catch {
|
||||||
console.error(error)
|
|
||||||
setMistakes([])
|
setMistakes([])
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false)
|
setLoading(false)
|
||||||
@@ -32,9 +30,7 @@ export default function MistakeTrackingPage() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!userLoading && userId) {
|
if (!userLoading && userId) fetchMistakes()
|
||||||
fetchMistakes()
|
|
||||||
}
|
|
||||||
}, [userId, userLoading])
|
}, [userId, userLoading])
|
||||||
|
|
||||||
const submitReview = async (mistakeId: string, selectedAnswer: string) => {
|
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 (
|
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">
|
<div className="p-6 md:p-8 space-y-8 max-w-7xl mx-auto animate-in fade-in duration-500 min-h-screen">
|
||||||
{/* Navigation Header */}
|
{/* Page 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="border-b border-border/50 pb-6">
|
||||||
<div>
|
<div className="flex items-start justify-between gap-4 flex-wrap">
|
||||||
<h1 className="text-3xl md:text-5xl font-black tracking-tight flex items-center gap-3">
|
<div>
|
||||||
<BrainCircuit className="text-[#00F0FF]" size={36} />
|
<h1 className="text-3xl md:text-4xl font-black tracking-tight flex items-center gap-3 mb-2">
|
||||||
AI Mistake Vault & SM-2 Engine
|
<span className="p-2.5 rounded-xl bg-[#00F0FF]/10 border border-[#00F0FF]/20">
|
||||||
</h1>
|
<BrainCircuit className="text-[#00F0FF]" size={28} />
|
||||||
<p className="text-muted-foreground text-base md:text-lg mt-1 font-medium">
|
</span>
|
||||||
Review logged errors, practice spaced-repetition cards, and observe subject heatmap trends.
|
Mistake Review & Spaced Repetition
|
||||||
</p>
|
</h1>
|
||||||
</div>
|
<p className="text-muted-foreground text-base font-medium max-w-2xl pl-1">
|
||||||
<div className="flex items-center gap-3 shrink-0">
|
Review logged errors using our SM-2 spaced repetition engine. Questions resurface at optimal intervals to cement long-term retention.
|
||||||
<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">
|
</p>
|
||||||
<Sparkles size={16} /> SM-2 Adaptive Algorithm Active
|
|
||||||
</div>
|
</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>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Main Review Workspace */}
|
{/* KPI Strip */}
|
||||||
<div className="grid grid-cols-1 lg:grid-cols-3 gap-8">
|
{!loading && (
|
||||||
<div className="lg:col-span-2 space-y-6">
|
<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">
|
<div className="flex items-center justify-between">
|
||||||
<h2 className="text-2xl font-extrabold tracking-tight flex items-center gap-2">
|
<h2 className="text-lg font-extrabold tracking-tight flex items-center gap-2 text-muted-foreground uppercase tracking-wider text-sm">
|
||||||
<Flame className="text-amber-500" size={24} /> Today's Active Review Queue
|
<Clock size={16} className="text-amber-400" />
|
||||||
|
Active Review Queue
|
||||||
</h2>
|
</h2>
|
||||||
<span className="text-sm font-bold text-[#00F0FF] bg-[#00F0FF]/10 px-4 py-1.5 rounded-full border border-[#00F0FF]/20">
|
{mistakes.length > 0 && (
|
||||||
{currentIndex < mistakes.length ? `Question ${currentIndex + 1} of ${mistakes.length}` : "Completed"}
|
<span className="text-sm font-bold text-[#00F0FF] bg-[#00F0FF]/10 px-4 py-1.5 rounded-full border border-[#00F0FF]/20">
|
||||||
</span>
|
{currentIndex < mistakes.length ? `${currentIndex + 1} / ${mistakes.length}` : "Complete"}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{loading ? (
|
{loading ? (
|
||||||
<Card className="p-16 flex items-center justify-center border-dashed">
|
<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] mr-3" size={24} />
|
<RefreshCw className="animate-spin text-[#00F0FF]" size={28} />
|
||||||
<span className="text-lg font-bold">Loading your active error queue...</span>
|
<span className="text-base font-semibold text-muted-foreground">Loading your review queue...</span>
|
||||||
</Card>
|
</Card>
|
||||||
) : mistakes.length === 0 ? (
|
) : mistakes.length === 0 ? (
|
||||||
<Card className="p-16 text-center border-dashed bg-secondary/10 rounded-3xl">
|
<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="mx-auto text-green-400 mb-4 opacity-80" />
|
<CheckCircle2 size={56} className="text-green-400 opacity-80" />
|
||||||
<h3 className="text-2xl font-extrabold mb-2">Zero Mistakes Pending!</h3>
|
<div>
|
||||||
<p className="text-muted-foreground text-base max-w-md mx-auto mb-6">
|
<h3 className="text-xl font-extrabold mb-2">Queue Complete</h3>
|
||||||
All scheduled errors have been reviewed. Solve more mock tests to automatically surface weak questions here.
|
<p className="text-muted-foreground text-sm max-w-md mx-auto font-medium">
|
||||||
</p>
|
All scheduled reviews for today are done. Solve more practice tests to surface new questions here.
|
||||||
<Button onClick={() => window.location.reload()} variant="outline" className="rounded-xl font-bold">
|
</p>
|
||||||
|
</div>
|
||||||
|
<Button onClick={() => window.location.reload()} variant="outline" className="rounded-xl font-bold h-10 px-6">
|
||||||
Refresh Queue
|
Refresh Queue
|
||||||
</Button>
|
</Button>
|
||||||
</Card>
|
</Card>
|
||||||
@@ -110,21 +152,27 @@ export default function MistakeTrackingPage() {
|
|||||||
onSubmitReview={submitReview}
|
onSubmitReview={submitReview}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<Card className="p-16 text-center border-dashed bg-[#00F0FF]/5 border-[#00F0FF]/30 rounded-3xl">
|
<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="mx-auto text-[#00F0FF] mb-4" />
|
<BrainCircuit size={56} className="text-[#00F0FF]" />
|
||||||
<h3 className="text-2xl font-extrabold mb-2 text-[#00F0FF]">Review Session Mastered 🎉</h3>
|
<div>
|
||||||
<p className="text-muted-foreground text-base max-w-md mx-auto mb-6">
|
<h3 className="text-xl font-extrabold mb-2 text-[#00F0FF]">Session Complete 🎉</h3>
|
||||||
Fantastic focus! Your memory decay intervals have been updated in the database.
|
<p className="text-muted-foreground text-sm max-w-md mx-auto font-medium">
|
||||||
</p>
|
All {mistakes.length} questions reviewed. The SM-2 engine has scheduled your next review intervals.
|
||||||
<Button onClick={() => window.location.reload()} className="bg-[#00F0FF] text-black font-bold rounded-xl px-6">
|
</p>
|
||||||
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>
|
</Button>
|
||||||
</Card>
|
</Card>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Heatmap & Info Column */}
|
{/* Heatmap Column */}
|
||||||
<div className="space-y-6">
|
<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 />
|
<MistakeHeatmap />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -3,75 +3,169 @@
|
|||||||
import { ReadinessGauge } from "@/components/AI/ReadinessGauge"
|
import { ReadinessGauge } from "@/components/AI/ReadinessGauge"
|
||||||
import { Card, CardContent } from "@/components/ui/card"
|
import { Card, CardContent } from "@/components/ui/card"
|
||||||
import { Button } from "@/components/ui/button"
|
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"
|
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() {
|
export default function ReadinessPage() {
|
||||||
|
const maxScore = Math.max(...SCORE_HISTORY.map(d => d.score))
|
||||||
|
|
||||||
return (
|
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">
|
<div className="p-6 md:p-8 space-y-8 max-w-7xl mx-auto animate-in fade-in duration-500 min-h-screen">
|
||||||
{/* Navigation Header */}
|
{/* Page Header */}
|
||||||
<div className="border-b border-border/50 pb-6">
|
<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">
|
<div className="flex items-start justify-between gap-4 flex-wrap">
|
||||||
<Gauge className="text-[#00F0FF]" size={36} />
|
<div>
|
||||||
Exam Readiness Analytics
|
<h1 className="text-3xl md:text-4xl font-black tracking-tight flex items-center gap-3 mb-2">
|
||||||
</h1>
|
<span className="p-2.5 rounded-xl bg-[#00F0FF]/10 border border-[#00F0FF]/20">
|
||||||
<p className="text-muted-foreground text-base md:text-lg mt-1 font-medium max-w-3xl">
|
<Gauge className="text-[#00F0FF]" size={28} />
|
||||||
Real-time algorithmic prediction of your readiness percentage for JEE & NEET based on speed, accuracy, and test history.
|
</span>
|
||||||
</p>
|
Exam Readiness Analytics
|
||||||
|
</h1>
|
||||||
|
<p className="text-muted-foreground text-base font-medium max-w-2xl pl-1">
|
||||||
|
Real-time algorithmic prediction of your exam readiness — based on speed, accuracy, syllabus coverage, and test history.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<span className="text-xs font-bold uppercase tracking-wider text-[#00F0FF] bg-[#00F0FF]/10 border border-[#00F0FF]/20 px-3 py-1.5 rounded-full flex items-center gap-1.5 shrink-0 self-start">
|
||||||
|
<Gauge size={12} /> AI Powered
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* KPI Strip */}
|
||||||
|
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
|
||||||
|
{[
|
||||||
|
{ label: "Overall Score", value: "72 / 100", trend: "+5 this week", trendUp: true, icon: <Gauge size={20} className="text-[#00F0FF]" /> },
|
||||||
|
{ label: "Accuracy Rate", value: "88%", trend: "+3% vs last week", trendUp: true, icon: <Target size={20} className="text-amber-400" /> },
|
||||||
|
{ label: "Avg Speed / Q", value: "45 sec", trend: "↓ Needs work", trendUp: false, icon: <Clock size={20} className="text-red-400" /> },
|
||||||
|
{ label: "Syllabus Covered", value: "64%", trend: "+8% this month", trendUp: true, icon: <BookOpen size={20} className="text-emerald-400" /> },
|
||||||
|
].map((kpi) => (
|
||||||
|
<Card key={kpi.label} className="border-border/60 bg-secondary/10 rounded-2xl hover:border-[#00F0FF]/30 transition-colors">
|
||||||
|
<CardContent className="p-5">
|
||||||
|
<div className="flex items-center justify-between mb-3">
|
||||||
|
{kpi.icon}
|
||||||
|
<span className={`text-xs font-bold px-2 py-0.5 rounded-full ${kpi.trendUp ? "bg-green-500/10 text-green-400" : "bg-red-500/10 text-red-400"}`}>
|
||||||
|
{kpi.trend}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div className="text-2xl font-black mb-0.5">{kpi.value}</div>
|
||||||
|
<div className="text-xs text-muted-foreground font-semibold uppercase tracking-wider">{kpi.label}</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="grid grid-cols-1 lg:grid-cols-3 gap-8">
|
<div className="grid grid-cols-1 lg:grid-cols-3 gap-8">
|
||||||
<div className="lg:col-span-2 space-y-8">
|
<div className="lg:col-span-2 space-y-8">
|
||||||
<Card className="p-8 bg-secondary/10 border-border/60 rounded-3xl">
|
{/* Readiness Score Card */}
|
||||||
<h2 className="text-2xl font-extrabold tracking-tight mb-6 flex items-center gap-2">
|
<Card className="p-6 bg-secondary/10 border-border/60 rounded-3xl">
|
||||||
<Award className="text-amber-400" size={24} /> Overall Readiness Index
|
<div className="flex items-center gap-2 mb-5">
|
||||||
</h2>
|
<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 />
|
<ReadinessGauge />
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
{/* Subject Confidence Breakdown */}
|
{/* Subject Confidence Matrix */}
|
||||||
<Card className="p-6 bg-secondary/10 border-border/60 rounded-3xl space-y-6">
|
<Card className="p-6 bg-secondary/10 border-border/60 rounded-3xl space-y-5">
|
||||||
<h3 className="text-xl font-extrabold tracking-tight">Subject Confidence Matrix</h3>
|
<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">
|
<div className="space-y-4">
|
||||||
{[
|
{[
|
||||||
{ subject: "Biology / Maths", score: 82, color: "bg-[#00F0FF]", status: "High Readiness" },
|
{ 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" },
|
{ 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" },
|
{ subject: "Physics", score: 61, color: "bg-red-400", status: "Needs Intensive Drill", statusColor: "text-red-400" },
|
||||||
].map((sub) => (
|
].map((sub) => (
|
||||||
<div key={sub.subject} className="space-y-2">
|
<div key={sub.subject} className="space-y-2">
|
||||||
<div className="flex justify-between items-center text-sm font-bold">
|
<div className="flex justify-between items-center">
|
||||||
<span>{sub.subject}</span>
|
<span className="text-sm font-black">{sub.subject}</span>
|
||||||
<span className="text-muted-foreground">{sub.score}% • {sub.status}</span>
|
<span className={`text-xs font-bold ${sub.statusColor}`}>{sub.score}% · {sub.status}</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="h-3 w-full bg-secondary rounded-full overflow-hidden">
|
<div className="h-2.5 w-full bg-secondary rounded-full overflow-hidden">
|
||||||
<div className={`h-full ${sub.color}`} style={{ width: `${sub.score}%` }} />
|
<div className={`h-full ${sub.color} rounded-full transition-all duration-700`} style={{ width: `${sub.score}%` }} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</Card>
|
</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>
|
</div>
|
||||||
|
|
||||||
|
{/* Sidebar */}
|
||||||
<div className="space-y-6">
|
<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">
|
<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">
|
<h3 className="text-base font-black tracking-tight text-amber-400 flex items-center gap-2">
|
||||||
<ShieldAlert size={20} /> Readiness Boosters
|
<ShieldAlert size={18} /> Readiness Boosters
|
||||||
</h3>
|
</h3>
|
||||||
<ul className="space-y-3 text-sm font-medium text-muted-foreground">
|
<ul className="space-y-3.5 text-sm font-semibold text-muted-foreground">
|
||||||
<li className="flex items-start gap-2">
|
<li className="flex items-start gap-2.5">
|
||||||
<CheckCircle2 size={16} className="text-amber-400 shrink-0 mt-0.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>
|
||||||
<li className="flex items-start gap-2">
|
<li className="flex items-start gap-2">
|
||||||
<CheckCircle2 size={16} className="text-amber-400 shrink-0 mt-0.5" />
|
<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>
|
||||||
<li className="flex items-start gap-2">
|
<li className="flex items-start gap-2">
|
||||||
<CheckCircle2 size={16} className="text-amber-400 shrink-0 mt-0.5" />
|
<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>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</Card>
|
</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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -3,39 +3,133 @@
|
|||||||
import { ExamStrategyCard } from "@/components/AI/ExamStrategyCard"
|
import { ExamStrategyCard } from "@/components/AI/ExamStrategyCard"
|
||||||
import { StudyTimeOptimizationCard } from "@/components/AI/StudyTimeOptimizationCard"
|
import { StudyTimeOptimizationCard } from "@/components/AI/StudyTimeOptimizationCard"
|
||||||
import { Card, CardContent } from "@/components/ui/card"
|
import { Card, CardContent } from "@/components/ui/card"
|
||||||
import { Button } from "@/components/ui/button"
|
import { Zap, Clock, Target, Lightbulb, CheckCircle2, AlertTriangle, TrendingUp } from "lucide-react"
|
||||||
import { Zap, ArrowLeft, Clock, Target, Lightbulb, CheckCircle2 } from "lucide-react"
|
|
||||||
import Link from "next/link"
|
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() {
|
export default function StrategyPage() {
|
||||||
return (
|
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">
|
<div className="p-6 md:p-8 space-y-8 max-w-7xl mx-auto animate-in fade-in duration-500 min-h-screen">
|
||||||
{/* Navigation Header */}
|
{/* Page Header */}
|
||||||
<div className="border-b border-border/50 pb-6">
|
<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">
|
<div className="flex items-start justify-between gap-4 flex-wrap">
|
||||||
<Zap className="text-[#00F0FF]" size={36} />
|
<div>
|
||||||
Exam Strategy & Time Optimization
|
<h1 className="text-3xl md:text-4xl font-black tracking-tight flex items-center gap-3 mb-2">
|
||||||
</h1>
|
<span className="p-2.5 rounded-xl bg-[#00F0FF]/10 border border-[#00F0FF]/20">
|
||||||
<p className="text-muted-foreground text-base md:text-lg mt-1 font-medium max-w-3xl">
|
<Zap className="text-[#00F0FF]" size={28} />
|
||||||
Master time allocation per question, eliminate negative marking traps, and fine-tune your subject sequence for maximum score yield.
|
</span>
|
||||||
</p>
|
Exam Strategy & Time Optimization
|
||||||
|
</h1>
|
||||||
|
<p className="text-muted-foreground text-base font-medium max-w-2xl pl-1">
|
||||||
|
Master time allocation per question, eliminate negative marking traps, and fine-tune your subject sequence for maximum score yield.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<span className="text-xs font-bold uppercase tracking-wider text-[#00F0FF] bg-[#00F0FF]/10 border border-[#00F0FF]/20 px-3 py-1.5 rounded-full flex items-center gap-1.5 shrink-0 self-start">
|
||||||
|
<Zap size={12} /> AI Powered
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-8">
|
{/* KPI Strip */}
|
||||||
<Card className="p-6 bg-secondary/10 border-border/60 rounded-3xl">
|
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
|
||||||
<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
|
{ 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" },
|
||||||
</h2>
|
{ 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 />
|
<ExamStrategyCard />
|
||||||
</Card>
|
</div>
|
||||||
|
|
||||||
<Card className="p-6 bg-secondary/10 border-border/60 rounded-3xl">
|
<div className="space-y-3">
|
||||||
<h2 className="text-2xl font-extrabold tracking-tight mb-4 flex items-center gap-2">
|
<div className="flex items-center gap-2 text-sm font-black uppercase tracking-wider text-muted-foreground">
|
||||||
<Clock className="text-amber-400" size={24} /> Study Time Optimization
|
<Clock size={16} className="text-amber-400" />
|
||||||
</h2>
|
Daily Peak Hours Analysis
|
||||||
|
</div>
|
||||||
<StudyTimeOptimizationCard />
|
<StudyTimeOptimizationCard />
|
||||||
</Card>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Negative Marking Analysis */}
|
||||||
|
<Card className="p-6 bg-secondary/10 border-border/60 rounded-3xl">
|
||||||
|
<div className="flex items-center gap-2 mb-5">
|
||||||
|
<AlertTriangle size={20} className="text-amber-400" />
|
||||||
|
<h3 className="text-lg font-extrabold tracking-tight">Negative Marking Analysis</h3>
|
||||||
|
<span className="ml-auto text-xs text-muted-foreground font-semibold">Last Mock Test · Aug 1</span>
|
||||||
|
</div>
|
||||||
|
<div className="space-y-4">
|
||||||
|
{NEG_MARKING_DATA.map((row) => (
|
||||||
|
<div key={row.subject} className="grid grid-cols-4 gap-4 items-center p-3 rounded-xl bg-white/5 border border-border/40">
|
||||||
|
<div className="font-black text-sm">{row.subject}</div>
|
||||||
|
<div className="text-center">
|
||||||
|
<div className="text-sm font-black text-foreground">{row.attempts}</div>
|
||||||
|
<div className="text-[11px] text-muted-foreground font-semibold">Attempted</div>
|
||||||
|
</div>
|
||||||
|
<div className="text-center">
|
||||||
|
<div className="text-sm font-black text-red-400">−{row.negMarks}</div>
|
||||||
|
<div className="text-[11px] text-muted-foreground font-semibold">Neg. Marks</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div className="flex justify-between text-xs font-bold mb-1">
|
||||||
|
<span className="text-muted-foreground">Accuracy</span>
|
||||||
|
<span className={row.accuracy >= 75 ? "text-green-400" : row.accuracy >= 60 ? "text-amber-400" : "text-red-400"}>{row.accuracy}%</span>
|
||||||
|
</div>
|
||||||
|
<div className="h-2 w-full bg-secondary rounded-full overflow-hidden">
|
||||||
|
<div
|
||||||
|
className={`h-full rounded-full ${row.accuracy >= 75 ? "bg-green-400" : row.accuracy >= 60 ? "bg-amber-400" : "bg-red-400"}`}
|
||||||
|
style={{ width: `${row.accuracy}%` }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
<p className="text-xs text-muted-foreground mt-4 font-medium pl-1">
|
||||||
|
<span className="font-black text-amber-400">Insight:</span> You're losing ~29 marks to wrong answers. Switching to "skip and revisit" for uncertain Physics questions could recover 15–20 marks.
|
||||||
|
</p>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
{/* Strategy Tips */}
|
||||||
|
<Card className="p-6 bg-gradient-to-br from-[#00F0FF]/5 via-secondary/10 to-background border border-[#00F0FF]/20 rounded-3xl">
|
||||||
|
<div className="flex items-center gap-2 mb-4">
|
||||||
|
<Lightbulb size={20} className="text-yellow-400" />
|
||||||
|
<h3 className="text-base font-black tracking-tight">AI Strategy Recommendations</h3>
|
||||||
|
</div>
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
|
||||||
|
{[
|
||||||
|
{ tip: "Start with Chemistry (highest accuracy) to build momentum and bank easy marks in the first 40 minutes.", color: "text-green-400", label: "Opening Strategy" },
|
||||||
|
{ tip: "Attempt Physics only if you are 80%+ confident per question. Otherwise, mark and skip — recover in last 10 min buffer.", color: "text-red-400", label: "Physics Guard" },
|
||||||
|
{ tip: "Reserve 10–15 minutes at the end to revisit starred questions. Your review accuracy is historically 20% higher than first-attempt.", color: "text-[#00F0FF]", label: "Review Window" },
|
||||||
|
].map((s) => (
|
||||||
|
<div key={s.label} className="p-4 rounded-xl bg-white/5 border border-border/30 space-y-2">
|
||||||
|
<div className={`text-xs font-black uppercase tracking-wider ${s.color}`}>{s.label}</div>
|
||||||
|
<p className="text-sm text-muted-foreground font-medium leading-relaxed">{s.tip}</p>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,62 +3,152 @@
|
|||||||
import { StudyPlanCard } from "@/components/AI/StudyPlanCard"
|
import { StudyPlanCard } from "@/components/AI/StudyPlanCard"
|
||||||
import { WeeklyProgressReport } from "@/components/AI/WeeklyProgressReport"
|
import { WeeklyProgressReport } from "@/components/AI/WeeklyProgressReport"
|
||||||
import { Card, CardContent } from "@/components/ui/card"
|
import { Card, CardContent } from "@/components/ui/card"
|
||||||
import { Button } from "@/components/ui/button"
|
import { Calendar, BookOpen, Clock, Target, CheckCircle2, TrendingUp, Flame } from "lucide-react"
|
||||||
import { Calendar, ArrowLeft, BookOpen, Clock, Target, CheckCircle2 } from "lucide-react"
|
|
||||||
import Link from "next/link"
|
|
||||||
|
|
||||||
export default function StudyPlanPage() {
|
export default function StudyPlanPage() {
|
||||||
return (
|
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">
|
<div className="p-6 md:p-8 space-y-8 max-w-7xl mx-auto animate-in fade-in duration-500 min-h-screen">
|
||||||
{/* Navigation Header */}
|
{/* Page Header */}
|
||||||
<div className="border-b border-border/50 pb-6">
|
<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">
|
<div className="flex items-start justify-between gap-4 flex-wrap">
|
||||||
<Calendar className="text-[#00F0FF]" size={36} />
|
<div>
|
||||||
Personalized Daily & Weekly Study Plan
|
<h1 className="text-3xl md:text-4xl font-black tracking-tight flex items-center gap-3 mb-2">
|
||||||
</h1>
|
<span className="p-2.5 rounded-xl bg-[#00F0FF]/10 border border-[#00F0FF]/20">
|
||||||
<p className="text-muted-foreground text-base md:text-lg mt-1 font-medium max-w-3xl">
|
<Calendar className="text-[#00F0FF]" size={28} />
|
||||||
AI-generated task schedule dynamically tailored to fix your weak areas and maintain optimal syllabus coverage.
|
</span>
|
||||||
</p>
|
Personalized Daily & Weekly Study Plan
|
||||||
|
</h1>
|
||||||
|
<p className="text-muted-foreground text-base font-medium max-w-2xl pl-1">
|
||||||
|
AI-generated task schedule dynamically tailored to fix your weak areas and maintain optimal syllabus coverage.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<span className="text-xs font-bold uppercase tracking-wider text-[#00F0FF] bg-[#00F0FF]/10 border border-[#00F0FF]/20 px-3 py-1.5 rounded-full flex items-center gap-1.5 shrink-0 self-start">
|
||||||
|
<Calendar size={12} /> AI Powered
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* KPI Strip */}
|
||||||
|
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
|
||||||
|
{[
|
||||||
|
{ label: "Tasks Today", value: "5", sub: "2 completed", icon: <CheckCircle2 size={18} className="text-green-400" />, color: "border-green-400/20 bg-green-400/5" },
|
||||||
|
{ label: "Study Hours Today", value: "3.8 hrs", sub: "Target: 5 hrs", icon: <Clock size={18} className="text-[#00F0FF]" />, color: "border-[#00F0FF]/20 bg-[#00F0FF]/5" },
|
||||||
|
{ label: "Syllabus Coverage", value: "64%", sub: "+8% this week", icon: <Target size={18} className="text-amber-400" />, color: "border-amber-400/20 bg-amber-400/5" },
|
||||||
|
{ label: "Current Streak", value: "12 Days", sub: "Keep it up!", icon: <Flame size={18} className="text-orange-400" />, color: "border-orange-400/20 bg-orange-400/5" },
|
||||||
|
].map((kpi) => (
|
||||||
|
<Card key={kpi.label} className={`rounded-2xl border ${kpi.color} hover:scale-[1.02] transition-transform`}>
|
||||||
|
<CardContent className="p-5 flex flex-col gap-2">
|
||||||
|
{kpi.icon}
|
||||||
|
<div className="text-2xl font-black">{kpi.value}</div>
|
||||||
|
<div className="text-xs font-semibold text-muted-foreground uppercase tracking-wider">{kpi.label}</div>
|
||||||
|
<div className="text-[11px] font-bold text-muted-foreground/60">{kpi.sub}</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="grid grid-cols-1 lg:grid-cols-3 gap-8">
|
<div className="grid grid-cols-1 lg:grid-cols-3 gap-8">
|
||||||
<div className="lg:col-span-2 space-y-8">
|
<div className="lg:col-span-2 space-y-8">
|
||||||
<Card className="p-6 bg-secondary/10 border-border/60 rounded-3xl">
|
{/* Today's Schedule */}
|
||||||
<h2 className="text-2xl font-extrabold tracking-tight mb-4 flex items-center gap-2">
|
<div className="space-y-3">
|
||||||
<Clock className="text-[#00F0FF]" size={24} /> Today's Action Plan
|
<div className="flex items-center justify-between">
|
||||||
</h2>
|
<div className="flex items-center gap-2 text-sm font-black uppercase tracking-wider text-muted-foreground">
|
||||||
|
<Clock size={16} className="text-[#00F0FF]" />
|
||||||
|
Today's Schedule
|
||||||
|
</div>
|
||||||
|
<span className="text-xs font-bold text-[#00F0FF]">
|
||||||
|
{new Date().toLocaleDateString("en-IN", { weekday: "long", day: "numeric", month: "long" })}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
<StudyPlanCard />
|
<StudyPlanCard />
|
||||||
</Card>
|
</div>
|
||||||
|
|
||||||
<Card className="p-6 bg-secondary/10 border-border/60 rounded-3xl">
|
{/* Weekly Overview */}
|
||||||
<h2 className="text-2xl font-extrabold tracking-tight mb-4 flex items-center gap-2">
|
<div className="space-y-3">
|
||||||
<Target className="text-emerald-400" size={24} /> Weekly Syllabus Target
|
<div className="flex items-center gap-2 text-sm font-black uppercase tracking-wider text-muted-foreground">
|
||||||
</h2>
|
<TrendingUp size={16} className="text-emerald-400" />
|
||||||
|
Weekly Syllabus Progress
|
||||||
|
</div>
|
||||||
<WeeklyProgressReport />
|
<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>
|
</Card>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Sidebar Info */}
|
{/* Sidebar */}
|
||||||
<div className="space-y-6">
|
<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">
|
<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">
|
<h3 className="text-base font-black tracking-tight text-[#00F0FF] flex items-center gap-2">
|
||||||
<BookOpen size={20} /> AI Scheduler Rules
|
<BookOpen size={18} /> AI Scheduler Rules
|
||||||
</h3>
|
</h3>
|
||||||
<ul className="space-y-3 text-sm font-medium text-muted-foreground">
|
<ul className="space-y-3 text-sm font-medium text-muted-foreground">
|
||||||
<li className="flex items-start gap-2">
|
<li className="flex items-start gap-2">
|
||||||
<CheckCircle2 size={16} className="text-[#00F0FF] shrink-0 mt-0.5" />
|
<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>
|
||||||
<li className="flex items-start gap-2">
|
<li className="flex items-start gap-2">
|
||||||
<CheckCircle2 size={16} className="text-[#00F0FF] shrink-0 mt-0.5" />
|
<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>
|
||||||
<li className="flex items-start gap-2">
|
<li className="flex items-start gap-2">
|
||||||
<CheckCircle2 size={16} className="text-[#00F0FF] shrink-0 mt-0.5" />
|
<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>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</Card>
|
</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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,24 +1,152 @@
|
|||||||
"use client"
|
"use client"
|
||||||
|
|
||||||
import { StudyTimeOptimizationCard } from "@/components/AI/StudyTimeOptimizationCard"
|
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"
|
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() {
|
export default function TimeOptimizationPage() {
|
||||||
return (
|
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">
|
<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">
|
<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">
|
<div className="flex items-start justify-between gap-4 flex-wrap">
|
||||||
<Clock className="text-[#00F0FF]" size={36} />
|
<div>
|
||||||
Study Time Optimization
|
<h1 className="text-3xl md:text-4xl font-black tracking-tight flex items-center gap-3 mb-2">
|
||||||
</h1>
|
<span className="p-2.5 rounded-xl bg-[#00F0FF]/10 border border-[#00F0FF]/20">
|
||||||
<p className="text-muted-foreground text-base md:text-lg mt-1 font-medium max-w-3xl">
|
<Clock className="text-[#00F0FF]" size={28} />
|
||||||
Detailed metrics on your productive hours and stamina blocks.
|
</span>
|
||||||
</p>
|
Study Time Optimization
|
||||||
|
</h1>
|
||||||
|
<p className="text-muted-foreground text-base font-medium max-w-2xl pl-1">
|
||||||
|
Understand your peak productive hours and schedule your hardest topics in your highest-efficiency windows.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<span className="text-xs font-bold uppercase tracking-wider text-[#00F0FF] bg-[#00F0FF]/10 border border-[#00F0FF]/20 px-3 py-1.5 rounded-full flex items-center gap-1.5 shrink-0 self-start">
|
||||||
|
<Zap size={12} /> Chronobiology Analytics
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="max-w-4xl mx-auto">
|
{/* KPI Strip */}
|
||||||
<StudyTimeOptimizationCard />
|
<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>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,11 +1,52 @@
|
|||||||
"use client"
|
"use client"
|
||||||
|
|
||||||
import { TutorChat } from "@/components/AI/TutorChat"
|
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() {
|
export default function TutorChatPage() {
|
||||||
return (
|
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="p-6 md:p-8 space-y-6 max-w-7xl mx-auto animate-in fade-in duration-500 min-h-screen">
|
||||||
<div className="flex-1 rounded-3xl overflow-hidden border border-border/50 shadow-xl bg-background">
|
{/* 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 />
|
<TutorChat />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,24 +1,129 @@
|
|||||||
"use client"
|
"use client"
|
||||||
|
|
||||||
import { WeeklyProgressReport } from "@/components/AI/WeeklyProgressReport"
|
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"
|
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() {
|
export default function WeeklyProgressPage() {
|
||||||
return (
|
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">
|
<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">
|
<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">
|
<div className="flex items-start justify-between gap-4 flex-wrap">
|
||||||
<Target className="text-[#00F0FF]" size={36} />
|
<div>
|
||||||
Weekly Progress Overview
|
<h1 className="text-3xl md:text-4xl font-black tracking-tight flex items-center gap-3 mb-1">
|
||||||
</h1>
|
<span className="p-2.5 rounded-xl bg-[#00F0FF]/10 border border-[#00F0FF]/20">
|
||||||
<p className="text-muted-foreground text-base md:text-lg mt-1 font-medium max-w-3xl">
|
<Target className="text-[#00F0FF]" size={28} />
|
||||||
Track your syllabus completion goals and weekly milestones.
|
</span>
|
||||||
</p>
|
Weekly Progress Overview
|
||||||
|
</h1>
|
||||||
|
<p className="text-muted-foreground text-base font-medium max-w-2xl pl-1">
|
||||||
|
Track your syllabus completion goals, study streaks, and weekly milestones at a glance.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-2 shrink-0">
|
||||||
|
<span className="text-xs font-bold uppercase tracking-wider text-[#00F0FF] bg-[#00F0FF]/10 border border-[#00F0FF]/20 px-3 py-1.5 rounded-full flex items-center gap-1.5">
|
||||||
|
<Zap size={12} /> Live Analytics
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="max-w-4xl mx-auto">
|
{/* KPI Stats Row */}
|
||||||
<WeeklyProgressReport />
|
<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>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -31,10 +31,19 @@ export default function AnalyticsPage() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
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>
|
<div className="flex flex-col md:flex-row md:items-end justify-between gap-4">
|
||||||
<h1 className="text-3xl font-bold tracking-tight">Performance Analytics</h1>
|
<div>
|
||||||
<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>
|
||||||
|
|
||||||
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
|
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import Link from "next/link";
|
|||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { Card, CardContent } from "@/components/ui/card";
|
import { Card, CardContent } from "@/components/ui/card";
|
||||||
import { Badge } from "@/components/ui/badge";
|
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";
|
import { useActiveUser } from "@/hooks/useActiveUser";
|
||||||
|
|
||||||
interface TestData {
|
interface TestData {
|
||||||
@@ -45,7 +45,7 @@ export default function CBTPage() {
|
|||||||
|
|
||||||
// Only set the initial pattern if it hasn't been changed by the user
|
// Only set the initial pattern if it hasn't been changed by the user
|
||||||
if (!selectedPattern) {
|
if (!selectedPattern) {
|
||||||
setSelectedPattern(pattern);
|
setSelectedPattern(pattern.startsWith("JEE") ? "JEE" : "NEET");
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("Error loading user:", err);
|
console.error("Error loading user:", err);
|
||||||
@@ -60,9 +60,16 @@ export default function CBTPage() {
|
|||||||
async function loadTests() {
|
async function loadTests() {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
try {
|
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) {
|
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);
|
setExams(testsData);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -108,12 +115,26 @@ export default function CBTPage() {
|
|||||||
return acc;
|
return acc;
|
||||||
}, {} as Record<string, any>);
|
}, {} as Record<string, any>);
|
||||||
|
|
||||||
|
if (userLoading || loading) {
|
||||||
|
return (
|
||||||
|
<div className="h-[70vh] flex flex-col items-center justify-center p-8">
|
||||||
|
<div className="h-8 w-8 border-4 border-[#00F0FF] border-t-transparent rounded-full animate-spin mb-4"></div>
|
||||||
|
<p className="text-muted-foreground font-medium animate-pulse">Loading exams...</p>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
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 justify-between items-end">
|
<div className="flex flex-col md:flex-row md:items-end justify-between gap-4">
|
||||||
<div>
|
<div>
|
||||||
<h1 className="text-3xl font-bold tracking-tight">Computer Based Tests (CBT)</h1>
|
<h1 className="text-3xl md:text-4xl font-black tracking-tight flex items-center gap-3 mb-1">
|
||||||
<p className="text-muted-foreground mt-1 font-medium">
|
<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"}.
|
Real exam environment simulations to conquer {targetExam || "NEET/JEE"}.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -123,7 +144,7 @@ export default function CBTPage() {
|
|||||||
type="date"
|
type="date"
|
||||||
value={dateFilter}
|
value={dateFilter}
|
||||||
onChange={(e) => setDateFilter(e.target.value)}
|
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>
|
||||||
<div className="relative w-full sm:w-64">
|
<div className="relative w-full sm:w-64">
|
||||||
@@ -140,17 +161,15 @@ export default function CBTPage() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex gap-3 overflow-x-auto pb-2 scrollbar-hide">
|
<div className="flex gap-3 overflow-x-auto pb-2 scrollbar-hide">
|
||||||
{["JEE_MAIN", "JEE_ADVANCED", "NEET"].map(p => {
|
{["JEE", "NEET"].map(p => {
|
||||||
// Normalize to match db/backend pattern
|
|
||||||
const dbPattern = p === "JEE_MAIN" ? "JEE_Main" : p === "JEE_ADVANCED" ? "JEE_Advanced" : "NEET";
|
|
||||||
return (
|
return (
|
||||||
<Button
|
<Button
|
||||||
key={p}
|
key={p}
|
||||||
variant={selectedPattern === dbPattern ? "default" : "outline"}
|
variant={selectedPattern === p ? "default" : "outline"}
|
||||||
onClick={() => setSelectedPattern(dbPattern)}
|
onClick={() => setSelectedPattern(p)}
|
||||||
className="rounded-full shadow-sm"
|
className="rounded-full shadow-sm px-6 font-bold"
|
||||||
>
|
>
|
||||||
{p === "JEE_MAIN" ? "JEE Main" : p === "JEE_ADVANCED" ? "JEE Advanced" : "NEET"}
|
{p}
|
||||||
</Button>
|
</Button>
|
||||||
)
|
)
|
||||||
})}
|
})}
|
||||||
@@ -386,11 +405,11 @@ export default function CBTPage() {
|
|||||||
<Activity size={20} className="text-muted-foreground" /> Past Attempts
|
<Activity size={20} className="text-muted-foreground" /> Past Attempts
|
||||||
</h2>
|
</h2>
|
||||||
<div className="space-y-4">
|
<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">
|
<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.
|
You haven't attempted any CBT exams yet.
|
||||||
</div>
|
</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 exam = exams.find(e => e.id === attempt.source_id);
|
||||||
const scoreCalc = (attempt.score * 4) - ((attempt.total_questions - attempt.score) * 1); // rough calc for display
|
const scoreCalc = (attempt.score * 4) - ((attempt.total_questions - attempt.score) * 1); // rough calc for display
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
import { useState } from "react"
|
import { useState } from "react"
|
||||||
import { Card, CardContent, CardHeader, CardTitle, CardFooter } from "@/components/ui/card"
|
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"
|
import { useActiveUser } from "@/hooks/useActiveUser"
|
||||||
|
|
||||||
interface Doubt {
|
interface Doubt {
|
||||||
@@ -21,9 +21,11 @@ interface Doubt {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default function CommunityPage() {
|
export default function CommunityPage() {
|
||||||
const { hasAccessTo } = useActiveUser()
|
const { hasAccessTo, loading } = useActiveUser()
|
||||||
const [activeFilter, setActiveFilter] = useState<'all' | 'my-doubts' | 'solved' | 'unsolved'>('all')
|
const [activeFilter, setActiveFilter] = useState<'all' | 'my-doubts' | 'solved' | 'unsolved'>('all')
|
||||||
const [searchQuery, setSearchQuery] = useState('')
|
const [searchQuery, setSearchQuery] = useState('')
|
||||||
|
const [isAskModalOpen, setIsAskModalOpen] = useState(false)
|
||||||
|
const [expandedDoubtId, setExpandedDoubtId] = useState<string | null>(null)
|
||||||
|
|
||||||
const mockDoubts: Doubt[] = [
|
const mockDoubts: Doubt[] = [
|
||||||
{
|
{
|
||||||
@@ -78,6 +80,15 @@ export default function CommunityPage() {
|
|||||||
return true
|
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")) {
|
if (!hasAccessTo("doubt-resolution-community")) {
|
||||||
return (
|
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">
|
<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 (
|
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 className="flex flex-col md:flex-row justify-between items-start md:items-end gap-4">
|
||||||
<div>
|
<div>
|
||||||
<h1 className="text-3xl font-bold tracking-tight text-foreground flex items-center gap-3">
|
<h1 className="text-3xl md:text-4xl font-black tracking-tight flex items-center gap-3 mb-1">
|
||||||
<MessageSquare className="text-[#00F0FF]" size={32} />
|
<span className="p-2.5 rounded-xl bg-[#00F0FF]/10 border border-[#00F0FF]/20">
|
||||||
|
<MessageSquare className="text-[#00F0FF]" size={28} />
|
||||||
|
</span>
|
||||||
Doubt Forum
|
Doubt Forum
|
||||||
</h1>
|
</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>
|
</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
|
Ask a Doubt
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -179,12 +197,46 @@ export default function CommunityPage() {
|
|||||||
<CheckCircle size={14} /> Solved
|
<CheckCircle size={14} /> Solved
|
||||||
</span>
|
</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} />
|
<MessageSquare size={16} />
|
||||||
{doubt.replies} Replies
|
{doubt.replies} Replies
|
||||||
</div>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</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>
|
</div>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
@@ -196,6 +248,77 @@ export default function CommunityPage() {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</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>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
import { Button } from "@/components/ui/button"
|
import { Button } from "@/components/ui/button"
|
||||||
import { Card, CardContent } from "@/components/ui/card"
|
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 { useEffect, useState } from "react"
|
||||||
import Link from "next/link"
|
import Link from "next/link"
|
||||||
@@ -15,6 +15,8 @@ export default function DPPPage() {
|
|||||||
const [loading, setLoading] = useState(true)
|
const [loading, setLoading] = useState(true)
|
||||||
const [dateFilter, setDateFilter] = useState("")
|
const [dateFilter, setDateFilter] = useState("")
|
||||||
const [selectedPdfDpp, setSelectedPdfDpp] = useState<any | null>(null)
|
const [selectedPdfDpp, setSelectedPdfDpp] = useState<any | null>(null)
|
||||||
|
const [isFullScreen, setIsFullScreen] = useState(false)
|
||||||
|
const [copiedShare, setCopiedShare] = useState(false)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
async function fetchDpps() {
|
async function fetchDpps() {
|
||||||
@@ -90,7 +92,7 @@ export default function DPPPage() {
|
|||||||
const cleanTitle = (title: string) => title.replace(/\s*\(ExamEnum\.[^)]+\)/, '');
|
const cleanTitle = (title: string) => title.replace(/\s*\(ExamEnum\.[^)]+\)/, '');
|
||||||
|
|
||||||
return (
|
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 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>
|
<div>
|
||||||
<h1 className="text-3xl font-bold tracking-tight mb-2">Daily Practice Problems (DPP)</h1>
|
<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
|
<Lock size={12}/> Pro
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<Button variant="outline" className="gap-2 rounded-xl text-sm font-bold">
|
<Button
|
||||||
<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>
|
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>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -216,18 +227,18 @@ export default function DPPPage() {
|
|||||||
|
|
||||||
{/* ── PROTECTED PDF READER MODAL (FREE TIER) ── */}
|
{/* ── PROTECTED PDF READER MODAL (FREE TIER) ── */}
|
||||||
{selectedPdfDpp && (
|
{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={`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 rounded-3xl w-full max-w-4xl max-h-[90vh] flex flex-col overflow-hidden shadow-2xl">
|
<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 */}
|
{/* 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="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} />
|
<FileText size={22} />
|
||||||
</div>
|
</div>
|
||||||
<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)}
|
{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
|
Protected In-App PDF View
|
||||||
</span>
|
</span>
|
||||||
</h3>
|
</h3>
|
||||||
@@ -236,20 +247,44 @@ export default function DPPPage() {
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<button
|
<div className="flex items-center gap-2">
|
||||||
onClick={() => setSelectedPdfDpp(null)}
|
<Button
|
||||||
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"
|
size="sm"
|
||||||
>
|
variant="outline"
|
||||||
<X size={18} />
|
onClick={() => {
|
||||||
</button>
|
navigator.clipboard.writeText(window.location.origin + '/dashboard/dpp');
|
||||||
|
setCopiedShare(true);
|
||||||
|
setTimeout(() => setCopiedShare(false), 2000);
|
||||||
|
}}
|
||||||
|
className="rounded-xl font-bold text-xs gap-1.5 h-9 px-3"
|
||||||
|
>
|
||||||
|
{copiedShare ? <CheckCircle2 size={14} className="text-green-500" /> : <Share2 size={14} />}
|
||||||
|
<span>{copiedShare ? "Copied" : "Share PDF"}</span>
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
size="sm"
|
||||||
|
variant="outline"
|
||||||
|
onClick={() => setIsFullScreen(!isFullScreen)}
|
||||||
|
className="rounded-xl font-bold text-xs gap-1.5 h-9 px-3 bg-primary/10 text-primary border-primary/20 hover:bg-primary/20"
|
||||||
|
>
|
||||||
|
{isFullScreen ? <Minimize2 size={14} /> : <Maximize2 size={14} />}
|
||||||
|
<span>{isFullScreen ? "Exit Fullscreen" : "Fullscreen"}</span>
|
||||||
|
</Button>
|
||||||
|
<button
|
||||||
|
onClick={() => { setSelectedPdfDpp(null); setIsFullScreen(false); }}
|
||||||
|
className="ml-1 flex-shrink-0 w-9 h-9 rounded-full border border-border bg-secondary flex items-center justify-center text-foreground hover:bg-destructive hover:border-destructive hover:text-white transition-all"
|
||||||
|
>
|
||||||
|
<X size={18} />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Security Notice Banner */}
|
{/* 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">
|
<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">
|
<span className="flex items-center gap-2 truncate">
|
||||||
<ShieldAlert size={16} /> Read-Only Mode (Free Tier) — Downloads, text copying, and sharing are restricted.
|
<ShieldAlert size={16} className="shrink-0" /> Read-Only Mode (Free Tier) — Downloads & direct copying are restricted.
|
||||||
</span>
|
</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">
|
<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 CBT Mode →
|
||||||
</Button>
|
</Button>
|
||||||
@@ -259,28 +294,33 @@ export default function DPPPage() {
|
|||||||
{/* Protected PDF Document Sheet Content (select-none) */}
|
{/* Protected PDF Document Sheet Content (select-none) */}
|
||||||
<div
|
<div
|
||||||
onContextMenu={(e) => e.preventDefault()}
|
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
|
<embed
|
||||||
src={getPdfUrl(selectedPdfDpp.title)}
|
src={getPdfUrl(selectedPdfDpp.title)}
|
||||||
type="application/pdf"
|
type="application/pdf"
|
||||||
className="w-full h-full"
|
className="w-full h-full"
|
||||||
style={{height: '55vh'}}
|
style={{height: isFullScreen ? 'calc(100vh - 120px)' : '75vh'}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Modal Footer */}
|
{/* Modal Footer */}
|
||||||
<div className="p-4 border-t border-border bg-secondary/30 flex items-center justify-between">
|
<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">
|
<span className="text-xs text-muted-foreground font-medium flex items-center gap-2">
|
||||||
📄 PragatiVeer Protected PDF Reader v2.0
|
📄 PragatiVeer Protected PDF Reader v2.0
|
||||||
</span>
|
</span>
|
||||||
<div className="flex gap-3">
|
<div className="flex gap-2 items-center">
|
||||||
<Button onClick={() => setSelectedPdfDpp(null)} variant="outline" className="rounded-xl font-bold">
|
<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">
|
||||||
Close PDF Reader
|
{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>
|
</Button>
|
||||||
<Link href="/upgrade">
|
<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)]">
|
<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={16} className="mr-2" /> Upgrade to AI Coach for Interactive CBT
|
<Sparkles size={14} className="mr-1.5" /> Upgrade to AI Coach for CBT
|
||||||
</Button>
|
</Button>
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
"use client"
|
"use client"
|
||||||
|
|
||||||
import { ReactNode, useState, useEffect, useRef } from "react"
|
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 Link from "next/link"
|
||||||
import { usePathname } from "next/navigation"
|
import { usePathname } from "next/navigation"
|
||||||
import { LanguageToggle } from "@/components/LanguageToggle"
|
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 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"
|
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;
|
// Trial logic removed as requested
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (maintenance) {
|
if (maintenance) {
|
||||||
return <MaintenanceScreen />;
|
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 (
|
return (
|
||||||
<div className="flex h-screen bg-background overflow-hidden text-foreground selection:bg-primary/20">
|
<div className="flex h-screen bg-background overflow-hidden text-foreground selection:bg-primary/20">
|
||||||
{/* Sidebar */}
|
{/* 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} />
|
<Logo href="/dashboard" className="w-full" imageClassName="object-contain object-left scale-110 origin-left" height={56} />
|
||||||
</div>
|
</div>
|
||||||
<nav className="flex-1 px-4 space-y-1.5 mt-4 overflow-y-auto">
|
<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">
|
<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
|
<span className="w-1.5 h-1.5 rounded-full bg-white"></span> Free Tier
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</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 className="pt-4 pb-2">
|
<div className="pt-3 pb-1">
|
||||||
<p className="px-3 text-xs font-bold uppercase tracking-widest text-muted-foreground flex items-center gap-2">
|
<p className="px-3 text-[10px] font-black uppercase tracking-widest text-muted-foreground/60 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
|
<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>
|
</p>
|
||||||
</div>
|
</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
|
<NavItem
|
||||||
href="/dashboard/progress"
|
href="/dashboard/progress"
|
||||||
icon={<Activity size={18} />}
|
icon={<BarChart3 size={17} />}
|
||||||
label="Syllabus Tracker"
|
label="Syllabus Tracker"
|
||||||
active={pathname.startsWith("/dashboard/progress")}
|
active={pathname.startsWith("/dashboard/progress")}
|
||||||
isLocked={!hasAccessTo("chapter-wise-progress-tracking")}
|
isLocked={!hasAccessTo("chapter-wise-progress-tracking")}
|
||||||
/>
|
/>
|
||||||
<NavItem
|
<NavItem
|
||||||
href="/dashboard/cbt"
|
href="/dashboard/cbt"
|
||||||
icon={<Activity size={18} />}
|
icon={<CheckSquare size={17} />}
|
||||||
label="Mock Tests (CBT)"
|
label="Mock Tests (CBT)"
|
||||||
active={pathname.startsWith("/dashboard/cbt")}
|
active={pathname.startsWith("/dashboard/cbt")}
|
||||||
isLocked={!hasAccessTo("weekly-test-series")}
|
isLocked={!hasAccessTo("weekly-test-series")}
|
||||||
/>
|
/>
|
||||||
<NavItem
|
<NavItem
|
||||||
href="/dashboard/materials"
|
href="/dashboard/materials"
|
||||||
icon={<BookOpen size={18} />}
|
icon={<BookOpen size={17} />}
|
||||||
label="Quick Notes"
|
label="Quick Notes"
|
||||||
active={pathname.startsWith("/dashboard/materials")}
|
active={pathname.startsWith("/dashboard/materials")}
|
||||||
isLocked={!hasAccessTo("detailed-performance-analytics")}
|
isLocked={!hasAccessTo("detailed-performance-analytics")}
|
||||||
/>
|
/>
|
||||||
<NavItem
|
<NavItem
|
||||||
href="/dashboard/analytics"
|
href="/dashboard/analytics"
|
||||||
icon={<LineChart size={18} />}
|
icon={<LineChart size={17} />}
|
||||||
label="Performance Analytics"
|
label="Performance Analytics"
|
||||||
active={pathname.startsWith("/dashboard/analytics")}
|
active={pathname.startsWith("/dashboard/analytics")}
|
||||||
isLocked={!hasAccessTo("detailed-performance-analytics")}
|
isLocked={!hasAccessTo("detailed-performance-analytics")}
|
||||||
/>
|
/>
|
||||||
<NavItem
|
<NavItem
|
||||||
href="/dashboard/community"
|
href="/dashboard/community"
|
||||||
icon={<BookOpen size={18} />}
|
icon={<School size={17} />}
|
||||||
label="Doubt Forum"
|
label="Doubt Forum"
|
||||||
active={pathname.startsWith("/dashboard/community")}
|
active={pathname.startsWith("/dashboard/community")}
|
||||||
isLocked={!hasAccessTo("doubt-resolution-community")}
|
isLocked={!hasAccessTo("doubt-resolution-community")}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div className="pt-4 pb-2">
|
<div className="pt-4 pb-1">
|
||||||
<p className="px-3 text-xs font-bold uppercase tracking-widest text-muted-foreground flex items-center gap-2">
|
<p className="px-3 text-[10px] font-black uppercase tracking-widest text-muted-foreground/60 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
|
<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>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<NavItem
|
<NavItem
|
||||||
@@ -142,63 +146,63 @@ export default function DashboardLayout({ children }: { children: ReactNode }) {
|
|||||||
/>
|
/>
|
||||||
<NavItem
|
<NavItem
|
||||||
href="/dashboard/ai-coach/cbt-dpp"
|
href="/dashboard/ai-coach/cbt-dpp"
|
||||||
icon={<FileText size={18} />}
|
icon={<Zap size={17} />}
|
||||||
label="PrepPilot AI"
|
label="PrepPilot AI"
|
||||||
active={pathname.startsWith("/dashboard/ai-coach/cbt-dpp")}
|
active={pathname.startsWith("/dashboard/ai-coach/cbt-dpp")}
|
||||||
isLocked={!hasAccessTo("ai-tutor-chat")}
|
isLocked={!hasAccessTo("ai-tutor-chat")}
|
||||||
/>
|
/>
|
||||||
<NavItem
|
<NavItem
|
||||||
href="/dashboard/ai-coach/readiness"
|
href="/dashboard/ai-coach/readiness"
|
||||||
icon={<Activity size={18} />}
|
icon={<Gauge size={17} />}
|
||||||
label="Exam Readiness"
|
label="Exam Readiness"
|
||||||
active={pathname.startsWith("/dashboard/ai-coach/readiness")}
|
active={pathname.startsWith("/dashboard/ai-coach/readiness")}
|
||||||
isLocked={!hasAccessTo("detailed-performance-analytics")}
|
isLocked={!hasAccessTo("detailed-performance-analytics")}
|
||||||
/>
|
/>
|
||||||
<NavItem
|
<NavItem
|
||||||
href="/dashboard/ai-coach/college-predictor"
|
href="/dashboard/ai-coach/college-predictor"
|
||||||
icon={<Trophy size={18} />}
|
icon={<School size={17} />}
|
||||||
label="College Predictor"
|
label="College Predictor"
|
||||||
active={pathname.startsWith("/dashboard/ai-coach/college-predictor")}
|
active={pathname.startsWith("/dashboard/ai-coach/college-predictor")}
|
||||||
isLocked={!hasAccessTo("college-predictor")}
|
isLocked={!hasAccessTo("college-predictor")}
|
||||||
/>
|
/>
|
||||||
<NavItem
|
<NavItem
|
||||||
href="/dashboard/ai-coach/strategy"
|
href="/dashboard/ai-coach/strategy"
|
||||||
icon={<BookOpen size={18} />}
|
icon={<Target size={17} />}
|
||||||
label="Exam Strategy"
|
label="Exam Strategy"
|
||||||
active={pathname.startsWith("/dashboard/ai-coach/strategy")}
|
active={pathname.startsWith("/dashboard/ai-coach/strategy")}
|
||||||
isLocked={!hasAccessTo("exam-day-strategy")}
|
isLocked={!hasAccessTo("exam-day-strategy")}
|
||||||
/>
|
/>
|
||||||
<NavItem
|
<NavItem
|
||||||
href="/dashboard/ai-coach/study-plan"
|
href="/dashboard/ai-coach/study-plan"
|
||||||
icon={<LineChart size={18} />}
|
icon={<Calendar size={17} />}
|
||||||
label="AI Study Plan"
|
label="AI Study Plan"
|
||||||
active={pathname.startsWith("/dashboard/ai-coach/study-plan")}
|
active={pathname.startsWith("/dashboard/ai-coach/study-plan")}
|
||||||
isLocked={!hasAccessTo("personalized-study-plan")}
|
isLocked={!hasAccessTo("personalized-study-plan")}
|
||||||
/>
|
/>
|
||||||
<NavItem
|
<NavItem
|
||||||
href="/dashboard/ai-coach/time-optimization"
|
href="/dashboard/ai-coach/time-optimization"
|
||||||
icon={<Activity size={18} />}
|
icon={<Clock size={17} />}
|
||||||
label="Study Time Optimization"
|
label="Time Optimization"
|
||||||
active={pathname.startsWith("/dashboard/ai-coach/time-optimization")}
|
active={pathname.startsWith("/dashboard/ai-coach/time-optimization")}
|
||||||
isLocked={!hasAccessTo("personalized-study-plan")}
|
isLocked={!hasAccessTo("personalized-study-plan")}
|
||||||
/>
|
/>
|
||||||
<NavItem
|
<NavItem
|
||||||
href="/dashboard/ai-coach/heatmap"
|
href="/dashboard/ai-coach/heatmap"
|
||||||
icon={<Flame size={18} />}
|
icon={<Flame size={17} />}
|
||||||
label="Mistake Heatmap"
|
label="Mistake Heatmap"
|
||||||
active={pathname.startsWith("/dashboard/ai-coach/heatmap")}
|
active={pathname.startsWith("/dashboard/ai-coach/heatmap")}
|
||||||
isLocked={!hasAccessTo("ai-mistake-tracking")}
|
isLocked={!hasAccessTo("ai-mistake-tracking")}
|
||||||
/>
|
/>
|
||||||
<NavItem
|
<NavItem
|
||||||
href="/dashboard/ai-coach/weekly-progress"
|
href="/dashboard/ai-coach/weekly-progress"
|
||||||
icon={<Target size={18} />}
|
icon={<Activity size={17} />}
|
||||||
label="Weekly Progress"
|
label="Weekly Progress"
|
||||||
active={pathname.startsWith("/dashboard/ai-coach/weekly-progress")}
|
active={pathname.startsWith("/dashboard/ai-coach/weekly-progress")}
|
||||||
isLocked={!hasAccessTo("detailed-performance-analytics")}
|
isLocked={!hasAccessTo("detailed-performance-analytics")}
|
||||||
/>
|
/>
|
||||||
<NavItem
|
<NavItem
|
||||||
href="/dashboard/ai-coach/mistakes"
|
href="/dashboard/ai-coach/mistakes"
|
||||||
icon={<Activity size={18} />}
|
icon={<BrainCircuit size={17} />}
|
||||||
label="Mistake Analyzer"
|
label="Mistake Analyzer"
|
||||||
active={pathname.startsWith("/dashboard/ai-coach/mistakes")}
|
active={pathname.startsWith("/dashboard/ai-coach/mistakes")}
|
||||||
isLocked={!hasAccessTo("ai-mistake-tracking")}
|
isLocked={!hasAccessTo("ai-mistake-tracking")}
|
||||||
@@ -272,27 +276,6 @@ export default function DashboardLayout({ children }: { children: ReactNode }) {
|
|||||||
{targetExamLabel} Target
|
{targetExamLabel} Target
|
||||||
</Badge>
|
</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>
|
||||||
|
|
||||||
<div className="flex items-center gap-4">
|
<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.NEHA.id}>Neha Verma (AI Coach)</option>
|
||||||
<option value={DEMO_USERS.AI_COACH.id}>Rahul (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.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>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -344,19 +327,24 @@ function NavItem({ href, icon, label, active, isLocked }: { href: string, icon:
|
|||||||
return (
|
return (
|
||||||
<Link
|
<Link
|
||||||
href={isLocked ? "#" : href}
|
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
|
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
|
: isLocked
|
||||||
? "text-muted-foreground/60 cursor-not-allowed border border-transparent"
|
? "text-muted-foreground/40 cursor-not-allowed border border-transparent"
|
||||||
: "text-muted-foreground hover:bg-secondary/50 hover:text-foreground 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">
|
||||||
{icon}
|
<span className={`transition-all duration-200 ${
|
||||||
<span className="text-sm">{label}</span>
|
active ? "text-[#00F0FF]" : isLocked ? "text-muted-foreground/30" : "text-muted-foreground/60 group-hover:text-foreground/80"
|
||||||
|
}`}>
|
||||||
|
{icon}
|
||||||
|
</span>
|
||||||
|
<span className="text-[13px] font-medium tracking-[-0.01em]">{label}</span>
|
||||||
</div>
|
</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>
|
</Link>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,14 +25,18 @@ export default function LeaderboardPage() {
|
|||||||
]
|
]
|
||||||
|
|
||||||
return (
|
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 className="flex flex-col md:flex-row justify-between items-start md:items-end gap-4">
|
||||||
<div>
|
<div>
|
||||||
<h1 className="text-3xl font-bold tracking-tight text-foreground flex items-center gap-3">
|
<h1 className="text-3xl md:text-4xl font-black tracking-tight flex items-center gap-3 mb-1">
|
||||||
<Trophy className="text-yellow-500" size={32} />
|
<span className="p-2.5 rounded-xl bg-[#00F0FF]/10 border border-[#00F0FF]/20">
|
||||||
|
<Trophy className="text-[#00F0FF]" size={28} />
|
||||||
|
</span>
|
||||||
Leaderboard
|
Leaderboard
|
||||||
</h1>
|
</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>
|
||||||
|
|
||||||
<div className="flex items-center gap-3 bg-secondary/50 p-1.5 rounded-xl border border-border">
|
<div className="flex items-center gap-3 bg-secondary/50 p-1.5 rounded-xl border border-border">
|
||||||
|
|||||||
@@ -1,7 +1,16 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import { useState } from "react"
|
||||||
import { Card, CardContent } from "@/components/ui/card"
|
import { 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() {
|
export default function MaterialsPage() {
|
||||||
|
const [selectedPdf, setSelectedPdf] = useState<any | null>(null)
|
||||||
|
const [isFullScreen, setIsFullScreen] = useState(false)
|
||||||
|
const [copiedShare, setCopiedShare] = useState(false)
|
||||||
|
|
||||||
const folders = [
|
const folders = [
|
||||||
{ name: "Physics Formula Sheets", items: 12, color: "text-blue-500" },
|
{ name: "Physics Formula Sheets", items: 12, color: "text-blue-500" },
|
||||||
{ name: "Organic Chem Mind Maps", items: 8, color: "text-emerald-500" },
|
{ name: "Organic Chem Mind Maps", items: 8, color: "text-emerald-500" },
|
||||||
@@ -10,16 +19,32 @@ export default function MaterialsPage() {
|
|||||||
]
|
]
|
||||||
|
|
||||||
const recentFiles = [
|
const recentFiles = [
|
||||||
{ name: "Kinematics 1D & 2D Rapid Revision.pdf", type: "PDF", size: "2.4 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" },
|
{ 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" },
|
{ 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 (
|
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>
|
<div className="flex flex-col md:flex-row md:items-end justify-between gap-4">
|
||||||
<h1 className="text-3xl font-bold tracking-tight">Study Materials</h1>
|
<div>
|
||||||
<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>
|
||||||
|
|
||||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4">
|
<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">
|
<Card key={i} className="hover:bg-secondary/50 cursor-pointer transition-colors border-border shadow-none">
|
||||||
<CardContent className="p-6">
|
<CardContent className="p-6">
|
||||||
<Folder size={40} className={`${folder.color} mb-4`} />
|
<Folder size={40} className={`${folder.color} mb-4`} />
|
||||||
<h3 className="font-bold">{folder.name}</h3>
|
<h3 className="font-bold text-base">{folder.name}</h3>
|
||||||
<p className="text-sm text-muted-foreground mt-1 font-medium">{folder.items} Files</p>
|
<p className="text-sm text-muted-foreground mt-1 font-medium">{folder.items} Protected Files</p>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="pt-6">
|
<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">
|
<div className="bg-secondary/10 border border-border rounded-xl overflow-hidden">
|
||||||
<table className="w-full text-left text-sm">
|
<table className="w-full text-left text-sm">
|
||||||
<thead className="bg-secondary/30 text-muted-foreground border-b border-border">
|
<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">File Name</th>
|
||||||
<th className="px-6 py-4 font-semibold">Type</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">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>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody className="divide-y divide-border">
|
<tbody className="divide-y divide-border">
|
||||||
{recentFiles.map((file, i) => (
|
{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">
|
<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.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>
|
||||||
<td className="px-6 py-4 text-muted-foreground">{file.type}</td>
|
<td className="px-6 py-4 text-muted-foreground font-medium">{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.size}</td>
|
||||||
<td className="px-6 py-4 text-right">
|
<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>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
))}
|
))}
|
||||||
@@ -64,6 +116,106 @@ export default function MaterialsPage() {
|
|||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
</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>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,14 +4,14 @@ import { useRef } from "react"
|
|||||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
|
||||||
import { Badge } from "@/components/ui/badge"
|
import { Badge } from "@/components/ui/badge"
|
||||||
import { Button } from "@/components/ui/button"
|
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 * as htmlToImage from 'html-to-image'
|
||||||
import { MistakeHeatmap } from "@/components/AI/MistakeHeatmap"
|
import { MistakeHeatmap } from "@/components/AI/MistakeHeatmap"
|
||||||
import { AdaptiveStudyPlan } from "@/components/AI/AdaptiveStudyPlan"
|
import { AdaptiveStudyPlan } from "@/components/AI/AdaptiveStudyPlan"
|
||||||
import { useActiveUser } from "@/hooks/useActiveUser"
|
import { useActiveUser } from "@/hooks/useActiveUser"
|
||||||
|
|
||||||
export default function DashboardPage() {
|
export default function DashboardPage() {
|
||||||
const { user, hasAccessTo } = useActiveUser()
|
const { user, hasAccessTo, loading } = useActiveUser()
|
||||||
const progressRef = useRef<HTMLDivElement>(null)
|
const progressRef = useRef<HTMLDivElement>(null)
|
||||||
|
|
||||||
const handleExport = async () => {
|
const handleExport = async () => {
|
||||||
@@ -28,37 +28,33 @@ export default function DashboardPage() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (loading) {
|
||||||
|
return (
|
||||||
|
<div className="h-[70vh] flex flex-col items-center justify-center p-8">
|
||||||
|
<div className="h-8 w-8 border-4 border-[#00F0FF] border-t-transparent rounded-full animate-spin mb-4"></div>
|
||||||
|
<p className="text-muted-foreground font-medium animate-pulse">Loading dashboard...</p>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="p-8 max-w-6xl mx-auto space-y-10 animate-in fade-in duration-500 slide-in-from-bottom-4">
|
<div className="p-8 max-w-7xl mx-auto space-y-10 animate-in fade-in duration-500 slide-in-from-bottom-4">
|
||||||
|
|
||||||
|
|
||||||
{/* Free Tier Upgrade CTA Banner */}
|
|
||||||
<Card className="bg-primary/5 border-primary/20 relative overflow-hidden">
|
|
||||||
<div className="absolute top-0 right-0 p-8 opacity-10">
|
|
||||||
<BrainCircuit size={120} />
|
|
||||||
</div>
|
|
||||||
<CardContent className="p-6 md:p-8 flex flex-col md:flex-row md:items-center justify-between gap-6 relative z-10">
|
|
||||||
<div className="space-y-2">
|
|
||||||
<Badge className="bg-primary text-primary-foreground font-bold tracking-widest uppercase mb-2">Upgrade to Pro</Badge>
|
|
||||||
<h2 className="text-2xl font-bold tracking-tight">Unlock Unlimited DPS & Mocks</h2>
|
|
||||||
<p className="text-muted-foreground max-w-xl font-medium">
|
|
||||||
You're currently on the Free Tier. Upgrade to Pro for ₹3499/year to access unlimited Daily Practice Sheets, weekly CBT tests, and advanced analytics.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<Button size="lg" className="shrink-0 rounded-xl shadow-[0_0_20px_-5px_var(--color-primary)] font-bold">
|
|
||||||
Upgrade Now
|
|
||||||
</Button>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
|
|
||||||
{/* Basic Performance Dashboard (F-05) */}
|
{/* Basic Performance Dashboard (F-05) */}
|
||||||
<section>
|
<section>
|
||||||
<div className="flex items-center justify-between mb-5">
|
<div className="flex flex-col md:flex-row md:items-end justify-between gap-4 mb-8">
|
||||||
<h2 className="text-xl font-semibold tracking-tight flex items-center gap-2">
|
<div>
|
||||||
Performance Overview
|
<h1 className="text-3xl md:text-4xl font-black tracking-tight flex items-center gap-3 mb-1">
|
||||||
<Badge variant="outline" className="border-white/30 text-muted-foreground bg-white/5 rounded-md text-[10px] uppercase font-bold">Free</Badge>
|
<span className="p-2.5 rounded-xl bg-[#00F0FF]/10 border border-[#00F0FF]/20">
|
||||||
</h2>
|
<LayoutDashboard className="text-[#00F0FF]" size={28} />
|
||||||
<Button onClick={handleExport} variant="outline" size="sm" className="gap-2 font-medium">
|
</span>
|
||||||
|
Performance Overview
|
||||||
|
</h1>
|
||||||
|
<p className="text-muted-foreground text-base font-medium max-w-2xl pl-1">
|
||||||
|
Your overall progress, scores, and active study plan at a glance.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<Button onClick={handleExport} variant="outline" size="sm" className="gap-2 font-medium shrink-0">
|
||||||
<Share2 size={16} /> Share Card
|
<Share2 size={16} /> Share Card
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</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="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">
|
<div className="flex items-center gap-2 mb-1">
|
||||||
<p className="text-sm font-bold text-muted-foreground uppercase tracking-wider">City Rank</p>
|
<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>
|
</div>
|
||||||
<h3 className="text-2xl font-bold">#42 <span className="text-base text-muted-foreground font-medium">/ 1,200</span></h3>
|
<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>
|
<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">
|
<div className="flex items-center justify-between mb-5">
|
||||||
<h2 className="text-xl font-semibold tracking-tight flex items-center gap-2">
|
<h2 className="text-xl font-semibold tracking-tight flex items-center gap-2">
|
||||||
Today's DPPs
|
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>
|
</h2>
|
||||||
<Button variant="ghost" size="sm" className="text-muted-foreground font-medium">View All</Button>
|
<Button variant="ghost" size="sm" className="text-muted-foreground font-medium">View All</Button>
|
||||||
</div>
|
</div>
|
||||||
@@ -183,7 +176,6 @@ export default function DashboardPage() {
|
|||||||
<div className="flex items-center justify-between mb-5">
|
<div className="flex items-center justify-between mb-5">
|
||||||
<h2 className="text-xl font-semibold tracking-tight flex items-center gap-2">
|
<h2 className="text-xl font-semibold tracking-tight flex items-center gap-2">
|
||||||
Upcoming CBTs
|
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>
|
</h2>
|
||||||
</div>
|
</div>
|
||||||
<Card className="overflow-hidden">
|
<Card className="overflow-hidden">
|
||||||
|
|||||||
@@ -104,13 +104,19 @@ export default function ProgressTrackerPage() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
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>
|
<div className="flex flex-col md:flex-row md:items-end justify-between gap-4">
|
||||||
<h1 className="text-3xl font-bold tracking-tight text-foreground flex items-center gap-3">
|
<div>
|
||||||
<Target className="text-[#00F0FF]" size={32} />
|
<h1 className="text-3xl md:text-4xl font-black tracking-tight flex items-center gap-3 mb-1">
|
||||||
Syllabus Tracker
|
<span className="p-2.5 rounded-xl bg-[#00F0FF]/10 border border-[#00F0FF]/20">
|
||||||
</h1>
|
<Target className="text-[#00F0FF]" size={28} />
|
||||||
<p className="text-muted-foreground mt-2 font-medium">Track your syllabus coverage and mastery per chapter to ensure no gaps.</p>
|
</span>
|
||||||
|
Syllabus Tracker
|
||||||
|
</h1>
|
||||||
|
<p className="text-muted-foreground text-base font-medium max-w-2xl pl-1">
|
||||||
|
Track your syllabus coverage and mastery per chapter to ensure no gaps.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-6 mb-8">
|
<div className="grid grid-cols-1 md:grid-cols-3 gap-6 mb-8">
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ export default function RankPredictorPage() {
|
|||||||
]
|
]
|
||||||
|
|
||||||
return (
|
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 */}
|
{/* Header */}
|
||||||
<div className="text-center max-w-3xl mx-auto space-y-3">
|
<div className="text-center max-w-3xl mx-auto space-y-3">
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ export default function StreaksPage() {
|
|||||||
];
|
];
|
||||||
|
|
||||||
return (
|
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="flex items-center gap-4">
|
||||||
<div className="size-16 rounded-2xl bg-orange-500/10 text-orange-500 flex items-center justify-center">
|
<div className="size-16 rounded-2xl bg-orange-500/10 text-orange-500 flex items-center justify-center">
|
||||||
<Flame size={32} />
|
<Flame size={32} />
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ const geistMono = Geist_Mono({
|
|||||||
|
|
||||||
export const metadata: Metadata = {
|
export const metadata: Metadata = {
|
||||||
title: "PragatiVeer - AI Powered NEET/JEE Preparation",
|
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",
|
keywords: "NEET, JEE, Prep, AI, CBT, DPP, Online Test, Study",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -10,17 +10,35 @@ import { Logo } from "@/components/Logo"
|
|||||||
|
|
||||||
export default function LoginPage() {
|
export default function LoginPage() {
|
||||||
const [showPassword, setShowPassword] = useState(false)
|
const [showPassword, setShowPassword] = useState(false)
|
||||||
const [email, setEmail] = useState("demo@pragativeer.com")
|
const [email, setEmail] = useState("")
|
||||||
const [password, setPassword] = useState("PragatiVeer@Secure2026!")
|
const [password, setPassword] = useState("")
|
||||||
const [error, setError] = useState("")
|
const [error, setError] = useState("")
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
|
||||||
const handleLogin = (e: React.FormEvent) => {
|
const handleLogin = async (e: React.FormEvent) => {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
if (email === "demo@pragativeer.com" && password === "PragatiVeer@Secure2026!") {
|
setError("")
|
||||||
router.push("/dashboard")
|
|
||||||
} else {
|
try {
|
||||||
setError("Invalid credentials. Please use the demo account.")
|
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 {
|
||||||
|
router.push("/dashboard")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
setError(data.error || "Invalid email or password.")
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
setError("An error occurred. Please try again.")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
@@ -55,7 +73,9 @@ export default function LoginPage() {
|
|||||||
</div>
|
</div>
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<div className="flex items-center justify-between">
|
<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>
|
<Link href="#" className="text-xs font-bold text-[#00F0FF] hover:text-[#00F0FF]/80 transition-colors">Forgot password?</Link>
|
||||||
</div>
|
</div>
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
|
|||||||
@@ -79,7 +79,7 @@ export default function MarketingPage() {
|
|||||||
<div className="hidden md:flex items-center gap-8 text-sm font-medium text-muted-foreground">
|
<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="#features" className="hover:text-foreground transition-colors">Features</Link>
|
||||||
<Link href="#pricing" className="hover:text-foreground transition-colors">Pricing</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>
|
||||||
<div className="flex items-center gap-4">
|
<div className="flex items-center gap-4">
|
||||||
<Link href="/login" className="text-sm font-bold hover:text-primary transition-colors hidden sm:block">Log in</Link>
|
<Link href="/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 }}
|
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"
|
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.p>
|
||||||
|
|
||||||
<motion.div
|
<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>
|
<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>
|
</motion.div>
|
||||||
|
|
||||||
<div className="grid grid-cols-1 lg:grid-cols-3 gap-8 max-w-6xl mx-auto">
|
<div className="grid grid-cols-1 lg:grid-cols-2 gap-8 max-w-4xl mx-auto items-stretch">
|
||||||
{/* Free Plan */}
|
{/* Free Plan
|
||||||
<motion.div
|
<motion.div
|
||||||
initial={{ opacity: 0, x: -50 }}
|
initial={{ opacity: 0, x: -50 }}
|
||||||
whileInView={{ opacity: 1, x: 0 }}
|
whileInView={{ opacity: 1, x: 0 }}
|
||||||
@@ -431,6 +431,7 @@ export default function MarketingPage() {
|
|||||||
</Link>
|
</Link>
|
||||||
</Card>
|
</Card>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
|
*/}
|
||||||
|
|
||||||
{/* Pro Plan */}
|
{/* Pro Plan */}
|
||||||
<motion.div
|
<motion.div
|
||||||
@@ -495,10 +496,10 @@ export default function MarketingPage() {
|
|||||||
<table className="w-full text-left border-collapse min-w-[700px]">
|
<table className="w-full text-left border-collapse min-w-[700px]">
|
||||||
<thead>
|
<thead>
|
||||||
<tr className="border-b border-border/50 bg-background/50">
|
<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 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/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/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/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 text-center w-1/4 text-[#00F0FF] bg-[#00F0FF]/5 border-x border-t border-[#00F0FF]">AI Coach</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody className="divide-y divide-border/30 text-sm md:text-base">
|
<tbody className="divide-y divide-border/30 text-sm md:text-base">
|
||||||
@@ -528,9 +529,9 @@ export default function MarketingPage() {
|
|||||||
].map((feature, idx) => (
|
].map((feature, idx) => (
|
||||||
<tr key={idx} className="hover:bg-background/40 transition-colors">
|
<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 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>}
|
{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">
|
<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>}
|
{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>
|
</td>
|
||||||
@@ -561,7 +562,7 @@ export default function MarketingPage() {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<p className="text-sm text-muted-foreground font-medium leading-relaxed">
|
<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>
|
</p>
|
||||||
<div className="flex items-center gap-4 pt-2">
|
<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">
|
<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="#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="#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>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -593,7 +592,6 @@ export default function MarketingPage() {
|
|||||||
<ul className="space-y-3 text-sm font-medium text-muted-foreground">
|
<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="/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="/contact" className="hover:text-primary transition-colors">Contact</Link></li>
|
||||||
<li><Link href="#" className="hover:text-primary transition-colors">Careers</Link></li>
|
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</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">
|
<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>
|
<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>
|
||||||
</div>
|
</div>
|
||||||
</footer>
|
</footer>
|
||||||
|
|||||||
@@ -12,7 +12,15 @@ export default function SignupPage() {
|
|||||||
const [step, setStep] = useState(1)
|
const [step, setStep] = useState(1)
|
||||||
const [showPassword, setShowPassword] = useState(false)
|
const [showPassword, setShowPassword] = useState(false)
|
||||||
const [showOTP, setShowOTP] = useState(false)
|
const [showOTP, setShowOTP] = useState(false)
|
||||||
|
const [password, setPassword] = useState("")
|
||||||
const [isSubmitted, setIsSubmitted] = useState(false)
|
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 router = useRouter()
|
||||||
|
|
||||||
const handleNextStep = () => {
|
const handleNextStep = () => {
|
||||||
@@ -51,8 +59,7 @@ export default function SignupPage() {
|
|||||||
<CheckCircle2 size={64} className="text-primary mb-4" />
|
<CheckCircle2 size={64} className="text-primary mb-4" />
|
||||||
<h2 className="text-3xl font-black tracking-tight text-foreground">Thanks for registering!</h2>
|
<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">
|
<p className="text-muted-foreground text-sm font-medium px-4 leading-relaxed">
|
||||||
Our team will get back to you shortly. <br/><br/>
|
Thank you for the registration. Our team will contact you shortly.
|
||||||
<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.
|
|
||||||
</p>
|
</p>
|
||||||
<Link href="/login" className="w-full mt-4">
|
<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)]">
|
<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="grid grid-cols-2 gap-4">
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<label className="text-sm font-bold text-foreground">First Name</label>
|
<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>
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<label className="text-sm font-bold text-foreground">Last Name</label>
|
<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>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -96,9 +103,11 @@ export default function SignupPage() {
|
|||||||
type="tel"
|
type="tel"
|
||||||
maxLength={10}
|
maxLength={10}
|
||||||
pattern="[0-9]*"
|
pattern="[0-9]*"
|
||||||
|
value={phone}
|
||||||
onInput={(e) => {
|
onInput={(e) => {
|
||||||
e.currentTarget.value = e.currentTarget.value.replace(/[^0-9]/g, '');
|
e.currentTarget.value = e.currentTarget.value.replace(/[^0-9]/g, '');
|
||||||
}}
|
}}
|
||||||
|
onChange={(e) => setPhone(e.target.value.replace(/[^0-9]/g, ''))}
|
||||||
placeholder="9876543210"
|
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"
|
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="space-y-2">
|
||||||
<label className="text-sm font-bold text-foreground">Password (Optional if using OTP)</label>
|
<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">
|
<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">
|
<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} />}
|
{showPassword ? <EyeOff size={16} /> : <Eye size={16} />}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</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>
|
||||||
) : (
|
) : (
|
||||||
<div className="space-y-4 animate-in fade-in slide-in-from-right-8">
|
<div className="space-y-4 animate-in fade-in slide-in-from-right-8">
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<label className="text-sm font-bold text-foreground">Target Exam</label>
|
<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">
|
<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">JEE Main</option>
|
<option value="jee_main" className="bg-background text-foreground font-medium">JEE Main</option>
|
||||||
<option value="jee_adv">JEE Advanced</option>
|
<option value="jee_adv" className="bg-background text-foreground font-medium">JEE Advanced</option>
|
||||||
<option value="neet">NEET</option>
|
<option value="neet" className="bg-background text-foreground font-medium">NEET</option>
|
||||||
<option value="jee_neet">JEE + NEET</option>
|
<option value="jee_neet" className="bg-background text-foreground font-medium">JEE + NEET</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<label className="text-sm font-bold text-foreground">Current Class</label>
|
<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">
|
<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">Class 11th</option>
|
<option value="11" className="bg-background text-foreground font-medium">Class 11th</option>
|
||||||
<option value="12">Class 12th</option>
|
<option value="12" className="bg-background text-foreground font-medium">Class 12th</option>
|
||||||
<option value="dropper">Dropper</option>
|
<option value="dropper" className="bg-background text-foreground font-medium">Dropper</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<label className="text-sm font-bold text-foreground">Study Medium</label>
|
<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">
|
<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">English</option>
|
<option value="en" className="bg-background text-foreground font-medium">English</option>
|
||||||
<option value="hi">Hindi (हिंदी)</option>
|
<option value="hi" className="bg-background text-foreground font-medium">Hindi (हिंदी)</option>
|
||||||
<option value="bi">Bilingual (English + Hindi)</option>
|
<option value="bi" className="bg-background text-foreground font-medium">Bilingual (English + Hindi)</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
@@ -166,7 +189,11 @@ export default function SignupPage() {
|
|||||||
</div>
|
</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"}
|
{step === 1 ? "Continue" : "Complete Setup"}
|
||||||
</Button>
|
</Button>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
|
|||||||
@@ -103,82 +103,71 @@ export default function UpgradePage() {
|
|||||||
<p className="text-xl text-muted-foreground">Unlock the full power of PragatiVeer to master JEE & NEET.</p>
|
<p className="text-xl text-muted-foreground">Unlock the full power of PragatiVeer to master JEE & NEET.</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-8 items-stretch">
|
<div className="grid grid-cols-1 lg:grid-cols-2 gap-8 max-w-5xl mx-auto items-stretch">
|
||||||
{/* Free Tier */}
|
{/* 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">
|
<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>
|
<div>
|
||||||
<CardHeader className="p-0 mb-6">
|
<h3 className="text-3xl font-bold mb-2 text-foreground">Free</h3>
|
||||||
<CardTitle className="text-3xl font-bold mb-1 text-foreground">Free</CardTitle>
|
<p className="text-muted-foreground mb-6 text-base">Perfect to get started.</p>
|
||||||
<CardDescription className="text-muted-foreground mb-6 text-base">Perfect to get started.</CardDescription>
|
<div className="text-6xl font-bold tracking-tighter mb-6 text-foreground">
|
||||||
<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>
|
||||||
₹0<span className="text-xl text-white/60 font-medium tracking-normal ml-1">/year</span>
|
</div>
|
||||||
</div>
|
<ul className="space-y-3 mb-6 font-medium text-lg">
|
||||||
</CardHeader>
|
<li className="flex items-center gap-4"><CheckCircle2 className="text-foreground shrink-0" size={24}/> 1 Daily Practice Sheet</li>
|
||||||
<CardContent className="p-0 space-y-3 mb-6 font-medium text-lg">
|
<li className="flex items-center gap-4"><CheckCircle2 className="text-foreground shrink-0" size={24}/> Basic Performance Analytics</li>
|
||||||
<div className="flex items-center gap-4"><CheckCircle2 className="text-foreground shrink-0" size={24}/> 1 Daily Practice Sheet</div>
|
<li className="flex items-center gap-4"><CheckCircle2 className="text-foreground shrink-0" size={24}/> Community Access</li>
|
||||||
<div className="flex items-center gap-4"><CheckCircle2 className="text-foreground shrink-0" size={24}/> Basic Performance Analytics</div>
|
</ul>
|
||||||
<div className="flex items-center gap-4"><CheckCircle2 className="text-foreground shrink-0" size={24}/> Community Access</div>
|
|
||||||
</CardContent>
|
|
||||||
</div>
|
</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>
|
||||||
<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>
|
</Card>
|
||||||
|
*/}
|
||||||
|
|
||||||
{/* Pro Tier */}
|
{/* 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">
|
<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>
|
<div>
|
||||||
<CardHeader className="p-0 mb-6">
|
<h3 className="text-3xl font-bold mb-2 text-yellow-500">Pro</h3>
|
||||||
<CardTitle className="text-3xl font-bold mb-1 text-yellow-500">Pro</CardTitle>
|
<p className="text-muted-foreground mb-6 text-base">Perfect for self-paced practice.</p>
|
||||||
<CardDescription className="text-muted-foreground mb-6 text-base">Perfect for self-paced practice.</CardDescription>
|
<div className="text-6xl font-bold tracking-tighter mb-6 text-yellow-500">
|
||||||
<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>
|
||||||
₹3,499<span className="text-xl text-yellow-500/60 font-medium tracking-normal ml-1">/year</span>
|
</div>
|
||||||
</div>
|
<ul className="space-y-3 mb-6 font-medium text-lg">
|
||||||
</CardHeader>
|
<li className="flex items-center gap-4"><CheckCircle2 className="text-yellow-500 shrink-0" size={24}/> Unlimited Daily Practice Sheets</li>
|
||||||
<CardContent className="p-0 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}/> Weekly Test Series & Mock Tests</li>
|
||||||
<div className="flex items-center gap-4"><CheckCircle2 className="text-yellow-500 shrink-0" size={24}/> Unlimited Daily Practice Sheets</div>
|
<li className="flex items-center gap-4"><CheckCircle2 className="text-yellow-500 shrink-0" size={24}/> Detailed Performance Analytics</li>
|
||||||
<div className="flex items-center gap-4"><CheckCircle2 className="text-yellow-500 shrink-0" size={24}/> Weekly Test Series & Mock Tests</div>
|
<li className="flex items-center gap-4"><CheckCircle2 className="text-yellow-500 shrink-0" size={24}/> Parent Dashboard Access</li>
|
||||||
<div className="flex items-center gap-4"><CheckCircle2 className="text-yellow-500 shrink-0" size={24}/> Detailed Performance Analytics</div>
|
</ul>
|
||||||
<div className="flex items-center gap-4"><CheckCircle2 className="text-yellow-500 shrink-0" size={24}/> Parent Dashboard Access</div>
|
|
||||||
</CardContent>
|
|
||||||
</div>
|
</div>
|
||||||
<CardFooter className="p-0">
|
<Button
|
||||||
<Button
|
className="w-full bg-yellow-500 text-black hover:bg-yellow-400 font-bold text-lg rounded-2xl h-12"
|
||||||
className="w-full bg-yellow-500 text-black hover:bg-yellow-400 font-bold text-lg rounded-2xl h-12"
|
onClick={() => handleUpgrade("Pro")}
|
||||||
onClick={() => handleUpgrade("Pro")}
|
disabled={!!loadingTier}
|
||||||
disabled={!!loadingTier}
|
>
|
||||||
>
|
{loadingTier === "Pro" ? <Loader2 className="animate-spin" /> : "Get Pro"}
|
||||||
{loadingTier === "Pro" ? <Loader2 className="animate-spin" /> : "Get Pro"}
|
</Button>
|
||||||
</Button>
|
|
||||||
</CardFooter>
|
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
{/* AI Coach Tier */}
|
{/* 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">
|
<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>
|
<div>
|
||||||
<CardHeader className="p-0 mb-6">
|
<h3 className="text-3xl font-bold mb-2 text-[#00F0FF]">AI Coach</h3>
|
||||||
<CardTitle className="text-3xl font-bold mb-2 text-[#00F0FF]">AI Coach</CardTitle>
|
<p className="text-muted-foreground mb-6 text-base">The complete AI-driven system.</p>
|
||||||
<CardDescription className="text-muted-foreground mb-6 text-base">The complete AI-driven system.</CardDescription>
|
<div className="text-6xl font-bold tracking-tighter mb-6 text-[#00F0FF]">
|
||||||
<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>
|
||||||
₹11,999<span className="text-xl text-[#00F0FF]/60 font-medium tracking-normal ml-1">/year</span>
|
</div>
|
||||||
</div>
|
<ul className="space-y-3 mb-6 font-medium text-lg">
|
||||||
</CardHeader>
|
<li className="flex items-center gap-4"><CheckCircle2 className="text-[#00F0FF] shrink-0" size={24}/> Everything in Pro</li>
|
||||||
<CardContent className="p-0 space-y-3 mb-6 font-medium text-lg">
|
<li className="flex items-center gap-4"><CheckCircle2 className="text-[#00F0FF] shrink-0" size={24}/> AI Spaced Repetition (Mistakes)</li>
|
||||||
<div className="flex items-center gap-4"><CheckCircle2 className="text-[#00F0FF] shrink-0" size={24}/> Everything in Pro</div>
|
<li className="flex items-center gap-4"><CheckCircle2 className="text-[#00F0FF] shrink-0" size={24}/> 24/7 Streaming AI Tutor Chat</li>
|
||||||
<div className="flex items-center gap-4"><CheckCircle2 className="text-[#00F0FF] shrink-0" size={24}/> AI Spaced Repetition (Mistakes)</div>
|
<li className="flex items-center gap-4"><CheckCircle2 className="text-[#00F0FF] shrink-0" size={24}/> Predictive Rank & College Predictor</li>
|
||||||
<div className="flex items-center gap-4"><CheckCircle2 className="text-[#00F0FF] shrink-0" size={24}/> 24/7 Streaming AI Tutor Chat</div>
|
</ul>
|
||||||
<div className="flex items-center gap-4"><CheckCircle2 className="text-[#00F0FF] shrink-0" size={24}/> Predictive Rank & College Predictor</div>
|
|
||||||
</CardContent>
|
|
||||||
</div>
|
</div>
|
||||||
<CardFooter className="p-0">
|
<Button
|
||||||
<Button
|
className="w-full font-bold text-lg rounded-2xl h-12 bg-[#00F0FF] text-black hover:bg-[#00F0FF]/90 transition-all"
|
||||||
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")}
|
||||||
onClick={() => handleUpgrade("Al_Coach")}
|
disabled={!!loadingTier}
|
||||||
disabled={!!loadingTier}
|
>
|
||||||
>
|
{loadingTier === "Al_Coach" ? <Loader2 className="animate-spin" /> : "Get AI Coach"}
|
||||||
{loadingTier === "Al_Coach" ? <Loader2 className="animate-spin" /> : "Get AI Coach"}
|
</Button>
|
||||||
</Button>
|
|
||||||
</CardFooter>
|
|
||||||
</Card>
|
</Card>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -199,10 +188,10 @@ export default function UpgradePage() {
|
|||||||
<table className="w-full text-left border-collapse min-w-[700px]">
|
<table className="w-full text-left border-collapse min-w-[700px]">
|
||||||
<thead>
|
<thead>
|
||||||
<tr className="border-b border-border/50 bg-background/50">
|
<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 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/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/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/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 text-center w-1/4 text-[#00F0FF] bg-[#00F0FF]/5 border-x border-t border-[#00F0FF]">AI Coach</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody className="divide-y divide-border/30 text-sm md:text-base">
|
<tbody className="divide-y divide-border/30 text-sm md:text-base">
|
||||||
@@ -232,9 +221,9 @@ export default function UpgradePage() {
|
|||||||
].map((feature, idx) => (
|
].map((feature, idx) => (
|
||||||
<tr key={idx} className="hover:bg-background/40 transition-colors">
|
<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 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>}
|
{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">
|
<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>}
|
{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>
|
</td>
|
||||||
|
|||||||
@@ -77,7 +77,7 @@ export function AITutorChat() {
|
|||||||
<Bot size={20} />
|
<Bot size={20} />
|
||||||
</div>
|
</div>
|
||||||
<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">
|
<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
|
<span className="size-2 bg-green-500 rounded-full animate-pulse"></span> Online
|
||||||
</p>
|
</p>
|
||||||
@@ -123,7 +123,7 @@ export function AITutorChat() {
|
|||||||
<CardFooter className="p-4 border-t border-border bg-background shrink-0">
|
<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">
|
<form onSubmit={(e) => { e.preventDefault(); handleSend(); }} className="flex w-full items-center gap-2">
|
||||||
<Input
|
<Input
|
||||||
placeholder="Ask your AI Coach..."
|
placeholder="Ask your AI Tutor..."
|
||||||
value={input}
|
value={input}
|
||||||
onChange={(e) => setInput(e.target.value)}
|
onChange={(e) => setInput(e.target.value)}
|
||||||
className="flex-1 bg-secondary/30 border-border focus-visible:ring-primary h-12 rounded-xl"
|
className="flex-1 bg-secondary/30 border-border focus-visible:ring-primary h-12 rounded-xl"
|
||||||
|
|||||||
@@ -2,37 +2,57 @@
|
|||||||
|
|
||||||
import { useEffect, useState } from "react"
|
import { useEffect, useState } from "react"
|
||||||
import { Card, CardContent } from "@/components/ui/card"
|
import { Card, CardContent } from "@/components/ui/card"
|
||||||
import { RefreshCw } from "lucide-react"
|
import { RefreshCw, TrendingUp, MapPin, Star } from "lucide-react"
|
||||||
import Link from "next/link"
|
|
||||||
|
|
||||||
import { useActiveUser } from "@/hooks/useActiveUser"
|
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() {
|
export function CollegePredictorCard() {
|
||||||
const [data, setData] = useState<any>(null)
|
|
||||||
const [loading, setLoading] = useState(true)
|
const [loading, setLoading] = useState(true)
|
||||||
const [activeTab, setActiveTab] = useState("Target")
|
const [activeTab, setActiveTab] = useState("Target")
|
||||||
const { userId, loading: userLoading } = useActiveUser();
|
const { userId, loading: userLoading } = useActiveUser()
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (userLoading || !userId) return;
|
if (userLoading || !userId) return
|
||||||
|
async function fetchData() {
|
||||||
async function fetchReadiness() {
|
|
||||||
try {
|
try {
|
||||||
const res = await fetch(`/api/ai-coach/readiness/${userId}`)
|
const res = await fetch(`/api/ai-coach/readiness/${userId}`)
|
||||||
if (!res.ok) throw new Error("Failed to fetch")
|
if (!res.ok) throw new Error("fallback")
|
||||||
const json = await res.json()
|
} catch {
|
||||||
setData(json)
|
// Use mock data — no-op
|
||||||
} catch (err) {
|
|
||||||
console.warn("Using mock fallback data for College Predictor.")
|
|
||||||
setData({
|
|
||||||
college_prediction: "NIT Trichy / NIT Surathkal on track.",
|
|
||||||
prediction_category: "Target"
|
|
||||||
})
|
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false)
|
setLoading(false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fetchReadiness()
|
fetchData()
|
||||||
}, [userId, userLoading])
|
}, [userId, userLoading])
|
||||||
|
|
||||||
if (loading) {
|
if (loading) {
|
||||||
@@ -43,77 +63,76 @@ export function CollegePredictorCard() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const prediction = data?.college_prediction || "Keep Practicing"
|
const colleges = MOCK_COLLEGES[activeTab] || []
|
||||||
const category = data?.prediction_category || "State Level"
|
|
||||||
|
|
||||||
const getCategoryColor = (cat: string) => {
|
const TAB_COLORS: Record<string, string> = {
|
||||||
switch(cat.toUpperCase()) {
|
Reach: "bg-purple-500/10 border-purple-500/20 text-purple-400",
|
||||||
case "REACH": return "bg-purple-500/10 text-purple-600 border-purple-500/20"
|
Target: "bg-[#00F0FF]/10 border-[#00F0FF]/20 text-[#00F0FF]",
|
||||||
case "TARGET": return "bg-blue-500/10 text-blue-600 border-blue-500/20"
|
Safety: "bg-green-500/10 border-green-500/20 text-green-400",
|
||||||
case "SAFETY": return "bg-green-500/10 text-green-600 border-green-500/20"
|
|
||||||
default: return "bg-secondary text-muted-foreground border-transparent"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const INACTIVE = "bg-white/5 border-transparent text-muted-foreground hover:bg-white/10"
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col h-full">
|
<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">
|
||||||
<div className="flex justify-between items-center pb-3">
|
<CardContent className="p-5 flex flex-col gap-4">
|
||||||
<h2 className="text-base font-bold text-primary flex items-center gap-2">
|
{/* Tabs */}
|
||||||
🎓 College Predictor
|
<div className="flex gap-1.5">
|
||||||
</h2>
|
{["Reach", "Target", "Safety"].map((tab) => (
|
||||||
</div>
|
<button
|
||||||
|
key={tab}
|
||||||
|
onClick={() => setActiveTab(tab)}
|
||||||
|
className={`flex-1 py-2 text-xs font-bold rounded-lg border transition-all ${activeTab === tab ? TAB_COLORS[tab] : INACTIVE}`}
|
||||||
|
>
|
||||||
|
{tab}
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
<Card className="border-border bg-card shadow-[0_4px_24px_rgba(0,0,0,0.3)] flex-1 overflow-hidden group hover:border-[#00F0FF]/30 transition-colors">
|
{/* Tab description */}
|
||||||
<CardContent className="p-4 flex flex-col h-full">
|
<div className="text-xs text-muted-foreground font-medium px-0.5">
|
||||||
<div className="flex gap-1.5 mb-3.5">
|
{activeTab === "Reach" && "Colleges where your current score gives you a ~50–65% admission probability. Requires improvement."}
|
||||||
{["Reach", "Target", "Safety"].map(tab => (
|
{activeTab === "Target" && "Colleges within strong reach. Your current trajectory suggests 68–90% match probability."}
|
||||||
<div
|
{activeTab === "Safety" && "Colleges highly likely with your current score. Aim to convert all safety options."}
|
||||||
key={tab}
|
</div>
|
||||||
onClick={() => setActiveTab(tab)}
|
|
||||||
className={`flex-1 p-2 text-center text-[11px] font-semibold rounded-md cursor-pointer transition-colors border ${
|
|
||||||
activeTab === tab
|
|
||||||
? "bg-[#00F0FF]/10 border-[#00F0FF]/20 text-[#00F0FF]"
|
|
||||||
: "bg-white/5 border-transparent text-muted-foreground hover:bg-white/10"
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
{tab}
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="flex flex-col gap-2 flex-1">
|
{/* College List */}
|
||||||
<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="flex flex-col gap-2">
|
||||||
<div className="w-7 h-7 rounded-full flex items-center justify-center text-[11px] font-bold shrink-0 bg-[#00F0FF]/10 text-[#00F0FF]">
|
{colleges.map((col) => (
|
||||||
#1
|
<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>
|
||||||
<div className="flex-1 min-w-0">
|
<div className="flex-1 min-w-0">
|
||||||
<div className="text-[13px] font-bold text-primary truncate">NIT Trichy</div>
|
<div className="flex items-center gap-1.5">
|
||||||
<div className="text-[11px] text-muted-foreground truncate">Computer Science</div>
|
<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>
|
||||||
<div className="text-[12px] font-bold px-2 py-0.5 rounded-md shrink-0 text-green-500 bg-green-500/10">
|
<div className={`text-xs font-black px-2.5 py-1 rounded-md shrink-0 ${MATCH_COLOR(col.match)}`}>
|
||||||
85%
|
{col.match}%
|
||||||
</div>
|
</div>
|
||||||
</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>
|
||||||
<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">
|
{/* Bottom Tip */}
|
||||||
Just 15 more marks in Maths can upgrade you to IIT Roorkee!
|
<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">
|
||||||
</div>
|
<TrendingUp size={14} className="shrink-0" />
|
||||||
</CardContent>
|
{activeTab === "Reach" && "Score +20 marks in Maths to convert IIT Roorkee from Reach to Target!"}
|
||||||
</Card>
|
{activeTab === "Target" && "Just 15 more marks in Maths can upgrade you from NIT Trichy to IIT Roorkee!"}
|
||||||
</div>
|
{activeTab === "Safety" && "You're on track for all safety colleges. Focus your energy on converting Target colleges."}
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
"use client"
|
"use client"
|
||||||
|
|
||||||
import { useState, useEffect } from "react"
|
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 = [
|
const FOCUS_TOPICS = [
|
||||||
{ subject: "Physics", topic: "Rotational Dynamics", color: "from-blue-600/30 to-[#00F0FF]/20", accent: "#00F0FF", 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 Reactions — Mechanism", color: "from-amber-600/30 to-yellow-500/20", accent: "#F59E0B", icon: "🧪" },
|
{ 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: "🧬" },
|
{ 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: "∫" },
|
{ 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() {
|
export function DailyChallengeBanner() {
|
||||||
@@ -53,25 +53,30 @@ export function DailyChallengeBanner() {
|
|||||||
</span>
|
</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">
|
<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} />
|
<Flame size={9} />
|
||||||
DAILY CHALLENGE
|
Daily Focus
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<h3 className="text-lg font-extrabold text-white tracking-tight leading-tight">
|
<h3 className="text-lg font-extrabold text-white tracking-tight leading-tight">
|
||||||
{todayFocus.subject}:{" "}
|
{todayFocus.subject}:{" "}
|
||||||
<span style={{ color: todayFocus.accent }}>{todayFocus.topic}</span>
|
<span style={{ color: todayFocus.accent }}>{todayFocus.topic}</span>
|
||||||
</h3>
|
</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">
|
||||||
<BookOpen size={11} />
|
<p className="text-xs text-white/50 flex items-center gap-1.5">
|
||||||
Mastering this will boost your rank by an estimated{" "}
|
<BookOpen size={11} />
|
||||||
<span className="font-bold text-white/70">+120 positions</span>
|
{todayFocus.questions} practice questions
|
||||||
</p>
|
</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>
|
</div>
|
||||||
|
|
||||||
<div className="flex items-center gap-3 relative z-10 shrink-0">
|
<div className="flex items-center gap-3 relative z-10 shrink-0">
|
||||||
<div className="text-right hidden sm:block">
|
<div className="text-right hidden sm:block">
|
||||||
<div className="text-[10px] text-white/40 font-semibold uppercase flex items-center gap-1 justify-end">
|
<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>
|
||||||
<div className="text-sm font-black text-white/80 font-mono tracking-wider">{timeLeft}</div>
|
<div className="text-sm font-black text-white/80 font-mono tracking-wider">{timeLeft}</div>
|
||||||
</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"
|
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 }}
|
style={{ background: todayFocus.accent }}
|
||||||
>
|
>
|
||||||
Start Session <ArrowRight size={15} />
|
Start Practice <ArrowRight size={15} />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -5,39 +5,33 @@ import Link from "next/link"
|
|||||||
export function ExamStrategyCard() {
|
export function ExamStrategyCard() {
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col h-full">
|
<div className="flex flex-col h-full">
|
||||||
<div className="flex justify-between items-center pb-3">
|
<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">
|
||||||
<h2 className="text-base font-bold text-primary flex items-center gap-2">
|
<CardContent className="p-5 flex flex-col h-full justify-between">
|
||||||
🧠 Exam Day Strategy
|
<div className="grid grid-cols-2 gap-3">
|
||||||
</h2>
|
<div className="p-3.5 rounded-lg bg-white/5 border border-border text-center flex flex-col justify-center">
|
||||||
</div>
|
<div className="text-2xl mb-1.5">🧪</div>
|
||||||
|
<div className="text-sm font-black text-primary">Chemistry First</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="text-xs font-semibold text-muted-foreground mt-1">Finish in 40 mins</div>
|
||||||
<CardContent className="p-4 flex flex-col h-full justify-between">
|
|
||||||
<div className="grid grid-cols-2 gap-2">
|
|
||||||
<div className="p-3 rounded-md bg-white/5 border border-border text-center">
|
|
||||||
<div className="text-xl mb-1">🧪</div>
|
|
||||||
<div className="text-[11px] font-bold text-primary">Chemistry First</div>
|
|
||||||
<div className="text-[10px] text-muted-foreground mt-0.5">Finish in 40 mins</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="p-3 rounded-md bg-white/5 border border-border text-center">
|
<div className="p-3.5 rounded-lg bg-white/5 border border-border text-center flex flex-col justify-center">
|
||||||
<div className="text-xl mb-1">🧲</div>
|
<div className="text-2xl mb-1.5">🧲</div>
|
||||||
<div className="text-[11px] font-bold text-primary">Physics Next</div>
|
<div className="text-sm font-black text-primary">Physics Next</div>
|
||||||
<div className="text-[10px] text-muted-foreground mt-0.5">Target 55 mins</div>
|
<div className="text-xs font-semibold text-muted-foreground mt-1">Target 55 mins</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="p-3 rounded-md bg-white/5 border border-border text-center">
|
<div className="p-3.5 rounded-lg bg-white/5 border border-border text-center flex flex-col justify-center">
|
||||||
<div className="text-xl mb-1">📐</div>
|
<div className="text-2xl mb-1.5">📐</div>
|
||||||
<div className="text-[11px] font-bold text-primary">Maths Last</div>
|
<div className="text-sm font-black text-primary">Maths Last</div>
|
||||||
<div className="text-[10px] text-muted-foreground mt-0.5">Reserve 75 mins</div>
|
<div className="text-xs font-semibold text-muted-foreground mt-1">Reserve 75 mins</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="p-3 rounded-md bg-white/5 border border-border text-center">
|
<div className="p-3.5 rounded-lg bg-white/5 border border-border text-center flex flex-col justify-center">
|
||||||
<div className="text-xl mb-1">⏱️</div>
|
<div className="text-2xl mb-1.5">⏱️</div>
|
||||||
<div className="text-[11px] font-bold text-primary">Buffer Time</div>
|
<div className="text-sm font-black text-primary">Buffer Time</div>
|
||||||
<div className="text-[10px] text-muted-foreground mt-0.5">10 mins for review</div>
|
<div className="text-xs font-semibold text-muted-foreground mt-1">10 mins for review</div>
|
||||||
</div>
|
</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">
|
<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={16} className="stroke-[#00F0FF]" />
|
<Printer size={18} className="stroke-[#00F0FF]" />
|
||||||
Print Exam Day Blueprint
|
Print Exam Day Blueprint
|
||||||
</div>
|
</div>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ export function MistakeHeatmap() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
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">
|
<CardHeader className="pb-4">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<CardTitle className="text-xl flex items-center gap-2">
|
<CardTitle className="text-xl flex items-center gap-2">
|
||||||
@@ -30,47 +30,45 @@ export function MistakeHeatmap() {
|
|||||||
</div>
|
</div>
|
||||||
<p className="text-sm text-muted-foreground">Topic-wise error rate across last 5 tests.</p>
|
<p className="text-sm text-muted-foreground">Topic-wise error rate across last 5 tests.</p>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="flex-1">
|
<CardContent className="flex-1 p-4 md:p-6 flex flex-col justify-between">
|
||||||
<div className="overflow-x-auto">
|
<div className="w-full">
|
||||||
<div className="min-w-[400px]">
|
{/* Header row */}
|
||||||
{/* Header row */}
|
<div className="flex items-center mb-3 text-xs sm:text-sm font-black text-muted-foreground uppercase tracking-wider">
|
||||||
<div className="flex mb-2">
|
<div className="w-32 sm:w-36 md:w-40 shrink-0 pr-2">Topic</div>
|
||||||
<div className="w-40 shrink-0 text-xs font-bold text-muted-foreground uppercase tracking-wider">Topic</div>
|
<div className="flex-1 grid grid-cols-5 gap-1 text-center">
|
||||||
<div className="flex-1 flex justify-between text-xs font-bold text-muted-foreground uppercase tracking-wider text-center">
|
<span>T-4</span>
|
||||||
<span>T-4</span>
|
<span>T-3</span>
|
||||||
<span>T-3</span>
|
<span>T-2</span>
|
||||||
<span>T-2</span>
|
<span>T-1</span>
|
||||||
<span>T-1</span>
|
<span>Latest</span>
|
||||||
<span>Latest</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
{/* Data rows */}
|
|
||||||
<div className="space-y-3">
|
{/* Data rows */}
|
||||||
{HEATMAP_DATA.map((row, idx) => (
|
<div className="space-y-3">
|
||||||
<div key={idx} className="flex items-center group">
|
{HEATMAP_DATA.map((row, idx) => (
|
||||||
<div className="w-40 shrink-0 text-sm font-medium truncate pr-2 group-hover:text-primary transition-colors">
|
<div key={idx} className="flex items-center group">
|
||||||
{row.topic}
|
<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}>
|
||||||
</div>
|
{row.topic}
|
||||||
<div className="flex-1 flex justify-between gap-1">
|
|
||||||
{row.attempts.map((errRate, i) => (
|
|
||||||
<div
|
|
||||||
key={i}
|
|
||||||
className={`flex-1 h-8 rounded-md border transition-all duration-300 group-hover:scale-105 flex items-center justify-center cursor-help ${getColor(errRate)}`}
|
|
||||||
title={`Error Rate: ${errRate}%`}
|
|
||||||
>
|
|
||||||
<span className="text-[10px] font-bold text-foreground/80">{errRate}%</span>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
))}
|
<div className="flex-1 grid grid-cols-5 gap-1">
|
||||||
</div>
|
{row.attempts.map((errRate, i) => (
|
||||||
|
<div
|
||||||
|
key={i}
|
||||||
|
className={`h-8 sm:h-9 rounded-md border transition-all duration-300 group-hover:scale-105 flex items-center justify-center cursor-help ${getColor(errRate)}`}
|
||||||
|
title={`Error Rate: ${errRate}%`}
|
||||||
|
>
|
||||||
|
<span className="text-[10px] sm:text-xs font-black text-foreground/90">{errRate}%</span>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Legend */}
|
{/* 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-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-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>
|
<div className="flex items-center gap-1.5"><div className="size-3 rounded-sm bg-amber-500/60 border border-amber-500"></div> {"30-50%"}</div>
|
||||||
|
|||||||
@@ -2,8 +2,7 @@
|
|||||||
|
|
||||||
import { useState } from "react"
|
import { useState } from "react"
|
||||||
import { Card, CardContent } from "@/components/ui/card"
|
import { Card, CardContent } from "@/components/ui/card"
|
||||||
import { Flame } from "lucide-react"
|
import { Flame, CheckCircle2, Clock } from "lucide-react"
|
||||||
import Link from "next/link"
|
|
||||||
import ReactMarkdown from 'react-markdown'
|
import ReactMarkdown from 'react-markdown'
|
||||||
import remarkMath from 'remark-math'
|
import remarkMath from 'remark-math'
|
||||||
import rehypeKatex from 'rehype-katex'
|
import rehypeKatex from 'rehype-katex'
|
||||||
@@ -37,6 +36,22 @@ interface MistakeReviewCardProps {
|
|||||||
onSubmitReview: (mistakeId: string, selectedAnswer: string) => Promise<{is_correct: boolean, correct_answer: string}>
|
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) {
|
export function MistakeReviewCard({ data, onNext, onSubmitReview }: MistakeReviewCardProps) {
|
||||||
const [selected, setSelected] = useState<string | null>(null)
|
const [selected, setSelected] = useState<string | null>(null)
|
||||||
const [result, setResult] = useState<'success' | 'error' | null>(null)
|
const [result, setResult] = useState<'success' | 'error' | null>(null)
|
||||||
@@ -46,7 +61,6 @@ export function MistakeReviewCard({ data, onNext, onSubmitReview }: MistakeRevie
|
|||||||
const handleCheck = async () => {
|
const handleCheck = async () => {
|
||||||
if (!selected) return;
|
if (!selected) return;
|
||||||
setIsSubmitting(true)
|
setIsSubmitting(true)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const res = await onSubmitReview(data.mistake.id, selected)
|
const res = await onSubmitReview(data.mistake.id, selected)
|
||||||
setResult(res.is_correct ? 'success' : 'error')
|
setResult(res.is_correct ? 'success' : 'error')
|
||||||
@@ -66,69 +80,70 @@ export function MistakeReviewCard({ data, onNext, onSubmitReview }: MistakeRevie
|
|||||||
}
|
}
|
||||||
|
|
||||||
const { mistake, question } = data;
|
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 (
|
return (
|
||||||
<div className="flex flex-col gap-4">
|
<div className="flex flex-col gap-4">
|
||||||
<div className="flex justify-between items-center pb-1">
|
{/* SM-2 Timeline */}
|
||||||
<h2 className="text-base font-bold text-primary flex items-center gap-2">
|
|
||||||
♻️ Spaced Repetition Queue
|
|
||||||
</h2>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Spaced Repetition Timeline */}
|
|
||||||
<div className="flex justify-between items-center relative py-4 px-2">
|
<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 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" />
|
{ label: "1st Attempt", date: dates.first, done: mistake.review_count >= 1 },
|
||||||
<span className="text-[10px] font-bold text-[#00F0FF]">Attempt 1</span>
|
{ label: "2nd Review", date: dates.second, done: mistake.review_count >= 2 },
|
||||||
<span className="text-[9px] text-muted-foreground font-semibold">10 Jul</span>
|
{ label: reviewLabel, date: dates.today, active: true },
|
||||||
</div>
|
{ label: "Mastered", date: dates.mastery, done: mistake.is_mastered },
|
||||||
|
].map((step, i) => (
|
||||||
<div className="relative z-10 flex flex-col items-center gap-1.5 opacity-60">
|
<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="w-3.5 h-3.5 rounded-full bg-[#00F0FF] border-2 border-background" />
|
<div className={`rounded-full border-2 border-background ${
|
||||||
<span className="text-[10px] font-bold text-[#00F0FF]">Attempt 2</span>
|
step.done ? "w-3.5 h-3.5 bg-green-500" :
|
||||||
<span className="text-[9px] text-muted-foreground font-semibold">14 Jul</span>
|
step.active ? "w-4 h-4 border-[3px] border-[#00F0FF] bg-background shadow-[0_0_10px_rgba(0,240,255,0.5)]" :
|
||||||
</div>
|
"w-3.5 h-3.5 bg-secondary"
|
||||||
|
}`} />
|
||||||
<div className="relative z-10 flex flex-col items-center gap-1.5 scale-110">
|
<span className={`text-xs font-bold ${step.active ? "text-[#00F0FF]" : step.done ? "text-green-400" : "text-muted-foreground"}`}>
|
||||||
<div className="w-4 h-4 rounded-full border-[3px] border-[#00F0FF] bg-background shadow-[0_0_10px_rgba(0,240,255,0.5)]" />
|
{step.label}
|
||||||
<span className="text-[11px] font-bold text-[#00F0FF]">Today</span>
|
</span>
|
||||||
<span className="text-[9px] text-muted-foreground font-semibold">19 Jul</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-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>
|
</div>
|
||||||
|
|
||||||
{/* Mistake Card */}
|
{/* Question 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">
|
<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-5 flex-1 flex flex-col">
|
<CardContent className="p-6 flex-1 flex flex-col justify-between">
|
||||||
<div className="flex items-center gap-2 mb-4">
|
{/* Meta Tags */}
|
||||||
<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">
|
<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}
|
{mistake.topic}
|
||||||
</span>
|
</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}
|
{mistake.error_type}
|
||||||
</span>
|
</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>
|
</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">
|
<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]}>
|
<ReactMarkdown remarkPlugins={[remarkMath]} rehypePlugins={[rehypeKatex]}>
|
||||||
{question.question_text}
|
{question.question_text}
|
||||||
</ReactMarkdown>
|
</ReactMarkdown>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Options */}
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-3 mt-auto">
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-3 mt-auto">
|
||||||
{Object.entries(question.options).map(([letter, text]) => {
|
{Object.entries(question.options).map(([letter, text]) => {
|
||||||
const isSelected = selected === letter
|
const isSelected = selected === letter
|
||||||
|
|
||||||
let btnClass = "border-border bg-white/5 hover:bg-white/10 hover:border-border text-muted-foreground"
|
let btnClass = "border-border bg-white/5 hover:bg-white/10 hover:border-border text-muted-foreground"
|
||||||
|
|
||||||
if (result) {
|
if (result) {
|
||||||
if (letter === correctAnswer) {
|
if (letter === correctAnswer) {
|
||||||
btnClass = "border-green-500 bg-green-500/10 text-green-500 shadow-[0_0_15px_rgba(34,197,94,0.15)]"
|
btnClass = "border-green-500 bg-green-500/10 text-green-500 shadow-[0_0_15px_rgba(34,197,94,0.15)]"
|
||||||
@@ -140,39 +155,39 @@ export function MistakeReviewCard({ data, onNext, onSubmitReview }: MistakeRevie
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
key={letter}
|
key={letter}
|
||||||
onClick={() => !result && setSelected(letter)}
|
onClick={() => !result && setSelected(letter)}
|
||||||
disabled={!!result || isSubmitting}
|
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="font-bold opacity-70">{letter})</span>
|
||||||
<span className="flex-1">
|
<span className="flex-1">
|
||||||
<ReactMarkdown remarkPlugins={[remarkMath]} rehypePlugins={[rehypeKatex]}>{text}</ReactMarkdown>
|
<ReactMarkdown remarkPlugins={[remarkMath]} rehypePlugins={[rehypeKatex]}>{text}</ReactMarkdown>
|
||||||
</span>
|
</span>
|
||||||
|
{result && letter === correctAnswer && <CheckCircle2 size={16} className="text-green-500 shrink-0" />}
|
||||||
</button>
|
</button>
|
||||||
)
|
)
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Action Button */}
|
{/* Actions */}
|
||||||
{!result ? (
|
{!result ? (
|
||||||
<div className="mt-5 flex justify-end">
|
<div className="mt-5 flex justify-end">
|
||||||
<button
|
<button
|
||||||
onClick={handleCheck}
|
onClick={handleCheck}
|
||||||
disabled={!selected || isSubmitting}
|
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"}
|
{isSubmitting ? "Checking..." : "Submit Answer"}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
{/* Solution Block */}
|
|
||||||
<div className="mt-5 p-4 rounded-xl border border-[#00F0FF]/20 bg-[#00F0FF]/5">
|
<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">
|
<h4 className="text-[13px] font-bold text-[#00F0FF] mb-2 flex items-center gap-2">
|
||||||
<Flame size={16} className="fill-[#00F0FF]/20" />
|
<Flame size={16} className="fill-[#00F0FF]/20" />
|
||||||
AI Coach Explanation
|
AI Explanation
|
||||||
</h4>
|
</h4>
|
||||||
<div className="prose prose-sm dark:prose-invert max-w-none text-[13px] text-muted-foreground leading-relaxed [&>p]:m-0">
|
<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]}>
|
<ReactMarkdown remarkPlugins={[remarkMath]} rehypePlugins={[rehypeKatex]}>
|
||||||
@@ -180,13 +195,12 @@ export function MistakeReviewCard({ data, onNext, onSubmitReview }: MistakeRevie
|
|||||||
</ReactMarkdown>
|
</ReactMarkdown>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="mt-5 flex justify-end">
|
<div className="mt-5 flex justify-end">
|
||||||
<button
|
<button
|
||||||
onClick={handleNext}
|
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>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
|
|||||||
@@ -2,17 +2,21 @@
|
|||||||
|
|
||||||
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/card"
|
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/card"
|
||||||
import { Button } from "@/components/ui/button"
|
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 { useState } from "react"
|
||||||
|
import { useActiveUser } from "@/hooks/useActiveUser"
|
||||||
|
|
||||||
export function ProgressShare() {
|
export function ProgressShare() {
|
||||||
|
const { user } = useActiveUser();
|
||||||
const [downloading, setDownloading] = useState(false)
|
const [downloading, setDownloading] = useState(false)
|
||||||
|
|
||||||
// Example dynamic params
|
// Example dynamic params
|
||||||
const streak = "12"
|
const streak = "12"
|
||||||
const accuracy = "85"
|
const accuracy = "85"
|
||||||
const name = "Student"
|
const name = user?.name || "Student"
|
||||||
const imageUrl = `/api/og/progress?streak=${streak}&accuracy=${accuracy}&name=${name}`
|
|
||||||
|
// 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 () => {
|
const handleDownload = async () => {
|
||||||
try {
|
try {
|
||||||
@@ -35,28 +39,61 @@ export function ProgressShare() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card className="border-border shadow-sm h-full flex flex-col">
|
<Card className="border-border shadow-sm h-full flex flex-col overflow-hidden group">
|
||||||
<CardHeader>
|
<CardHeader className="pb-2">
|
||||||
<CardTitle className="text-xl flex items-center gap-2">
|
<CardTitle className="text-xl flex items-center justify-between">
|
||||||
<ImageIcon className="text-primary" size={24} />
|
<div className="flex items-center gap-2">
|
||||||
Share Progress
|
<ImageIcon className="text-[#00F0FF]" size={24} />
|
||||||
|
Share Your Wins
|
||||||
|
</div>
|
||||||
</CardTitle>
|
</CardTitle>
|
||||||
<CardDescription>Auto-generated dynamic scorecard.</CardDescription>
|
<CardDescription>Inspire your friends with your weekly stats.</CardDescription>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
|
|
||||||
<CardContent className="flex-1 flex flex-col items-center justify-center gap-6">
|
<CardContent className="flex-1 flex flex-col justify-between gap-6 pt-2">
|
||||||
<div className="w-full max-w-sm rounded-xl overflow-hidden border border-border shadow-sm bg-secondary/20 aspect-[1200/630] relative">
|
{/* Mock Preview of the OG Image */}
|
||||||
<img
|
<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]">
|
||||||
src={imageUrl}
|
<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" />
|
||||||
alt="Progress Scorecard"
|
|
||||||
className="w-full h-full object-cover"
|
<div className="relative z-10 w-full flex flex-col h-full">
|
||||||
loading="lazy"
|
<div className="flex justify-between items-start w-full">
|
||||||
/>
|
<div className="flex items-center gap-2">
|
||||||
|
<div className="w-8 h-8 rounded-lg bg-[#00F0FF] flex items-center justify-center">
|
||||||
|
<Sparkles size={16} className="text-black" />
|
||||||
|
</div>
|
||||||
|
<span className="text-sm font-black text-white tracking-wider">PRAGATIVEER</span>
|
||||||
|
</div>
|
||||||
|
<div className="text-[10px] font-bold text-[#00F0FF] bg-[#00F0FF]/10 px-2 py-1 rounded-full border border-[#00F0FF]/20">
|
||||||
|
Weekly Report
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="mt-auto mb-auto text-center">
|
||||||
|
<h3 className="text-2xl font-black text-white mb-4">You crushed it this week!</h3>
|
||||||
|
<div className="flex justify-center gap-4">
|
||||||
|
<div className="bg-white/10 backdrop-blur-sm border border-white/20 rounded-xl p-3 w-28">
|
||||||
|
<TrendingUp className="text-amber-400 mx-auto mb-1" size={20} />
|
||||||
|
<div className="text-2xl font-black text-white">{streak}</div>
|
||||||
|
<div className="text-[9px] text-white/70 uppercase font-bold tracking-wider">Day Streak</div>
|
||||||
|
</div>
|
||||||
|
<div className="bg-white/10 backdrop-blur-sm border border-white/20 rounded-xl p-3 w-28">
|
||||||
|
<Target className="text-green-400 mx-auto mb-1" size={20} />
|
||||||
|
<div className="text-2xl font-black text-white">{accuracy}%</div>
|
||||||
|
<div className="text-[9px] text-white/70 uppercase font-bold tracking-wider">Accuracy</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="text-left mt-auto">
|
||||||
|
<div className="text-sm font-bold text-white">{name}</div>
|
||||||
|
<div className="text-[10px] text-white/50">JEE Aspirant</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Button onClick={handleDownload} variant="outline" className="w-full" disabled={downloading}>
|
<Button onClick={handleDownload} variant="outline" className="w-full h-11 font-bold text-sm hover:bg-[#00F0FF]/10 hover:text-[#00F0FF] hover:border-[#00F0FF]/30 transition-colors" disabled={downloading}>
|
||||||
{downloading ? <Loader2 size={18} className="mr-2 animate-spin" /> : <Download size={18} className="mr-2" />}
|
{downloading ? <Loader2 size={18} className="mr-2 animate-spin" /> : <Download size={18} className="mr-2" />}
|
||||||
Download as PNG
|
Download Scorecard PNG
|
||||||
</Button>
|
</Button>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|||||||
@@ -61,13 +61,8 @@ export function ReadinessGauge() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col h-full">
|
<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 */}
|
{/* 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" />
|
<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>
|
</div>
|
||||||
|
|
||||||
{/* Quick metric grid beside ring */}
|
{/* Quick metric grid beside ring */}
|
||||||
<div className="flex-1 grid grid-cols-2 gap-2 w-full">
|
<div className="flex-1 grid grid-cols-2 gap-2.5 w-full">
|
||||||
<div className="bg-white/5 rounded-md p-2">
|
<div className="bg-white/5 rounded-lg p-2.5">
|
||||||
<div className="text-sm font-bold flex items-baseline gap-1">88% <span className="text-[10px] text-green-500">↑</span></div>
|
<div className="text-base font-black flex items-baseline gap-1">88% <span className="text-xs text-green-500">↑</span></div>
|
||||||
<div className="text-[10px] font-medium text-muted-foreground">Accuracy</div>
|
<div className="text-xs font-bold text-muted-foreground">Accuracy</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="bg-white/5 rounded-md p-2">
|
<div className="bg-white/5 rounded-lg p-2.5">
|
||||||
<div className="text-sm font-bold flex items-baseline gap-1">45s <span className="text-[10px] text-red-500">↓</span></div>
|
<div className="text-base font-black flex items-baseline gap-1">45s <span className="text-xs text-red-500">↓</span></div>
|
||||||
<div className="text-[10px] font-medium text-muted-foreground">Speed/Q</div>
|
<div className="text-xs font-bold text-muted-foreground">Speed/Q</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="bg-white/5 rounded-md p-2">
|
<div className="bg-white/5 rounded-lg p-2.5">
|
||||||
<div className="text-sm font-bold flex items-baseline gap-1">High <span className="text-[10px] text-green-500">↑</span></div>
|
<div className="text-base font-black flex items-baseline gap-1">High <span className="text-xs text-green-500">↑</span></div>
|
||||||
<div className="text-[10px] font-medium text-muted-foreground">Consistency</div>
|
<div className="text-xs font-bold text-muted-foreground">Consistency</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="bg-white/5 rounded-md p-2">
|
<div className="bg-white/5 rounded-lg p-2.5">
|
||||||
<div className="text-sm font-bold flex items-baseline gap-1">64% <span className="text-[10px] text-green-500">↑</span></div>
|
<div className="text-base font-black flex items-baseline gap-1">64% <span className="text-xs text-green-500">↑</span></div>
|
||||||
<div className="text-[10px] font-medium text-muted-foreground">Syllabus</div>
|
<div className="text-xs font-bold text-muted-foreground">Syllabus</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Subject Bars */}
|
{/* 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: "Physics", val: 68, color: "#00F0FF" },
|
||||||
{ name: "Chemistry", val: 74, color: "#F59E0B" },
|
{ name: "Chemistry", val: 74, color: "#F59E0B" },
|
||||||
{ name: "Biology", val: 82, color: "#10B981" },
|
{ name: "Biology", val: 82, color: "#10B981" },
|
||||||
].map(sub => (
|
].map(sub => (
|
||||||
<div key={sub.name} className="flex items-center gap-2">
|
<div key={sub.name} className="flex items-center gap-3">
|
||||||
<span className="text-[9px] font-semibold text-muted-foreground w-14 shrink-0">{sub.name}</span>
|
<span className="text-xs font-bold text-muted-foreground w-20 shrink-0">{sub.name}</span>
|
||||||
<div className="flex-1 h-1.5 rounded-full bg-white/10 overflow-hidden">
|
<div className="flex-1 h-2 rounded-full bg-white/10 overflow-hidden">
|
||||||
<div
|
<div
|
||||||
className="h-full rounded-full transition-all duration-1000"
|
className="h-full rounded-full transition-all duration-1000"
|
||||||
style={{ width: `${sub.val}%`, background: sub.color }}
|
style={{ width: `${sub.val}%`, background: sub.color }}
|
||||||
/>
|
/>
|
||||||
</div>
|
</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>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* National Avg */}
|
{/* National Avg */}
|
||||||
<div className="flex items-center justify-between px-2 py-1.5 bg-white/5 rounded-md mb-3 relative z-10">
|
<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-[10px] text-muted-foreground font-semibold">National Avg</span>
|
<span className="text-muted-foreground">National Avg</span>
|
||||||
<span className="text-[10px] font-bold text-muted-foreground">58 / 100</span>
|
<span className="text-muted-foreground">58 / 100</span>
|
||||||
<span className="text-[10px] font-bold text-green-500">You: +{score - 58} above avg</span>
|
<span className="text-green-500">You: +{score - 58} above avg</span>
|
||||||
</div>
|
</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">
|
<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={16} className="fill-[#00F0FF]/20 shrink-0" />
|
<Flame size={18} className="fill-[#00F0FF]/20 shrink-0" />
|
||||||
Your speed has dropped slightly. Focus on time-bound practice today.
|
Your speed has dropped slightly. Focus on time-bound practice today.
|
||||||
</div>
|
</div>
|
||||||
</Card>
|
</Card>
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
import { useEffect, useState } from "react"
|
import { useEffect, useState } from "react"
|
||||||
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/card"
|
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/card"
|
||||||
import { Button } from "@/components/ui/button"
|
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"
|
import { useActiveUser } from "@/hooks/useActiveUser"
|
||||||
|
|
||||||
@@ -30,6 +30,8 @@ export function ReferralCard() {
|
|||||||
|
|
||||||
const link = data?.referral_link || `https://pragativeer.com/join/${userId || "demo"}`
|
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 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 = () => {
|
const copyToClipboard = () => {
|
||||||
navigator.clipboard.writeText(link)
|
navigator.clipboard.writeText(link)
|
||||||
@@ -44,30 +46,56 @@ export function ReferralCard() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Card className="border-border shadow-sm h-full flex flex-col relative overflow-hidden bg-gradient-to-br from-background to-primary/5">
|
<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">
|
<div className="absolute -right-6 -bottom-6 opacity-[0.03] pointer-events-none">
|
||||||
<Gift size={120} />
|
<Gift size={150} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<CardHeader>
|
<CardHeader className="pb-2">
|
||||||
<CardTitle className="text-xl flex items-center gap-2">
|
<CardTitle className="text-xl flex items-center justify-between">
|
||||||
<Gift className="text-primary" size={24} />
|
<div className="flex items-center gap-2">
|
||||||
Invite & Earn
|
<Gift className="text-primary" size={24} />
|
||||||
|
Invite & Earn
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-1.5 text-xs font-bold text-muted-foreground bg-secondary/30 px-2 py-1 rounded-md">
|
||||||
|
<Users size={14} /> {referralCount} Joined
|
||||||
|
</div>
|
||||||
</CardTitle>
|
</CardTitle>
|
||||||
<CardDescription>Give 7 days of AI Coach, get 7 days free.</CardDescription>
|
<CardDescription>Give 7 days of AI Coach, get 7 days free.</CardDescription>
|
||||||
</CardHeader>
|
</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">
|
||||||
<div className="bg-secondary/50 border border-border p-3 rounded-lg flex items-center justify-between">
|
{/* Progress Bar for Rewards */}
|
||||||
<code className="text-sm font-semibold truncate mr-4">{link}</code>
|
<div className="bg-white/5 border border-border/50 rounded-xl p-3 space-y-2">
|
||||||
<Button variant="secondary" size="sm" onClick={copyToClipboard} className="shrink-0 rounded-md">
|
<div className="flex justify-between items-center text-xs font-bold">
|
||||||
{copied ? <Check size={16} className="text-green-500" /> : <LinkIcon size={16} />}
|
<span className="text-muted-foreground flex items-center gap-1">
|
||||||
<span className="ml-2 hidden sm:inline">{copied ? "Copied" : "Copy"}</span>
|
<Trophy size={14} className="text-yellow-500" /> Free Month Tier
|
||||||
|
</span>
|
||||||
|
<span>{referralCount} / {nextTier}</span>
|
||||||
|
</div>
|
||||||
|
<div className="h-2 w-full bg-secondary rounded-full overflow-hidden">
|
||||||
|
<div
|
||||||
|
className="h-full bg-gradient-to-r from-primary to-teal-400 rounded-full transition-all duration-500"
|
||||||
|
style={{ width: `${Math.min((referralCount / nextTier) * 100, 100)}%` }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<p className="text-[10px] text-muted-foreground font-medium text-center">
|
||||||
|
Invite {nextTier - referralCount} more friends to unlock 1 free month!
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="mt-auto space-y-3">
|
||||||
|
<div className="bg-secondary/50 border border-border p-3 rounded-lg flex items-center justify-between">
|
||||||
|
<code className="text-sm font-semibold truncate mr-4 text-muted-foreground">{link}</code>
|
||||||
|
<Button variant="secondary" size="sm" onClick={copyToClipboard} className="shrink-0 rounded-md bg-white/10 hover:bg-white/20">
|
||||||
|
{copied ? <Check size={16} className="text-green-500" /> : <LinkIcon size={16} />}
|
||||||
|
<span className="ml-2 hidden sm:inline">{copied ? "Copied" : "Copy"}</span>
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Button onClick={shareToWhatsApp} className="w-full bg-[#25D366] hover:bg-[#1ebd5a] text-white font-bold h-11">
|
||||||
|
<Share2 size={18} className="mr-2" /> Share via WhatsApp
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Button onClick={shareToWhatsApp} className="w-full bg-[#25D366] hover:bg-[#1ebd5a] text-white">
|
|
||||||
<Share2 size={18} className="mr-2" /> Share via WhatsApp
|
|
||||||
</Button>
|
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -30,11 +30,11 @@ export function StudyPlanCard() {
|
|||||||
}
|
}
|
||||||
// Mock fallback if celery hasn't seeded yet
|
// Mock fallback if celery hasn't seeded yet
|
||||||
setTasks([
|
setTasks([
|
||||||
{ type: "DPS", description: "DPS — Electrostatics + Kinematics", duration_minutes: 20, status: "Done" },
|
{ type: "DPS", description: "Electrostatics + Kinematics", duration_minutes: 20, status: "Done" },
|
||||||
{ type: "Mistake_Review", description: "Mistake Review — Thermodynamics", duration_minutes: 10, status: "Done" },
|
{ type: "Mistake_Review", description: "Thermodynamics error queue", duration_minutes: 10, status: "Done" },
|
||||||
{ type: "Micro_test", description: "Micro-test — GOC (General Organic Chemistry)", duration_minutes: 5, status: "Pending" },
|
{ type: "Micro_test", description: "GOC — General Organic Chemistry", duration_minutes: 5, status: "Pending" },
|
||||||
{ type: "Test", description: "Weekly Test — Full Syllabus", duration_minutes: 180, status: "Pending" },
|
{ type: "Test", description: "Weekly Full-Syllabus Mock Test", duration_minutes: 180, status: "Pending" },
|
||||||
{ type: "Revision", description: "Revise — Circles + Straight Lines", duration_minutes: 15, status: "Pending" }
|
{ type: "Revision", description: "Circles + Straight Lines (Maths)", duration_minutes: 15, status: "Pending" }
|
||||||
])
|
])
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false)
|
setLoading(false)
|
||||||
@@ -79,18 +79,13 @@ export function StudyPlanCard() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col h-full">
|
<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">
|
<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="px-5 py-4 flex justify-between items-center border-b border-border">
|
||||||
<div className="text-[13px] font-bold text-muted-foreground">
|
<div className="text-sm font-bold text-muted-foreground">
|
||||||
{new Date().toLocaleDateString('en-US', { weekday: 'long', day: 'numeric', month: 'short' })}
|
{new Date().toLocaleDateString('en-US', { weekday: 'long', day: 'numeric', month: 'short' })}
|
||||||
</div>
|
</div>
|
||||||
<div className="text-[12px] text-[#00F0FF] font-bold">{progressPercent}% Completed</div>
|
<div className="text-xs text-[#00F0FF] font-bold">{progressPercent}% Completed</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Progress bar */}
|
{/* Progress bar */}
|
||||||
@@ -132,15 +127,15 @@ export function StudyPlanCard() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex-1 min-w-0">
|
<div className="flex-1 min-w-0">
|
||||||
<div className="text-[13px] font-bold text-primary truncate">{task.description}</div>
|
<div className="text-sm 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-xs text-muted-foreground mt-0.5 font-medium">{getDisplayType(task.type)} · {task.duration_minutes} min</div>
|
||||||
</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)}
|
{getDisplayType(task.type)}
|
||||||
</div>
|
</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
|
{task.duration_minutes}m
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,73 +1,123 @@
|
|||||||
"use client"
|
"use client"
|
||||||
import { Card, CardContent } from "@/components/ui/card"
|
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 { useState } from "react"
|
||||||
import Link from "next/link"
|
|
||||||
|
|
||||||
const SLOTS = [
|
const SLOTS = [
|
||||||
{ label: "6 AM", h: 30, peak: false },
|
{ label: "6 AM", h: 30, score: 52, peak: false },
|
||||||
{ label: "9 AM", h: 50, peak: false },
|
{ label: "9 AM", h: 55, score: 68, peak: false },
|
||||||
{ label: "12 PM", h: 65, peak: false },
|
{ label: "12 PM", h: 65, score: 74, peak: false },
|
||||||
{ label: "3 PM", h: 100, peak: true },
|
{ label: "2 PM", h: 90, score: 91, peak: false },
|
||||||
{ label: "6 PM", h: 80, peak: false },
|
{ label: "3 PM", h: 100, score: 95, peak: true },
|
||||||
{ label: "9 PM", h: 60, peak: false },
|
{ label: "5 PM", h: 82, score: 85, peak: false },
|
||||||
{ label: "12 AM", h: 38, 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() {
|
export function StudyTimeOptimizationCard() {
|
||||||
const [hovered, setHovered] = useState<number | null>(null)
|
const [hovered, setHovered] = useState<number | null>(null)
|
||||||
|
const maxH = Math.max(...SLOTS.map(s => s.h))
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<div className="flex justify-between items-center pb-3">
|
<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">
|
||||||
<h2 className="text-base font-bold text-primary flex items-center gap-2">
|
<CardContent className="p-5 flex-1 flex flex-col gap-4">
|
||||||
📈 Study Time Optimization
|
{/* Header stats */}
|
||||||
</h2>
|
<div className="flex items-center gap-3 flex-wrap">
|
||||||
</div>
|
<div className="flex items-center gap-2">
|
||||||
<Card className="border-border bg-card shadow-[0_4px_24px_rgba(0,0,0,0.3)] group hover:border-[#00F0FF]/30 transition-colors">
|
<Star size={15} className="fill-yellow-500 text-yellow-500" />
|
||||||
<CardContent className="p-4">
|
<span className="text-sm font-bold text-yellow-500">Peak Window:</span>
|
||||||
{/* Peak window badge */}
|
<span className="text-sm font-bold bg-yellow-500/10 border border-yellow-500/20 px-2.5 py-0.5 rounded-full">
|
||||||
<div className="flex items-center gap-2 mb-4">
|
2 PM – 5 PM
|
||||||
<Star size={13} className="fill-yellow-500 text-yellow-500" />
|
</span>
|
||||||
<span className="text-xs font-bold text-yellow-500">Peak Productivity:</span>
|
</div>
|
||||||
<span className="text-xs font-semibold text-foreground bg-yellow-500/10 border border-yellow-500/20 px-2 py-0.5 rounded-full">
|
<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">
|
||||||
2 PM – 5 PM
|
<TrendingUp size={11} /> 95% efficiency at peak
|
||||||
</span>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Bar chart */}
|
{/* Bar chart with Y-axis */}
|
||||||
<div className="flex items-end gap-1.5 h-20 px-1 mb-6">
|
<div className="flex gap-2 flex-1">
|
||||||
{SLOTS.map((slot, i) => (
|
{/* Y-axis labels */}
|
||||||
<div
|
<div className="flex flex-col justify-between pb-6 text-right pr-1.5">
|
||||||
key={slot.label}
|
{[100, 75, 50, 25].map(v => (
|
||||||
className="flex-1 flex flex-col items-center gap-0.5 cursor-pointer"
|
<span key={v} className="text-[10px] text-muted-foreground font-semibold leading-none">{v}%</span>
|
||||||
onMouseEnter={() => setHovered(i)}
|
))}
|
||||||
onMouseLeave={() => setHovered(null)}
|
</div>
|
||||||
>
|
|
||||||
<div
|
{/* Bars */}
|
||||||
className={`w-full rounded-t-md transition-all duration-300 ${
|
<div className="flex-1 flex flex-col gap-1">
|
||||||
slot.peak
|
{/* Grid lines */}
|
||||||
? "bg-gradient-to-t from-yellow-600/60 to-yellow-400 shadow-[0_0_10px_rgba(234,179,8,0.4)]"
|
<div className="relative flex-1 min-h-[120px]">
|
||||||
: hovered === i
|
{[0, 25, 50, 75].map(pct => (
|
||||||
? "bg-gradient-to-t from-[#00F0FF]/50 to-[#00F0FF]"
|
<div
|
||||||
: "bg-gradient-to-t from-[#00F0FF]/20 to-[#00F0FF]/60"
|
key={pct}
|
||||||
}`}
|
className="absolute w-full border-t border-border/20"
|
||||||
style={{ height: `${slot.h}%` }}
|
style={{ bottom: `${pct}%` }}
|
||||||
/>
|
/>
|
||||||
<span className="text-[8px] text-muted-foreground font-medium whitespace-nowrap mt-1">
|
))}
|
||||||
{slot.label}
|
<div className="flex items-end gap-1 h-full absolute inset-0">
|
||||||
</span>
|
{SLOTS.map((slot, i) => (
|
||||||
|
<div
|
||||||
|
key={slot.label}
|
||||||
|
className="flex-1 flex flex-col items-center justify-end h-full cursor-pointer relative group/bar"
|
||||||
|
onMouseEnter={() => setHovered(i)}
|
||||||
|
onMouseLeave={() => setHovered(null)}
|
||||||
|
>
|
||||||
|
{/* Tooltip */}
|
||||||
|
{hovered === i && (
|
||||||
|
<div className="absolute bottom-full mb-1.5 left-1/2 -translate-x-1/2 bg-background border border-border rounded-lg px-2.5 py-1.5 text-xs font-bold whitespace-nowrap shadow-lg z-10 pointer-events-none">
|
||||||
|
<div className="text-foreground">{slot.label}</div>
|
||||||
|
<div className={slot.peak ? "text-yellow-400" : "text-[#00F0FF]"}>Score: {slot.score}%</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<div
|
||||||
|
className={`w-full rounded-t-md transition-all duration-300 ${
|
||||||
|
slot.peak
|
||||||
|
? "bg-gradient-to-t from-yellow-600/80 to-yellow-400 shadow-[0_0_14px_rgba(234,179,8,0.5)]"
|
||||||
|
: hovered === i
|
||||||
|
? "bg-gradient-to-t from-[#00F0FF]/70 to-[#00F0FF]"
|
||||||
|
: "bg-gradient-to-t from-[#00F0FF]/20 to-[#00F0FF]/50"
|
||||||
|
}`}
|
||||||
|
style={{ height: `${(slot.h / maxH) * 100}%` }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/* X-axis labels */}
|
||||||
|
<div className="flex gap-1 pt-1">
|
||||||
|
{SLOTS.map((slot) => (
|
||||||
|
<div key={slot.label} className="flex-1 text-center">
|
||||||
|
<span className="text-[10px] text-muted-foreground font-bold whitespace-nowrap">{slot.label}</span>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Efficiency breakdown */}
|
||||||
|
<div className="grid grid-cols-3 gap-2 text-center">
|
||||||
|
{[
|
||||||
|
{ label: "Morning", time: "6–9 AM", eff: 60, color: "text-muted-foreground" },
|
||||||
|
{ label: "Peak", time: "2–5 PM", eff: 95, color: "text-yellow-400" },
|
||||||
|
{ label: "Evening", time: "7–9 PM", eff: 73, color: "text-[#00F0FF]" },
|
||||||
|
].map(slot => (
|
||||||
|
<div key={slot.label} className="p-2.5 rounded-xl bg-white/5 border border-border/30">
|
||||||
|
<div className={`text-base font-black ${slot.color}`}>{slot.eff}%</div>
|
||||||
|
<div className="text-[11px] font-bold text-muted-foreground">{slot.label}</div>
|
||||||
|
<div className="text-[10px] text-muted-foreground/60">{slot.time}</div>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</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">
|
<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={16} className="fill-yellow-500 shrink-0" />
|
<Flame size={18} className="fill-yellow-500 shrink-0" />
|
||||||
You perform best 2 PM – 5 PM. Schedule your hardest mock tests in this window.
|
You perform best 2–5 PM. Schedule your hardest mock tests and DPPs in this window.
|
||||||
</div>
|
</div>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,8 +2,7 @@
|
|||||||
|
|
||||||
import { useState, useRef, useEffect } from "react"
|
import { useState, useRef, useEffect } from "react"
|
||||||
import { Card, CardContent } from "@/components/ui/card"
|
import { Card, CardContent } from "@/components/ui/card"
|
||||||
import { Send, Loader2 } from "lucide-react"
|
import { Send, Loader2, BrainCircuit, Sparkles } from "lucide-react"
|
||||||
import Link from "next/link"
|
|
||||||
import ReactMarkdown from "react-markdown"
|
import ReactMarkdown from "react-markdown"
|
||||||
import remarkMath from "remark-math"
|
import remarkMath from "remark-math"
|
||||||
import rehypeKatex from "rehype-katex"
|
import rehypeKatex from "rehype-katex"
|
||||||
@@ -16,14 +15,21 @@ interface Message {
|
|||||||
content: string
|
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() {
|
export function TutorChat() {
|
||||||
const [messages, setMessages] = useState<Message[]>([
|
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 [input, setInput] = useState("")
|
||||||
const [streaming, setStreaming] = useState(false)
|
const [streaming, setStreaming] = useState(false)
|
||||||
const messagesEndRef = useRef<HTMLDivElement>(null)
|
const messagesEndRef = useRef<HTMLDivElement>(null)
|
||||||
|
|
||||||
const { userId, loading: userLoading } = useActiveUser();
|
const { userId, loading: userLoading } = useActiveUser();
|
||||||
|
|
||||||
const scrollToBottom = () => {
|
const scrollToBottom = () => {
|
||||||
@@ -34,15 +40,11 @@ export function TutorChat() {
|
|||||||
scrollToBottom()
|
scrollToBottom()
|
||||||
}, [messages])
|
}, [messages])
|
||||||
|
|
||||||
const handleSend = async (e?: React.FormEvent) => {
|
const sendMessage = async (text: string) => {
|
||||||
e?.preventDefault()
|
if (!text.trim() || streaming || userLoading || !userId) return
|
||||||
if (!input.trim() || streaming || userLoading || !userId) return
|
|
||||||
|
|
||||||
const userMsg = input.trim()
|
|
||||||
setInput("")
|
setInput("")
|
||||||
setMessages(prev => [...prev, { role: "user", content: userMsg }])
|
setMessages(prev => [...prev, { role: "user", content: text.trim() }])
|
||||||
|
|
||||||
// Add empty assistant message placeholder
|
|
||||||
setMessages(prev => [...prev, { role: "assistant", content: "" }])
|
setMessages(prev => [...prev, { role: "assistant", content: "" }])
|
||||||
setStreaming(true)
|
setStreaming(true)
|
||||||
|
|
||||||
@@ -50,14 +52,10 @@ export function TutorChat() {
|
|||||||
const res = await fetch("/api/ai-coach/chat/stream", {
|
const res = await fetch("/api/ai-coach/chat/stream", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: { "Content-Type": "application/json" },
|
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) {
|
if (!res.ok || !res.body) throw new Error("stream_unavailable")
|
||||||
throw new Error("Backend streaming failed. Using mock fallback.")
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!res.body) throw new Error("No readable stream")
|
|
||||||
|
|
||||||
const reader = res.body.getReader()
|
const reader = res.body.getReader()
|
||||||
const decoder = new TextDecoder()
|
const decoder = new TextDecoder()
|
||||||
@@ -67,23 +65,21 @@ export function TutorChat() {
|
|||||||
if (done) break
|
if (done) break
|
||||||
const chunk = decoder.decode(value, { stream: true })
|
const chunk = decoder.decode(value, { stream: true })
|
||||||
setMessages(prev => {
|
setMessages(prev => {
|
||||||
const newMessages = [...prev]
|
const msgs = [...prev]
|
||||||
newMessages[newMessages.length - 1].content += chunk
|
msgs[msgs.length - 1].content += chunk
|
||||||
return newMessages
|
return msgs
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch {
|
||||||
console.warn("Falling back to mock stream due to error:", err)
|
// Graceful fallback — don't expose internal errors
|
||||||
// Mock Fallback Streaming logic
|
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 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 = fallback.split(" ")
|
||||||
const words = mockResponse.split(" ")
|
|
||||||
|
|
||||||
for (let i = 0; i < words.length; i++) {
|
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 => {
|
setMessages(prev => {
|
||||||
const newMessages = [...prev]
|
const msgs = [...prev]
|
||||||
newMessages[newMessages.length - 1].content += (i === 0 ? "" : " ") + words[i]
|
msgs[msgs.length - 1].content += (i === 0 ? "" : " ") + words[i]
|
||||||
return newMessages
|
return msgs
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
@@ -91,45 +87,43 @@ export function TutorChat() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleSend = (e?: React.FormEvent) => {
|
||||||
|
e?.preventDefault()
|
||||||
|
sendMessage(input)
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col h-full min-h-[500px]">
|
<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">
|
<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">
|
{/* Chat Header */}
|
||||||
<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">
|
<div className="px-4 py-3 bg-[#00F0FF]/5 border-b border-border flex items-center gap-3">
|
||||||
AI
|
<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>
|
<div className="flex-1">
|
||||||
<div className="text-[13px] font-bold text-primary">PragatiVeer Tutor</div>
|
<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">
|
<div className="text-[11px] text-green-500 flex items-center gap-1 font-medium">
|
||||||
<span className="w-1.5 h-1.5 rounded-full bg-green-500"></span> Online
|
<span className="w-1.5 h-1.5 rounded-full bg-green-500" /> Online · JEE & NEET specialist
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="ml-auto text-[11px] text-muted-foreground">
|
<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">
|
||||||
<span className="text-yellow-500 font-bold">14</span> msgs left
|
<Sparkles size={10} /> AI Powered
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Messages */}
|
||||||
<CardContent className="flex-1 overflow-y-auto p-4 space-y-4">
|
<CardContent className="flex-1 overflow-y-auto p-4 space-y-4">
|
||||||
{messages.map((msg, idx) => (
|
{messages.map((msg, idx) => (
|
||||||
<div key={idx} className={`flex ${msg.role === "user" ? "justify-end" : "justify-start"}`}>
|
<div key={idx} className={`flex ${msg.role === "user" ? "justify-end" : "justify-start"}`}>
|
||||||
<div className="max-w-[85%] flex flex-col gap-1">
|
<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"
|
msg.role === "user"
|
||||||
? "bg-[#00F0FF] text-background rounded-[12px] rounded-br-[4px] font-medium"
|
? "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]"
|
: "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">
|
<div className="prose prose-sm dark:prose-invert max-w-none [&>p]:m-0">
|
||||||
{msg.role === "assistant" ? (
|
{msg.role === "assistant" ? (
|
||||||
<ReactMarkdown
|
<ReactMarkdown remarkPlugins={[remarkMath]} rehypePlugins={[rehypeKatex]}>
|
||||||
remarkPlugins={[remarkMath]}
|
|
||||||
rehypePlugins={[rehypeKatex]}
|
|
||||||
>
|
|
||||||
{msg.content || "..."}
|
{msg.content || "..."}
|
||||||
</ReactMarkdown>
|
</ReactMarkdown>
|
||||||
) : (
|
) : (
|
||||||
@@ -137,28 +131,39 @@ export function TutorChat() {
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className={`text-[10px] text-muted-foreground ${msg.role === "user" ? "text-right" : "text-left"}`}>
|
|
||||||
Now
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
<div ref={messagesEndRef} />
|
<div ref={messagesEndRef} />
|
||||||
</CardContent>
|
</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">
|
<div className="p-2.5 border-t border-border bg-black/20 flex items-center gap-2">
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
value={input}
|
value={input}
|
||||||
onChange={(e) => setInput(e.target.value)}
|
onChange={e => setInput(e.target.value)}
|
||||||
onKeyDown={(e) => {
|
onKeyDown={e => { if (e.key === "Enter") handleSend() }}
|
||||||
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"
|
||||||
placeholder="Message AI Tutor..."
|
|
||||||
className="flex-1 bg-white/5 border border-border rounded-full px-4 py-2 text-[13px] text-primary focus:outline-none focus:border-[#00F0FF] focus:bg-[#00F0FF]/5 transition-colors"
|
|
||||||
disabled={streaming}
|
disabled={streaming}
|
||||||
/>
|
/>
|
||||||
<button
|
<button
|
||||||
onClick={handleSend}
|
onClick={handleSend}
|
||||||
disabled={!input.trim() || streaming}
|
disabled={!input.trim() || streaming}
|
||||||
className="w-9 h-9 rounded-full bg-[#00F0FF] flex items-center justify-center shrink-0 hover:bg-[#00F0FF]/90 transition-transform hover:scale-105 disabled:opacity-50 disabled:hover:scale-100"
|
className="w-9 h-9 rounded-full bg-[#00F0FF] flex items-center justify-center shrink-0 hover:bg-[#00F0FF]/90 transition-transform hover:scale-105 disabled:opacity-50 disabled:hover:scale-100"
|
||||||
|
|||||||
@@ -1,37 +1,40 @@
|
|||||||
"use client"
|
"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 { 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 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 ACCURACY = [82, 78, 91, 88, 85, 79, 93]
|
||||||
|
const HOURS = [2.5, 3.5, 2, 4.5, 4, 3, 3.8]
|
||||||
|
|
||||||
export function WeeklyProgressReport() {
|
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 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 (
|
return (
|
||||||
<div className="flex flex-col h-full">
|
<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">
|
<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 */}
|
{/* Summary chips */}
|
||||||
<div className="flex items-center gap-2 flex-wrap">
|
<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">
|
<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} />
|
<TrendingUp size={12} />
|
||||||
{avgAccuracy}% avg accuracy
|
{avgAccuracy}% avg accuracy
|
||||||
</div>
|
</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"}`}>
|
<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"}`}>
|
||||||
{trend >= 0 ? <TrendingUp size={12} /> : <TrendingDown size={12} />}
|
{trendUp ? <TrendingUp size={12} /> : <TrendingDown size={12} />}
|
||||||
{trend >= 0 ? "+" : ""}{trend}% vs Mon
|
{trendUp ? "+" : ""}{trend}{UNIT} vs Mon
|
||||||
</div>
|
</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">
|
<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} />
|
<Zap size={12} />
|
||||||
@@ -39,32 +42,97 @@ export function WeeklyProgressReport() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Bar Chart */}
|
{/* Metric Toggle */}
|
||||||
<div className="flex items-end gap-2 h-24 flex-1">
|
<div className="flex gap-1.5">
|
||||||
{DAYS.map((day, i) => (
|
{(["accuracy", "questions", "hours"] as const).map((m) => (
|
||||||
<div key={day} className="flex flex-col items-center gap-1 flex-1">
|
<button
|
||||||
<div className="relative w-full flex items-end justify-center" style={{ height: "80px" }}>
|
key={m}
|
||||||
<div
|
onClick={() => setMetric(m)}
|
||||||
className={`w-full rounded-t-md transition-all duration-700 ${
|
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"}`}
|
||||||
i === 6
|
>
|
||||||
? "bg-gradient-to-t from-[#00F0FF]/60 to-[#00F0FF]"
|
{m === "accuracy" ? "Accuracy" : m === "questions" ? "Questions" : "Study Hrs"}
|
||||||
: "bg-gradient-to-t from-white/10 to-white/20"
|
</button>
|
||||||
}`}
|
))}
|
||||||
style={{ height: `${(BAR_HEIGHTS[i] / maxBar) * 80}px` }}
|
</div>
|
||||||
title={`${ACCURACY[i]}% accuracy`}
|
|
||||||
/>
|
{/* Bar Chart with Y-axis */}
|
||||||
|
<div className="flex gap-2 flex-1 min-h-[120px]">
|
||||||
|
{/* Y-axis */}
|
||||||
|
<div className="flex flex-col justify-between pb-5 text-right pr-1">
|
||||||
|
{[100, 75, 50, 25, 0].map(v => (
|
||||||
|
<span key={v} className="text-[10px] text-muted-foreground font-semibold leading-none">
|
||||||
|
{metric === "hours" ? (v / 100 * 5).toFixed(1) : `${v}${metric === "accuracy" ? "%" : ""}`}
|
||||||
|
</span>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex-1 flex flex-col gap-0.5">
|
||||||
|
{/* Grid + Bars */}
|
||||||
|
<div className="relative flex-1 min-h-[120px]">
|
||||||
|
{[25, 50, 75].map(pct => (
|
||||||
|
<div key={pct} className="absolute w-full border-t border-border/15" style={{ bottom: `${pct}%` }} />
|
||||||
|
))}
|
||||||
|
<div className="absolute inset-0 flex items-end gap-1.5">
|
||||||
|
{DAYS.map((day, i) => (
|
||||||
|
<div
|
||||||
|
key={day}
|
||||||
|
className="flex-1 flex flex-col justify-end items-center h-full relative cursor-pointer"
|
||||||
|
onMouseEnter={() => setHovered(i)}
|
||||||
|
onMouseLeave={() => setHovered(null)}
|
||||||
|
>
|
||||||
|
{/* Tooltip */}
|
||||||
|
{hovered === i && (
|
||||||
|
<div className="absolute bottom-full mb-1.5 left-1/2 -translate-x-1/2 bg-background border border-border rounded-lg px-2.5 py-1.5 text-xs font-bold whitespace-nowrap shadow-lg z-10 pointer-events-none">
|
||||||
|
<div>{day}</div>
|
||||||
|
<div className="text-[#00F0FF]">{DATA[i]}{UNIT}</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<div
|
||||||
|
className={`w-full rounded-t-md transition-all duration-700 ${
|
||||||
|
i === 6
|
||||||
|
? "bg-gradient-to-t from-[#00F0FF]/60 to-[#00F0FF]"
|
||||||
|
: hovered === i
|
||||||
|
? "bg-gradient-to-t from-[#00F0FF]/40 to-[#00F0FF]/80"
|
||||||
|
: "bg-gradient-to-t from-white/10 to-white/25"
|
||||||
|
}`}
|
||||||
|
style={{ height: `${(DATA[i] / maxVal) * 100}%` }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
</div>
|
</div>
|
||||||
<span className="text-[9px] text-muted-foreground font-semibold">{day}</span>
|
</div>
|
||||||
|
{/* X labels */}
|
||||||
|
<div className="flex gap-1.5 pt-1">
|
||||||
|
{DAYS.map((day) => (
|
||||||
|
<div key={day} className="flex-1 text-center">
|
||||||
|
<span className="text-[10px] text-muted-foreground font-bold">{day}</span>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Quick stats row */}
|
||||||
|
<div className="grid grid-cols-3 gap-2">
|
||||||
|
{[
|
||||||
|
{ label: "Avg Accuracy", value: `${avgAccuracy}%`, icon: <Target size={12} className="text-green-400" /> },
|
||||||
|
{ label: "Questions / Day", value: `${Math.round(QUESTIONS.reduce((a, b) => a + b, 0) / 7)}`, icon: <Zap size={12} className="text-[#00F0FF]" /> },
|
||||||
|
{ label: "Study Hrs / Day", value: `${(HOURS.reduce((a, b) => a + b, 0) / 7).toFixed(1)}h`, icon: <Clock size={12} className="text-amber-400" /> },
|
||||||
|
].map(stat => (
|
||||||
|
<div key={stat.label} className="p-2.5 rounded-xl bg-white/5 border border-border/30 text-center">
|
||||||
|
<div className="flex items-center justify-center gap-1 mb-0.5">{stat.icon}</div>
|
||||||
|
<div className="text-base font-black">{stat.value}</div>
|
||||||
|
<div className="text-[10px] text-muted-foreground font-semibold">{stat.label}</div>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* AI insight */}
|
{/* 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">
|
<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={16} className="shrink-0 mt-0.5" />
|
<Brain size={15} className="shrink-0 mt-0.5" />
|
||||||
<div className="flex flex-col gap-1.5">
|
<div>
|
||||||
<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="font-black text-white">Sign errors in Thermodynamics</span> — detected in your last 3 attempts (W = −PΔV misapplied). We've queued 3 targeted practice questions for tomorrow.{" "}
|
||||||
<span className="text-[#00F0FF]/80">💡 <strong className="text-white">Tip:</strong> You perform best between 7-9 PM. Schedule difficult topics then.</span>
|
<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>
|
||||||
</div>
|
</div>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
|
|||||||
@@ -133,7 +133,7 @@ export function ExamResult({ payload, attemptId, examType, onRetake }: ExamResul
|
|||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("Error exiting fullscreen", 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 Solutions
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ export function PublicLayout({ children }: { children: React.ReactNode }) {
|
|||||||
<div className="hidden md:flex items-center gap-8 text-sm font-medium text-muted-foreground">
|
<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="/#features" className="hover:text-foreground transition-colors">Features</Link>
|
||||||
<Link href="/#pricing" className="hover:text-foreground transition-colors">Pricing</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>
|
||||||
<div className="flex items-center gap-4">
|
<div className="flex items-center gap-4">
|
||||||
<Link href="/login" className="text-sm font-bold hover:text-primary transition-colors hidden sm:block">Log in</Link>
|
<Link href="/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>
|
</div>
|
||||||
<p className="text-sm text-muted-foreground font-medium leading-relaxed">
|
<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>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -62,8 +62,6 @@ export function PublicLayout({ children }: { children: React.ReactNode }) {
|
|||||||
<ul className="space-y-3 text-sm font-medium text-muted-foreground">
|
<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="/#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="/#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>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -72,7 +70,6 @@ export function PublicLayout({ children }: { children: React.ReactNode }) {
|
|||||||
<ul className="space-y-3 text-sm font-medium text-muted-foreground">
|
<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="/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="/contact" className="hover:text-primary transition-colors">Contact</Link></li>
|
||||||
<li><Link href="#" className="hover:text-primary transition-colors">Careers</Link></li>
|
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</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">
|
<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>
|
<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
|
Made with <div className="size-2 rounded-full bg-primary animate-pulse"></div> by AI Agents
|
||||||
</div>
|
</div> */}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</footer>
|
</footer>
|
||||||
|
|||||||
27
src/proxy.ts
Normal file
27
src/proxy.ts
Normal 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
18
test.js
@@ -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));
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
})
|
|
||||||
|
|
||||||
19
test2.js
19
test2.js
@@ -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));
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
})
|
|
||||||
|
|
||||||
19
test3.js
19
test3.js
@@ -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));
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
})
|
|
||||||
|
|
||||||
20
test4.js
20
test4.js
@@ -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));
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
})
|
|
||||||
|
|
||||||
Reference in New Issue
Block a user