From 6307a24c4ca03a4a31307dc2aaf00b7936411c94 Mon Sep 17 00:00:00 2001 From: ectasy663 Date: Sun, 20 Sep 2026 12:37:11 +0530 Subject: [PATCH] fix(demo & streak): fix Neha demo account hang, enhance streak animation for light/dark modes and cries/celebration emojis --- backend/main.py | 36 +- public/streak-animation.html | 1179 +++++++++++------ src/app/dashboard/layout.tsx | 13 +- .../Dashboard/StreakOnboardingModal.tsx | 71 +- 4 files changed, 866 insertions(+), 433 deletions(-) diff --git a/backend/main.py b/backend/main.py index 6ef6431..87b0650 100644 --- a/backend/main.py +++ b/backend/main.py @@ -660,7 +660,7 @@ DEMO_DEFINITIONS = { }, UUID("33333333-3333-3333-3333-333333333333"): { "name": "Neha Verma", - "email": "demo@pragativeer.com", + "email": "neha.demo@pragativeer.com", "phone": "9999999997", "student_class": models.ClassEnum.TWELFTH, "medium": models.MediumEnum.ENGLISH, @@ -708,18 +708,30 @@ def ensure_demo_user(user_id: UUID, session: Session) -> Optional[models.User]: if user: return user if user_id in DEMO_DEFINITIONS: - data = DEMO_DEFINITIONS[user_id] + data = DEMO_DEFINITIONS[user_id].copy() import bcrypt - new_demo = models.User( - id=user_id, - password_hash=bcrypt.hashpw("Pv9#kL2$mQ8!vX4@".encode('utf-8'), bcrypt.gensalt()).decode('utf-8'), - last_active=datetime.utcnow(), - **data - ) - session.add(new_demo) - session.commit() - session.refresh(new_demo) - return new_demo + try: + # Defensive duplicate check + existing_email = session.exec(select(models.User).where(models.User.email == data["email"])).first() + if existing_email and existing_email.id != user_id: + data["email"] = f"demo_{str(user_id)[:8]}@pragativeer.com" + existing_phone = session.exec(select(models.User).where(models.User.phone == data["phone"])).first() + if existing_phone and existing_phone.id != user_id: + data["phone"] = f"99{str(int(user_id.int))[-8:]}" + + new_demo = models.User( + id=user_id, + password_hash=bcrypt.hashpw("Pv9#kL2$mQ8!vX4@".encode('utf-8'), bcrypt.gensalt()).decode('utf-8'), + last_active=datetime.utcnow(), + **data + ) + session.add(new_demo) + session.commit() + session.refresh(new_demo) + return new_demo + except Exception: + session.rollback() + return session.get(models.User, user_id) return None @app.get("/api/user/switch", tags=["Users"]) diff --git a/public/streak-animation.html b/public/streak-animation.html index 597e128..c105054 100644 --- a/public/streak-animation.html +++ b/public/streak-animation.html @@ -1,450 +1,833 @@ + + + -PRAGATIVEER · Reveal +PRAGATIVEER · Streak - + + + +
-
+
-
+
+
+
+
+
- -
-
0
-
GROWTH SCORE
-
- -
- - - - -
-
-
- under 5 = sad · higher = bigger celebration +
✨ Click badge to replay
+ // Click badge/stage to replay animation anytime + stage.addEventListener('click', () => { + play(); + }); - - \ No newline at end of file + + \ No newline at end of file diff --git a/src/app/dashboard/layout.tsx b/src/app/dashboard/layout.tsx index 0f9fe62..3dde3d1 100644 --- a/src/app/dashboard/layout.tsx +++ b/src/app/dashboard/layout.tsx @@ -1,4 +1,4 @@ -"use client" +"use client" import { ReactNode, useState, useEffect, useRef } from "react" import { BookOpen, LayoutDashboard, Settings, FileText, Activity, LineChart, Trophy, User, ChevronUp, Lock, ShieldAlert, Flame, Target, Gauge, Zap, Calendar, Clock, BrainCircuit, BarChart3, School, CheckSquare } from "lucide-react" @@ -358,9 +358,14 @@ export default function DashboardLayout({ children }: { children: ReactNode }) {
{/* Streak count moved to top header */} {user && ( - + )} {/* Demo Switcher — only visible when logged in as a known demo account */} @@ -381,7 +386,7 @@ export default function DashboardLayout({ children }: { children: ReactNode }) { - {/* */} +
)} diff --git a/src/components/Dashboard/StreakOnboardingModal.tsx b/src/components/Dashboard/StreakOnboardingModal.tsx index 48c608b..3767522 100644 --- a/src/components/Dashboard/StreakOnboardingModal.tsx +++ b/src/components/Dashboard/StreakOnboardingModal.tsx @@ -1,6 +1,6 @@ -"use client"; +"use client"; -import { useEffect, useState } from "react"; +import { useEffect, useState, useRef } from "react"; import { Button } from "@/components/ui/button"; import { Flame, Sparkles, X } from "lucide-react"; import { useActiveUser } from "@/hooks/useActiveUser"; @@ -8,23 +8,48 @@ import { useTheme } from "next-themes"; export function StreakOnboardingModal() { const { user, loading } = useActiveUser(); - const { resolvedTheme, theme } = useTheme(); - const activeTheme = resolvedTheme || theme || "dark"; + const { resolvedTheme } = useTheme(); + const activeTheme = resolvedTheme ?? "light"; + const [isOpen, setIsOpen] = useState(false); + const lastUserIdRef = useRef(null); + const lastThemeRef = useRef(null); useEffect(() => { if (loading || !user) return; - // Check sessionStorage + // Detect theme transitions on dashboard + const isFirstThemeLoad = lastThemeRef.current === null; + const themeChanged = !isFirstThemeLoad && lastThemeRef.current !== activeTheme; + lastThemeRef.current = activeTheme; + + // Detect account switches (e.g. demo switcher between Neha, Rahul, Priya, Amit) + const userChanged = lastUserIdRef.current !== null && lastUserIdRef.current !== user.id; + lastUserIdRef.current = user.id; + + // First visit check per user account const hasSeen = sessionStorage.getItem(`hasSeenStreakPopup_${user.id}`); - if (!hasSeen) { + + // Re-trigger modal when: + // 1) First time user visits (has not seen popup) + // 2) User account switched (e.g., Demo switch between Neha, Priya, Rahul, Amit) + // 3) Theme switched on dashboard (light <-> dark transition for testing & visual feedback) + if (!hasSeen || userChanged || themeChanged) { setIsOpen(true); } - }, [user, loading]); + }, [user, loading, activeTheme]); + + // Listen for global custom trigger event (e.g. clicking streak counter in top navbar) + useEffect(() => { + const handleOpen = () => setIsOpen(true); + window.addEventListener("pragativeer-show-streak-animation", handleOpen); + return () => window.removeEventListener("pragativeer-show-streak-animation", handleOpen); + }, []); if (!isOpen || !user) return null; const streak = user.streak_count; + const isSad = streak <= 0; const targetExam = user.target_exam?.replace("_", " ") || "JEE/NEET"; // Streak-based wishes and titles @@ -56,21 +81,24 @@ export function StreakOnboardingModal() { trialDaysLeft = Math.ceil(diffTime / (1000 * 60 * 60 * 24)); } + // Key forces iframe remount whenever user or theme changes so animation replays fresh + const iframeKey = `${user.id}-${activeTheme}-${streak}`; + const iframeSrc = `/streak-animation.html?streak=${streak}&theme=${activeTheme}${isSad ? '&mood=sad' : ''}`; + return ( - // BUG-01 fix: backdrop click dismisses modal
- {/* Centered Modal Card — stop propagation so inner clicks don't close */} + {/* Centered Modal Card */}
e.stopPropagation()} > - {/* BUG-01 fix: X close button */} + {/* Close button */}