diff --git a/backend/main.py b/backend/main.py index 87b0650..1346c7d 100644 --- a/backend/main.py +++ b/backend/main.py @@ -223,7 +223,7 @@ def login(payload: LoginPayload, session: Session = Depends(get_session)): )).first() token_id = str(demo_user.id) if demo_user else NEHA_UUID 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"} user = session.exec(select(models.User).where(models.User.email == payload.email)).first() diff --git a/public/streak-animation.html b/public/streak-animation.html index ecedf67..761a2aa 100644 --- a/public/streak-animation.html +++ b/public/streak-animation.html @@ -455,7 +455,7 @@ x: xr * W, y: -35 * 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, vr: (-0.02 + Math.random() * 0.04), life: 1, diff --git a/src/app/admin/layout.tsx b/src/app/admin/layout.tsx index f17498d..1a6fdc1 100644 --- a/src/app/admin/layout.tsx +++ b/src/app/admin/layout.tsx @@ -1,14 +1,21 @@ -"use client" +"use client" 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 { usePathname } from "next/navigation" +import { usePathname, useRouter } from "next/navigation" import { Logo } from "@/components/Logo" import { AdminThemeToggle } from "@/components/AdminThemeToggle" export default function AdminLayout({ children }: { children: ReactNode }) { const pathname = usePathname() + const router = useRouter() + + const handleLogout = () => { + localStorage.removeItem("auth_token"); + document.cookie = "auth_token=; path=/; max-age=0;"; + router.push("/login"); + }; return (
@@ -26,8 +33,15 @@ export default function AdminLayout({ children }: { children: ReactNode }) { } label="Students" active={pathname.startsWith("/admin/students")} /> } label="Coupons" active={pathname.startsWith("/admin/coupons")} /> -
+
} label="System Settings" active={pathname.startsWith("/admin/settings")} /> +
diff --git a/src/app/cbt/[examId]/page.tsx b/src/app/cbt/[examId]/page.tsx index f17be0f..2f55268 100644 --- a/src/app/cbt/[examId]/page.tsx +++ b/src/app/cbt/[examId]/page.tsx @@ -1,6 +1,7 @@ -"use client" +"use client" import { useEffect, useState, use, useRef } from "react" +import { useActiveUser } from "@/hooks/useActiveUser" import { Button } from "@/components/ui/button" 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" @@ -32,6 +33,9 @@ export default function CBTExamPage({ params }: { params: Promise<{ examId: stri const examId = resolvedParams.examId; const [mounted, setMounted] = useState(false); const [examType, setExamType] = useState('Test'); + const [testName, setTestName] = useState('AITS Full Syllabus Test'); + const { user } = useActiveUser(); + const userId = user?.id || 'b0e17378-864d-439e-8806-708dc48a891b'; const [showCalc, setShowCalc] = useState(false); const [showScratchpad, setShowScratchpad] = useState(false); const [showPeriodicTable, setShowPeriodicTable] = useState(false); @@ -95,7 +99,7 @@ export default function CBTExamPage({ params }: { params: Promise<{ examId: stri async function fetchExamData() { try { 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"); const testData = await testRes.json(); @@ -113,6 +117,7 @@ export default function CBTExamPage({ params }: { params: Promise<{ examId: stri } setExamType(testData.type); + if (testData.title) setTestName(testData.title); // Extract all question IDs from sections const questionIds: string[] = []; @@ -155,7 +160,7 @@ export default function CBTExamPage({ params }: { params: Promise<{ examId: stri } } fetchExamData(); - }, [examId, initExam]); + }, [examId, initExam, userId]); // Track time spent per question 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) { hasAutoSubmitted.current = true; submitExam('SUBMITTED'); - submitAttemptToServer('b0e17378-864d-439e-8806-708dc48a891b') + submitAttemptToServer(userId) .then(() => { 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 () => { setShowSubmitConfirm(false); submitExam('SUBMITTED'); - const payload = getAttemptPayload('b0e17378-864d-439e-8806-708dc48a891b'); + const payload = getAttemptPayload(userId); 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) { @@ -309,7 +314,7 @@ export default function CBTExamPage({ params }: { params: Promise<{ examId: stri } if (status === 'SUBMITTED' || status === 'TIMED_OUT') { - const payload = getAttemptPayload('b0e17378-864d-439e-8806-708dc48a891b'); + const payload = getAttemptPayload(userId); if (!payload) return
Loading results...
; return ( @@ -415,7 +420,7 @@ export default function CBTExamPage({ params }: { params: Promise<{ examId: stri
-

AITS Full Syllabus Test

+

{testName}

{currentQuestion.subject}

diff --git a/src/app/dashboard/dpp/page.tsx b/src/app/dashboard/dpp/page.tsx index 9ff4287..0883a31 100644 --- a/src/app/dashboard/dpp/page.tsx +++ b/src/app/dashboard/dpp/page.tsx @@ -1,4 +1,4 @@ -"use client" +"use client" import { Button } from "@/components/ui/button" 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]; 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; }); diff --git a/src/app/dashboard/layout.tsx b/src/app/dashboard/layout.tsx index 3dde3d1..3027485 100644 --- a/src/app/dashboard/layout.tsx +++ b/src/app/dashboard/layout.tsx @@ -398,6 +398,14 @@ export default function DashboardLayout({ children }: { children: ReactNode }) { + {/* Free Trial Banner */} + {trialInfo.isTrialActive && ( +
+ + 15 Days Free Trial — Day {trialInfo.trialDayNumber} +
+ )} + {/* Scrollable page body */}
{children} diff --git a/src/app/dashboard/leaderboard/page.tsx b/src/app/dashboard/leaderboard/page.tsx index c954e37..573514b 100644 --- a/src/app/dashboard/leaderboard/page.tsx +++ b/src/app/dashboard/leaderboard/page.tsx @@ -1,4 +1,4 @@ -"use client" +"use client" import { useState, useEffect } from "react" import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card" @@ -77,7 +77,7 @@ export default function LeaderboardPage() { 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'}`} > - School + Institute diff --git a/src/app/dashboard/page.tsx b/src/app/dashboard/page.tsx index e1c4e51..4768d1a 100644 --- a/src/app/dashboard/page.tsx +++ b/src/app/dashboard/page.tsx @@ -1,4 +1,4 @@ -"use client" +"use client" import { useRef, useState, useEffect } from "react" 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 isNeet = user?.target_exam === "NEET" return (
@@ -188,7 +189,9 @@ export default function DashboardPage() {
-

Biology: Human Physiology

+

+ {isNeet ? "Biology: Human Physiology" : "Maths: Integral Calculus"} +

30 Questions • Medium Priority

@@ -205,7 +208,9 @@ export default function DashboardPage() {
-

Biology: Human Physiology

+

+ {isNeet ? "Biology: Human Physiology" : "Maths: Integral Calculus"} +

Pro Feature

diff --git a/src/components/AI/CollegePredictorCard.tsx b/src/components/AI/CollegePredictorCard.tsx index 60caa0d..1732bbe 100644 --- a/src/components/AI/CollegePredictorCard.tsx +++ b/src/components/AI/CollegePredictorCard.tsx @@ -1,11 +1,11 @@ -"use client" +"use client" import { useEffect, useState } from "react" import { Card, CardContent } from "@/components/ui/card" import { RefreshCw, TrendingUp, MapPin, Star } from "lucide-react" import { useActiveUser } from "@/hooks/useActiveUser" -const MOCK_COLLEGES: Record> = { +const MOCK_COLLEGES_JEE: Record> = { Reach: [ { 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" }, @@ -29,6 +29,23 @@ const MOCK_COLLEGES: Record> = { + 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) => { if (match >= 80) return "text-green-500 bg-green-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() { const [loading, setLoading] = useState(true) const [activeTab, setActiveTab] = useState("Target") - const { userId, loading: userLoading } = useActiveUser() + const { userId, user, loading: userLoading } = useActiveUser() useEffect(() => { 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 = { Reach: "bg-purple-500/10 border-purple-500/20 text-purple-400", @@ -128,8 +146,8 @@ export function CollegePredictorCard() { {/* Bottom Tip */}
- {activeTab === "Reach" && "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 === "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" && (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."}
diff --git a/src/components/AI/StudyPlanCard.tsx b/src/components/AI/StudyPlanCard.tsx index 1a1f21c..015607e 100644 --- a/src/components/AI/StudyPlanCard.tsx +++ b/src/components/AI/StudyPlanCard.tsx @@ -1,4 +1,4 @@ -"use client" +"use client" import { useEffect, useState } from "react" import { Card, CardContent } from "@/components/ui/card" @@ -10,7 +10,7 @@ import { useActiveUser, isDemoUser } from "@/hooks/useActiveUser" export function StudyPlanCard() { const [tasks, setTasks] = useState([]) const [loading, setLoading] = useState(true) - const { userId, loading: userLoading } = useActiveUser(); + const { userId, user, loading: userLoading } = useActiveUser(); useEffect(() => { if (userLoading || !userId) return; @@ -30,12 +30,13 @@ export function StudyPlanCard() { } if (isDemoUser(userId)) { // Demo user gets showcase active tasks + const isNeet = user?.target_exam === "NEET"; setTasks([ - { type: "DPS", description: "Electrostatics + Kinematics", duration_minutes: 20, status: "Done" }, - { type: "Mistake_Review", description: "Thermodynamics error queue", duration_minutes: 10, status: "Done" }, + { type: "DPS", description: isNeet ? "Genetics + Evolution" : "Electrostatics + Kinematics", duration_minutes: 20, 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: "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 { // Real user gets a fresh Day 1 plan with all tasks pending diff --git a/src/components/Dashboard/StreakOnboardingModal.tsx b/src/components/Dashboard/StreakOnboardingModal.tsx index 6277dce..77265e2 100644 --- a/src/components/Dashboard/StreakOnboardingModal.tsx +++ b/src/components/Dashboard/StreakOnboardingModal.tsx @@ -28,10 +28,9 @@ export function StreakOnboardingModal() { // 1. Observe HTML class mutations (for immediate theme change detection) const observer = new MutationObserver(() => { const newTheme = checkTheme(); - setCurrentTheme(newTheme); - // Re-trigger streak celebration whenever theme transitions on dashboard - setIsOpen(true); - setRenderCount((c) => c + 1); + if (newTheme !== currentTheme) { + setCurrentTheme(newTheme); + } }); observer.observe(document.documentElement, { @@ -43,8 +42,6 @@ export function StreakOnboardingModal() { const handleThemeSwitched = (e: any) => { const t = e.detail?.theme || checkTheme(); setCurrentTheme(t); - setIsOpen(true); - setRenderCount((c) => c + 1); }; window.addEventListener("pragativeer-theme-switched", handleThemeSwitched); @@ -80,9 +77,8 @@ export function StreakOnboardingModal() { // Show popup if: // 1. User account switched (demo plans: Neha, Rahul, Priya, Amit) // 2. First time user visits (not yet dismissed in session) - // 3. User is on a demo account const hasSeen = sessionStorage.getItem(`hasSeenStreakPopup_${user.id}`); - if (isDifferentUser || !hasSeen || isDemoUser(user.id)) { + if (isDifferentUser || !hasSeen) { setIsOpen(true); } }, [user, loading]);