Redesign college predictor, swap DPP contents, reorder sidebar, fix imports, add sql dump

This commit is contained in:
2026-09-25 12:44:25 +05:30
parent a91abd5a2b
commit c99cfd9d5d
6 changed files with 188 additions and 218 deletions

0
seeds.sql Normal file
View File

BIN
server_tests.sql Normal file

Binary file not shown.

View File

@@ -1,21 +1,20 @@
"use client"
"use client"
import { fetchWithAuth } from "@/lib/apiClient";
import { Button } from "@/components/ui/button"
import { Card, CardContent } from "@/components/ui/card"
import { BrainCircuit, Play, CheckCircle2, Download, Filter, Lock, FileText, Eye, ShieldAlert, X, Sparkles, Share2, Maximize2, Minimize2, Zap } from "lucide-react"
import { BrainCircuit, Play, CheckCircle2, Download, Filter, Lock, FileText, Eye, ShieldAlert, X, Sparkles, Share2, Maximize2, Minimize2 } from "lucide-react"
import { useEffect, useState } from "react"
import Link from "next/link"
import { useActiveUser } from "@/hooks/useActiveUser"
export default function AIDailyPracticePage() {
export default function DPPPage() {
const { user, userId } = useActiveUser()
const [dpps, setDpps] = useState<any[]>([])
const [attempts, setAttempts] = useState<any[]>([])
const [loading, setLoading] = useState(true)
const [dateFilter, setDateFilter] = useState("")
const [diffFilter, setDiffFilter] = useState("Mixed")
const [selectedPdfDpp, setSelectedPdfDpp] = useState<any | null>(null)
const [isFullScreen, setIsFullScreen] = useState(false)
const [copiedShare, setCopiedShare] = useState(false)
@@ -74,9 +73,10 @@ export default function AIDailyPracticePage() {
const scheduledDate = new Date(dpp.scheduled_date).toISOString().split('T')[0];
if (scheduledDate !== dateFilter) return false;
}
if (diffFilter !== "Mixed" && dpp.difficulty !== diffFilter) {
return false;
}
const isNeet = user?.target_exam === "NEET";
const title = dpp.title.toLowerCase();
if (isNeet && (title.includes("math") || title.includes("mth"))) return false;
if (!isNeet && title.includes("bio")) return false;
return true;
});
@@ -97,27 +97,19 @@ export default function AIDailyPracticePage() {
const cleanTitle = (title: string) => title.replace(/\s*\(ExamEnum\.[^)]+\)/, '');
return (
<div className="p-6 md:p-8 space-y-6 animate-in fade-in zoom-in-95 origin-top duration-500 max-w-7xl mx-auto">
{/* Page Header */}
<div className="border-b border-border/50 pb-6">
<div className="flex items-start justify-between gap-4 flex-wrap">
<div className="p-8 space-y-8 animate-in fade-in zoom-in-95 origin-top duration-500 max-w-7xl mx-auto">
<div className="flex flex-col md:flex-row md:items-center justify-between p-8 bg-gradient-to-r from-secondary/50 to-background border border-border rounded-2xl shadow-sm gap-6">
<div>
<h1 className="text-3xl md:text-4xl font-black tracking-tight flex items-center gap-3 mb-1">
<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-cyan-50 dark:bg-[var(--cyan-ai-10)] border border-cyan-300/60 dark:border-[var(--cyan-ai-20)]">
<Zap className="text-cyan-700 dark:text-[var(--cyan-ai)]" size={28} />
</span>
PrepPilot AI
</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 className="text-muted-foreground font-medium max-w-xl">
Hyper-personalized problem sets generated daily by our AI Evaluator to target your specific weak areas based on past exam performance.
</p>
</div>
<div className="flex items-center gap-2 shrink-0">
<span className="text-xs font-bold uppercase tracking-wider text-[var(--cyan-ai)] bg-cyan-50 dark:bg-[var(--cyan-ai-10)] border border-cyan-300/60 dark:border-[var(--cyan-ai-20)] px-3 py-1.5 rounded-full flex items-center gap-1.5">
<BrainCircuit size={12} /> AI Powered
</span>
</div>
</div>
</div>
<div className="flex flex-col sm:flex-row justify-between items-center gap-4 border-b border-border pb-6">
@@ -134,9 +126,21 @@ export default function AIDailyPracticePage() {
{diff}
</button>
))}
<div className="flex items-center gap-1 ml-2 text-xs text-muted-foreground bg-secondary/50 px-2 py-1 rounded-md">
<Lock size={12}/> Pro
</div>
<Button variant="outline" className="gap-2 rounded-xl text-sm font-bold">
<Download size={16} /> Export as PDF
</div>
<Button
variant="outline"
onClick={() => {
navigator.clipboard.writeText(window.location.origin + '/dashboard/dpp');
setCopiedShare(true);
setTimeout(() => setCopiedShare(false), 2000);
}}
className="gap-2 rounded-xl text-sm font-bold border-primary/30 hover:bg-primary/10 text-primary transition-all shadow-sm"
>
{copiedShare ? <CheckCircle2 size={16} className="text-green-500" /> : <Share2 size={16} />}
{copiedShare ? "Share Link Copied!" : "Share DPPs Link"}
</Button>
</div>
@@ -190,12 +194,10 @@ export default function AIDailyPracticePage() {
</div>
<h3 className="font-bold text-lg leading-tight group-hover:text-[var(--cyan-ai)] transition-colors">
{cleanTitle(dpp.title)}
</h3>
<div className="mt-1">
<span className={`text-[10px] uppercase px-2 py-0.5 rounded-md border font-bold tracking-wider ${getDifficultyColor(dpp.difficulty)}`}>
<span className={`ml-2 text-[10px] uppercase px-2 py-0.5 rounded-md border font-bold tracking-wider ${getDifficultyColor(dpp.difficulty)}`}>
{dpp.difficulty}
</span>
</div>
</h3>
<div className="text-sm text-muted-foreground mt-2 font-medium">
{count} Questions • {dpp.total_time} mins
{dpp.scheduled_date && (
@@ -217,19 +219,15 @@ export default function AIDailyPracticePage() {
<Button variant="outline" size="sm" className="font-bold border-border">Result</Button>
</Link>
{!isExpired && (
<Link href={`/cbt/${dpp.id}`}>
<Button variant="secondary" size="sm" className="font-bold bg-[var(--cyan-ai-10)] text-[var(--cyan-ai)] hover:bg-[var(--cyan-ai-20)] gap-2">
<Play size={14} className="fill-current" /> Retake Test
<Button onClick={() => setSelectedPdfDpp(dpp)} variant="secondary" size="sm" className="font-bold bg-[var(--cyan-ai-10)] text-[var(--cyan-ai)] hover:bg-[var(--cyan-ai-20)] gap-2">
<Eye size={14} /> Read PDF
</Button>
</Link>
)}
</>
) : (
<Link href={`/cbt/${dpp.id}`}>
<Button variant="secondary" size="sm" className="rounded-xl bg-[var(--cyan-ai)] text-black hover:bg-[var(--cyan-ai-80)] font-extrabold gap-2 text-xs shadow-[0_0_12px_rgba(12,96,96,0.2)]">
<Play size={14} className="fill-black" /> Launch Practice Test
<Button onClick={() => setSelectedPdfDpp(dpp)} variant="secondary" className="rounded-xl bg-primary/10 text-primary hover:bg-primary/20 font-bold gap-2 text-xs">
<Eye size={14} /> Open PDF
</Button>
</Link>
)}
</div>
</CardContent>
@@ -249,7 +247,7 @@ export default function AIDailyPracticePage() {
</div>
<div>
<h3 className="font-black text-base sm:text-lg flex items-center gap-2">
{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 hidden md:inline-block">
Protected In-App PDF View
</span>
@@ -264,7 +262,7 @@ export default function AIDailyPracticePage() {
size="sm"
variant="outline"
onClick={() => {
navigator.clipboard.writeText(window.location.origin + '/dashboard/ai-coach/cbt-dpp');
navigator.clipboard.writeText(window.location.origin + '/dashboard/dpp');
setCopiedShare(true);
setTimeout(() => setCopiedShare(false), 2000);
}}
@@ -298,7 +296,7 @@ export default function AIDailyPracticePage() {
</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 Interactive Practice Mode →
Upgrade for Interactive CBT Mode →
</Button>
</Link>
</div>
@@ -332,7 +330,7 @@ export default function AIDailyPracticePage() {
</Button>
<Link href="/upgrade">
<Button className="bg-[var(--cyan-ai)] text-black hover:bg-[var(--cyan-ai-80)] font-extrabold rounded-xl shadow-[0_0_15px_rgba(12,96,96,0.2)] text-xs h-9">
<Sparkles size={14} className="mr-1.5" /> Upgrade to AI Coach
<Sparkles size={14} className="mr-1.5" /> Upgrade to AI Coach for CBT
</Button>
</Link>
</div>

View File

@@ -1,39 +1,45 @@
"use client"
import { useState, useEffect } from "react"
import { CollegePredictorCard } from "@/components/AI/CollegePredictorCard"
import { Card, CardContent } from "@/components/ui/card"
import { GraduationCap, Building2, Award, CheckCircle2, TrendingUp, MapPin, Users, BarChart2 } from "lucide-react"
import Link from "next/link"
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/card"
import { GraduationCap, BrainCircuit, LineChart as LineChartIcon } from "lucide-react"
import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer } from 'recharts'
import { useActiveUser, isDemoUser } from "@/hooks/useActiveUser"
const DEMO_IDS = new Set([
"11111111-1111-1111-1111-111111111111",
"22222222-2222-2222-2222-222222222222",
"33333333-3333-3333-3333-333333333333",
"44444444-4444-4444-4444-444444444444"
]);
function isDemoUser(id: string): boolean {
return !!id && DEMO_IDS.has(id);
}
const CUTOFF_TRENDS = [
{ year: "2021", rank: 3800 },
{ year: "2022", rank: 3500 },
{ year: "2023", rank: 3200 },
{ year: "2024", rank: 2950 },
{ year: "2025", rank: 2750 },
const DEMO_TREND_DATA = [
{ name: 'Week 1', rank: 4500, label: 'NIT Trichy CSE' },
{ name: 'Week 2', rank: 4100 },
{ name: 'Week 3', rank: 3800 },
{ name: 'Week 4', rank: 3300 },
{ name: 'Week 5', rank: 2900 },
{ name: 'Week 6', rank: 2750, label: 'IIT Roorkee' },
]
const ZERO_TREND_DATA = [
{ name: 'Week 1', rank: 0 },
{ name: 'Week 2', rank: 0 },
{ name: 'Week 3', rank: 0 },
{ name: 'Week 4', rank: 0 },
{ name: 'Week 5', rank: 0 },
{ name: 'Week 6', rank: 0 },
]
// Custom Label to permanently show text on the graph (not on hover)
const CustomLabel = (props: any) => {
const { x, y, stroke, value, index, data } = props;
const point = data[index];
if (!point || !point.label) return null;
return (
<text x={x} y={y - 15} fill="#fff" fontSize={12} fontWeight="bold" textAnchor="middle">
{point.label}
</text>
);
};
export default function CollegePredictorPage() {
const [isDemo, setIsDemo] = useState(false);
useEffect(() => {
if (typeof window !== 'undefined') {
const id = localStorage.getItem('activeUserId');
setIsDemo(isDemoUser(id ?? ''));
}
}, []);
const maxRank = Math.max(...CUTOFF_TRENDS.map(d => d.rank))
const { userId } = useActiveUser()
const isDemo = isDemoUser(userId)
const trendData = isDemo ? DEMO_TREND_DATA : ZERO_TREND_DATA
return (
<div className="p-6 md:p-8 space-y-8 animate-in fade-in zoom-in-95 origin-top duration-500 max-w-7xl mx-auto min-h-screen">
@@ -45,10 +51,10 @@ export default function CollegePredictorPage() {
<span className="p-2.5 rounded-xl bg-cyan-50 dark:bg-[var(--cyan-ai-10)] border border-cyan-300/60 dark:border-[var(--cyan-ai-20)]">
<GraduationCap className="text-cyan-700 dark:text-[var(--cyan-ai)]" size={28} />
</span>
AI College &amp; Cutoff Predictor
College Predictor
</h1>
<p className="text-muted-foreground text-base font-medium max-w-2xl pl-1">
Discover your probable target medical &amp; engineering colleges based on current mock exam scores and historical cutoffs.
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-[var(--cyan-ai)] bg-cyan-50 dark:bg-[var(--cyan-ai-10)] border border-cyan-300/60 dark:border-[var(--cyan-ai-20)] px-3 py-1.5 rounded-full flex items-center gap-1.5 shrink-0 self-start">
@@ -57,97 +63,69 @@ export default function CollegePredictorPage() {
</div>
</div>
{/* Summary KPI Strip */}
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
{[
{ label: "Predicted Rank", value: isDemo ? "~4,200" : "--", icon: <TrendingUp size={18} className="text-[var(--cyan-ai)]" />, color: "border-[var(--cyan-ai-20)] bg-[var(--cyan-ai-10)]" },
{ label: "Target Colleges", value: isDemo ? "12" : "0", icon: <Building2 size={18} className="text-amber-400" />, color: "border-amber-400/20 bg-amber-400/5" },
{ label: "Reach Colleges", value: isDemo ? "5" : "0", icon: <MapPin size={18} className="text-purple-400" />, color: "border-purple-400/20 bg-purple-400/5" },
{ label: "Safe Colleges", value: isDemo ? "18" : "0", 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>
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
{/* Left Box: Graph */}
<Card className="col-span-1 lg:col-span-2 border-border shadow-sm bg-background">
<CardHeader>
<CardTitle className="flex items-center gap-2"><LineChartIcon className="text-[var(--cyan-ai)]" size={20} /> College & Branch Legacy Prediction</CardTitle>
<CardDescription>
{isDemo ? "Your projected admission chances mapped over your prep weeks." : "Your college trajectory will plot here as you complete tests."}
</CardDescription>
</CardHeader>
<CardContent className="h-[350px] mt-4">
<ResponsiveContainer width="100%" height="100%">
<LineChart data={trendData} margin={{ top: 25, right: 30, bottom: 5, left: 10 }}>
{/* User said static line, not dotted. So removed strokeDasharray from grid? Let's make it solid if they mean grid, or just the line. Line is monotone. */}
<CartesianGrid stroke="#333" vertical={false} />
<XAxis dataKey="name" stroke="#888" tick={{ fill: '#888' }} />
<YAxis stroke="#888" tick={{ fill: '#888' }} domain={[0, 5000]} reversed={true} />
<Tooltip
contentStyle={{ backgroundColor: '#1f2937', borderColor: '#374151', borderRadius: '8px' }}
itemStyle={{ color: 'var(--cyan-ai)' }}
formatter={(value: any) => [`Rank: ${value}`, 'Prediction']}
/>
<Line
type="linear"
dataKey="rank"
stroke="var(--cyan-ai)"
strokeWidth={3}
dot={{ r: 4, fill: 'var(--cyan-ai)' }}
activeDot={{ r: 6 }}
label={<CustomLabel data={trendData} />}
/>
</LineChart>
</ResponsiveContainer>
</CardContent>
</Card>
))}
</div>
<div className="grid grid-cols-1 lg:grid-cols-3 gap-8">
<div className="lg:col-span-2 space-y-8">
{/* College Predictor */}
<CollegePredictorCard />
{/* Cutoff Trend Chart */}
<Card className="p-6 bg-secondary/10 border-border/60 rounded-3xl">
<div className="flex items-center gap-2 mb-6">
<BarChart2 size={20} className="text-[var(--cyan-ai)]" />
<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-[var(--cyan-ai-50)] to-[var(--cyan-ai)]" : "bg-gradient-to-t from-white/10 to-white/25"}`}
style={{ height: `${(d.rank / maxRank) * 80}px` }}
/>
<span className="text-xs text-muted-foreground font-semibold">{d.year}</span>
</div>
))}
</div>
<p className="text-xs text-muted-foreground mt-4 pl-1">Closing rank trend: cutoffs are improving, meaning competition is easing. Your predicted rank of ~4,200 is within Target range.</p>
</Card>
</div>
{/* Right Box: AI Analysis (Text Only) */}
<div className="space-y-6">
<Card className="p-6 bg-gradient-to-br from-[var(--cyan-ai-10)] via-secondary/20 to-background border border-[var(--cyan-ai-30)] rounded-3xl space-y-4">
<h3 className="text-base font-black tracking-tight text-[var(--cyan-ai)] flex items-center gap-2">
<Award size={18} /> Cutoff Modelling Parameters
</h3>
<ul className="space-y-3 text-sm font-medium text-muted-foreground">
<li className="flex items-start gap-2">
<CheckCircle2 size={16} className="text-[var(--cyan-ai)] shrink-0 mt-0.5" />
<span>Trained on <strong className="text-foreground">100,000+ candidate rank trends</strong> across 2021–2025 counselling rounds.</span>
</li>
<li className="flex items-start gap-2">
<CheckCircle2 size={16} className="text-[var(--cyan-ai)] shrink-0 mt-0.5" />
<span>Supports <strong className="text-foreground">All India Quota (AIQ)</strong> &amp; State Quota seat adjustments.</span>
</li>
<li className="flex items-start gap-2">
<CheckCircle2 size={16} className="text-[var(--cyan-ai)] shrink-0 mt-0.5" />
<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-[var(--cyan-ai)] 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">
<Card className="border-[var(--cyan-ai-30)] bg-[var(--cyan-ai-10)] h-full">
<CardHeader className="pb-2">
<CardTitle className="text-lg flex items-center gap-2"><BrainCircuit size={18} className="text-[var(--cyan-ai)]"/> AI Analysis</CardTitle>
</CardHeader>
<CardContent>
{isDemo ? (
<ul className="space-y-4 text-sm text-foreground/90 font-medium">
<li className="flex items-start gap-3">
<span className="text-amber-400 font-black shrink-0">+15 marks</span>
<span>in Maths &rarr; upgrades you from NIT Trichy to <strong className="text-foreground">IIT Roorkee</strong></span>
</li>
<li className="flex items-start gap-2">
<li className="flex items-start gap-3">
<span className="text-[var(--cyan-ai)] 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">
<li className="flex items-start gap-3">
<span className="text-green-400 font-black shrink-0">Boost Speed</span>
<span>from 45s → 38s/Q gives you ~12 extra attempt opportunities</span>
<span>from 45s ? 38s/Q gives you ~12 extra attempt opportunities</span>
</li>
</ul>
) : (
<p className="text-sm text-foreground/80 leading-relaxed font-medium">
Complete your first diagnostic test or DPP to unlock personalized AI college predictions and target recommendations.
</p>
)}
</CardContent>
</Card>
</div>
</div>

View File

@@ -1,20 +1,21 @@
"use client"
"use client"
import { fetchWithAuth } from "@/lib/apiClient";
import { Button } from "@/components/ui/button"
import { Card, CardContent } from "@/components/ui/card"
import { BrainCircuit, Play, CheckCircle2, Download, Filter, Lock, FileText, Eye, ShieldAlert, X, Sparkles, Share2, Maximize2, Minimize2 } from "lucide-react"
import { BrainCircuit, Play, CheckCircle2, Download, Filter, Lock, FileText, Eye, ShieldAlert, X, Sparkles, Share2, Maximize2, Minimize2, Zap } from "lucide-react"
import { useEffect, useState } from "react"
import Link from "next/link"
import { useActiveUser } from "@/hooks/useActiveUser"
export default function DPPPage() {
export default function AIDailyPracticePage() {
const { user, userId } = useActiveUser()
const [dpps, setDpps] = useState<any[]>([])
const [attempts, setAttempts] = useState<any[]>([])
const [loading, setLoading] = useState(true)
const [dateFilter, setDateFilter] = useState("")
const [selectedPdfDpp, setSelectedPdfDpp] = useState<any | null>(null)
const [isFullScreen, setIsFullScreen] = useState(false)
const [copiedShare, setCopiedShare] = useState(false)
@@ -73,10 +74,7 @@ export default function DPPPage() {
const scheduledDate = new Date(dpp.scheduled_date).toISOString().split('T')[0];
if (scheduledDate !== dateFilter) return false;
}
const isNeet = user?.target_exam === "NEET";
const title = dpp.title.toLowerCase();
if (isNeet && (title.includes("math") || title.includes("mth"))) return false;
if (!isNeet && title.includes("bio")) return false;
return true;
});
@@ -97,14 +95,22 @@ export default function DPPPage() {
const cleanTitle = (title: string) => title.replace(/\s*\(ExamEnum\.[^)]+\)/, '');
return (
<div className="p-8 space-y-8 animate-in fade-in zoom-in-95 origin-top 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="p-6 md:p-8 space-y-6 animate-in fade-in zoom-in-95 origin-top duration-500 max-w-7xl mx-auto">
{/* Page Header */}
<div className="border-b border-border/50 pb-6">
<div className="flex items-start justify-between gap-4 flex-wrap">
<div>
<h1 className="text-3xl font-bold tracking-tight mb-2">Daily Practice Problems (DPP)</h1>
<p className="text-muted-foreground font-medium max-w-xl">
Hyper-personalized problem sets generated daily by our AI Evaluator to target your specific weak areas based on past exam performance.
<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-cyan-50 dark:bg-[var(--cyan-ai-10)] border border-cyan-300/60 dark:border-[var(--cyan-ai-20)]">
<FileText className="text-primary" size={28} /></span>
Daily Practice (DPP)
</h1>
<p className="text-muted-foreground text-base font-medium max-w-2xl pl-1">
Launch Daily Practice Problems (DPPs) tailored to your exam pattern.
</p>
</div>
</div>
</div>
<div className="flex flex-col sm:flex-row justify-between items-center gap-4 border-b border-border pb-6">
@@ -115,27 +121,10 @@ export default function DPPPage() {
onChange={(e) => setDateFilter(e.target.value)}
className="px-3 py-1.5 bg-background border border-border rounded-lg text-xs font-bold focus:ring-2 focus:ring-[var(--cyan-ai)] outline-none text-muted-foreground mr-2"
/>
<span className="text-sm font-bold text-muted-foreground mr-2"><Filter size={16} className="inline mr-1"/> Difficulty:</span>
{['Mixed', 'Easy', 'Medium', 'Hard'].map((diff) => (
<button key={diff} className={`px-3 py-1.5 rounded-lg text-xs font-bold border transition-colors ${diff === 'Mixed' ? 'bg-primary text-primary-foreground border-primary' : 'bg-background text-muted-foreground border-border hover:border-primary/50'}`}>
{diff}
</button>
))}
<div className="flex items-center gap-1 ml-2 text-xs text-muted-foreground bg-secondary/50 px-2 py-1 rounded-md">
<Lock size={12}/> Pro
</div>
</div>
<Button
variant="outline"
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 variant="outline" className="gap-2 rounded-xl text-sm font-bold">
<Download size={16} /> Export as PDF
</Button>
</div>
@@ -189,10 +178,12 @@ export default function DPPPage() {
</div>
<h3 className="font-bold text-lg leading-tight group-hover:text-[var(--cyan-ai)] transition-colors">
{cleanTitle(dpp.title)}
<span className={`ml-2 text-[10px] uppercase px-2 py-0.5 rounded-md border font-bold tracking-wider ${getDifficultyColor(dpp.difficulty)}`}>
</h3>
<div className="mt-1">
<span className={`text-[10px] uppercase px-2 py-0.5 rounded-md border font-bold tracking-wider ${getDifficultyColor(dpp.difficulty)}`}>
{dpp.difficulty}
</span>
</h3>
</div>
<div className="text-sm text-muted-foreground mt-2 font-medium">
{count} Questions • {dpp.total_time} mins
{dpp.scheduled_date && (
@@ -214,15 +205,19 @@ export default function DPPPage() {
<Button variant="outline" size="sm" className="font-bold border-border">Result</Button>
</Link>
{!isExpired && (
<Button onClick={() => setSelectedPdfDpp(dpp)} variant="secondary" size="sm" className="font-bold bg-[var(--cyan-ai-10)] text-[var(--cyan-ai)] hover:bg-[var(--cyan-ai-20)] gap-2">
<Eye size={14} /> Read PDF
<Link href={`/cbt/${dpp.id}`}>
<Button variant="secondary" size="sm" className="font-bold bg-[var(--cyan-ai-10)] text-[var(--cyan-ai)] hover:bg-[var(--cyan-ai-20)] gap-2">
<Play size={14} className="fill-current" /> Retake Test
</Button>
</Link>
)}
</>
) : (
<Button onClick={() => setSelectedPdfDpp(dpp)} variant="secondary" className="rounded-xl bg-primary/10 text-primary hover:bg-primary/20 font-bold gap-2 text-xs">
<Eye size={14} /> Open PDF
<Link href={`/cbt/${dpp.id}`}>
<Button variant="secondary" size="sm" className="rounded-xl bg-[var(--cyan-ai)] text-black hover:bg-[var(--cyan-ai-80)] font-extrabold gap-2 text-xs shadow-[0_0_12px_rgba(12,96,96,0.2)]">
<Play size={14} className="fill-black" /> Launch Practice Test
</Button>
</Link>
)}
</div>
</CardContent>
@@ -242,7 +237,7 @@ export default function DPPPage() {
</div>
<div>
<h3 className="font-black text-base sm:text-lg flex items-center gap-2">
{cleanTitle(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 hidden md:inline-block">
Protected In-App PDF View
</span>
@@ -257,7 +252,7 @@ export default function DPPPage() {
size="sm"
variant="outline"
onClick={() => {
navigator.clipboard.writeText(window.location.origin + '/dashboard/dpp');
navigator.clipboard.writeText(window.location.origin + '/dashboard/ai-coach/cbt-dpp');
setCopiedShare(true);
setTimeout(() => setCopiedShare(false), 2000);
}}
@@ -291,7 +286,7 @@ export default function DPPPage() {
</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 Interactive CBT Mode →
Upgrade for Interactive Practice Mode →
</Button>
</Link>
</div>
@@ -325,7 +320,7 @@ export default function DPPPage() {
</Button>
<Link href="/upgrade">
<Button className="bg-[var(--cyan-ai)] text-black hover:bg-[var(--cyan-ai-80)] font-extrabold rounded-xl shadow-[0_0_15px_rgba(12,96,96,0.2)] text-xs h-9">
<Sparkles size={14} className="mr-1.5" /> Upgrade to AI Coach for CBT
<Sparkles size={14} className="mr-1.5" /> Upgrade to AI Coach
</Button>
</Link>
</div>

View File

@@ -144,7 +144,14 @@ export default function DashboardLayout({ children }: { children: ReactNode }) {
)}
<NavItem section="pro" href="/dashboard" icon={<LayoutDashboard size={17} />} label="Overview" active={pathname === "/dashboard"} />
<NavItem section="pro" href="/dashboard/leaderboard" icon={<Trophy size={17} />} label="Leaderboard" active={pathname.startsWith("/dashboard/leaderboard")} isLocked={!hasAccessTo("chapter-wise-progress-tracking")} />
<NavItem
section="pro"
href="/dashboard/dpp"
icon={<FileText size={17} />}
label="Daily Practice (DPP)"
active={pathname.startsWith("/dashboard/dpp")}
isLocked={!hasAccessTo("chapter-wise-progress-tracking")}
/>
<NavItem
section="pro"
href="/dashboard/progress"
@@ -161,15 +168,7 @@ export default function DashboardLayout({ children }: { children: ReactNode }) {
active={pathname.startsWith("/dashboard/cbt")}
isLocked={!hasAccessTo("weekly-test-series")}
/>
{/* <NavItem
section="pro"
href="/dashboard/dpp"
icon={<FileText size={17} />}
label="Daily Practice (DPP)"
active={pathname.startsWith("/dashboard/dpp")}
isLocked={!hasAccessTo("chapter-wise-progress-tracking")}
/> */}
<NavItem section="pro" href="/dashboard/leaderboard" icon={<Trophy size={17} />} label="Leaderboard" active={pathname.startsWith("/dashboard/leaderboard")} isLocked={!hasAccessTo("chapter-wise-progress-tracking")} />
<NavItem
section="pro"
href="/dashboard/community"