Update AI coach calibration, fix streak animation speed, add dashboard features and target filtering
This commit is contained in:
@@ -223,7 +223,7 @@ def login(payload: LoginPayload, session: Session = Depends(get_session)):
|
|||||||
)).first()
|
)).first()
|
||||||
token_id = str(demo_user.id) if demo_user else NEHA_UUID
|
token_id = str(demo_user.id) if demo_user else NEHA_UUID
|
||||||
return {"success": True, "role": "user", "token": token_id}
|
return {"success": True, "role": "user", "token": token_id}
|
||||||
if payload.email == "admin@pragativeer.com" and payload.password == "admin123":
|
if payload.email == "admin@pragativeer.com" and payload.password == "Pv9#kL2$mQ8!vX4@":
|
||||||
return {"success": True, "role": "admin", "token": "admin"}
|
return {"success": True, "role": "admin", "token": "admin"}
|
||||||
|
|
||||||
user = session.exec(select(models.User).where(models.User.email == payload.email)).first()
|
user = session.exec(select(models.User).where(models.User.email == payload.email)).first()
|
||||||
|
|||||||
@@ -455,7 +455,7 @@
|
|||||||
x: xr * W,
|
x: xr * W,
|
||||||
y: -35 * DPR,
|
y: -35 * DPR,
|
||||||
vx: (isRight ? 0.3 : -0.3) + (Math.random() - 0.5) * 0.8 * DPR,
|
vx: (isRight ? 0.3 : -0.3) + (Math.random() - 0.5) * 0.8 * DPR,
|
||||||
vy: (1.5 + Math.random() * 2.2) * DPR,
|
vy: (3.0 + Math.random() * 4.4) * DPR,
|
||||||
rot: (-20 + Math.random() * 40) * Math.PI / 180,
|
rot: (-20 + Math.random() * 40) * Math.PI / 180,
|
||||||
vr: (-0.02 + Math.random() * 0.04),
|
vr: (-0.02 + Math.random() * 0.04),
|
||||||
life: 1,
|
life: 1,
|
||||||
|
|||||||
@@ -1,14 +1,21 @@
|
|||||||
"use client"
|
"use client"
|
||||||
|
|
||||||
import { ReactNode } from "react"
|
import { ReactNode } from "react"
|
||||||
import { LayoutDashboard, Users, Database, Terminal, Settings, FileText, Tag } from "lucide-react"
|
import { LayoutDashboard, Users, Database, Terminal, Settings, FileText, Tag, LogOut } from "lucide-react"
|
||||||
import Link from "next/link"
|
import Link from "next/link"
|
||||||
import { usePathname } from "next/navigation"
|
import { usePathname, useRouter } from "next/navigation"
|
||||||
import { Logo } from "@/components/Logo"
|
import { Logo } from "@/components/Logo"
|
||||||
import { AdminThemeToggle } from "@/components/AdminThemeToggle"
|
import { AdminThemeToggle } from "@/components/AdminThemeToggle"
|
||||||
|
|
||||||
export default function AdminLayout({ children }: { children: ReactNode }) {
|
export default function AdminLayout({ children }: { children: ReactNode }) {
|
||||||
const pathname = usePathname()
|
const pathname = usePathname()
|
||||||
|
const router = useRouter()
|
||||||
|
|
||||||
|
const handleLogout = () => {
|
||||||
|
localStorage.removeItem("auth_token");
|
||||||
|
document.cookie = "auth_token=; path=/; max-age=0;";
|
||||||
|
router.push("/login");
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex h-screen bg-background overflow-hidden text-foreground selection:bg-[var(--cyan-ai-20)]">
|
<div className="flex h-screen bg-background overflow-hidden text-foreground selection:bg-[var(--cyan-ai-20)]">
|
||||||
@@ -26,8 +33,15 @@ export default function AdminLayout({ children }: { children: ReactNode }) {
|
|||||||
<NavItem href="/admin/students" icon={<Users size={18} />} label="Students" active={pathname.startsWith("/admin/students")} />
|
<NavItem href="/admin/students" icon={<Users size={18} />} label="Students" active={pathname.startsWith("/admin/students")} />
|
||||||
<NavItem href="/admin/coupons" icon={<Tag size={18} />} label="Coupons" active={pathname.startsWith("/admin/coupons")} />
|
<NavItem href="/admin/coupons" icon={<Tag size={18} />} label="Coupons" active={pathname.startsWith("/admin/coupons")} />
|
||||||
</nav>
|
</nav>
|
||||||
<div className="p-4 border-t border-border">
|
<div className="p-4 border-t border-border flex flex-col gap-2">
|
||||||
<NavItem href="/admin/settings" icon={<Settings size={18} />} label="System Settings" active={pathname.startsWith("/admin/settings")} />
|
<NavItem href="/admin/settings" icon={<Settings size={18} />} label="System Settings" active={pathname.startsWith("/admin/settings")} />
|
||||||
|
<button
|
||||||
|
onClick={handleLogout}
|
||||||
|
className="flex items-center w-full text-left gap-3 px-3 py-2.5 rounded-xl transition-all duration-300 text-red-500 hover:bg-red-500/10 font-medium"
|
||||||
|
>
|
||||||
|
<LogOut size={18} />
|
||||||
|
<span className="text-sm">Logout</span>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</aside>
|
</aside>
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
"use client"
|
"use client"
|
||||||
|
|
||||||
import { useEffect, useState, use, useRef } from "react"
|
import { useEffect, useState, use, useRef } from "react"
|
||||||
|
import { useActiveUser } from "@/hooks/useActiveUser"
|
||||||
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 { Clock, Flag, ChevronLeft, ChevronRight, Pause as PauseIcon, MoreVertical, Calculator as CalculatorIcon, FlaskConical, Edit3, XCircle, Play, Shield, AlertTriangle, Moon, Sun } from "lucide-react"
|
import { Clock, Flag, ChevronLeft, ChevronRight, Pause as PauseIcon, MoreVertical, Calculator as CalculatorIcon, FlaskConical, Edit3, XCircle, Play, Shield, AlertTriangle, Moon, Sun } from "lucide-react"
|
||||||
@@ -32,6 +33,9 @@ export default function CBTExamPage({ params }: { params: Promise<{ examId: stri
|
|||||||
const examId = resolvedParams.examId;
|
const examId = resolvedParams.examId;
|
||||||
const [mounted, setMounted] = useState(false);
|
const [mounted, setMounted] = useState(false);
|
||||||
const [examType, setExamType] = useState<string>('Test');
|
const [examType, setExamType] = useState<string>('Test');
|
||||||
|
const [testName, setTestName] = useState<string>('AITS Full Syllabus Test');
|
||||||
|
const { user } = useActiveUser();
|
||||||
|
const userId = user?.id || 'b0e17378-864d-439e-8806-708dc48a891b';
|
||||||
const [showCalc, setShowCalc] = useState(false);
|
const [showCalc, setShowCalc] = useState(false);
|
||||||
const [showScratchpad, setShowScratchpad] = useState(false);
|
const [showScratchpad, setShowScratchpad] = useState(false);
|
||||||
const [showPeriodicTable, setShowPeriodicTable] = useState(false);
|
const [showPeriodicTable, setShowPeriodicTable] = useState(false);
|
||||||
@@ -95,7 +99,7 @@ export default function CBTExamPage({ params }: { params: Promise<{ examId: stri
|
|||||||
async function fetchExamData() {
|
async function fetchExamData() {
|
||||||
try {
|
try {
|
||||||
const apiUrl = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:8000';
|
const apiUrl = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:8000';
|
||||||
const testRes = await fetch(`${apiUrl}/api/tests/${examId}?user_id=b0e17378-864d-439e-8806-708dc48a891b`);
|
const testRes = await fetch(`${apiUrl}/api/tests/${examId}?user_id=${userId}`);
|
||||||
if (!testRes.ok) throw new Error("Failed to fetch test structure");
|
if (!testRes.ok) throw new Error("Failed to fetch test structure");
|
||||||
const testData = await testRes.json();
|
const testData = await testRes.json();
|
||||||
|
|
||||||
@@ -113,6 +117,7 @@ export default function CBTExamPage({ params }: { params: Promise<{ examId: stri
|
|||||||
}
|
}
|
||||||
|
|
||||||
setExamType(testData.type);
|
setExamType(testData.type);
|
||||||
|
if (testData.title) setTestName(testData.title);
|
||||||
|
|
||||||
// Extract all question IDs from sections
|
// Extract all question IDs from sections
|
||||||
const questionIds: string[] = [];
|
const questionIds: string[] = [];
|
||||||
@@ -155,7 +160,7 @@ export default function CBTExamPage({ params }: { params: Promise<{ examId: stri
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
fetchExamData();
|
fetchExamData();
|
||||||
}, [examId, initExam]);
|
}, [examId, initExam, userId]);
|
||||||
|
|
||||||
// Track time spent per question
|
// Track time spent per question
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -186,7 +191,7 @@ export default function CBTExamPage({ params }: { params: Promise<{ examId: stri
|
|||||||
} else if (violationCount >= 2 && status !== 'SUBMITTED' && status !== 'TIMED_OUT' && !hasAutoSubmitted.current) {
|
} else if (violationCount >= 2 && status !== 'SUBMITTED' && status !== 'TIMED_OUT' && !hasAutoSubmitted.current) {
|
||||||
hasAutoSubmitted.current = true;
|
hasAutoSubmitted.current = true;
|
||||||
submitExam('SUBMITTED');
|
submitExam('SUBMITTED');
|
||||||
submitAttemptToServer('b0e17378-864d-439e-8806-708dc48a891b')
|
submitAttemptToServer(userId)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
window.alert("Test Auto-Submitted due to multiple tab switching violations.");
|
window.alert("Test Auto-Submitted due to multiple tab switching violations.");
|
||||||
})
|
})
|
||||||
@@ -242,9 +247,9 @@ export default function CBTExamPage({ params }: { params: Promise<{ examId: stri
|
|||||||
const confirmSubmit = async () => {
|
const confirmSubmit = async () => {
|
||||||
setShowSubmitConfirm(false);
|
setShowSubmitConfirm(false);
|
||||||
submitExam('SUBMITTED');
|
submitExam('SUBMITTED');
|
||||||
const payload = getAttemptPayload('b0e17378-864d-439e-8806-708dc48a891b');
|
const payload = getAttemptPayload(userId);
|
||||||
console.log("EXAM SUBMITTED. PAYLOAD:", JSON.stringify(payload, null, 2));
|
console.log("EXAM SUBMITTED. PAYLOAD:", JSON.stringify(payload, null, 2));
|
||||||
await submitAttemptToServer('b0e17378-864d-439e-8806-708dc48a891b');
|
await submitAttemptToServer(userId);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!mounted || questions.length === 0) {
|
if (!mounted || questions.length === 0) {
|
||||||
@@ -309,7 +314,7 @@ export default function CBTExamPage({ params }: { params: Promise<{ examId: stri
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (status === 'SUBMITTED' || status === 'TIMED_OUT') {
|
if (status === 'SUBMITTED' || status === 'TIMED_OUT') {
|
||||||
const payload = getAttemptPayload('b0e17378-864d-439e-8806-708dc48a891b');
|
const payload = getAttemptPayload(userId);
|
||||||
if (!payload) return <div className="p-8 text-center mt-20">Loading results...</div>;
|
if (!payload) return <div className="p-8 text-center mt-20">Loading results...</div>;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -415,7 +420,7 @@ export default function CBTExamPage({ params }: { params: Promise<{ examId: stri
|
|||||||
</div>
|
</div>
|
||||||
<div className="h-6 w-px bg-border hidden md:block"></div>
|
<div className="h-6 w-px bg-border hidden md:block"></div>
|
||||||
<div className="hidden md:block">
|
<div className="hidden md:block">
|
||||||
<h1 className="font-semibold text-sm">AITS Full Syllabus Test</h1>
|
<h1 className="font-semibold text-sm">{testName}</h1>
|
||||||
<p className="text-xs text-muted-foreground font-medium mt-0.5">{currentQuestion.subject}</p>
|
<p className="text-xs text-muted-foreground font-medium mt-0.5">{currentQuestion.subject}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
"use client"
|
"use client"
|
||||||
|
|
||||||
import { Button } from "@/components/ui/button"
|
import { Button } from "@/components/ui/button"
|
||||||
import { Card, CardContent } from "@/components/ui/card"
|
import { Card, CardContent } from "@/components/ui/card"
|
||||||
@@ -72,6 +72,10 @@ export default function DPPPage() {
|
|||||||
const scheduledDate = new Date(dpp.scheduled_date).toISOString().split('T')[0];
|
const scheduledDate = new Date(dpp.scheduled_date).toISOString().split('T')[0];
|
||||||
if (scheduledDate !== dateFilter) return false;
|
if (scheduledDate !== dateFilter) return false;
|
||||||
}
|
}
|
||||||
|
const isNeet = user?.target_exam === "NEET";
|
||||||
|
const title = dpp.title.toLowerCase();
|
||||||
|
if (isNeet && (title.includes("math") || title.includes("mth"))) return false;
|
||||||
|
if (!isNeet && title.includes("bio")) return false;
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -398,6 +398,14 @@ export default function DashboardLayout({ children }: { children: ReactNode }) {
|
|||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
|
{/* Free Trial Banner */}
|
||||||
|
{trialInfo.isTrialActive && (
|
||||||
|
<div className="bg-primary text-primary-foreground px-4 py-2.5 text-center text-sm font-bold flex justify-center items-center gap-2 shadow-sm shrink-0 z-30">
|
||||||
|
<span className="size-2 rounded-full bg-primary-foreground animate-pulse"></span>
|
||||||
|
15 Days Free Trial — Day {trialInfo.trialDayNumber}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* Scrollable page body */}
|
{/* Scrollable page body */}
|
||||||
<main className="flex-1 overflow-y-auto relative bg-background/50">
|
<main className="flex-1 overflow-y-auto relative bg-background/50">
|
||||||
{children}
|
{children}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
"use client"
|
"use client"
|
||||||
|
|
||||||
import { useState, useEffect } from "react"
|
import { useState, useEffect } from "react"
|
||||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
|
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
|
||||||
@@ -77,7 +77,7 @@ export default function LeaderboardPage() {
|
|||||||
onClick={() => setActiveTab('school')}
|
onClick={() => setActiveTab('school')}
|
||||||
className={`px-4 py-2 rounded-lg text-sm font-medium transition-colors ${activeTab === 'school' ? 'bg-background shadow-sm border border-border text-foreground' : 'text-muted-foreground hover:text-foreground'}`}
|
className={`px-4 py-2 rounded-lg text-sm font-medium transition-colors ${activeTab === 'school' ? 'bg-background shadow-sm border border-border text-foreground' : 'text-muted-foreground hover:text-foreground'}`}
|
||||||
>
|
>
|
||||||
School
|
Institute
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
"use client"
|
"use client"
|
||||||
|
|
||||||
import { useRef, useState, useEffect } 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"
|
||||||
@@ -56,6 +56,7 @@ export default function DashboardPage() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const isNew = userStats ? userStats.is_new_user : true
|
const isNew = userStats ? userStats.is_new_user : true
|
||||||
|
const isNeet = user?.target_exam === "NEET"
|
||||||
|
|
||||||
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">
|
||||||
@@ -188,7 +189,9 @@ export default function DashboardPage() {
|
|||||||
<BrainCircuit size={20} />
|
<BrainCircuit size={20} />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h3 className="font-medium text-base group-hover:text-primary transition-colors">Biology: Human Physiology</h3>
|
<h3 className="font-medium text-base group-hover:text-primary transition-colors">
|
||||||
|
{isNeet ? "Biology: Human Physiology" : "Maths: Integral Calculus"}
|
||||||
|
</h3>
|
||||||
<p className="text-sm text-muted-foreground mt-0.5">30 Questions • Medium Priority</p>
|
<p className="text-sm text-muted-foreground mt-0.5">30 Questions • Medium Priority</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -205,7 +208,9 @@ export default function DashboardPage() {
|
|||||||
<Lock size={20} />
|
<Lock size={20} />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h3 className="font-medium text-base text-muted-foreground">Biology: Human Physiology</h3>
|
<h3 className="font-medium text-base text-muted-foreground">
|
||||||
|
{isNeet ? "Biology: Human Physiology" : "Maths: Integral Calculus"}
|
||||||
|
</h3>
|
||||||
<p className="text-sm text-muted-foreground mt-0.5 flex items-center gap-1"><Lock size={12}/> Pro Feature</p>
|
<p className="text-sm text-muted-foreground mt-0.5 flex items-center gap-1"><Lock size={12}/> Pro Feature</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
"use client"
|
"use client"
|
||||||
|
|
||||||
import { useEffect, useState } from "react"
|
import { useEffect, useState } from "react"
|
||||||
import { Card, CardContent } from "@/components/ui/card"
|
import { Card, CardContent } from "@/components/ui/card"
|
||||||
import { RefreshCw, TrendingUp, MapPin, Star } from "lucide-react"
|
import { RefreshCw, TrendingUp, MapPin, Star } from "lucide-react"
|
||||||
import { useActiveUser } from "@/hooks/useActiveUser"
|
import { useActiveUser } from "@/hooks/useActiveUser"
|
||||||
|
|
||||||
const MOCK_COLLEGES: Record<string, Array<{ rank: number; name: string; branch: string; match: number; location: string; type: string; starred?: boolean }>> = {
|
const MOCK_COLLEGES_JEE: Record<string, Array<{ rank: number; name: string; branch: string; match: number; location: string; type: string; starred?: boolean }>> = {
|
||||||
Reach: [
|
Reach: [
|
||||||
{ rank: 1, name: "IIT Bombay", branch: "Computer Science", match: 48, location: "Mumbai", type: "IIT", starred: true },
|
{ rank: 1, name: "IIT Bombay", branch: "Computer Science", match: 48, location: "Mumbai", type: "IIT", starred: true },
|
||||||
{ rank: 2, name: "IIT Delhi", branch: "Electrical Engineering", match: 52, location: "Delhi", type: "IIT" },
|
{ rank: 2, name: "IIT Delhi", branch: "Electrical Engineering", match: 52, location: "Delhi", type: "IIT" },
|
||||||
@@ -29,6 +29,23 @@ const MOCK_COLLEGES: Record<string, Array<{ rank: number; name: string; branch:
|
|||||||
],
|
],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const MOCK_COLLEGES_NEET: Record<string, Array<{ rank: number; name: string; branch: string; match: number; location: string; type: string; starred?: boolean }>> = {
|
||||||
|
Reach: [
|
||||||
|
{ rank: 1, name: "AIIMS New Delhi", branch: "MBBS", match: 48, location: "Delhi", type: "AIIMS", starred: true },
|
||||||
|
{ rank: 2, name: "JIPMER", branch: "MBBS", match: 52, location: "Puducherry", type: "Govt" },
|
||||||
|
{ rank: 3, name: "MAMC", branch: "MBBS", match: 55, location: "Delhi", type: "Govt" },
|
||||||
|
],
|
||||||
|
Target: [
|
||||||
|
{ rank: 1, name: "KGMU", branch: "MBBS", match: 85, location: "Lucknow", type: "Govt", starred: true },
|
||||||
|
{ rank: 2, name: "CMC Vellore", branch: "MBBS", match: 78, location: "Vellore", type: "Private" },
|
||||||
|
{ rank: 3, name: "VMMC", branch: "MBBS", match: 74, location: "Delhi", type: "Govt" },
|
||||||
|
],
|
||||||
|
Safety: [
|
||||||
|
{ rank: 1, name: "State Govt Medical College", branch: "MBBS", match: 94, location: "Local", type: "Govt", starred: true },
|
||||||
|
{ rank: 2, name: "IMS BHU", branch: "BDS", match: 91, location: "Varanasi", type: "Govt" },
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
const MATCH_COLOR = (match: number) => {
|
const MATCH_COLOR = (match: number) => {
|
||||||
if (match >= 80) return "text-green-500 bg-green-500/10"
|
if (match >= 80) return "text-green-500 bg-green-500/10"
|
||||||
if (match >= 60) return "text-amber-500 bg-amber-500/10"
|
if (match >= 60) return "text-amber-500 bg-amber-500/10"
|
||||||
@@ -38,7 +55,7 @@ const MATCH_COLOR = (match: number) => {
|
|||||||
export function CollegePredictorCard() {
|
export function CollegePredictorCard() {
|
||||||
const [loading, setLoading] = useState(true)
|
const [loading, setLoading] = useState(true)
|
||||||
const [activeTab, setActiveTab] = useState("Target")
|
const [activeTab, setActiveTab] = useState("Target")
|
||||||
const { userId, loading: userLoading } = useActiveUser()
|
const { userId, user, loading: userLoading } = useActiveUser()
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (userLoading || !userId) return
|
if (userLoading || !userId) return
|
||||||
@@ -63,7 +80,8 @@ export function CollegePredictorCard() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const colleges = MOCK_COLLEGES[activeTab] || []
|
const isNeet = user?.target_exam === "NEET";
|
||||||
|
const colleges = (isNeet ? MOCK_COLLEGES_NEET[activeTab] : MOCK_COLLEGES_JEE[activeTab]) || []
|
||||||
|
|
||||||
const TAB_COLORS: Record<string, string> = {
|
const TAB_COLORS: Record<string, string> = {
|
||||||
Reach: "bg-purple-500/10 border-purple-500/20 text-purple-400",
|
Reach: "bg-purple-500/10 border-purple-500/20 text-purple-400",
|
||||||
@@ -128,8 +146,8 @@ export function CollegePredictorCard() {
|
|||||||
{/* Bottom Tip */}
|
{/* Bottom Tip */}
|
||||||
<div className="mt-1 p-3 bg-yellow-500/10 border border-yellow-500/20 rounded-xl text-xs text-yellow-500 font-bold flex items-center gap-2">
|
<div className="mt-1 p-3 bg-yellow-500/10 border border-yellow-500/20 rounded-xl text-xs text-yellow-500 font-bold flex items-center gap-2">
|
||||||
<TrendingUp size={14} className="shrink-0" />
|
<TrendingUp size={14} className="shrink-0" />
|
||||||
{activeTab === "Reach" && "Score +20 marks in Maths to convert IIT Roorkee from Reach to Target!"}
|
{activeTab === "Reach" && (isNeet ? "Score +20 marks in Biology to convert AIIMS New Delhi from Reach to Target!" : "Score +20 marks in Maths to convert IIT Roorkee from Reach to Target!")}
|
||||||
{activeTab === "Target" && "Just 15 more marks in Maths can upgrade you from NIT Trichy to IIT Roorkee!"}
|
{activeTab === "Target" && (isNeet ? "Just 15 more marks in Chemistry can upgrade you from KGMU to AIIMS!" : "Just 15 more marks in Maths can upgrade you from NIT Trichy to IIT Roorkee!")}
|
||||||
{activeTab === "Safety" && "You're on track for all safety colleges. Focus your energy on converting Target colleges."}
|
{activeTab === "Safety" && "You're on track for all safety colleges. Focus your energy on converting Target colleges."}
|
||||||
</div>
|
</div>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
"use client"
|
"use client"
|
||||||
|
|
||||||
import { useEffect, useState } from "react"
|
import { useEffect, useState } from "react"
|
||||||
import { Card, CardContent } from "@/components/ui/card"
|
import { Card, CardContent } from "@/components/ui/card"
|
||||||
@@ -10,7 +10,7 @@ import { useActiveUser, isDemoUser } from "@/hooks/useActiveUser"
|
|||||||
export function StudyPlanCard() {
|
export function StudyPlanCard() {
|
||||||
const [tasks, setTasks] = useState<any[]>([])
|
const [tasks, setTasks] = useState<any[]>([])
|
||||||
const [loading, setLoading] = useState(true)
|
const [loading, setLoading] = useState(true)
|
||||||
const { userId, loading: userLoading } = useActiveUser();
|
const { userId, user, loading: userLoading } = useActiveUser();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (userLoading || !userId) return;
|
if (userLoading || !userId) return;
|
||||||
@@ -30,12 +30,13 @@ export function StudyPlanCard() {
|
|||||||
}
|
}
|
||||||
if (isDemoUser(userId)) {
|
if (isDemoUser(userId)) {
|
||||||
// Demo user gets showcase active tasks
|
// Demo user gets showcase active tasks
|
||||||
|
const isNeet = user?.target_exam === "NEET";
|
||||||
setTasks([
|
setTasks([
|
||||||
{ type: "DPS", description: "Electrostatics + Kinematics", duration_minutes: 20, status: "Done" },
|
{ type: "DPS", description: isNeet ? "Genetics + Evolution" : "Electrostatics + Kinematics", duration_minutes: 20, status: "Done" },
|
||||||
{ type: "Mistake_Review", description: "Thermodynamics error queue", duration_minutes: 10, status: "Done" },
|
{ type: "Mistake_Review", description: isNeet ? "Human Physiology error queue" : "Thermodynamics error queue", duration_minutes: 10, status: "Done" },
|
||||||
{ type: "Micro_test", description: "GOC — General Organic Chemistry", duration_minutes: 5, status: "Pending" },
|
{ type: "Micro_test", description: "GOC — General Organic Chemistry", duration_minutes: 5, status: "Pending" },
|
||||||
{ 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: isNeet ? "Plant Anatomy (Biology)" : "Circles + Straight Lines (Maths)", duration_minutes: 15, status: "Pending" }
|
||||||
])
|
])
|
||||||
} else {
|
} else {
|
||||||
// Real user gets a fresh Day 1 plan with all tasks pending
|
// Real user gets a fresh Day 1 plan with all tasks pending
|
||||||
|
|||||||
@@ -28,10 +28,9 @@ export function StreakOnboardingModal() {
|
|||||||
// 1. Observe HTML class mutations (for immediate theme change detection)
|
// 1. Observe HTML class mutations (for immediate theme change detection)
|
||||||
const observer = new MutationObserver(() => {
|
const observer = new MutationObserver(() => {
|
||||||
const newTheme = checkTheme();
|
const newTheme = checkTheme();
|
||||||
setCurrentTheme(newTheme);
|
if (newTheme !== currentTheme) {
|
||||||
// Re-trigger streak celebration whenever theme transitions on dashboard
|
setCurrentTheme(newTheme);
|
||||||
setIsOpen(true);
|
}
|
||||||
setRenderCount((c) => c + 1);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
observer.observe(document.documentElement, {
|
observer.observe(document.documentElement, {
|
||||||
@@ -43,8 +42,6 @@ export function StreakOnboardingModal() {
|
|||||||
const handleThemeSwitched = (e: any) => {
|
const handleThemeSwitched = (e: any) => {
|
||||||
const t = e.detail?.theme || checkTheme();
|
const t = e.detail?.theme || checkTheme();
|
||||||
setCurrentTheme(t);
|
setCurrentTheme(t);
|
||||||
setIsOpen(true);
|
|
||||||
setRenderCount((c) => c + 1);
|
|
||||||
};
|
};
|
||||||
window.addEventListener("pragativeer-theme-switched", handleThemeSwitched);
|
window.addEventListener("pragativeer-theme-switched", handleThemeSwitched);
|
||||||
|
|
||||||
@@ -80,9 +77,8 @@ export function StreakOnboardingModal() {
|
|||||||
// Show popup if:
|
// Show popup if:
|
||||||
// 1. User account switched (demo plans: Neha, Rahul, Priya, Amit)
|
// 1. User account switched (demo plans: Neha, Rahul, Priya, Amit)
|
||||||
// 2. First time user visits (not yet dismissed in session)
|
// 2. First time user visits (not yet dismissed in session)
|
||||||
// 3. User is on a demo account
|
|
||||||
const hasSeen = sessionStorage.getItem(`hasSeenStreakPopup_${user.id}`);
|
const hasSeen = sessionStorage.getItem(`hasSeenStreakPopup_${user.id}`);
|
||||||
if (isDifferentUser || !hasSeen || isDemoUser(user.id)) {
|
if (isDifferentUser || !hasSeen) {
|
||||||
setIsOpen(true);
|
setIsOpen(true);
|
||||||
}
|
}
|
||||||
}, [user, loading]);
|
}, [user, loading]);
|
||||||
|
|||||||
Reference in New Issue
Block a user