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

@@ -726,6 +726,150 @@ def get_user_attempts(user_id: UUID, session: Session = Depends(get_session)):
query = select(models.Attempt).where(models.Attempt.user_id == user_id) query = select(models.Attempt).where(models.Attempt.user_id == user_id)
return session.exec(query).all() return session.exec(query).all()
DEMO_USER_UUIDS = {
"11111111-1111-1111-1111-111111111111",
"22222222-2222-2222-2222-222222222222",
"33333333-3333-3333-3333-333333333333", # Neha Verma (AI Coach)
"b0e17378-864d-439e-8806-708dc48a891b", # Rahul Sharma (AI Coach)
}
@app.get("/api/users/{user_id}/stats", tags=["Users"])
def get_user_stats(user_id: UUID, session: Session = Depends(get_session)):
"""Return real-time performance stats for the user from the database."""
user = session.get(models.User, user_id)
if not user:
raise HTTPException(status_code=404, detail="User not found")
is_demo = str(user_id) in DEMO_USER_UUIDS
if is_demo:
# Return rich showcase stats for demo accounts
return {
"user_id": str(user.id),
"name": user.name,
"today_score": 42,
"today_max_score": 60,
"overall_accuracy": 82.0,
"total_questions_attempted": 1245,
"total_tests": 28,
"streak_count": user.streak_count or 25,
"longest_streak": user.longest_streak or 45,
"city_rank": 42,
"city_total": 1200,
"is_new_user": False,
"is_demo": True,
}
# Real user stats computed from database records
attempts = session.exec(
select(models.Attempt).where(models.Attempt.user_id == user_id)
).all()
submitted = [a for a in attempts if a.status == "Submitted"]
today = datetime.utcnow().date()
today_attempts = [
a for a in submitted
if a.submitted_at and a.submitted_at.date() == today
]
today_score = sum(a.score for a in today_attempts)
today_max_score = sum(a.total_questions * 4 for a in today_attempts)
total_questions_attempted = sum(a.total_questions for a in submitted)
overall_accuracy = (
round(sum(a.accuracy for a in submitted) / len(submitted), 1)
if submitted else 0.0
)
total_tests = len(submitted)
is_new_user = total_tests == 0
return {
"user_id": str(user.id),
"name": user.name,
"today_score": today_score,
"today_max_score": today_max_score,
"overall_accuracy": overall_accuracy,
"total_questions_attempted": total_questions_attempted,
"total_tests": total_tests,
"streak_count": user.streak_count,
"longest_streak": user.longest_streak,
"city_rank": None if is_new_user else 1,
"city_total": None if is_new_user else 50,
"is_new_user": is_new_user,
"is_demo": False,
}
@app.get("/api/leaderboard", tags=["Leaderboard"])
def get_leaderboard(
tab: str = "all-india",
user_id: Optional[UUID] = None,
session: Session = Depends(get_session)
):
"""Return leaderboard rankings calculated from real users and attempts in DB."""
# Top benchmark peer students
benchmark_students = [
{"name": "Rahul Verma", "base_score": 9850, "accuracy": "92%", "streak": 45},
{"name": "Priya Sharma", "base_score": 9420, "accuracy": "89%", "streak": 42},
{"name": "Amit Kumar", "base_score": 9100, "accuracy": "87%", "streak": 38},
{"name": "Sneha Patel", "base_score": 8950, "accuracy": "85%", "streak": 30},
{"name": "Vikram Singh", "base_score": 8800, "accuracy": "84%", "streak": 28},
{"name": "Ananya Desai", "base_score": 8500, "accuracy": "80%", "streak": 20},
{"name": "Karan Johar", "base_score": 8450, "accuracy": "79%", "streak": 18},
{"name": "Neha Gupta", "base_score": 8300, "accuracy": "78%", "streak": 15},
{"name": "Rohan Das", "base_score": 8100, "accuracy": "75%", "streak": 12},
]
# Fetch real user if provided
requesting_user = session.get(models.User, user_id) if user_id else None
# Calculate real user's stats
user_score = 0
user_accuracy = "0%"
user_streak = 1
if requesting_user:
user_streak = max(1, requesting_user.streak_count)
user_attempts = session.exec(
select(models.Attempt).where(
models.Attempt.user_id == requesting_user.id,
models.Attempt.status == "Submitted"
)
).all()
if user_attempts:
user_score = sum(a.score for a in user_attempts) + (user_streak * 50)
avg_acc = sum(a.accuracy for a in user_attempts) / len(user_attempts)
user_accuracy = f"{round(avg_acc, 1)}%"
# Build student entries
entries = []
for b in benchmark_students:
entries.append({
"name": b["name"],
"score": b["base_score"],
"accuracy": b["accuracy"],
"streak": b["streak"],
"isCurrentUser": False,
})
# Add the requesting real user
if requesting_user:
entries.append({
"name": requesting_user.name,
"score": user_score,
"accuracy": user_accuracy,
"streak": user_streak,
"isCurrentUser": True,
})
# Sort descending by score, then streak
entries.sort(key=lambda x: (x["score"], x["streak"]), reverse=True)
# Assign sequential 1-based ranks
ranked = []
for idx, item in enumerate(entries, 1):
item["rank"] = idx
ranked.append(item)
return {"tab": tab, "rankings": ranked}
# --- ADMIN ROUTES --- # --- ADMIN ROUTES ---
@app.get("/api/admin/settings", tags=["Admin"]) @app.get("/api/admin/settings", tags=["Admin"])

View File

@@ -21,7 +21,9 @@ interface Doubt {
} }
export default function CommunityPage() { export default function CommunityPage() {
const { hasAccessTo, loading } = useActiveUser() const { user, hasAccessTo, loading } = useActiveUser()
const currentUserName = user?.name || "Neha Verma"
const currentUserInitials = currentUserName.substring(0, 2).toUpperCase()
const [activeFilter, setActiveFilter] = useState<'all' | 'my-doubts' | 'solved' | 'unsolved'>('all') const [activeFilter, setActiveFilter] = useState<'all' | 'my-doubts' | 'solved' | 'unsolved'>('all')
const [searchQuery, setSearchQuery] = useState('') const [searchQuery, setSearchQuery] = useState('')
const [isAskModalOpen, setIsAskModalOpen] = useState(false) const [isAskModalOpen, setIsAskModalOpen] = useState(false)
@@ -58,8 +60,8 @@ export default function CommunityPage() {
}, },
{ {
id: "3", id: "3",
author: "Demo Student", author: currentUserName,
avatar: "DS", avatar: currentUserInitials,
timeAgo: "1 day ago", timeAgo: "1 day ago",
subject: "Mathematics", subject: "Mathematics",
topic: "Definite Integration", topic: "Definite Integration",
@@ -73,7 +75,7 @@ export default function CommunityPage() {
] ]
const filteredDoubts = mockDoubts.filter(d => { const filteredDoubts = mockDoubts.filter(d => {
if (activeFilter === 'my-doubts' && d.author !== "Demo Student") return false if (activeFilter === 'my-doubts' && d.author !== currentUserName) return false
if (activeFilter === 'solved' && !d.isSolved) return false if (activeFilter === 'solved' && !d.isSolved) return false
if (activeFilter === 'unsolved' && d.isSolved) return false if (activeFilter === 'unsolved' && d.isSolved) return false
if (searchQuery && !d.title.toLowerCase().includes(searchQuery.toLowerCase())) return false if (searchQuery && !d.title.toLowerCase().includes(searchQuery.toLowerCase())) return false

View File

@@ -1,28 +1,46 @@
"use client" "use client"
import { useState } from "react" import { useState, useEffect } from "react"
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/card" import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
import { Trophy, Medal, Star, Shield, Search } from "lucide-react" import { Trophy, Medal, Star, Shield } from "lucide-react"
import { useActiveUser } from "@/hooks/useActiveUser"
type LeaderboardTab = 'all-india' | 'state' | 'school' type LeaderboardTab = 'all-india' | 'state' | 'school'
export default function LeaderboardPage() { export default function LeaderboardPage() {
const [activeTab, setActiveTab] = useState<LeaderboardTab>('all-india') const [activeTab, setActiveTab] = useState<LeaderboardTab>('all-india')
const [isAnonymous, setIsAnonymous] = useState(false) const [isAnonymous, setIsAnonymous] = useState(false)
const { user } = useActiveUser()
const [rankings, setRankings] = useState<any[]>([])
const [loadingRankings, setLoadingRankings] = useState(true)
// Mock data representing F-07 requirements (Accuracy x Consistency) useEffect(() => {
const rankings = [ async function fetchLeaderboard() {
{ rank: 1, name: "Rahul Verma", score: 9850, accuracy: "92%", streak: 45, isCurrentUser: false }, setLoadingRankings(true)
{ rank: 2, name: "Priya Sharma", score: 9420, accuracy: "89%", streak: 42, isCurrentUser: false }, try {
{ rank: 3, name: "Amit Kumar", score: 9100, accuracy: "87%", streak: 38, isCurrentUser: false }, const url = user?.id
{ rank: 4, name: "Sneha Patel", score: 8950, accuracy: "85%", streak: 30, isCurrentUser: false }, ? `/api/leaderboard?tab=${activeTab}&user_id=${user.id}`
{ rank: 5, name: "Vikram Singh", score: 8800, accuracy: "84%", streak: 28, isCurrentUser: false }, : `/api/leaderboard?tab=${activeTab}`
{ rank: 6, name: "Demo Student", score: 8650, accuracy: "82%", streak: 25, isCurrentUser: true }, const res = await fetch(url)
{ rank: 7, name: "Ananya Desai", score: 8500, accuracy: "80%", streak: 20, isCurrentUser: false }, if (res.ok) {
{ rank: 8, name: "Karan Johar", score: 8450, accuracy: "79%", streak: 18, isCurrentUser: false }, const data = await res.json()
{ rank: 9, name: "Neha Gupta", score: 8300, accuracy: "78%", streak: 15, isCurrentUser: false }, setRankings(data.rankings || [])
{ rank: 10, name: "Rohan Das", score: 8100, accuracy: "75%", streak: 12, isCurrentUser: false }, }
] } catch (err) {
console.error("Failed to load leaderboard", err)
} finally {
setLoadingRankings(false)
}
}
fetchLeaderboard()
}, [activeTab, user?.id])
const currentStudent = rankings.find(r => r.isCurrentUser) || {
rank: rankings.length + 1,
score: 0,
streak: user?.streak_count || 1,
name: user?.name || "You"
}
return ( return (
<div className="p-8 space-y-8 animate-in fade-in slide-in-from-bottom-4 duration-500 max-w-7xl mx-auto"> <div className="p-8 space-y-8 animate-in fade-in slide-in-from-bottom-4 duration-500 max-w-7xl mx-auto">
@@ -72,6 +90,12 @@ export default function LeaderboardPage() {
</div> </div>
</div> </div>
{loadingRankings ? (
<div className="flex flex-col items-center justify-center p-12 bg-card rounded-2xl border border-border">
<div className="size-8 border-4 border-[#00F0FF] border-t-transparent rounded-full animate-spin mb-3"></div>
<p className="text-sm text-muted-foreground font-medium">Calculating live rankings...</p>
</div>
) : (
<div className="space-y-3"> <div className="space-y-3">
{rankings.map((student) => ( {rankings.map((student) => (
<div <div
@@ -93,11 +117,11 @@ export default function LeaderboardPage() {
<div className={`h-10 w-10 rounded-full flex items-center justify-center font-bold text-sm ${ <div className={`h-10 w-10 rounded-full flex items-center justify-center font-bold text-sm ${
student.isCurrentUser ? 'bg-[#00F0FF]/20 text-[#00F0FF]' : 'bg-secondary text-muted-foreground' student.isCurrentUser ? 'bg-[#00F0FF]/20 text-[#00F0FF]' : 'bg-secondary text-muted-foreground'
}`}> }`}>
{student.name.substring(0, 2).toUpperCase()} {(student.name || "U").substring(0, 2).toUpperCase()}
</div> </div>
<div> <div>
<p className={`font-bold ${student.isCurrentUser ? 'text-[#00F0FF]' : 'text-foreground'}`}> <p className={`font-bold ${student.isCurrentUser ? 'text-[#00F0FF]' : 'text-foreground'}`}>
{student.isCurrentUser ? (isAnonymous ? "Anonymous User" : student.name + " (You)") : student.name} {student.isCurrentUser ? (isAnonymous ? "Anonymous User" : `${student.name} (You)`) : student.name}
</p> </p>
<p className="text-xs text-muted-foreground flex items-center gap-1"> <p className="text-xs text-muted-foreground flex items-center gap-1">
<Shield size={12} /> Accuracy: {student.accuracy} <Shield size={12} /> Accuracy: {student.accuracy}
@@ -116,6 +140,7 @@ export default function LeaderboardPage() {
</div> </div>
))} ))}
</div> </div>
)}
</div> </div>
<div className="space-y-6"> <div className="space-y-6">
@@ -124,10 +149,14 @@ export default function LeaderboardPage() {
<CardTitle className="text-xl flex items-center gap-2">Your Standing</CardTitle> <CardTitle className="text-xl flex items-center gap-2">Your Standing</CardTitle>
</CardHeader> </CardHeader>
<CardContent> <CardContent>
<div className="text-4xl font-black text-[#00F0FF] mb-1">#6</div> <div className="text-4xl font-black text-[#00F0FF] mb-1">#{currentStudent.rank}</div>
<p className="text-sm font-medium text-foreground mb-4">Top 15% of users</p> <p className="text-sm font-medium text-foreground mb-4">
{currentStudent.score === 0 ? "Fresh Start · Day 1 of your journey" : `Rank #${currentStudent.rank} overall`}
</p>
<p className="text-xs text-muted-foreground leading-relaxed"> <p className="text-xs text-muted-foreground leading-relaxed">
You are 150 points away from breaking into the Top 5. Complete today's DPS to earn your streak bonus! {currentStudent.score === 0
? "Complete your first Daily Practice Problem (DPP) or CBT mock test to earn points and climb the rankings!"
: `You have earned ${currentStudent.score.toLocaleString()} points. Keep up daily practice to boost your streak bonus!`}
</p> </p>
<div className="mt-6 flex items-center justify-between border-t border-border pt-4"> <div className="mt-6 flex items-center justify-between border-t border-border pt-4">

View File

@@ -1,6 +1,6 @@
"use client" "use client"
import { useRef } from "react" import { useRef, useState, useEffect } from "react"
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card" import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
import { Badge } from "@/components/ui/badge" import { Badge } from "@/components/ui/badge"
import { Button } from "@/components/ui/button" import { Button } from "@/components/ui/button"
@@ -13,6 +13,24 @@ import { useActiveUser } from "@/hooks/useActiveUser"
export default function DashboardPage() { export default function DashboardPage() {
const { user, hasAccessTo, loading } = useActiveUser() const { user, hasAccessTo, loading } = useActiveUser()
const progressRef = useRef<HTMLDivElement>(null) 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 () => { const handleExport = async () => {
if (progressRef.current) { if (progressRef.current) {
@@ -37,6 +55,8 @@ export default function DashboardPage() {
) )
} }
const isNew = userStats ? userStats.is_new_user : true
return ( return (
<div className="p-8 max-w-7xl mx-auto space-y-10 animate-in fade-in duration-500 slide-in-from-bottom-4"> <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> </Button>
</div> </div>
<div ref={progressRef} className="grid grid-cols-2 md:grid-cols-4 gap-4 p-4 -mx-4 bg-background rounded-2xl"> <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> <Card>
<CardContent className="p-5 flex flex-col items-center text-center"> <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> <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> <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> <h3 className="text-2xl font-bold">
<p className="text-xs text-green-500 font-bold mt-2 flex items-center gap-1"><TrendingUp size={12}/> +5 from yesterday</p> {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> </CardContent>
</Card> </Card>
{/* Overall Accuracy */}
<Card> <Card>
<CardContent className="p-5 flex flex-col items-center text-center"> <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> <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> <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> <h3 className="text-2xl font-bold">
<p className="text-xs text-amber-500 font-bold mt-2">Needs Improvement</p> {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> </CardContent>
</Card> </Card>
{/* Qs Attempted */}
<Card> <Card>
<CardContent className="p-5 flex flex-col items-center text-center"> <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> <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> <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> <h3 className="text-2xl font-bold">
<p className="text-xs text-muted-foreground font-medium mt-2">Lifetime total</p> {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> </CardContent>
</Card> </Card>
{/* City Rank */}
<Card className="relative overflow-hidden group"> <Card className="relative overflow-hidden group">
<div className="absolute inset-0 bg-gradient-to-br from-background to-secondary/50 z-0"></div> <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"> <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"> <div className="flex items-center gap-2 mb-1">
<p className="text-sm font-bold text-muted-foreground uppercase tracking-wider">City Rank</p> <p className="text-sm font-bold text-muted-foreground uppercase tracking-wider">City Rank</p>
</div> </div>
<h3 className="text-2xl font-bold">#42 <span className="text-base text-muted-foreground font-medium">/ 1,200</span></h3> <h3 className="text-2xl font-bold">
<p className="text-xs text-muted-foreground font-medium mt-2 text-[#00F0FF]">Top 5% in Jaipur</p> {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> </CardContent>
</Card> </Card>
</div> </div>

View File

@@ -2,17 +2,19 @@
import { Card, CardContent } from "@/components/ui/card" import { Card, CardContent } from "@/components/ui/card"
import { Flame, Gift, Star, Trophy, Target, ArrowRight, CheckCircle2 } from "lucide-react" import { Flame, Gift, Star, Trophy, Target, ArrowRight, CheckCircle2 } from "lucide-react"
import { useActiveUser } from "@/hooks/useActiveUser"
export default function StreaksPage() { export default function StreaksPage() {
const currentStreak = 12; const { user } = useActiveUser()
const bestStreak = 45; const currentStreak = user?.streak_count ?? 1
const bestStreak = Math.max(currentStreak, user?.longest_streak ?? 1)
const rewards = [ const rewards = [
{ streak: 7, title: "Consistent Learner", reward: "1 Free Premium DPP", unlocked: true }, { streak: 7, title: "Consistent Learner", reward: "1 Free Premium DPP", unlocked: currentStreak >= 7 },
{ streak: 14, title: "Dedicated Scholar", reward: "AI Performance Analysis Report", unlocked: false }, { streak: 14, title: "Dedicated Scholar", reward: "AI Performance Analysis Report", unlocked: currentStreak >= 14 },
{ streak: 30, title: "Exam Warrior", reward: "1 Full Length Mock Test", unlocked: false }, { streak: 30, title: "Exam Warrior", reward: "1 Full Length Mock Test", unlocked: currentStreak >= 30 },
{ streak: 50, title: "PragatiVeer Champion", reward: "1-on-1 AI Tutor Session", unlocked: false }, { streak: 50, title: "PragatiVeer Champion", reward: "1-on-1 AI Tutor Session", unlocked: currentStreak >= 50 },
{ streak: 100, title: "Unstoppable", reward: "Merch + 50% Off Next Renewal", unlocked: false }, { streak: 100, title: "Unstoppable", reward: "Merch + 50% Off Next Renewal", unlocked: currentStreak >= 100 },
]; ];
return ( return (
@@ -55,7 +57,7 @@ export default function StreaksPage() {
</div> </div>
<div> <div>
<p className="text-sm text-muted-foreground mb-1">Total Streak Points Earned</p> <p className="text-sm text-muted-foreground mb-1">Total Streak Points Earned</p>
<p className="text-3xl font-bold text-[#00F0FF]">1,450</p> <p className="text-3xl font-bold text-[#00F0FF]">{(currentStreak * 50).toLocaleString()}</p>
</div> </div>
</div> </div>
</div> </div>

View File

@@ -3,7 +3,7 @@
import { Button } from "@/components/ui/button" import { Button } from "@/components/ui/button"
import { Badge } from "@/components/ui/badge" import { Badge } from "@/components/ui/badge"
import { Card, CardContent } from "@/components/ui/card" import { Card, CardContent } from "@/components/ui/card"
import { BrainCircuit, Activity, Zap, CheckCircle2, Shield, ArrowRight, Play, BookOpen, LineChart, Trophy, Sparkles, MessageSquare, X, Minus } from "lucide-react" import { BrainCircuit, Activity, Zap, CheckCircle2, Shield, ArrowRight, Play, BookOpen, LineChart, Trophy, Sparkles, MessageSquare, X, Minus, Target, Calendar } from "lucide-react"
import Image from "next/image" import Image from "next/image"
import Link from "next/link" import Link from "next/link"
import { motion, useScroll, useTransform, Variants } from "framer-motion" import { motion, useScroll, useTransform, Variants } from "framer-motion"
@@ -185,13 +185,25 @@ export default function MarketingPage() {
</div> </div>
<div className="pt-6 border-t border-border/50"> <div className="pt-6 border-t border-border/50">
<p className="px-3 text-[10px] font-bold uppercase tracking-widest text-muted-foreground mb-3">AI Intelligence</p> <p className="px-3 text-[10px] font-bold uppercase tracking-widest text-muted-foreground mb-3">AI Intelligence</p>
<div className="space-y-2"> <div className="space-y-1.5">
<div className="flex items-center gap-3 px-3 py-2.5 rounded-xl text-muted-foreground hover:bg-secondary/30 hover:text-foreground text-sm font-semibold transition-all"> <div className="flex items-center gap-3 px-3 py-2 rounded-xl text-muted-foreground hover:bg-secondary/30 hover:text-foreground text-sm font-semibold transition-all">
<LineChart size={18} /> <Zap size={17} className="text-yellow-500 shrink-0" />
Prep Pilot AI
</div>
<div className="flex items-center gap-3 px-3 py-2 rounded-xl text-muted-foreground hover:bg-secondary/30 hover:text-foreground text-sm font-semibold transition-all">
<Target size={17} className="text-[#00F0FF] shrink-0" />
Exam Strategy
</div>
<div className="flex items-center gap-3 px-3 py-2 rounded-xl text-muted-foreground hover:bg-secondary/30 hover:text-foreground text-sm font-semibold transition-all">
<Calendar size={17} className="text-primary shrink-0" />
AI Study Plan
</div>
<div className="flex items-center gap-3 px-3 py-2 rounded-xl text-muted-foreground hover:bg-secondary/30 hover:text-foreground text-sm font-semibold transition-all">
<LineChart size={17} className="shrink-0" />
Analytics Analytics
</div> </div>
<div className="flex items-center gap-3 px-3 py-2.5 rounded-xl text-muted-foreground hover:bg-secondary/30 hover:text-foreground text-sm font-semibold transition-all"> <div className="flex items-center gap-3 px-3 py-2 rounded-xl text-muted-foreground hover:bg-secondary/30 hover:text-foreground text-sm font-semibold transition-all">
<Trophy size={18} /> <Trophy size={17} className="shrink-0" />
Rank Predictor Rank Predictor
</div> </div>
</div> </div>

View File

@@ -5,7 +5,7 @@ import { Card, CardContent } from "@/components/ui/card"
import { Check, RefreshCw } from "lucide-react" import { Check, RefreshCw } from "lucide-react"
import Link from "next/link" import Link from "next/link"
import { useActiveUser } from "@/hooks/useActiveUser" import { useActiveUser, isDemoUser } from "@/hooks/useActiveUser"
export function StudyPlanCard() { export function StudyPlanCard() {
const [tasks, setTasks] = useState<any[]>([]) const [tasks, setTasks] = useState<any[]>([])
@@ -19,7 +19,7 @@ export function StudyPlanCard() {
try { try {
const res = await fetch(`/api/ai-coach/study-plan/today?user_id=${userId}`) const res = await fetch(`/api/ai-coach/study-plan/today?user_id=${userId}`)
if (!res.ok) { if (!res.ok) {
// If no plan is generated yet, fallback to mock silently // If no plan is generated yet, fallback silently
throw new Error("silent_fallback") throw new Error("silent_fallback")
} }
const data = await res.json() const data = await res.json()
@@ -28,7 +28,8 @@ export function StudyPlanCard() {
if (err.message !== "silent_fallback") { if (err.message !== "silent_fallback") {
console.error("Study plan fetch error:", err) console.error("Study plan fetch error:", err)
} }
// Mock fallback if celery hasn't seeded yet if (isDemoUser(userId)) {
// Demo user gets showcase active tasks
setTasks([ setTasks([
{ type: "DPS", description: "Electrostatics + Kinematics", duration_minutes: 20, status: "Done" }, { type: "DPS", description: "Electrostatics + Kinematics", duration_minutes: 20, status: "Done" },
{ type: "Mistake_Review", description: "Thermodynamics error queue", duration_minutes: 10, status: "Done" }, { type: "Mistake_Review", description: "Thermodynamics error queue", duration_minutes: 10, status: "Done" },
@@ -36,6 +37,15 @@ export function StudyPlanCard() {
{ type: "Test", description: "Weekly Full-Syllabus Mock Test", duration_minutes: 180, status: "Pending" }, { type: "Test", description: "Weekly Full-Syllabus Mock Test", duration_minutes: 180, status: "Pending" },
{ type: "Revision", description: "Circles + Straight Lines (Maths)", duration_minutes: 15, status: "Pending" } { type: "Revision", description: "Circles + Straight Lines (Maths)", duration_minutes: 15, status: "Pending" }
]) ])
} else {
// Real user gets a fresh Day 1 plan with all tasks pending
setTasks([
{ type: "DPS", description: "Day 1 Diagnostic Practice Sheet", duration_minutes: 20, status: "Pending" },
{ type: "Micro_test", description: "Foundation Concept Check", duration_minutes: 15, status: "Pending" },
{ type: "Revision", description: "Target Exam Core Topics Orientation", duration_minutes: 30, status: "Pending" },
{ type: "Mistake_Review", description: "Initialize AI Mistake Engine", duration_minutes: 10, status: "Pending" }
])
}
} finally { } finally {
setLoading(false) setLoading(false)
} }