feat: add AI options to landing mock sidebar and wire database-backed stats & leaderboard with fresh zero states

This commit is contained in:
2026-09-13 21:57:12 +05:30
parent a6a8eda51b
commit d9bd142e6a
7 changed files with 343 additions and 97 deletions

View File

@@ -1,6 +1,6 @@
"use client"
import { useRef } from "react"
import { useRef, useState, useEffect } from "react"
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
import { Badge } from "@/components/ui/badge"
import { Button } from "@/components/ui/button"
@@ -13,6 +13,24 @@ import { useActiveUser } from "@/hooks/useActiveUser"
export default function DashboardPage() {
const { user, hasAccessTo, loading } = useActiveUser()
const progressRef = useRef<HTMLDivElement>(null)
const [userStats, setUserStats] = useState<any>(null)
useEffect(() => {
const userId = user?.id
if (!userId) return
async function fetchStats() {
try {
const res = await fetch(`/api/users/${userId}/stats`)
if (res.ok) {
const data = await res.json()
setUserStats(data)
}
} catch (e) {
console.error("Failed to load user stats", e)
}
}
fetchStats()
}, [user?.id])
const handleExport = async () => {
if (progressRef.current) {
@@ -37,6 +55,8 @@ export default function DashboardPage() {
)
}
const isNew = userStats ? userStats.is_new_user : true
return (
<div className="p-8 max-w-7xl mx-auto space-y-10 animate-in fade-in duration-500 slide-in-from-bottom-4">
@@ -59,30 +79,49 @@ export default function DashboardPage() {
</Button>
</div>
<div ref={progressRef} className="grid grid-cols-2 md:grid-cols-4 gap-4 p-4 -mx-4 bg-background rounded-2xl">
{/* Today's Score */}
<Card>
<CardContent className="p-5 flex flex-col items-center text-center">
<div className="p-3 bg-secondary rounded-full mb-3 text-primary"><Target size={24} /></div>
<p className="text-sm font-bold text-muted-foreground uppercase tracking-wider mb-1">Today's Score</p>
<h3 className="text-2xl font-bold">42 / 60</h3>
<p className="text-xs text-green-500 font-bold mt-2 flex items-center gap-1"><TrendingUp size={12}/> +5 from yesterday</p>
<h3 className="text-2xl font-bold">
{isNew ? "0 / 0" : `${userStats.today_score} / ${userStats.today_max_score || 60}`}
</h3>
<p className="text-xs text-muted-foreground font-medium mt-2">
{isNew ? "No tests taken today" : userStats.today_score > 0 ? "Daily session score" : "Start today's DPP"}
</p>
</CardContent>
</Card>
{/* Overall Accuracy */}
<Card>
<CardContent className="p-5 flex flex-col items-center text-center">
<div className="p-3 bg-secondary rounded-full mb-3 text-amber-500"><Activity size={24} /></div>
<p className="text-sm font-bold text-muted-foreground uppercase tracking-wider mb-1">Overall Accuracy</p>
<h3 className="text-2xl font-bold">68%</h3>
<p className="text-xs text-amber-500 font-bold mt-2">Needs Improvement</p>
<h3 className="text-2xl font-bold">
{isNew ? "0%" : `${userStats.overall_accuracy}%`}
</h3>
<p className={`text-xs font-bold mt-2 ${isNew ? 'text-muted-foreground font-normal' : userStats.overall_accuracy >= 70 ? 'text-green-500' : 'text-amber-500'}`}>
{isNew ? "Start a DPP to calculate" : userStats.overall_accuracy >= 70 ? "On Track" : "Needs Improvement"}
</p>
</CardContent>
</Card>
{/* Qs Attempted */}
<Card>
<CardContent className="p-5 flex flex-col items-center text-center">
<div className="p-3 bg-secondary rounded-full mb-3 text-[#00F0FF]"><CheckCircle2 size={24} /></div>
<p className="text-sm font-bold text-muted-foreground uppercase tracking-wider mb-1">Qs Attempted</p>
<h3 className="text-2xl font-bold">1,245</h3>
<p className="text-xs text-muted-foreground font-medium mt-2">Lifetime total</p>
<h3 className="text-2xl font-bold">
{isNew ? "0" : (userStats.total_questions_attempted || 0).toLocaleString()}
</h3>
<p className="text-xs text-muted-foreground font-medium mt-2">
{isNew ? "0 questions answered" : "Lifetime total"}
</p>
</CardContent>
</Card>
{/* City Rank */}
<Card className="relative overflow-hidden group">
<div className="absolute inset-0 bg-gradient-to-br from-background to-secondary/50 z-0"></div>
<CardContent className="p-5 flex flex-col items-center text-center relative z-10">
@@ -90,8 +129,16 @@ export default function DashboardPage() {
<div className="flex items-center gap-2 mb-1">
<p className="text-sm font-bold text-muted-foreground uppercase tracking-wider">City Rank</p>
</div>
<h3 className="text-2xl font-bold">#42 <span className="text-base text-muted-foreground font-medium">/ 1,200</span></h3>
<p className="text-xs text-muted-foreground font-medium mt-2 text-[#00F0FF]">Top 5% in Jaipur</p>
<h3 className="text-2xl font-bold">
{isNew ? (
<span className="text-muted-foreground text-xl">#--</span>
) : (
<>#{userStats.city_rank || 1} <span className="text-base text-muted-foreground font-medium">/ {userStats.city_total || 50}</span></>
)}
</h3>
<p className="text-xs text-muted-foreground font-medium mt-2 text-[#00F0FF]">
{isNew ? "Unranked · Take a test" : `Top rank in ${user?.city || "your city"}`}
</p>
</CardContent>
</Card>
</div>