fix(dashboard): DPP tags properly dynamic based on user target exam

This commit is contained in:
2026-07-08 12:46:44 +05:30
parent c87b305646
commit 1a6168f724
28 changed files with 853 additions and 304 deletions

View File

@@ -83,7 +83,7 @@ export default function AnalyticsPage() {
<Tooltip
cursor={{fill: 'transparent'}}
contentStyle={{ backgroundColor: '#1f2937', borderColor: '#374151', borderRadius: '8px', color: '#fff' }}
formatter={(value: number) => [`${value}% Accuracy`, 'Accuracy']}
formatter={(value: any) => [`${value}% Accuracy`, 'Accuracy']}
/>
<Bar dataKey="accuracy" radius={[0, 4, 4, 0]} barSize={12}>
{heatmapData.map((entry, index) => (

View File

@@ -123,7 +123,7 @@ export default function AttemptHistoryPage({ params }: { params: Promise<{ attem
{idx + 1}
</div>
<div>
<h3 className="font-medium text-lg leading-relaxed">{q.text}</h3>
<h3 className="font-medium text-lg leading-relaxed">{q.question_text || q.text_en || q.text}</h3>
<div className="flex gap-2 mt-2">
<Badge variant="outline" className="text-xs">{q.topic}</Badge>
<Badge variant="outline" className="text-xs">{q.difficulty}</Badge>
@@ -141,13 +141,19 @@ export default function AttemptHistoryPage({ params }: { params: Promise<{ attem
<div className="space-y-2">
<p className="text-sm font-bold text-muted-foreground uppercase">Your Answer</p>
<div className={`p-3 rounded-lg border ${ans.is_correct ? 'bg-green-500/10 border-green-500/30 text-green-500' : 'bg-red-500/10 border-red-500/30 text-red-500'}`}>
{q.options[ans.selected_answer] ? `${ans.selected_answer}) ${q.options[ans.selected_answer]}` : 'Not Attempted'}
{ans.is_subjective || q.type === 'Subjective' || q.type === 'Numerical'
? (ans.selected_answer || 'Not Attempted')
: (q.options && q.options[ans.selected_answer] ? `${ans.selected_answer}) ${q.options[ans.selected_answer]}` : ans.selected_answer || 'Not Attempted')}
</div>
</div>
<div className="space-y-2">
<p className="text-sm font-bold text-muted-foreground uppercase">Correct Answer</p>
<div className="p-3 rounded-lg border bg-green-500/10 border-green-500/30 text-green-500">
{q.correct_answer?.correct}) {q.options[q.correct_answer?.correct]}
{ans.is_subjective || q.type === 'Subjective' || q.type === 'Numerical'
? (q.correct_answer?.value || q.correct_answer?.correct || 'Subjective text')
: (q.options && q.options[q.correct_answer?.correct || q.correct_answer?.value]
? `${q.correct_answer?.correct || q.correct_answer?.value}) ${q.options[q.correct_answer?.correct || q.correct_answer?.value]}`
: (q.correct_answer?.correct || q.correct_answer?.value || 'N/A'))}
</div>
</div>
</div>

View File

@@ -38,7 +38,7 @@ export default function CBTPage() {
if (!userRes.ok) throw new Error("Failed to load user target exam");
const userData = await userRes.json();
const pattern = userData.target_exam;
setTargetExam(pattern === "NEET" ? "NEET" : "JEE Main");
setTargetExam(pattern === "NEET" ? "NEET" : pattern === "JEE_Advanced" ? "JEE Advanced" : "JEE Main");
// 2. Fetch tests filtered by exam
const testRes = await fetch(`/api/tests?exam_pattern=${pattern}&user_id=${userId}`);

View File

@@ -2,7 +2,8 @@
import { useState } from "react"
import { Card, CardContent, CardHeader, CardTitle, CardFooter } from "@/components/ui/card"
import { MessageSquare, ThumbsUp, ChevronUp, Search, Image as ImageIcon, CheckCircle } from "lucide-react"
import { MessageSquare, ThumbsUp, ChevronUp, Search, Image as ImageIcon, CheckCircle, Lock } from "lucide-react"
import { useActiveUser } from "@/hooks/useActiveUser"
interface Doubt {
id: string
@@ -20,6 +21,7 @@ interface Doubt {
}
export default function CommunityPage() {
const { hasAccessTo } = useActiveUser()
const [activeFilter, setActiveFilter] = useState<'all' | 'my-doubts' | 'solved' | 'unsolved'>('all')
const [searchQuery, setSearchQuery] = useState('')
@@ -87,9 +89,16 @@ export default function CommunityPage() {
<p className="text-muted-foreground mt-2 font-medium">Ask questions, share knowledge, and learn together with the community.</p>
</div>
<button className="px-5 py-2.5 bg-[#00F0FF] text-black font-bold rounded-xl shadow-[0_0_15px_-3px_rgba(0,240,255,0.4)] hover:bg-[#00d0dd] transition-colors">
Ask a Doubt
</button>
{hasAccessTo("ask-doubts-from-tutors") ? (
<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">
Ask a Doubt
</button>
) : (
<button disabled className="px-5 py-2.5 bg-secondary text-muted-foreground font-bold rounded-xl flex items-center gap-2 cursor-not-allowed">
<Lock size={16} />
Pro Feature
</button>
)}
</div>
<div className="flex flex-col md:flex-row gap-4">

View File

@@ -6,15 +6,21 @@ import { BrainCircuit, Play, CheckCircle2, Download, Filter, Lock } from "lucide
import { useEffect, useState } from "react"
import Link from "next/link"
import { useActiveUser } from "@/hooks/useActiveUser"
export default function DPPPage() {
const { user } = useActiveUser()
const [dpps, setDpps] = useState<any[]>([])
const [loading, setLoading] = useState(true)
useEffect(() => {
async function fetchDpps() {
if (!user) return; // Wait for user to be available
try {
const res = await fetch("http://localhost:8000/api/tests?type=DPS")
const pattern = user.target_exam || "JEE_Main";
// Also fetch from the correct Next.js proxied route instead of localhost:8000
const res = await fetch(`/api/tests?type=DPS&exam_pattern=${pattern}`);
if (res.ok) {
const data = await res.json()
setDpps(data)
@@ -26,7 +32,7 @@ export default function DPPPage() {
}
}
fetchDpps()
}, [])
}, [user])
const getDifficultyColor = (diff: string) => {
switch(diff) {
@@ -80,7 +86,7 @@ export default function DPPPage() {
Object.values(dpp.sections).forEach((s: any) => { count += (s.questions?.length || 0) });
}
// Temporary mock for status
const status = "Pending"
let status: string = "Pending"
return (
<Card key={dpp.id} className={`group cursor-pointer transition-all hover:shadow-md ${status === 'Completed' ? 'opacity-70 hover:opacity-100 bg-secondary/10' : 'hover:border-[#00F0FF]/50 bg-background'}`}>
@@ -91,7 +97,9 @@ export default function DPPPage() {
</div>
<div>
<div className="flex items-center gap-2 mb-1">
<span className="text-xs font-bold uppercase tracking-wider text-[#00F0FF]">{dpp.exam_pattern?.replace("_", " ") || "JEE/NEET"}</span>
<span className="text-xs font-bold uppercase tracking-wider text-[#00F0FF]">
{user?.target_exam?.replace("_", " ") || "JEE/NEET"}
</span>
<span className="text-muted-foreground text-xs font-bold">•</span>
<span className="text-xs font-bold uppercase tracking-wider text-muted-foreground">{dpp.difficulty} Level</span>
</div>
@@ -113,7 +121,7 @@ export default function DPPPage() {
</Link>
) : (
<Link href={`/cbt/${dpp.id}`}>
<Button variant="secondary" size="icon" className="rounded-full group-hover:bg-[#00F0FF] group-hover:text-black dark:group-hover:text-black hover:bg-[#00F0FF] hover:text-black dark:hover:text-black shadow-sm transition-all duration-300">
<Button variant="secondary" size="icon" className="rounded-full group-hover:bg-primary group-hover:text-primary-foreground hover:bg-primary hover:text-primary-foreground shadow-sm transition-all duration-300">
<Play size={16} className="ml-1" />
</Button>
</Link>

View File

@@ -59,7 +59,13 @@ export default function DashboardLayout({ children }: { children: ReactNode }) {
</div>
<NavItem href="/dashboard/leaderboard" icon={<Trophy size={18} />} label="Leaderboard" active={pathname.startsWith("/dashboard/leaderboard")} />
<NavItem href="/dashboard/progress" icon={<Activity size={18} />} label="Syllabus Tracker" active={pathname.startsWith("/dashboard/progress")} />
<NavItem href="/dashboard/community" icon={<BookOpen size={18} />} label="Doubt Forum" active={pathname.startsWith("/dashboard/community")} />
<NavItem
href="/dashboard/community"
icon={<BookOpen size={18} />}
label="Doubt Forum"
active={pathname.startsWith("/dashboard/community")}
isLocked={!hasAccessTo("ask-doubts-from-tutors")}
/>
<div className="pt-4 pb-2">
<p className="px-3 text-xs font-bold uppercase tracking-widest text-muted-foreground">AI Intelligence</p>
@@ -159,7 +165,7 @@ export default function DashboardLayout({ children }: { children: ReactNode }) {
<div className="flex items-center gap-4">
{/* Streak count moved to top header */}
{user && (
<Link href="/dashboard/streaks" className="hover:opacity-85 transition-opacity flex items-center gap-1.5 bg-secondary/50 border border-border px-3 py-1 rounded-xl">
<Link href="/dashboard/streaks" className="hover:opacity-85 transition-opacity">
<StreakCounter streak={user.streak_count} />
</Link>
)}

View File

@@ -30,12 +30,7 @@ export default function DashboardPage() {
return (
<div className="p-8 max-w-6xl mx-auto space-y-10 animate-in fade-in duration-500 slide-in-from-bottom-4">
<header className="flex justify-end mb-2">
<Button size="lg" className="gap-2 shadow-sm bg-[#00F0FF] text-black hover:bg-[#00F0FF]/90 font-bold">
<BrainCircuit size={18} />
Generate AI Plan
</Button>
</header>
{/* Free Tier Upgrade CTA Banner */}
<Card className="bg-primary/5 border-primary/20 relative overflow-hidden">
@@ -47,7 +42,7 @@ export default function DashboardPage() {
<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 ₹299/mo to access unlimited Daily Practice Sheets, weekly CBT tests, and advanced analytics.
You're currently on the Free Tier. Upgrade to Pro for ₹2499/yr 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">