feat: complete comprehensive zero-state metrics across all dashboard pages for new accounts
This commit is contained in:
@@ -5,12 +5,13 @@ import { Card, CardContent } from "@/components/ui/card"
|
||||
import { TrendingUp, Flame, RefreshCw } from "lucide-react"
|
||||
import Link from "next/link"
|
||||
|
||||
import { useActiveUser } from "@/hooks/useActiveUser"
|
||||
import { useActiveUser, isDemoUser } from "@/hooks/useActiveUser"
|
||||
|
||||
export function ReadinessGauge() {
|
||||
const [data, setData] = useState<any>(null)
|
||||
const [loading, setLoading] = useState(true)
|
||||
const { userId, loading: userLoading } = useActiveUser();
|
||||
const isDemo = isDemoUser(userId);
|
||||
|
||||
useEffect(() => {
|
||||
if (userLoading || !userId) return;
|
||||
@@ -22,23 +23,35 @@ export function ReadinessGauge() {
|
||||
const json = await res.json()
|
||||
setData(json)
|
||||
} catch (err) {
|
||||
console.warn("Using mock fallback data for Readiness Gauge.")
|
||||
setData({
|
||||
score: 72,
|
||||
breakdown: {
|
||||
accuracy: 30,
|
||||
speed: 15,
|
||||
consistency: 8,
|
||||
recovery: 5,
|
||||
coverage: 14
|
||||
}
|
||||
})
|
||||
if (isDemo) {
|
||||
setData({
|
||||
score: 72,
|
||||
breakdown: {
|
||||
accuracy: 30,
|
||||
speed: 15,
|
||||
consistency: 8,
|
||||
recovery: 5,
|
||||
coverage: 14
|
||||
}
|
||||
})
|
||||
} else {
|
||||
setData({
|
||||
score: 0,
|
||||
breakdown: {
|
||||
accuracy: 0,
|
||||
speed: 0,
|
||||
consistency: 0,
|
||||
recovery: 0,
|
||||
coverage: 0
|
||||
}
|
||||
})
|
||||
}
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
}
|
||||
fetchReadiness()
|
||||
}, [userId, userLoading])
|
||||
}, [userId, userLoading, isDemo])
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
@@ -49,16 +62,10 @@ export function ReadinessGauge() {
|
||||
}
|
||||
|
||||
const score = data?.score || 0
|
||||
const breakdown = data?.breakdown || {}
|
||||
const isZero = score === 0 && !isDemo
|
||||
const circumference = 2 * Math.PI * 45 // r=45
|
||||
const strokeDashoffset = circumference - (score / 100) * circumference
|
||||
|
||||
const getColor = (s: number) => {
|
||||
if (s >= 75) return "text-green-500"
|
||||
if (s >= 50) return "text-yellow-500"
|
||||
return "text-red-500"
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex flex-col h-full">
|
||||
|
||||
@@ -67,10 +74,12 @@ export function ReadinessGauge() {
|
||||
<div className="absolute -top-1/2 -right-1/4 w-48 h-48 bg-[radial-gradient(circle,rgba(94,196,184,0.06)_0%,transparent_70%)] rounded-full pointer-events-none" />
|
||||
|
||||
<div className="flex justify-between items-start mb-4 relative z-10">
|
||||
<span className="text-[11px] font-bold text-muted-foreground uppercase tracking-widest">Updated daily • Today</span>
|
||||
<span className="flex items-center gap-1 text-xs font-bold text-green-500 bg-green-500/10 px-2.5 py-0.5 rounded-full">
|
||||
<span className="text-[11px] font-bold text-muted-foreground uppercase tracking-widest">
|
||||
{isZero ? "Algorithmic Baseline" : "Updated daily • Today"}
|
||||
</span>
|
||||
<span className={`flex items-center gap-1 text-xs font-bold px-2.5 py-0.5 rounded-full ${isZero ? "text-muted-foreground bg-white/5" : "text-green-500 bg-green-500/10"}`}>
|
||||
<TrendingUp size={14} className="stroke-[2.5px]" />
|
||||
+5 this week
|
||||
{isZero ? "Day 1 Baseline" : "+5 this week"}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@@ -91,7 +100,7 @@ export function ReadinessGauge() {
|
||||
strokeWidth="6"
|
||||
fill="transparent"
|
||||
strokeDasharray={circumference}
|
||||
strokeDashoffset={strokeDashoffset}
|
||||
strokeDashoffset={isZero ? circumference : strokeDashoffset}
|
||||
strokeLinecap="round"
|
||||
className="transition-all duration-1500 ease-out"
|
||||
/>
|
||||
@@ -105,19 +114,31 @@ export function ReadinessGauge() {
|
||||
{/* Quick metric grid beside ring */}
|
||||
<div className="flex-1 grid grid-cols-2 gap-2.5 w-full">
|
||||
<div className="bg-white/5 rounded-lg p-2.5">
|
||||
<div className="text-base font-black flex items-baseline gap-1">88% <span className="text-xs text-green-500">↑</span></div>
|
||||
<div className="text-base font-black flex items-baseline gap-1">
|
||||
{isZero ? "0%" : "88%"}
|
||||
{!isZero && <span className="text-xs text-green-500">↑</span>}
|
||||
</div>
|
||||
<div className="text-xs font-bold text-muted-foreground">Accuracy</div>
|
||||
</div>
|
||||
<div className="bg-white/5 rounded-lg p-2.5">
|
||||
<div className="text-base font-black flex items-baseline gap-1">45s <span className="text-xs text-red-500">↓</span></div>
|
||||
<div className="text-base font-black flex items-baseline gap-1">
|
||||
{isZero ? "-- s" : "45s"}
|
||||
{!isZero && <span className="text-xs text-red-500">↓</span>}
|
||||
</div>
|
||||
<div className="text-xs font-bold text-muted-foreground">Speed/Q</div>
|
||||
</div>
|
||||
<div className="bg-white/5 rounded-lg p-2.5">
|
||||
<div className="text-base font-black flex items-baseline gap-1">High <span className="text-xs text-green-500">↑</span></div>
|
||||
<div className="text-base font-black flex items-baseline gap-1">
|
||||
{isZero ? "0%" : "High"}
|
||||
{!isZero && <span className="text-xs text-green-500">↑</span>}
|
||||
</div>
|
||||
<div className="text-xs font-bold text-muted-foreground">Consistency</div>
|
||||
</div>
|
||||
<div className="bg-white/5 rounded-lg p-2.5">
|
||||
<div className="text-base font-black flex items-baseline gap-1">64% <span className="text-xs text-green-500">↑</span></div>
|
||||
<div className="text-base font-black flex items-baseline gap-1">
|
||||
{isZero ? "0%" : "64%"}
|
||||
{!isZero && <span className="text-xs text-green-500">↑</span>}
|
||||
</div>
|
||||
<div className="text-xs font-bold text-muted-foreground">Syllabus</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -126,12 +147,12 @@ export function ReadinessGauge() {
|
||||
{/* Subject Bars */}
|
||||
<div className="space-y-2 mb-4 relative z-10">
|
||||
{[
|
||||
{ name: "Physics", val: 68, color: "#00F0FF" },
|
||||
{ name: "Chemistry", val: 74, color: "#F59E0B" },
|
||||
{ name: "Biology", val: 82, color: "#10B981" },
|
||||
{ name: "Physics", val: isZero ? 0 : 68, color: "#00F0FF" },
|
||||
{ name: "Chemistry", val: isZero ? 0 : 74, color: "#F59E0B" },
|
||||
{ name: "Biology / Maths", val: isZero ? 0 : 82, color: "#10B981" },
|
||||
].map(sub => (
|
||||
<div key={sub.name} className="flex items-center gap-3">
|
||||
<span className="text-xs font-bold text-muted-foreground w-20 shrink-0">{sub.name}</span>
|
||||
<span className="text-xs font-bold text-muted-foreground w-24 shrink-0 truncate">{sub.name}</span>
|
||||
<div className="flex-1 h-2 rounded-full bg-white/10 overflow-hidden">
|
||||
<div
|
||||
className="h-full rounded-full transition-all duration-1000"
|
||||
@@ -147,15 +168,20 @@ export function ReadinessGauge() {
|
||||
<div className="flex items-center justify-between px-3 py-2 bg-white/5 rounded-lg mb-4 relative z-10 text-xs font-bold">
|
||||
<span className="text-muted-foreground">National Avg</span>
|
||||
<span className="text-muted-foreground">58 / 100</span>
|
||||
<span className="text-green-500">You: +{score - 58} above avg</span>
|
||||
<span className={isZero ? "text-muted-foreground" : "text-green-500"}>
|
||||
{isZero ? "Pending test data" : `You: +${score - 58} above avg`}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="mt-auto px-3.5 py-3 bg-[#00F0FF]/10 border border-[#00F0FF]/20 rounded-lg text-xs md:text-sm text-[#00F0FF] font-bold flex items-center gap-2 relative z-10">
|
||||
<Flame size={18} className="fill-[#00F0FF]/20 shrink-0" />
|
||||
Your speed has dropped slightly. Focus on time-bound practice today.
|
||||
{isZero
|
||||
? "Complete your first diagnostic test to establish your personalized readiness score."
|
||||
: "Your speed has dropped slightly. Focus on time-bound practice today."}
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user