Fix UI bugs, admin bulk upload limits, test ingestion bugs, and reset demo users
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
"use client"
|
||||
|
||||
import { fetchWithAuth } from "@/lib/apiClient";
|
||||
import Link from "next/link";
|
||||
import { useRef, useState, useEffect } from "react"
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
|
||||
import { Badge } from "@/components/ui/badge"
|
||||
@@ -14,6 +15,8 @@ export default function DashboardPage() {
|
||||
const { user, userId, loading: userLoading, hasAccessTo, trialInfo } = useActiveUser()
|
||||
const [userStats, setUserStats] = useState<any>(null)
|
||||
const [loading, setLoading] = useState(true)
|
||||
const [dpps, setDpps] = useState<any[]>([])
|
||||
const [cbts, setCbts] = useState<any[]>([])
|
||||
const [downloading, setDownloading] = useState(false)
|
||||
const [shareMenuOpen, setShareMenuOpen] = useState(false)
|
||||
const progressRef = useRef<HTMLDivElement>(null)
|
||||
@@ -25,20 +28,26 @@ export default function DashboardPage() {
|
||||
setLoading(false)
|
||||
return
|
||||
}
|
||||
async function fetchStats() {
|
||||
async function fetchStatsAndTests() {
|
||||
try {
|
||||
const res = await fetchWithAuth(`/api/users/${currentUserId}/stats`)
|
||||
if (res.ok) {
|
||||
const data = await res.json()
|
||||
setUserStats(data)
|
||||
}
|
||||
const pattern = user?.target_exam || "JEE_Main";
|
||||
const [statsRes, dppRes, cbtRes] = await Promise.all([
|
||||
fetchWithAuth(`/api/users/${currentUserId}/stats`),
|
||||
fetchWithAuth(`/api/tests?type=DPS&exam_pattern=${pattern}`),
|
||||
fetchWithAuth(`/api/tests?type=Mock&exam_pattern=${pattern}`)
|
||||
]);
|
||||
|
||||
if (statsRes.ok) setUserStats(await statsRes.json());
|
||||
if (dppRes.ok) setDpps((await dppRes.json()).slice(0, 2));
|
||||
if (cbtRes.ok) setCbts((await cbtRes.json()).slice(0, 2));
|
||||
|
||||
} catch (e) {
|
||||
console.error("Failed to load user stats", e)
|
||||
console.error("Failed to load user data", e)
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
}
|
||||
fetchStats()
|
||||
fetchStatsAndTests()
|
||||
}, [userLoading, user?.id, userId])
|
||||
|
||||
const getShareUrl = () => {
|
||||
@@ -227,61 +236,29 @@ export default function DashboardPage() {
|
||||
<Button variant="ghost" size="sm" className="text-muted-foreground font-medium">View All</Button>
|
||||
</div>
|
||||
<div className="space-y-4">
|
||||
<Card className="hover:border-primary/30 hover:shadow-md cursor-pointer transition-all duration-300 group">
|
||||
<CardContent className="p-5 flex items-center justify-between">
|
||||
<div className="flex gap-4 items-center">
|
||||
<div className="p-3 bg-secondary rounded-xl text-foreground">
|
||||
<BrainCircuit size={20} />
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="font-medium text-base group-hover:text-primary transition-colors">Physics: Rotational Dynamics</h3>
|
||||
<p className="text-sm text-muted-foreground mt-0.5">20 Questions • High Priority</p>
|
||||
</div>
|
||||
</div>
|
||||
<Button variant="outline" size="sm" className="gap-2 rounded-xl group-hover:bg-primary group-hover:text-primary-foreground group-hover:border-primary transition-all">
|
||||
Start <ArrowRight size={14} />
|
||||
</Button>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{hasAccessTo("topic-wise-microtests-unlimited") ? (
|
||||
<Card className="hover:border-primary/30 hover:shadow-md cursor-pointer transition-all duration-300 group">
|
||||
<CardContent className="p-5 flex items-center justify-between">
|
||||
<div className="flex gap-4 items-center">
|
||||
<div className="p-3 bg-secondary rounded-xl text-foreground">
|
||||
<BrainCircuit size={20} />
|
||||
{dpps.length > 0 ? (
|
||||
dpps.map((dpp) => (
|
||||
<Card key={dpp.id} className="hover:border-primary/30 hover:shadow-md cursor-pointer transition-all duration-300 group">
|
||||
<CardContent className="p-5 flex items-center justify-between">
|
||||
<div className="flex gap-4 items-center">
|
||||
<div className="p-3 bg-secondary rounded-xl text-foreground">
|
||||
<BrainCircuit size={20} />
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="font-medium text-base group-hover:text-primary transition-colors">{dpp.title}</h3>
|
||||
<p className="text-sm text-muted-foreground mt-0.5">{dpp.total_time} Minutes • {dpp.difficulty || "Medium"} Priority</p>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="font-medium text-base group-hover:text-primary transition-colors">
|
||||
{isNeet ? "Biology: Human Physiology" : "Maths: Integral Calculus"}
|
||||
</h3>
|
||||
<p className="text-sm text-muted-foreground mt-0.5">30 Questions • Medium Priority</p>
|
||||
</div>
|
||||
</div>
|
||||
<Button variant="outline" size="sm" className="gap-2 rounded-xl group-hover:bg-primary group-hover:text-primary-foreground group-hover:border-primary transition-all">
|
||||
Start <ArrowRight size={14} />
|
||||
</Button>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<Link href={`/dashboard/dpp`}>
|
||||
<Button variant="outline" size="sm" className="gap-2 rounded-xl group-hover:bg-primary group-hover:text-primary-foreground group-hover:border-primary transition-all">
|
||||
Start <ArrowRight size={14} />
|
||||
</Button>
|
||||
</Link>
|
||||
</CardContent>
|
||||
</Card>
|
||||
))
|
||||
) : (
|
||||
<Card className="relative opacity-60 cursor-not-allowed border-dashed">
|
||||
<CardContent className="p-5 flex items-center justify-between">
|
||||
<div className="flex gap-4 items-center">
|
||||
<div className="p-3 bg-secondary rounded-xl text-muted-foreground">
|
||||
<Lock size={20} />
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="font-medium text-base text-muted-foreground">
|
||||
{isNeet ? "Biology: Human Physiology" : "Maths: Integral Calculus"}
|
||||
</h3>
|
||||
<p className="text-sm text-muted-foreground mt-0.5 flex items-center gap-1"><Lock size={12}/> Pro Feature</p>
|
||||
</div>
|
||||
</div>
|
||||
<Button variant="outline" size="sm" disabled className="gap-2 rounded-xl">
|
||||
Locked
|
||||
</Button>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<p className="text-muted-foreground text-sm py-4">No DPPs scheduled for today.</p>
|
||||
)}
|
||||
</div>
|
||||
</section>
|
||||
@@ -293,38 +270,33 @@ export default function DashboardPage() {
|
||||
Upcoming CBTs
|
||||
</h2>
|
||||
</div>
|
||||
<Card className="overflow-hidden">
|
||||
<div className="p-6 border-b border-border flex items-center justify-between bg-background">
|
||||
<div>
|
||||
<div className="flex items-center gap-3 mb-2">
|
||||
<Badge variant="outline" className="border-primary/20 bg-primary/5 text-primary">Mock Test</Badge>
|
||||
<span className="text-xs text-muted-foreground font-semibold uppercase tracking-wider">Tomorrow, 10:00 AM</span>
|
||||
</div>
|
||||
<h3 className="font-semibold text-lg tracking-tight">{isNeet ? 'NEET Mock Test 4' : 'JEE Mock Test 4'}</h3>
|
||||
<p className="text-sm text-muted-foreground flex items-center gap-1.5 mt-1.5">
|
||||
<Clock size={14} /> 180 Minutes • 720 Marks
|
||||
</p>
|
||||
</div>
|
||||
<Button variant="secondary" className="rounded-xl font-medium">Remind Me</Button>
|
||||
</div>
|
||||
{hasAccessTo("weekly-test-series") ? (
|
||||
<div className="p-5 bg-secondary/50 flex items-center justify-between transition-colors hover:bg-secondary/70 cursor-pointer group">
|
||||
<div>
|
||||
<h3 className="font-medium text-sm text-foreground flex items-center gap-2"><Activity size={14} className="text-primary group-hover:animate-pulse"/> Part Test 3: Mechanics</h3>
|
||||
<p className="text-xs text-muted-foreground mt-0.5 font-medium ml-6">Oct 24, 2026</p>
|
||||
</div>
|
||||
<Button variant="outline" size="sm" className="h-7 text-xs rounded-lg group-hover:bg-primary group-hover:text-primary-foreground group-hover:border-primary">Start <ArrowRight size={12} className="ml-1" /></Button>
|
||||
</div>
|
||||
<div className="space-y-4">
|
||||
{cbts.length > 0 ? (
|
||||
cbts.map((cbt, idx) => (
|
||||
<Card key={cbt.id} className="overflow-hidden">
|
||||
<div className="p-6 border-b border-border flex items-center justify-between bg-background">
|
||||
<div>
|
||||
<div className="flex items-center gap-3 mb-2">
|
||||
<Badge variant="outline" className="border-primary/20 bg-primary/5 text-primary">{cbt.exam_pattern}</Badge>
|
||||
<span className="text-xs text-muted-foreground font-semibold uppercase tracking-wider">{cbt.scheduled_date ? new Date(cbt.scheduled_date).toLocaleDateString() : 'Available Now'}</span>
|
||||
</div>
|
||||
<h3 className="font-semibold text-lg tracking-tight">{cbt.title}</h3>
|
||||
<p className="text-sm text-muted-foreground flex items-center gap-1.5 mt-1.5">
|
||||
<Clock size={14} /> {cbt.total_time} Minutes • {cbt.difficulty || 'Medium'}
|
||||
</p>
|
||||
</div>
|
||||
<Link href={`/dashboard/cbt`}>
|
||||
<Button variant={idx === 0 ? "secondary" : "outline"} className="rounded-xl font-medium">Start</Button>
|
||||
</Link>
|
||||
</div>
|
||||
</Card>
|
||||
))
|
||||
) : (
|
||||
<div className="p-5 bg-secondary/50 flex items-center justify-between transition-colors hover:bg-secondary/70 opacity-75">
|
||||
<div>
|
||||
<h3 className="font-medium text-sm text-foreground flex items-center gap-2"><Lock size={14} className="text-muted-foreground"/> Part Test 3: Mechanics</h3>
|
||||
<p className="text-xs text-muted-foreground mt-0.5 font-medium ml-6">Oct 24, 2026</p>
|
||||
</div>
|
||||
<Badge variant="secondary" className="text-xs">Pro Only</Badge>
|
||||
</div>
|
||||
<Card className="overflow-hidden p-6 border-dashed opacity-70">
|
||||
<p className="text-muted-foreground text-sm">No upcoming CBTs at the moment.</p>
|
||||
</Card>
|
||||
)}
|
||||
</Card>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
@@ -340,3 +312,4 @@ export default function DashboardPage() {
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user