feat: add AI options to landing mock sidebar and wire database-backed stats & leaderboard with fresh zero states
This commit is contained in:
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user