fix(demo & streak): fix Neha demo account hang, enhance streak animation for light/dark modes and cries/celebration emojis

This commit is contained in:
2026-09-20 12:37:11 +05:30
parent c17785e38a
commit 6307a24c4c
4 changed files with 866 additions and 433 deletions

View File

@@ -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<string | null>(null);
const lastThemeRef = useRef<string | null>(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
<div
className="fixed inset-0 z-50 flex items-center justify-center bg-black/70 backdrop-blur-md animate-in fade-in duration-300"
onClick={handleDismiss}
>
{/* Centered Modal Card — stop propagation so inner clicks don't close */}
{/* Centered Modal Card */}
<div
className="bg-background border border-border shadow-[0_0_60px_-10px_rgba(12,96,96,0.25)] rounded-3xl w-[90vw] max-w-5xl h-[85vh] max-h-[800px] relative overflow-hidden animate-in zoom-in-95 slide-in-from-bottom-4 duration-300 z-20 pointer-events-auto mx-4 flex flex-col justify-start"
onClick={(e) => e.stopPropagation()}
>
{/* BUG-01 fix: X close button */}
{/* Close button */}
<button
onClick={handleDismiss}
className="absolute top-4 right-4 z-30 size-9 flex items-center justify-center rounded-full bg-background/80 border border-border text-muted-foreground hover:text-foreground hover:bg-secondary transition-all duration-200 shadow-sm"
className="absolute top-4 right-4 z-30 size-9 flex items-center justify-center rounded-full bg-background/80 border border-border text-muted-foreground hover:text-foreground hover:bg-secondary transition-all duration-200 shadow-sm cursor-pointer"
aria-label="Close streak modal"
>
<X size={18} />
@@ -80,16 +108,19 @@ export function StreakOnboardingModal() {
<div className="absolute -right-20 -top-20 size-48 bg-[var(--cyan-ai-10)] rounded-full blur-3xl pointer-events-none" />
<div className="absolute -left-20 -bottom-20 size-48 bg-primary/5 rounded-full blur-3xl pointer-events-none" />
{/* Top Celebration Animation Panel (Iframe with height 60%, matching bg) */}
{/* Top Celebration Animation Panel
key forces iframe remount when user switches or theme changes
so animation always replays fresh with the correct colours */}
<div className="relative w-full h-[60%] z-10 pointer-events-none overflow-hidden bg-background">
<iframe
src={`/streak-animation.html?streak=${streak}&theme=${activeTheme === 'light' ? 'light' : 'dark'}`}
key={iframeKey}
src={iframeSrc}
className="w-full h-full border-none"
title="Streak Celebration"
/>
</div>
{/* Bottom Greetings & Action Panel (height 40%, matching bg) */}
{/* Bottom Greetings & Action Panel (height 40%) */}
<div className="p-8 text-center flex-1 flex flex-col justify-between bg-background z-20">
<div className="space-y-4">
@@ -100,13 +131,13 @@ export function StreakOnboardingModal() {
<Flame size={16} className="fill-current animate-bounce" /> {streak} Day Streak Active
</div>
) : (
<div className="inline-flex items-center gap-2 px-4 py-1.5 bg-blue-500/10 border border-blue-500/30 text-blue-400 text-sm md:text-base font-extrabold rounded-full tracking-wider uppercase">
<div className="inline-flex items-center gap-2 px-4 py-1.5 bg-blue-500/10 border border-blue-500/30 text-blue-500 dark:text-blue-400 text-sm md:text-base font-extrabold rounded-full tracking-wider uppercase">
<Sparkles size={16} className="animate-spin" style={{ animationDuration: '4s' }} /> Day 1 Awaits
</div>
)}
{trialDaysLeft > 0 && (
<div className="inline-flex items-center gap-2 px-4 py-1.5 bg-emerald-500/10 border border-emerald-500/30 text-emerald-400 text-sm md:text-base font-semibold rounded-full tracking-wider">
<div className="inline-flex items-center gap-2 px-4 py-1.5 bg-emerald-500/10 border border-emerald-500/30 text-emerald-600 dark:text-emerald-400 text-sm md:text-base font-semibold rounded-full tracking-wider">
🎁 Free Trial: {trialDaysLeft} {trialDaysLeft === 1 ? "day" : "days"} remaining
</div>
)}
@@ -114,7 +145,7 @@ export function StreakOnboardingModal() {
{/* Greetings Text */}
<div className="space-y-3">
<h3 className="text-2xl md:text-3xl font-black tracking-tight bg-gradient-to-r dark:from-white dark:via-white dark:to-gray-400 from-black via-black to-gray-600 bg-clip-text text-transparent">
<h3 className="text-2xl md:text-3xl font-black tracking-tight bg-gradient-to-r dark:from-white dark:via-white dark:to-gray-400 from-gray-900 via-gray-900 to-gray-600 bg-clip-text text-transparent">
{wishTitle}
</h3>
<p className="text-muted-foreground text-sm md:text-base leading-relaxed max-w-lg mx-auto">
@@ -127,12 +158,14 @@ export function StreakOnboardingModal() {
<div className="pt-2 max-w-sm w-full mx-auto">
<Button
onClick={handleDismiss}
className="w-full h-12 rounded-xl text-base font-black bg-[var(--cyan-ai)] text-black hover:bg-[var(--cyan-ai-80)] shadow-[0_0_15px_-3px_var(--cyan-ai)] hover:shadow-[0_0_25px_var(--cyan-ai)] hover:-translate-y-0.5 transition-all duration-300"
className="w-full py-6 text-base font-bold bg-[var(--cyan-ai)] hover:opacity-90 text-white rounded-2xl shadow-lg transition-all transform active:scale-95 cursor-pointer"
>
Enter Workspace
Continue Preparation 🚀
</Button>
</div>
</div>
</div>
</div>
);