diff --git a/backend/main.py b/backend/main.py index 84e5fdd..8de6646 100644 --- a/backend/main.py +++ b/backend/main.py @@ -73,12 +73,25 @@ def health_check(): # --- ACCESS CONTROL DEPENDENCY --- +def get_effective_tier(user: models.User, session: Session) -> models.TierEnum: + if user.tier in (models.TierEnum.AI_COACH, models.TierEnum.PRO): + return user.tier + # Check if trial is active + now = datetime.utcnow() + if user.trial_end and user.trial_end > now: + settings = session.get(models.SystemSettings, "global") + if settings and settings.enable_free_trial: + return settings.trial_tier + return models.TierEnum.AI_COACH + return user.tier + def require_tier(allowed_tiers: List[models.TierEnum]): def tier_checker(user_id: UUID, session: Session = Depends(get_session)): user = session.get(models.User, user_id) if not user: raise HTTPException(status_code=404, detail="User not found.") - if user.tier not in allowed_tiers: + effective_tier = get_effective_tier(user, session) + if effective_tier not in allowed_tiers: raise HTTPException( status_code=403, detail=f"Access denied. Requires one of: {[t.value for t in allowed_tiers]}" @@ -597,7 +610,8 @@ async def ai_tutor_chat_stream(request: ChatRequest, session: Session = Depends( user = session.get(models.User, request.user_id) if not user: raise HTTPException(status_code=404, detail="Student profile not found.") - if user.tier != models.TierEnum.AI_COACH: + effective_tier = get_effective_tier(user, session) + if effective_tier != models.TierEnum.AI_COACH: raise HTTPException(status_code=403, detail="Access denied. Gated to AI_COACH tier.") return StreamingResponse( @@ -826,17 +840,25 @@ def get_leaderboard( 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)}%" + if str(requesting_user.id) in DEMO_USER_UUIDS: + user_score = 9250 + user_accuracy = "88.5%" + user_streak = requesting_user.streak_count or 25 + else: + 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)}%" + else: + user_score = 0 + user_accuracy = "0%" # Build student entries entries = [] diff --git a/backend/predictor.py b/backend/predictor.py index eedce53..59ab82d 100644 --- a/backend/predictor.py +++ b/backend/predictor.py @@ -17,7 +17,19 @@ def calculate_readiness_score(user_id: UUID, session: Session) -> dict: ).all() if not attempts: - return {"score": 0, "status": "Not enough data"} + return { + "score": 0, + "status": "Not enough data", + "prediction": "Start practice to calculate", + "target_type": "Unranked", + "breakdown": { + "accuracy": 0, + "speed": 0, + "consistency": 0, + "recovery": 0, + "coverage": 0, + } + } # 1. Accuracy (40% Weight) avg_accuracy = sum(a.accuracy for a in attempts) / len(attempts) diff --git a/src/app/dashboard/ai-coach/college-predictor/page.tsx b/src/app/dashboard/ai-coach/college-predictor/page.tsx index 45980ea..5a6a578 100644 --- a/src/app/dashboard/ai-coach/college-predictor/page.tsx +++ b/src/app/dashboard/ai-coach/college-predictor/page.tsx @@ -5,6 +5,8 @@ import { Card, CardContent } from "@/components/ui/card" import { GraduationCap, Building2, Award, CheckCircle2, TrendingUp, MapPin, Users, BarChart2 } from "lucide-react" import Link from "next/link" +import { useActiveUser, isDemoUser } from "@/hooks/useActiveUser" + const CUTOFF_TRENDS = [ { year: "2021", rank: 3800 }, { year: "2022", rank: 3500 }, @@ -14,6 +16,8 @@ const CUTOFF_TRENDS = [ ] export default function CollegePredictorPage() { + const { userId } = useActiveUser() + const isDemo = isDemoUser(userId) const maxRank = Math.max(...CUTOFF_TRENDS.map(d => d.rank)) return ( @@ -41,10 +45,10 @@ export default function CollegePredictorPage() { {/* Summary KPI Strip */}
{[ - { label: "Predicted Rank", value: "~4,200", icon: , color: "border-[#00F0FF]/20 bg-[#00F0FF]/5" }, - { label: "Target Colleges", value: "12", icon: , color: "border-amber-400/20 bg-amber-400/5" }, - { label: "Reach Colleges", value: "5", icon: , color: "border-purple-400/20 bg-purple-400/5" }, - { label: "Safe Colleges", value: "18", icon: , color: "border-green-400/20 bg-green-400/5" }, + { label: "Predicted Rank", value: isDemo ? "~4,200" : "--", icon: , color: "border-[#00F0FF]/20 bg-[#00F0FF]/5" }, + { label: "Target Colleges", value: isDemo ? "12" : "0", icon: , color: "border-amber-400/20 bg-amber-400/5" }, + { label: "Reach Colleges", value: isDemo ? "5" : "0", icon: , color: "border-purple-400/20 bg-purple-400/5" }, + { label: "Safe Colleges", value: isDemo ? "18" : "0", icon: , color: "border-green-400/20 bg-green-400/5" }, ].map((kpi) => ( diff --git a/src/app/dashboard/ai-coach/heatmap/page.tsx b/src/app/dashboard/ai-coach/heatmap/page.tsx index 94b1bd1..20841e5 100644 --- a/src/app/dashboard/ai-coach/heatmap/page.tsx +++ b/src/app/dashboard/ai-coach/heatmap/page.tsx @@ -1,11 +1,12 @@ "use client" import { MistakeHeatmap } from "@/components/AI/MistakeHeatmap" -import { Flame, AlertTriangle, TrendingDown, BookOpen, ArrowRight, BarChart2, Target } from "lucide-react" +import { Flame, AlertTriangle, TrendingDown, BookOpen, ArrowRight, BarChart2, Target, CheckCircle2 } from "lucide-react" import { Card, CardContent } from "@/components/ui/card" import Link from "next/link" +import { useActiveUser, isDemoUser } from "@/hooks/useActiveUser" -const CRITICAL_TOPICS = [ +const DEMO_CRITICAL_TOPICS = [ { name: "Rotational Dynamics", rate: "80%", rateNum: 80, subject: "Physics" }, { name: "Optics", rate: "60%", rateNum: 60, subject: "Physics" }, { name: "Electromagnetism", rate: "30%", rateNum: 30, subject: "Physics" }, @@ -13,14 +14,26 @@ const CRITICAL_TOPICS = [ { name: "Thermodynamics", rate: "25%", rateNum: 25, subject: "Physics" }, ] -const SUMMARY_STATS = [ +const DEMO_SUMMARY_STATS = [ { label: "Total Errors (Last 5 Tests)", value: "87", icon: , color: "border-red-400/20 bg-red-400/5" }, { label: "Critical Topics", value: "4", icon: , color: "border-orange-400/20 bg-orange-400/5" }, { label: "Improving Topics", value: "3", icon: , color: "border-green-400/20 bg-green-400/5" }, { label: "Avg Error Rate", value: "38%", icon: , color: "border-amber-400/20 bg-amber-400/5" }, ] +const ZERO_SUMMARY_STATS = [ + { label: "Total Errors (Last 5 Tests)", value: "0", icon: , color: "border-white/10 bg-white/5" }, + { label: "Critical Topics", value: "0", icon: , color: "border-white/10 bg-white/5" }, + { label: "Improving Topics", value: "0", icon: , color: "border-white/10 bg-white/5" }, + { label: "Avg Error Rate", value: "0%", icon: , color: "border-green-400/20 bg-green-400/5" }, +] + export default function HeatmapPage() { + const { userId } = useActiveUser() + const isDemo = isDemoUser(userId) + const summaryStats = isDemo ? DEMO_SUMMARY_STATS : ZERO_SUMMARY_STATS + const criticalTopics = isDemo ? DEMO_CRITICAL_TOPICS : [] + return (
{/* Page Header */} @@ -45,7 +58,7 @@ export default function HeatmapPage() { {/* KPI Strip */}
- {SUMMARY_STATS.map((kpi) => ( + {summaryStats.map((kpi) => ( {kpi.icon} @@ -96,25 +109,35 @@ export default function HeatmapPage() { Priority Topics by Error Rate
- {CRITICAL_TOPICS.map((t) => ( -
-
-
-

{t.name}

-

{t.subject}

-
- = 50 ? "text-red-400 bg-red-400/10" : t.rateNum >= 30 ? "text-amber-400 bg-amber-400/10" : "text-yellow-400 bg-yellow-400/10"}`}> - {t.rate} - -
-
-
= 50 ? "bg-red-500" : t.rateNum >= 30 ? "bg-amber-500" : "bg-yellow-500"}`} - style={{ width: `${t.rateNum}%` }} - /> -
+ {criticalTopics.length === 0 ? ( +
+ +

Clean Error Log

+

+ No mistake hotspots detected yet. Practice questions to see priority areas here. +

- ))} + ) : ( + criticalTopics.map((t) => ( +
+
+
+

{t.name}

+

{t.subject}

+
+ = 50 ? "text-red-400 bg-red-400/10" : t.rateNum >= 30 ? "text-amber-400 bg-amber-400/10" : "text-yellow-400 bg-yellow-400/10"}`}> + {t.rate} + +
+
+
= 50 ? "bg-red-500" : t.rateNum >= 30 ? "bg-amber-500" : "bg-yellow-500"}`} + style={{ width: `${t.rateNum}%` }} + /> +
+
+ )) + )}
diff --git a/src/app/dashboard/ai-coach/mistakes/page.tsx b/src/app/dashboard/ai-coach/mistakes/page.tsx index 92d4f21..5db4ed1 100644 --- a/src/app/dashboard/ai-coach/mistakes/page.tsx +++ b/src/app/dashboard/ai-coach/mistakes/page.tsx @@ -54,6 +54,7 @@ export default function MistakeTrackingPage() { const completedCount = Math.min(currentIndex, mistakes.length) const progressPct = mistakes.length > 0 ? Math.round((completedCount / mistakes.length) * 100) : 0 + const avgErrorRate = mistakes.length > 0 ? "38%" : "0%" return (
@@ -84,7 +85,7 @@ export default function MistakeTrackingPage() { { label: "In Today's Queue", value: mistakes.length.toString(), icon: , color: "border-amber-400/20 bg-amber-400/5" }, { label: "Reviewed This Session", value: completedCount.toString(), icon: , color: "border-green-400/20 bg-green-400/5" }, { label: "Session Progress", value: `${progressPct}%`, icon: , color: "border-[#00F0FF]/20 bg-[#00F0FF]/5" }, - { label: "Avg Error Rate", value: "38%", icon: , color: "border-red-400/20 bg-red-400/5" }, + { label: "Avg Error Rate", value: avgErrorRate, icon: , color: "border-red-400/20 bg-red-400/5" }, ].map(kpi => ( diff --git a/src/app/dashboard/ai-coach/page.tsx b/src/app/dashboard/ai-coach/page.tsx index 58a1be5..105abe6 100644 --- a/src/app/dashboard/ai-coach/page.tsx +++ b/src/app/dashboard/ai-coach/page.tsx @@ -16,7 +16,7 @@ import { WeeklyProgressReport } from "@/components/AI/WeeklyProgressReport" import { Card, CardContent } from "@/components/ui/card" import { Button } from "@/components/ui/button" import { BrainCircuit, RefreshCw, CheckCircle2, Flame, Trophy, Target, Zap, BookOpen, Bot, FileText } from "lucide-react" -import { useActiveUser } from "@/hooks/useActiveUser" +import { useActiveUser, isDemoUser } from "@/hooks/useActiveUser" import Link from "next/link" @@ -36,8 +36,10 @@ export default function AICoachDashboard() { const [mistakes, setMistakes] = useState([]) const [currentIndex, setCurrentIndex] = useState(0) const [loading, setLoading] = useState(true) + const [stats, setStats] = useState(null) const { userId, user, hasAccessTo, loading: userLoading } = useActiveUser(); + const isDemo = isDemoUser(userId); const fetchMistakes = async () => { if (!userId) return @@ -58,6 +60,10 @@ export default function AICoachDashboard() { useEffect(() => { if (!userLoading && userId) { fetchMistakes() + fetch(`/api/users/${userId}/stats`) + .then(res => res.ok ? res.json() : null) + .then(data => setStats(data)) + .catch(() => setStats(null)) } }, [userId, userLoading]) @@ -114,9 +120,11 @@ export default function AICoachDashboard() { } const daysLeft = getDaysToExam(user?.target_exam || "NEET") - const physicsErrors = mistakes.filter(m => m.question.subject === "Physics").length - const chemErrors = mistakes.filter(m => m.question.subject === "Chemistry").length - const mathErrors = mistakes.filter(m => m.question.subject === "Maths" || m.question.subject === "Mathematics").length + const questionsSolved = isDemo ? "1,247" : (stats?.total_questions_attempted ?? 0).toLocaleString() + const overallAccuracy = isDemo ? "74%" : `${stats?.overall_accuracy ?? 0}%` + const mistakesMastered = isDemo ? 38 : 0 + const aiSessions = isDemo ? 124 : 0 + const currentStreakDays = `${user?.streak_count || (isDemo ? 12 : 1)} d` return (
@@ -125,7 +133,7 @@ export default function AICoachDashboard() {
- {user?.streak_count || 0}-Day Streak + {user?.streak_count || 1}-Day Streak
@@ -143,11 +151,11 @@ export default function AICoachDashboard() { {/* ── QUICK STATS ── */}
{[ - { label: "Questions Solved", value: "1,247", color: "text-[#00F0FF]", bg: "bg-[#00F0FF]/5", border: "border-[#00F0FF]/20", icon: }, - { label: "Overall Accuracy", value: "74%", color: "text-amber-500", bg: "bg-amber-500/5", border: "border-amber-500/20", icon: }, - { label: "Current Streak", value: `${user?.streak_count || 0} d`, color: "text-yellow-400", bg: "bg-yellow-400/5", border: "border-yellow-400/20", icon: }, - { label: "Mistakes Mastered", value: 38, color: "text-green-400", bg: "bg-green-400/5", border: "border-green-400/20", icon: }, - { label: "AI Sessions", value: 124, color: "text-purple-400", bg: "bg-purple-400/5", border: "border-purple-400/20", icon: }, + { label: "Questions Solved", value: questionsSolved, color: "text-[#00F0FF]", bg: "bg-[#00F0FF]/5", border: "border-[#00F0FF]/20", icon: }, + { label: "Overall Accuracy", value: overallAccuracy, color: "text-amber-500", bg: "bg-amber-500/5", border: "border-amber-500/20", icon: }, + { label: "Current Streak", value: currentStreakDays, color: "text-yellow-400", bg: "bg-yellow-400/5", border: "border-yellow-400/20", icon: }, + { label: "Mistakes Mastered", value: mistakesMastered, color: "text-green-400", bg: "bg-green-400/5", border: "border-green-400/20", icon: }, + { label: "AI Sessions", value: aiSessions, color: "text-purple-400", bg: "bg-purple-400/5", border: "border-purple-400/20", icon: }, ].map((stat) => ( diff --git a/src/app/dashboard/ai-coach/readiness/page.tsx b/src/app/dashboard/ai-coach/readiness/page.tsx index 6a4efaf..9b85b87 100644 --- a/src/app/dashboard/ai-coach/readiness/page.tsx +++ b/src/app/dashboard/ai-coach/readiness/page.tsx @@ -5,8 +5,9 @@ import { Card, CardContent } from "@/components/ui/card" import { Button } from "@/components/ui/button" import { Gauge, TrendingUp, Target, Award, ShieldAlert, CheckCircle2, BookOpen, Zap, BarChart2, Clock } from "lucide-react" import Link from "next/link" +import { useActiveUser, isDemoUser } from "@/hooks/useActiveUser" -const SCORE_HISTORY = [ +const DEMO_SCORE_HISTORY = [ { week: "Wk 1", score: 54 }, { week: "Wk 2", score: 60 }, { week: "Wk 3", score: 63 }, @@ -15,8 +16,20 @@ const SCORE_HISTORY = [ { week: "Wk 6", score: 78 }, ] +const ZERO_SCORE_HISTORY = [ + { week: "Wk 1", score: 0 }, + { week: "Wk 2", score: 0 }, + { week: "Wk 3", score: 0 }, + { week: "Wk 4", score: 0 }, + { week: "Wk 5", score: 0 }, + { week: "Wk 6", score: 0 }, +] + export default function ReadinessPage() { - const maxScore = Math.max(...SCORE_HISTORY.map(d => d.score)) + const { userId } = useActiveUser() + const isDemo = isDemoUser(userId) + const scoreHistory = isDemo ? DEMO_SCORE_HISTORY : ZERO_SCORE_HISTORY + const maxScore = Math.max(1, ...scoreHistory.map(d => d.score)) return (
@@ -43,16 +56,16 @@ export default function ReadinessPage() { {/* KPI Strip */}
{[ - { label: "Overall Score", value: "72 / 100", trend: "+5 this week", trendUp: true, icon: }, - { label: "Accuracy Rate", value: "88%", trend: "+3% vs last week", trendUp: true, icon: }, - { label: "Avg Speed / Q", value: "45 sec", trend: "↓ Needs work", trendUp: false, icon: }, - { label: "Syllabus Covered", value: "64%", trend: "+8% this month", trendUp: true, icon: }, + { label: "Overall Score", value: isDemo ? "72 / 100" : "0 / 100", trend: isDemo ? "+5 this week" : "Baseline", trendUp: true, icon: }, + { label: "Accuracy Rate", value: isDemo ? "88%" : "0%", trend: isDemo ? "+3% vs last week" : "0 attempts", trendUp: isDemo, icon: }, + { label: "Avg Speed / Q", value: isDemo ? "45 sec" : "-- sec", trend: isDemo ? "↓ Needs work" : "Not tested", trendUp: false, icon: }, + { label: "Syllabus Covered", value: isDemo ? "64%" : "0%", trend: isDemo ? "+8% this month" : "Day 1", trendUp: true, icon: }, ].map((kpi) => (
{kpi.icon} - + {kpi.trend}
@@ -70,7 +83,9 @@ export default function ReadinessPage() {

Composite Readiness Index

- Updated Daily + + {isDemo ? "Updated Daily" : "Day 1 Initializing"} +
@@ -83,9 +98,9 @@ export default function ReadinessPage() {
{[ - { subject: "Biology / Maths", score: 82, color: "bg-[#00F0FF]", status: "High Readiness", statusColor: "text-[#00F0FF]" }, - { subject: "Chemistry", score: 74, color: "bg-amber-400", status: "Moderate Readiness", statusColor: "text-amber-400" }, - { subject: "Physics", score: 61, color: "bg-red-400", status: "Needs Intensive Drill", statusColor: "text-red-400" }, + { subject: "Biology / Maths", score: isDemo ? 82 : 0, color: "bg-[#00F0FF]", status: isDemo ? "High Readiness" : "Not Started", statusColor: isDemo ? "text-[#00F0FF]" : "text-muted-foreground" }, + { subject: "Chemistry", score: isDemo ? 74 : 0, color: "bg-amber-400", status: isDemo ? "Moderate Readiness" : "Not Started", statusColor: isDemo ? "text-amber-400" : "text-muted-foreground" }, + { subject: "Physics", score: isDemo ? 61 : 0, color: "bg-red-400", status: isDemo ? "Needs Intensive Drill" : "Not Started", statusColor: isDemo ? "text-red-400" : "text-muted-foreground" }, ].map((sub) => (
@@ -107,12 +122,18 @@ export default function ReadinessPage() {

6-Week Readiness Trend

- {SCORE_HISTORY.map((d, i) => ( + {scoreHistory.map((d, i) => (
{d.score}
{d.week}
@@ -130,19 +151,19 @@ export default function ReadinessPage() {
  • - Complete 2 full-length mock tests this week to boost consistency score. + {isDemo ? "Complete 2 full-length mock tests this week to boost consistency score." : "Complete your first diagnostic test to calculate your initial readiness baseline."}
  • - Clear 15 pending errors in your Physics queue to gain ~4 readiness points. + {isDemo ? "Clear 15 pending errors in your Physics queue to gain ~4 readiness points." : "Attempt daily DPPs to populate your question error log."}
  • - Maintain a 7+ day streak — consistency adds 6% to your readiness index. + Maintain daily streak — consistency adds directly to your readiness algorithm.
  • - Reduce avg time-per-question below 40s with daily timed DPP sessions. + Target under 45s per question in timed test series practice.
diff --git a/src/app/dashboard/ai-coach/strategy/page.tsx b/src/app/dashboard/ai-coach/strategy/page.tsx index 4a6d9c9..cad5562 100644 --- a/src/app/dashboard/ai-coach/strategy/page.tsx +++ b/src/app/dashboard/ai-coach/strategy/page.tsx @@ -4,14 +4,25 @@ import { ExamStrategyCard } from "@/components/AI/ExamStrategyCard" import { StudyTimeOptimizationCard } from "@/components/AI/StudyTimeOptimizationCard" import { Card, CardContent } from "@/components/ui/card" import { Zap, Clock, Target, Lightbulb, CheckCircle2, AlertTriangle, TrendingUp } from "lucide-react" +import { useActiveUser, isDemoUser } from "@/hooks/useActiveUser" -const NEG_MARKING_DATA = [ +const DEMO_NEG_MARKING_DATA = [ { subject: "Physics", attempts: 30, negMarks: 12, accuracy: 60 }, { subject: "Chemistry", attempts: 35, negMarks: 7, accuracy: 80 }, - { subject: "Maths", attempts: 28, negMarks: 10, accuracy: 64 }, + { subject: "Maths / Biology", attempts: 28, negMarks: 10, accuracy: 64 }, +] + +const ZERO_NEG_MARKING_DATA = [ + { subject: "Physics", attempts: 0, negMarks: 0, accuracy: 0 }, + { subject: "Chemistry", attempts: 0, negMarks: 0, accuracy: 0 }, + { subject: "Maths / Biology", attempts: 0, negMarks: 0, accuracy: 0 }, ] export default function StrategyPage() { + const { userId } = useActiveUser() + const isDemo = isDemoUser(userId) + const negMarkingData = isDemo ? DEMO_NEG_MARKING_DATA : ZERO_NEG_MARKING_DATA + return (
{/* Page Header */} @@ -37,10 +48,10 @@ export default function StrategyPage() { {/* KPI Strip */}
{[ - { label: "Time Per Question", value: "45 sec", note: "Target: 38 sec", icon: , color: "border-red-400/20 bg-red-400/5" }, - { label: "Negative Marks Lost", value: "−29", note: "Last mock test", icon: , color: "border-amber-400/20 bg-amber-400/5" }, - { label: "Attempted Questions", value: "93 / 120", note: "+7 vs last test", icon: , color: "border-[#00F0FF]/20 bg-[#00F0FF]/5" }, - { label: "Score Yield", value: "214", note: "Predicted score", icon: , color: "border-green-400/20 bg-green-400/5" }, + { label: "Time Per Question", value: isDemo ? "45 sec" : "-- sec", note: isDemo ? "Target: 38 sec" : "Not calibrated", icon: , color: "border-red-400/20 bg-red-400/5" }, + { label: "Negative Marks Lost", value: isDemo ? "−29" : "0", note: isDemo ? "Last mock test" : "0 negative marks", icon: , color: "border-amber-400/20 bg-amber-400/5" }, + { label: "Attempted Questions", value: isDemo ? "93 / 120" : "0 / 120", note: isDemo ? "+7 vs last test" : "0 attempted", icon: , color: "border-[#00F0FF]/20 bg-[#00F0FF]/5" }, + { label: "Score Yield", value: isDemo ? "214" : "0", note: isDemo ? "Predicted score" : "Baseline", icon: , color: "border-green-400/20 bg-green-400/5" }, ].map((kpi) => ( @@ -77,10 +88,12 @@ export default function StrategyPage() {

Negative Marking Analysis

- Last Mock Test · Aug 1 + + {isDemo ? "Last Mock Test · Aug 1" : "Day 1 Initializing"} +
- {NEG_MARKING_DATA.map((row) => ( + {negMarkingData.map((row) => (
{row.subject}
@@ -88,17 +101,19 @@ export default function StrategyPage() {
Attempted
-
−{row.negMarks}
+
{row.negMarks === 0 ? "0" : `−${row.negMarks}`}
Neg. Marks
Accuracy - = 75 ? "text-green-400" : row.accuracy >= 60 ? "text-amber-400" : "text-red-400"}>{row.accuracy}% + = 75 ? "text-green-400" : row.accuracy >= 60 ? "text-amber-400" : row.accuracy === 0 ? "text-muted-foreground" : "text-red-400"}> + {row.accuracy}% +
= 75 ? "bg-green-400" : row.accuracy >= 60 ? "bg-amber-400" : "bg-red-400"}`} + className={`h-full rounded-full ${row.accuracy >= 75 ? "bg-green-400" : row.accuracy >= 60 ? "bg-amber-400" : isDemo ? "bg-red-400" : "bg-white/10"}`} style={{ width: `${row.accuracy}%` }} />
@@ -107,7 +122,11 @@ export default function StrategyPage() { ))}

- Insight: You're losing ~29 marks to wrong answers. Switching to "skip and revisit" for uncertain Physics questions could recover 15–20 marks. + Insight: {isDemo ? ( + <>You're losing ~29 marks to wrong answers. Switching to "skip and revisit" for uncertain Physics questions could recover 15–20 marks. + ) : ( + <>Complete your first mock test or timed DPP to calculate your negative marking profile and fine-tune your subject pacing. + )}

diff --git a/src/app/dashboard/ai-coach/study-plan/page.tsx b/src/app/dashboard/ai-coach/study-plan/page.tsx index d6bec6d..1ab18cf 100644 --- a/src/app/dashboard/ai-coach/study-plan/page.tsx +++ b/src/app/dashboard/ai-coach/study-plan/page.tsx @@ -4,8 +4,13 @@ import { StudyPlanCard } from "@/components/AI/StudyPlanCard" import { WeeklyProgressReport } from "@/components/AI/WeeklyProgressReport" import { Card, CardContent } from "@/components/ui/card" import { Calendar, BookOpen, Clock, Target, CheckCircle2, TrendingUp, Flame } from "lucide-react" +import { useActiveUser, isDemoUser } from "@/hooks/useActiveUser" export default function StudyPlanPage() { + const { user, userId } = useActiveUser() + const isDemo = isDemoUser(userId) + const streakCount = user?.streak_count || (isDemo ? 12 : 1) + return (
{/* Page Header */} @@ -31,10 +36,10 @@ export default function StudyPlanPage() { {/* KPI Strip */}
{[ - { label: "Tasks Today", value: "5", sub: "2 completed", icon: , color: "border-green-400/20 bg-green-400/5" }, - { label: "Study Hours Today", value: "3.8 hrs", sub: "Target: 5 hrs", icon: , color: "border-[#00F0FF]/20 bg-[#00F0FF]/5" }, - { label: "Syllabus Coverage", value: "64%", sub: "+8% this week", icon: , color: "border-amber-400/20 bg-amber-400/5" }, - { label: "Current Streak", value: "12 Days", sub: "Keep it up!", icon: , color: "border-orange-400/20 bg-orange-400/5" }, + { label: "Tasks Today", value: isDemo ? "5" : "3", sub: isDemo ? "2 completed" : "0 completed", icon: , color: "border-green-400/20 bg-green-400/5" }, + { label: "Study Hours Today", value: isDemo ? "3.8 hrs" : "0.0 hrs", sub: "Target: 5 hrs", icon: , color: "border-[#00F0FF]/20 bg-[#00F0FF]/5" }, + { label: "Syllabus Coverage", value: isDemo ? "64%" : "0%", sub: isDemo ? "+8% this week" : "Day 1 Baseline", icon: , color: "border-amber-400/20 bg-amber-400/5" }, + { label: "Current Streak", value: `${streakCount} Day${streakCount === 1 ? '' : 's'}`, sub: isDemo ? "Keep it up!" : "Day 1 active", icon: , color: "border-orange-400/20 bg-orange-400/5" }, ].map((kpi) => ( diff --git a/src/app/dashboard/ai-coach/time-optimization/page.tsx b/src/app/dashboard/ai-coach/time-optimization/page.tsx index 8948871..4236a39 100644 --- a/src/app/dashboard/ai-coach/time-optimization/page.tsx +++ b/src/app/dashboard/ai-coach/time-optimization/page.tsx @@ -4,8 +4,9 @@ import { StudyTimeOptimizationCard } from "@/components/AI/StudyTimeOptimization import { Clock, Zap, Moon, Sun, AlertCircle, ArrowRight, BarChart2 } from "lucide-react" import { Card, CardContent } from "@/components/ui/card" import Link from "next/link" +import { useActiveUser, isDemoUser } from "@/hooks/useActiveUser" -const INSIGHTS = [ +const DEMO_INSIGHTS = [ { icon: , title: "Peak Cognitive Window", @@ -26,7 +27,7 @@ const INSIGHTS = [ }, ] -const HOURLY_EFFICIENCY = [ +const DEMO_HOURLY_EFFICIENCY = [ { slot: "6–9 AM", pct: 60, label: "Morning" }, { slot: "9–12 PM", pct: 72, label: "Mid-Morning" }, { slot: "12–2 PM", pct: 55, label: "Post-Lunch dip" }, @@ -37,7 +38,36 @@ const HOURLY_EFFICIENCY = [ { slot: "11 PM+", pct: 32, label: "Avoid" }, ] +const ZERO_HOURLY_EFFICIENCY = [ + { slot: "6–9 AM", pct: 0, label: "Pending" }, + { slot: "9–12 PM", pct: 0, label: "Pending" }, + { slot: "12–2 PM", pct: 0, label: "Pending" }, + { slot: "2–5 PM", pct: 0, label: "Calibrating", highlight: false }, + { slot: "5–7 PM", pct: 0, label: "Pending" }, + { slot: "7–9 PM", pct: 0, label: "Pending" }, + { slot: "9–11 PM", pct: 0, label: "Pending" }, + { slot: "11 PM+", pct: 0, label: "Pending" }, +] + export default function TimeOptimizationPage() { + const { userId } = useActiveUser() + const isDemo = isDemoUser(userId) + const hourlySlots = isDemo ? DEMO_HOURLY_EFFICIENCY : ZERO_HOURLY_EFFICIENCY + const insights = isDemo ? DEMO_INSIGHTS : [ + { + icon: , + title: "Peak Window Calibrating", + desc: "Complete 3 test sessions at different times of day to establish your circadian peak focus hours.", + color: "border-yellow-400/20 bg-yellow-400/5", + }, + { + icon: , + title: "Consistency Booster", + desc: "Start with a 30-minute daily review routine to jumpstart your spaced repetition learning schedule.", + color: "border-indigo-400/20 bg-indigo-400/5", + }, + ] + return (
{/* Page Header */} @@ -63,10 +93,10 @@ export default function TimeOptimizationPage() { {/* KPI Strip */}
{[ - { label: "Peak Efficiency", value: "95%", sub: "2–5 PM window", color: "border-yellow-400/20 bg-yellow-400/5", icon: }, - { label: "Study Hrs Today", value: "3.8 h", sub: "Target: 6 h", color: "border-[#00F0FF]/20 bg-[#00F0FF]/5", icon: }, - { label: "Morning Block", value: "60%", sub: "6–9 AM", color: "border-indigo-400/20 bg-indigo-400/5", icon: }, - { label: "Evening Block", value: "74%", sub: "7–9 PM", color: "border-green-400/20 bg-green-400/5", icon: }, + { label: "Peak Efficiency", value: isDemo ? "95%" : "--", sub: isDemo ? "2–5 PM window" : "Calibrating", color: "border-yellow-400/20 bg-yellow-400/5", icon: }, + { label: "Study Hrs Today", value: isDemo ? "3.8 h" : "0.0 h", sub: "Target: 6 h", color: "border-[#00F0FF]/20 bg-[#00F0FF]/5", icon: }, + { label: "Morning Block", value: isDemo ? "60%" : "0%", sub: "6–9 AM", color: "border-indigo-400/20 bg-indigo-400/5", icon: }, + { label: "Evening Block", value: isDemo ? "74%" : "0%", sub: "7–9 PM", color: "border-green-400/20 bg-green-400/5", icon: }, ].map(kpi => ( @@ -96,12 +126,12 @@ export default function TimeOptimizationPage() {

Time Slot Breakdown

- {HOURLY_EFFICIENCY.map(slot => ( + {hourlySlots.map(slot => (
{slot.slot}
= 80 ? (slot.highlight ? "bg-yellow-400" : "bg-green-400") : slot.pct >= 60 ? "bg-[#00F0FF]" : "bg-red-400"}`} + className={`h-full rounded-full ${slot.pct >= 80 ? (slot.highlight ? "bg-yellow-400" : "bg-green-400") : slot.pct >= 60 ? "bg-[#00F0FF]" : isDemo ? "bg-red-400" : "bg-white/10"}`} style={{ width: `${slot.pct}%` }} />
@@ -115,7 +145,7 @@ export default function TimeOptimizationPage() { {/* AI Insights */}
- {INSIGHTS.map(insight => ( + {insights.map(insight => (
{insight.icon}
diff --git a/src/app/dashboard/ai-coach/weekly-progress/page.tsx b/src/app/dashboard/ai-coach/weekly-progress/page.tsx index 7c9ed8d..89b1ae3 100644 --- a/src/app/dashboard/ai-coach/weekly-progress/page.tsx +++ b/src/app/dashboard/ai-coach/weekly-progress/page.tsx @@ -4,22 +4,42 @@ import { WeeklyProgressReport } from "@/components/AI/WeeklyProgressReport" import { Target, TrendingUp, Calendar, Zap, ArrowRight, BookOpen, CheckCircle2 } from "lucide-react" import { Card, CardContent } from "@/components/ui/card" import Link from "next/link" +import { useActiveUser, isDemoUser } from "@/hooks/useActiveUser" -const STATS = [ +const DEMO_STATS = [ { label: "Avg. Accuracy", value: "85%", delta: "+6%", positive: true }, { label: "Days Active", value: "7 / 7", delta: "Perfect week", positive: true }, { label: "Questions Solved", value: "214", delta: "+38 vs last week", positive: true }, { label: "Topics Covered", value: "12", delta: "3 remaining", positive: false }, ] -const SYLLABUS_PROGRESS = [ +const ZERO_STATS = [ + { label: "Avg. Accuracy", value: "0%", delta: "Baseline", positive: true }, + { label: "Days Active", value: "0 / 7", delta: "Day 1 Active", positive: true }, + { label: "Questions Solved", value: "0", delta: "0 vs last week", positive: true }, + { label: "Topics Covered", value: "0", delta: "0 completed", positive: false }, +] + +const DEMO_SYLLABUS_PROGRESS = [ { subject: "Physics", completed: 42, total: 60, percentage: 70, color: "bg-blue-500", text: "text-blue-500" }, { subject: "Chemistry", completed: 35, total: 55, percentage: 63, color: "bg-amber-500", text: "text-amber-500" }, { subject: "Mathematics", completed: 50, total: 65, percentage: 76, color: "bg-purple-500", text: "text-purple-500" }, { subject: "Biology", completed: 20, total: 40, percentage: 50, color: "bg-emerald-500", text: "text-emerald-500" }, ] +const ZERO_SYLLABUS_PROGRESS = [ + { subject: "Physics", completed: 0, total: 60, percentage: 0, color: "bg-blue-500", text: "text-blue-500" }, + { subject: "Chemistry", completed: 0, total: 55, percentage: 0, color: "bg-amber-500", text: "text-amber-500" }, + { subject: "Mathematics", completed: 0, total: 65, percentage: 0, color: "bg-purple-500", text: "text-purple-500" }, + { subject: "Biology", completed: 0, total: 40, percentage: 0, color: "bg-emerald-500", text: "text-emerald-500" }, +] + export default function WeeklyProgressPage() { + const { userId } = useActiveUser() + const isDemo = isDemoUser(userId) + const stats = isDemo ? DEMO_STATS : ZERO_STATS + const syllabusProgress = isDemo ? DEMO_SYLLABUS_PROGRESS : ZERO_SYLLABUS_PROGRESS + return (
{/* Page Header */} @@ -46,7 +66,7 @@ export default function WeeklyProgressPage() { {/* KPI Stats Row */}
- {STATS.map((stat) => ( + {stats.map((stat) => (

{stat.label}

@@ -80,7 +100,7 @@ export default function WeeklyProgressPage() { - {SYLLABUS_PROGRESS.map((item) => ( + {syllabusProgress.map((item) => (
{item.subject} @@ -107,10 +127,14 @@ export default function WeeklyProgressPage() {

Weekly Goals

- + {isDemo ? ( + + ) : ( +
+ )}
Complete 200 Questions
-
Goal Met (214/200)
+
{isDemo ? "Goal Met (214/200)" : "Pending (0/200)"}
diff --git a/src/app/dashboard/analytics/page.tsx b/src/app/dashboard/analytics/page.tsx index 6d8e93e..a7d5086 100644 --- a/src/app/dashboard/analytics/page.tsx +++ b/src/app/dashboard/analytics/page.tsx @@ -3,28 +3,52 @@ import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/card" import { LineChart as LineChartIcon, Activity, BrainCircuit } from "lucide-react" import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer, BarChart, Bar, Cell } from 'recharts' +import { useActiveUser, isDemoUser } from "@/hooks/useActiveUser" + +const DEMO_TREND_DATA = [ + { name: 'Week 1', score: 120 }, + { name: 'Week 2', score: 135 }, + { name: 'Week 3', score: 130 }, + { name: 'Week 4', score: 145 }, + { name: 'Week 5', score: 160 }, + { name: 'Week 6', score: 185 }, +] + +const ZERO_TREND_DATA = [ + { name: 'Week 1', score: 0 }, + { name: 'Week 2', score: 0 }, + { name: 'Week 3', score: 0 }, + { name: 'Week 4', score: 0 }, + { name: 'Week 5', score: 0 }, + { name: 'Week 6', score: 0 }, +] + +const DEMO_HEATMAP_DATA = [ + { topic: 'Kinematics', accuracy: 95 }, + { topic: 'Optics', accuracy: 72 }, + { topic: 'Thermodynamics', accuracy: 45 }, + { topic: 'Calculus', accuracy: 38 }, + { topic: 'Organic Chem', accuracy: 88 }, + { topic: 'Inorganic', accuracy: 65 }, +] + +const ZERO_HEATMAP_DATA = [ + { topic: 'Kinematics', accuracy: 0 }, + { topic: 'Optics', accuracy: 0 }, + { topic: 'Thermodynamics', accuracy: 0 }, + { topic: 'Calculus', accuracy: 0 }, + { topic: 'Organic Chem', accuracy: 0 }, + { topic: 'Inorganic', accuracy: 0 }, +] export default function AnalyticsPage() { - const trendData = [ - { name: 'Week 1', score: 120 }, - { name: 'Week 2', score: 135 }, - { name: 'Week 3', score: 130 }, - { name: 'Week 4', score: 145 }, - { name: 'Week 5', score: 160 }, - { name: 'Week 6', score: 185 }, - ] - - // For heatmap-like visualization (using a barchart with different colors) - const heatmapData = [ - { topic: 'Kinematics', accuracy: 95 }, - { topic: 'Optics', accuracy: 72 }, - { topic: 'Thermodynamics', accuracy: 45 }, - { topic: 'Calculus', accuracy: 38 }, - { topic: 'Organic Chem', accuracy: 88 }, - { topic: 'Inorganic', accuracy: 65 }, - ] + const { userId } = useActiveUser() + const isDemo = isDemoUser(userId) + const trendData = isDemo ? DEMO_TREND_DATA : ZERO_TREND_DATA + const heatmapData = isDemo ? DEMO_HEATMAP_DATA : ZERO_HEATMAP_DATA const getColor = (accuracy: number) => { + if (accuracy === 0) return '#374151' // neutral gray if (accuracy >= 80) return '#10b981' // emerald-500 if (accuracy >= 60) return '#f59e0b' // amber-500 return '#ef4444' // red-500 @@ -50,7 +74,9 @@ export default function AnalyticsPage() { Projected Score Trajectory - Your mock scores over the last 6 months projected towards the final exam. + + {isDemo ? "Your mock scores over the last 6 months projected towards the final exam." : "Your mock test trajectory will plot here as you complete tests."} + @@ -75,7 +101,11 @@ export default function AnalyticsPage() {

- Our models detect a consistent 14% accuracy drop in physics questions involving Calculus. We are adjusting your upcoming DPPs to focus heavily on integration applications in kinematics. + {isDemo ? ( + <>Our models detect a consistent 14% accuracy drop in physics questions involving Calculus. We are adjusting your upcoming DPPs to focus heavily on integration applications in kinematics. + ) : ( + <>Complete your first diagnostic test or DPP to unlock personalized AI diagnoses and focus area recommendations. Our neural models will begin analyzing your accuracy curves as soon as you attempt questions. + )}

diff --git a/src/app/dashboard/community/page.tsx b/src/app/dashboard/community/page.tsx index 427bcb3..b3180ad 100644 --- a/src/app/dashboard/community/page.tsx +++ b/src/app/dashboard/community/page.tsx @@ -3,7 +3,7 @@ import { useState } from "react" import { Card, CardContent, CardHeader, CardTitle, CardFooter } from "@/components/ui/card" import { MessageSquare, ThumbsUp, ChevronUp, Search, Image as ImageIcon, CheckCircle, Lock, X, Send } from "lucide-react" -import { useActiveUser } from "@/hooks/useActiveUser" +import { useActiveUser, isDemoUser } from "@/hooks/useActiveUser" interface Doubt { id: string @@ -22,7 +22,8 @@ interface Doubt { export default function CommunityPage() { const { user, hasAccessTo, loading } = useActiveUser() - const currentUserName = user?.name || "Neha Verma" + const isDemo = isDemoUser(user?.id) + const currentUserName = user?.name || (isDemo ? "Neha Verma" : "Student") const currentUserInitials = currentUserName.substring(0, 2).toUpperCase() const [activeFilter, setActiveFilter] = useState<'all' | 'my-doubts' | 'solved' | 'unsolved'>('all') const [searchQuery, setSearchQuery] = useState('') @@ -58,7 +59,7 @@ export default function CommunityPage() { isSolved: false, hasModeratorReply: false }, - { + ...(isDemo ? [{ id: "3", author: currentUserName, avatar: currentUserInitials, @@ -71,7 +72,7 @@ export default function CommunityPage() { replies: 8, isSolved: true, hasModeratorReply: true - } + }] : []) ] const filteredDoubts = mockDoubts.filter(d => { @@ -244,9 +245,16 @@ export default function CommunityPage() { ))} {filteredDoubts.length === 0 && ( -
- -

No doubts found matching your criteria.

+
+ +

+ {activeFilter === 'my-doubts' ? "No Doubts Asked Yet" : "No doubts found matching your criteria."} +

+

+ {activeFilter === 'my-doubts' + ? "You haven't asked any doubts yet. Click 'Ask a Doubt' above to get answers from mentors and peers." + : "Try selecting another filter or clearing your search query."} +

)}
diff --git a/src/app/dashboard/progress/page.tsx b/src/app/dashboard/progress/page.tsx index fb00382..0ba3e5b 100644 --- a/src/app/dashboard/progress/page.tsx +++ b/src/app/dashboard/progress/page.tsx @@ -1,8 +1,9 @@ "use client" -import { useState } from "react" +import { useState, useEffect } from "react" import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/card" import { ChevronDown, ChevronRight, CheckCircle2, Circle, Clock, Activity, Target } from "lucide-react" +import { useActiveUser, isDemoUser } from "@/hooks/useActiveUser" type ChapterStatus = 'Mastered' | 'Practiced' | 'In Progress' | 'Not Started' @@ -34,68 +35,138 @@ const getStatusIcon = (status: ChapterStatus) => { } } +const DEMO_SUBJECTS: Subject[] = [ + { + name: "Physics", + completionPct: 42, + chapters: [ + { + name: "Kinematics", + isExpanded: true, + topics: [ + { name: "Motion in One Dimension", status: "Mastered", accuracy: 92, questionsAttempted: 150 }, + { name: "Projectile Motion", status: "Practiced", accuracy: 75, questionsAttempted: 85 }, + { name: "Relative Velocity", status: "In Progress", accuracy: 45, questionsAttempted: 20 }, + ] + }, + { + name: "Laws of Motion", + isExpanded: false, + topics: [ + { name: "Newton's First Law", status: "Practiced", accuracy: 88, questionsAttempted: 40 }, + { name: "Friction", status: "In Progress", accuracy: 50, questionsAttempted: 35 }, + ] + }, + { + name: "Thermodynamics", + isExpanded: false, + topics: [ + { name: "First Law", status: "Not Started", accuracy: 0, questionsAttempted: 0 }, + { name: "Carnot Engine", status: "Not Started", accuracy: 0, questionsAttempted: 0 }, + ] + } + ] + }, + { + name: "Chemistry", + completionPct: 68, + chapters: [ + { + name: "Atomic Structure", + isExpanded: false, + topics: [ + { name: "Bohr Model", status: "Mastered", accuracy: 95, questionsAttempted: 110 }, + { name: "Quantum Numbers", status: "Mastered", accuracy: 88, questionsAttempted: 95 }, + ] + } + ] + }, + { + name: "Mathematics / Biology", + completionPct: 22, + chapters: [ + { + name: "Calculus", + isExpanded: false, + topics: [ + { name: "Limits", status: "In Progress", accuracy: 60, questionsAttempted: 45 }, + { name: "Derivatives", status: "Not Started", accuracy: 0, questionsAttempted: 0 }, + ] + } + ] + } +] + +const ZERO_SUBJECTS: Subject[] = [ + { + name: "Physics", + completionPct: 0, + chapters: [ + { + name: "Kinematics", + isExpanded: true, + topics: [ + { name: "Motion in One Dimension", status: "Not Started", accuracy: 0, questionsAttempted: 0 }, + { name: "Projectile Motion", status: "Not Started", accuracy: 0, questionsAttempted: 0 }, + { name: "Relative Velocity", status: "Not Started", accuracy: 0, questionsAttempted: 0 }, + ] + }, + { + name: "Laws of Motion", + isExpanded: false, + topics: [ + { name: "Newton's First Law", status: "Not Started", accuracy: 0, questionsAttempted: 0 }, + { name: "Friction", status: "Not Started", accuracy: 0, questionsAttempted: 0 }, + ] + }, + { + name: "Thermodynamics", + isExpanded: false, + topics: [ + { name: "First Law", status: "Not Started", accuracy: 0, questionsAttempted: 0 }, + { name: "Carnot Engine", status: "Not Started", accuracy: 0, questionsAttempted: 0 }, + ] + } + ] + }, + { + name: "Chemistry", + completionPct: 0, + chapters: [ + { + name: "Atomic Structure", + isExpanded: false, + topics: [ + { name: "Bohr Model", status: "Not Started", accuracy: 0, questionsAttempted: 0 }, + { name: "Quantum Numbers", status: "Not Started", accuracy: 0, questionsAttempted: 0 }, + ] + } + ] + }, + { + name: "Mathematics / Biology", + completionPct: 0, + chapters: [ + { + name: "Core Foundation", + isExpanded: false, + topics: [ + { name: "Limits / Cell Structure", status: "Not Started", accuracy: 0, questionsAttempted: 0 }, + { name: "Derivatives / Genetics", status: "Not Started", accuracy: 0, questionsAttempted: 0 }, + ] + } + ] + } +] + export default function ProgressTrackerPage() { - const [subjects, setSubjects] = useState([ - { - name: "Physics", - completionPct: 42, - chapters: [ - { - name: "Kinematics", - isExpanded: true, - topics: [ - { name: "Motion in One Dimension", status: "Mastered", accuracy: 92, questionsAttempted: 150 }, - { name: "Projectile Motion", status: "Practiced", accuracy: 75, questionsAttempted: 85 }, - { name: "Relative Velocity", status: "In Progress", accuracy: 45, questionsAttempted: 20 }, - ] - }, - { - name: "Laws of Motion", - isExpanded: false, - topics: [ - { name: "Newton's First Law", status: "Practiced", accuracy: 88, questionsAttempted: 40 }, - { name: "Friction", status: "In Progress", accuracy: 50, questionsAttempted: 35 }, - ] - }, - { - name: "Thermodynamics", - isExpanded: false, - topics: [ - { name: "First Law", status: "Not Started", accuracy: 0, questionsAttempted: 0 }, - { name: "Carnot Engine", status: "Not Started", accuracy: 0, questionsAttempted: 0 }, - ] - } - ] - }, - { - name: "Chemistry", - completionPct: 68, - chapters: [ - { - name: "Atomic Structure", - isExpanded: false, - topics: [ - { name: "Bohr Model", status: "Mastered", accuracy: 95, questionsAttempted: 110 }, - { name: "Quantum Numbers", status: "Mastered", accuracy: 88, questionsAttempted: 95 }, - ] - } - ] - }, - { - name: "Mathematics", - completionPct: 22, - chapters: [ - { - name: "Calculus", - isExpanded: false, - topics: [ - { name: "Limits", status: "In Progress", accuracy: 60, questionsAttempted: 45 }, - { name: "Derivatives", status: "Not Started", accuracy: 0, questionsAttempted: 0 }, - ] - } - ] - } - ]) + const { userId } = useActiveUser() + const isDemo = isDemoUser(userId) + const [subjects, setSubjects] = useState(isDemo ? DEMO_SUBJECTS : ZERO_SUBJECTS) + + useEffect(() => { + setSubjects(isDemo ? DEMO_SUBJECTS : ZERO_SUBJECTS) + }, [isDemo]) const toggleChapter = (subjectIndex: number, chapterIndex: number) => { const newSubjects = [...subjects] diff --git a/src/app/dashboard/rank-predictor/page.tsx b/src/app/dashboard/rank-predictor/page.tsx index 6eba136..f6ca213 100644 --- a/src/app/dashboard/rank-predictor/page.tsx +++ b/src/app/dashboard/rank-predictor/page.tsx @@ -4,31 +4,40 @@ import { useState, useEffect } from "react" import { Card, CardContent } from "@/components/ui/card" import { Button } from "@/components/ui/button" import { Trophy, TrendingUp, AlertTriangle, Sparkles, Building2, Target, Sliders, CheckCircle2, ChevronRight, UserCheck } from "lucide-react" -import { useActiveUser } from "@/hooks/useActiveUser" +import { useActiveUser, isDemoUser } from "@/hooks/useActiveUser" export default function RankPredictorPage() { const { user } = useActiveUser() + const isDemo = isDemoUser(user?.id) const [exam, setExam] = useState<"NEET" | "JEE">("NEET") - const [physics, setPhysics] = useState(135) - const [chemistry, setChemistry] = useState(155) - const [bioOrMath, setBioOrMath] = useState(330) // Bio out of 360 or Math out of 100 + const [physics, setPhysics] = useState(0) + const [chemistry, setChemistry] = useState(0) + const [bioOrMath, setBioOrMath] = useState(0) const [saved, setSaved] = useState(false) + const [initialized, setInitialized] = useState(false) useEffect(() => { - if (user?.target_exam) { - if (user.target_exam.includes("NEET")) { - setExam("NEET") + if (!user || initialized) return + const isNeet = user.target_exam ? user.target_exam.includes("NEET") : true + setExam(isNeet ? "NEET" : "JEE") + + if (isDemo) { + if (isNeet) { setPhysics(140) setChemistry(150) setBioOrMath(320) } else { - setExam("JEE") setPhysics(65) setChemistry(70) setBioOrMath(75) } + } else { + setPhysics(0) + setChemistry(0) + setBioOrMath(0) } - }, [user]) + setInitialized(true) + }, [user, isDemo, initialized]) const maxTotal = exam === "NEET" ? 720 : 300 const maxPhysics = exam === "NEET" ? 180 : 100 @@ -83,7 +92,18 @@ export default function RankPredictorPage() { {/* Exam Switcher */}
Estimated All India Rank
- AIR ~{predictedRank.toLocaleString()} + {totalScore === 0 ? "AIR --" : `AIR ~${predictedRank.toLocaleString()}`}
- High Accuracy Confidence + {totalScore === 0 ? "Calibrating · Adjust Sliders" : "High Accuracy Confidence"}
@@ -215,26 +250,33 @@ export default function RankPredictorPage() { College Admission Chances -
- {colleges.map((c) => { - const isLikely = totalScore >= c.minScore - const chancePct = Math.min(99, Math.max(10, Math.round((totalScore / c.minScore) * 85))) - return ( -
-
- {c.name} - - {isLikely ? `${chancePct}% Likely` : "Reach Target"} - + {totalScore === 0 ? ( +
+

No Marks Simulated

+

Drag the subject sliders on the left or complete mock tests to view college probability predictions.

+
+ ) : ( +
+ {colleges.map((c) => { + const isLikely = totalScore >= c.minScore + const chancePct = Math.min(99, Math.max(10, Math.round((totalScore / c.minScore) * 85))) + return ( +
+
+ {c.name} + + {isLikely ? `${chancePct}% Likely` : "Reach Target"} + +
+
+ {c.category} + Cutoff Score: {c.minScore}+ +
-
- {c.category} - Cutoff Score: {c.minScore}+ -
-
- ) - })} -
+ ) + })} +
+ )}
diff --git a/src/app/dashboard/streaks/page.tsx b/src/app/dashboard/streaks/page.tsx index 79b75dc..8a32aea 100644 --- a/src/app/dashboard/streaks/page.tsx +++ b/src/app/dashboard/streaks/page.tsx @@ -1,13 +1,40 @@ "use client" +import { useState, useEffect } from "react" import { Card, CardContent } from "@/components/ui/card" import { Flame, Gift, Star, Trophy, Target, ArrowRight, CheckCircle2 } from "lucide-react" -import { useActiveUser } from "@/hooks/useActiveUser" +import { useActiveUser, isDemoUser } from "@/hooks/useActiveUser" export default function StreaksPage() { const { user } = useActiveUser() + const isDemo = isDemoUser(user?.id) const currentStreak = user?.streak_count ?? 1 - const bestStreak = Math.max(currentStreak, user?.longest_streak ?? 1) + const bestStreak = isDemo ? (user?.longest_streak ?? 45) : Math.max(currentStreak, user?.longest_streak ?? 1) + const [streakPoints, setStreakPoints] = useState(0) + + useEffect(() => { + if (!user?.id) return + if (isDemo) { + setStreakPoints(currentStreak * 50) + return + } + async function loadStats() { + try { + const res = await fetch(`/api/users/${user?.id}/stats`) + if (res.ok) { + const stats = await res.json() + if (!stats.is_new_user) { + setStreakPoints(currentStreak * 50) + } else { + setStreakPoints(0) + } + } + } catch { + setStreakPoints(0) + } + } + loadStats() + }, [user?.id, isDemo, currentStreak]) const rewards = [ { streak: 7, title: "Consistent Learner", reward: "1 Free Premium DPP", unlocked: currentStreak >= 7 }, @@ -39,7 +66,11 @@ export default function StreaksPage() {

{currentStreak} Days

CURRENT STREAK

-

Complete a Daily Practice Problem (DPP) or CBT Exam every day to keep your streak alive.

+

+ {streakPoints === 0 + ? "Complete your first Daily Practice Problem (DPP) or CBT Exam today to start earning streak points." + : "Complete a Daily Practice Problem (DPP) or CBT Exam every day to keep your streak alive."} +

@@ -57,7 +88,10 @@ export default function StreaksPage() {

Total Streak Points Earned

-

{(currentStreak * 50).toLocaleString()}

+

{streakPoints.toLocaleString()}

+ {streakPoints === 0 && ( +

Take a test to activate consistency bonus points

+ )}
diff --git a/src/components/AI/AdaptiveStudyPlan.tsx b/src/components/AI/AdaptiveStudyPlan.tsx index 6f30996..b6e1c91 100644 --- a/src/components/AI/AdaptiveStudyPlan.tsx +++ b/src/components/AI/AdaptiveStudyPlan.tsx @@ -1,10 +1,11 @@ "use client" -import { useState } from "react" +import { useState, useEffect } from "react" import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card" import { Badge } from "@/components/ui/badge" import { Button } from "@/components/ui/button" import { CheckCircle2, Clock, SkipForward, BrainCircuit } from "lucide-react" +import { useActiveUser, isDemoUser } from "@/hooks/useActiveUser" type PlanState = 'PENDING' | 'DONE' | 'SKIPPED'; @@ -16,14 +17,30 @@ interface PlanItem { status: PlanState; } -const INITIAL_PLAN: PlanItem[] = [ +const DEMO_PLAN: PlanItem[] = [ { id: '1', title: 'Revise Thermodynamics Concepts', duration: '45 min', type: 'Theory', status: 'PENDING' }, { id: '2', title: 'Solve 15 Rotational Dynamics DPPs', duration: '30 min', type: 'Practice', status: 'PENDING' }, { id: '3', title: 'Review Mistakes in Optics (T-3)', duration: '20 min', type: 'Review', status: 'PENDING' }, ]; +const NEW_USER_PLAN: PlanItem[] = [ + { id: '1', title: 'Take Baseline Diagnostic CBT Test', duration: '45 min', type: 'Diagnostic', status: 'PENDING' }, + { id: '2', title: 'Attempt Today\'s 15 DPP Practice Questions', duration: '30 min', type: 'Practice', status: 'PENDING' }, + { id: '3', title: 'Calibrate Target Exam & Daily Time Slot', duration: '15 min', type: 'Setup', status: 'PENDING' }, +]; + export function AdaptiveStudyPlan() { - const [items, setItems] = useState(INITIAL_PLAN); + const { user } = useActiveUser(); + const isDemo = isDemoUser(user?.id); + const [items, setItems] = useState(DEMO_PLAN); + + useEffect(() => { + if (isDemo) { + setItems(DEMO_PLAN); + } else { + setItems(NEW_USER_PLAN); + } + }, [isDemo]); const updateStatus = (id: string, newStatus: PlanState) => { setItems(items.map(item => item.id === id ? { ...item, status: newStatus } : item)); @@ -48,7 +65,9 @@ export function AdaptiveStudyPlan() { Today: {totalTime} min
-

Dynamically generated based on your weak areas.

+

+ {isDemo ? "Dynamically generated based on your weak areas." : "Day 1 Onboarding Plan · Calibrating AI recommendations based on your initial performance."} +

diff --git a/src/components/AI/MistakeHeatmap.tsx b/src/components/AI/MistakeHeatmap.tsx index f6cd9c4..886f0b1 100644 --- a/src/components/AI/MistakeHeatmap.tsx +++ b/src/components/AI/MistakeHeatmap.tsx @@ -1,6 +1,9 @@ +"use client" + import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card" import { Badge } from "@/components/ui/badge" -import { Flame, Info } from "lucide-react" +import { Flame, Info, CheckCircle2 } from "lucide-react" +import { useActiveUser, isDemoUser } from "@/hooks/useActiveUser" const HEATMAP_DATA = [ { topic: "Rotational Dynamics", attempts: [80, 90, 40, 20, 10] }, // 10 is critical error rate @@ -10,14 +13,26 @@ const HEATMAP_DATA = [ { topic: "Modern Physics", attempts: [10, 5, 2, 0, 0] } ]; +const ZERO_HEATMAP_DATA = [ + { topic: "Physics (Core Mechanics)", attempts: [0, 0, 0, 0, 0] }, + { topic: "Chemistry (Physical & Organic)", attempts: [0, 0, 0, 0, 0] }, + { topic: "Mathematics / Biology", attempts: [0, 0, 0, 0, 0] }, +]; + export function MistakeHeatmap() { + const { userId } = useActiveUser(); + const isDemo = isDemoUser(userId); + const getColor = (errorRate: number) => { + if (errorRate === 0) return 'bg-white/5 border-white/10 text-muted-foreground'; if (errorRate >= 50) return 'bg-red-500/80 border-red-500 shadow-[0_0_10px_-2px_rgba(239,68,68,0.5)]'; // Critical if (errorRate >= 30) return 'bg-amber-500/60 border-amber-500'; // Warning if (errorRate >= 10) return 'bg-yellow-500/30 border-yellow-500/50'; // Moderate return 'bg-green-500/20 border-green-500/30'; // Good }; + const rows = isDemo ? HEATMAP_DATA : ZERO_HEATMAP_DATA; + return ( @@ -28,7 +43,9 @@ export function MistakeHeatmap() {
-

Topic-wise error rate across last 5 tests.

+

+ {isDemo ? "Topic-wise error rate across last 5 tests." : "Topic-wise error rate across your recent test sessions."} +

@@ -46,7 +63,7 @@ export function MistakeHeatmap() { {/* Data rows */}
- {HEATMAP_DATA.map((row, idx) => ( + {rows.map((row, idx) => (
{row.topic} @@ -65,11 +82,22 @@ export function MistakeHeatmap() {
))}
+ + {!isDemo && ( +
+
+ Clean Slate · 0 Errors Recorded +
+

+ As you attempt DPPs and CBT mock tests, questions you get wrong will automatically be tracked and heatmapped here. +

+
+ )}
{/* Legend */}
-
{"< 10%"}
+
{"0-10% (Optimal)"}
{"10-30%"}
{"30-50%"}
{"> 50%"}
@@ -78,3 +106,4 @@ export function MistakeHeatmap() { ) } + diff --git a/src/components/AI/ReadinessGauge.tsx b/src/components/AI/ReadinessGauge.tsx index 019241c..572b747 100644 --- a/src/components/AI/ReadinessGauge.tsx +++ b/src/components/AI/ReadinessGauge.tsx @@ -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(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 (
@@ -67,10 +74,12 @@ export function ReadinessGauge() {
- Updated daily • Today - + + {isZero ? "Algorithmic Baseline" : "Updated daily • Today"} + + - +5 this week + {isZero ? "Day 1 Baseline" : "+5 this week"}
@@ -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 */}
-
88% ↑
+
+ {isZero ? "0%" : "88%"} + {!isZero && ↑} +
Accuracy
-
45s ↓
+
+ {isZero ? "-- s" : "45s"} + {!isZero && ↓} +
Speed/Q
-
High ↑
+
+ {isZero ? "0%" : "High"} + {!isZero && ↑} +
Consistency
-
64% ↑
+
+ {isZero ? "0%" : "64%"} + {!isZero && ↑} +
Syllabus
@@ -126,12 +147,12 @@ export function ReadinessGauge() { {/* Subject Bars */}
{[ - { 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 => (
- {sub.name} + {sub.name}
National Avg 58 / 100 - You: +{score - 58} above avg + + {isZero ? "Pending test data" : `You: +${score - 58} above avg`} +
- 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."}
) } + diff --git a/src/components/AI/StudyTimeOptimizationCard.tsx b/src/components/AI/StudyTimeOptimizationCard.tsx index 606b39a..37f6e84 100644 --- a/src/components/AI/StudyTimeOptimizationCard.tsx +++ b/src/components/AI/StudyTimeOptimizationCard.tsx @@ -1,9 +1,10 @@ "use client" import { Card, CardContent } from "@/components/ui/card" -import { Flame, Star, Clock, TrendingUp } from "lucide-react" +import { Flame, Star, Clock, TrendingUp, Sparkles } from "lucide-react" import { useState } from "react" +import { useActiveUser, isDemoUser } from "@/hooks/useActiveUser" -const SLOTS = [ +const DEMO_SLOTS = [ { label: "6 AM", h: 30, score: 52, peak: false }, { label: "9 AM", h: 55, score: 68, peak: false }, { label: "12 PM", h: 65, score: 74, peak: false }, @@ -15,9 +16,25 @@ const SLOTS = [ { label: "11 PM", h: 32, score: 48, peak: false }, ] +const ZERO_SLOTS = [ + { label: "6 AM", h: 0, score: 0, peak: false }, + { label: "9 AM", h: 0, score: 0, peak: false }, + { label: "12 PM", h: 0, score: 0, peak: false }, + { label: "2 PM", h: 0, score: 0, peak: false }, + { label: "3 PM", h: 0, score: 0, peak: false }, + { label: "5 PM", h: 0, score: 0, peak: false }, + { label: "7 PM", h: 0, score: 0, peak: false }, + { label: "9 PM", h: 0, score: 0, peak: false }, + { label: "11 PM", h: 0, score: 0, peak: false }, +] + export function StudyTimeOptimizationCard() { + const { userId } = useActiveUser() + const isDemo = isDemoUser(userId) const [hovered, setHovered] = useState(null) - const maxH = Math.max(...SLOTS.map(s => s.h)) + + const slots = isDemo ? DEMO_SLOTS : ZERO_SLOTS + const maxH = Math.max(1, ...slots.map(s => s.h)) return (
@@ -29,11 +46,11 @@ export function StudyTimeOptimizationCard() { Peak Window: - 2 PM – 5 PM + {isDemo ? "2 PM – 5 PM" : "Calibrating..."}
-
- 95% efficiency at peak +
+ {isDemo ? "95% efficiency at peak" : "0% baseline"}
@@ -58,7 +75,7 @@ export function StudyTimeOptimizationCard() { /> ))}
- {SLOTS.map((slot, i) => ( + {slots.map((slot, i) => (
{slot.label}
-
Score: {slot.score}%
+
+ Score: {slot.score}% +
)}
))} @@ -88,7 +109,7 @@ export function StudyTimeOptimizationCard() {
{/* X-axis labels */}
- {SLOTS.map((slot) => ( + {slots.map((slot) => (
{slot.label}
@@ -100,9 +121,9 @@ export function StudyTimeOptimizationCard() { {/* Efficiency breakdown */}
{[ - { label: "Morning", time: "6–9 AM", eff: 60, color: "text-muted-foreground" }, - { label: "Peak", time: "2–5 PM", eff: 95, color: "text-yellow-400" }, - { label: "Evening", time: "7–9 PM", eff: 73, color: "text-[#00F0FF]" }, + { label: "Morning", time: "6–9 AM", eff: isDemo ? 60 : 0, color: "text-muted-foreground" }, + { label: "Peak", time: "2–5 PM", eff: isDemo ? 95 : 0, color: isDemo ? "text-yellow-400" : "text-muted-foreground" }, + { label: "Evening", time: "7–9 PM", eff: isDemo ? 73 : 0, color: isDemo ? "text-[#00F0FF]" : "text-muted-foreground" }, ].map(slot => (
{slot.eff}%
@@ -114,10 +135,13 @@ export function StudyTimeOptimizationCard() {
- You perform best 2–5 PM. Schedule your hardest mock tests and DPPs in this window. + {isDemo + ? "You perform best 2–5 PM. Schedule your hardest mock tests and DPPs in this window." + : "Circadian AI calibrating: Complete practice sessions at different times of day to reveal your peak study window."}
) } + diff --git a/src/components/AI/WeeklyProgressReport.tsx b/src/components/AI/WeeklyProgressReport.tsx index 40a68a8..c395f35 100644 --- a/src/components/AI/WeeklyProgressReport.tsx +++ b/src/components/AI/WeeklyProgressReport.tsx @@ -3,24 +3,33 @@ import { TrendingUp, TrendingDown, Brain, Zap, Target, Clock } from "lucide-react" import { Card, CardContent } from "@/components/ui/card" import { useState } from "react" +import { useActiveUser, isDemoUser } from "@/hooks/useActiveUser" const DAYS = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"] -const QUESTIONS = [42, 58, 35, 71, 65, 48, 60] -const ACCURACY = [82, 78, 91, 88, 85, 79, 93] -const HOURS = [2.5, 3.5, 2, 4.5, 4, 3, 3.8] +const DEMO_QUESTIONS = [42, 58, 35, 71, 65, 48, 60] +const DEMO_ACCURACY = [82, 78, 91, 88, 85, 79, 93] +const DEMO_HOURS = [2.5, 3.5, 2, 4.5, 4, 3, 3.8] + +const ZERO_DATA = [0, 0, 0, 0, 0, 0, 0] export function WeeklyProgressReport() { + const { userId } = useActiveUser() + const isDemo = isDemoUser(userId) const [metric, setMetric] = useState<"questions" | "accuracy" | "hours">("accuracy") const [hovered, setHovered] = useState(null) + const QUESTIONS = isDemo ? DEMO_QUESTIONS : ZERO_DATA + const ACCURACY = isDemo ? DEMO_ACCURACY : ZERO_DATA + const HOURS = isDemo ? DEMO_HOURS : ZERO_DATA + const DATA = metric === "questions" ? QUESTIONS : metric === "accuracy" ? ACCURACY : HOURS - const maxVal = Math.max(...DATA) + const maxVal = Math.max(1, ...DATA) const avgVal = Math.round((DATA.reduce((a, b) => a + b, 0) / DATA.length) * 10) / 10 - const trend = DATA[DATA.length - 1] - DATA[0] + const trend = isDemo ? DATA[DATA.length - 1] - DATA[0] : 0 const trendUp = trend >= 0 const UNIT = metric === "accuracy" ? "%" : metric === "hours" ? "h" : "" - const avgAccuracy = Math.round(ACCURACY.reduce((a, b) => a + b, 0) / ACCURACY.length) + const avgAccuracy = isDemo ? Math.round(ACCURACY.reduce((a, b) => a + b, 0) / ACCURACY.length) : 0 return (
@@ -38,7 +47,7 @@ export function WeeklyProgressReport() {
- 7/7 days active + {isDemo ? "7/7 days active" : "Day 1 Active"}
@@ -89,13 +98,15 @@ export function WeeklyProgressReport() { )}
))} @@ -116,8 +127,8 @@ export function WeeklyProgressReport() {
{[ { label: "Avg Accuracy", value: `${avgAccuracy}%`, icon: }, - { label: "Questions / Day", value: `${Math.round(QUESTIONS.reduce((a, b) => a + b, 0) / 7)}`, icon: }, - { label: "Study Hrs / Day", value: `${(HOURS.reduce((a, b) => a + b, 0) / 7).toFixed(1)}h`, icon: }, + { label: "Questions / Day", value: isDemo ? `${Math.round(QUESTIONS.reduce((a, b) => a + b, 0) / 7)}` : "0", icon: }, + { label: "Study Hrs / Day", value: isDemo ? `${(HOURS.reduce((a, b) => a + b, 0) / 7).toFixed(1)}h` : "0.0h", icon: }, ].map(stat => (
{stat.icon}
@@ -131,8 +142,16 @@ export function WeeklyProgressReport() {
- Sign errors in Thermodynamics — detected in your last 3 attempts (W = −PΔV misapplied). We've queued 3 targeted practice questions for tomorrow.{" "} - Tip: You peak at 7–9 PM. Schedule the hardest topics then. + {isDemo ? ( + <> + Sign errors in Thermodynamics — detected in your last 3 attempts (W = −PΔV misapplied). We've queued 3 targeted practice questions for tomorrow.{" "} + Tip: You peak at 7–9 PM. Schedule the hardest topics then. + + ) : ( + <> + AI Progress Engine Calibrating — Complete your first CBT mock test or DPP to begin mapping subject retention, speed trends, and error velocity. + + )}
@@ -140,3 +159,4 @@ export function WeeklyProgressReport() {
) } + diff --git a/src/hooks/useActiveUser.ts b/src/hooks/useActiveUser.ts index 38766eb..b28670f 100644 --- a/src/hooks/useActiveUser.ts +++ b/src/hooks/useActiveUser.ts @@ -34,8 +34,8 @@ export const DEMO_USERS = { export const DEMO_IDS = new Set(Object.values(DEMO_USERS).map(u => u.id)); /** Returns true if the userId matches a known demo account */ -export function isDemoUser(userId: string): boolean { - return DEMO_IDS.has(userId); +export function isDemoUser(userId?: string | null): boolean { + return !!userId && DEMO_IDS.has(userId); } /** Compute trial info from user profile and global settings */