diff --git a/frontend/src/Pages/Leaderboard.tsx b/frontend/src/Pages/Leaderboard.tsx index d4a4d4a..17bc191 100644 --- a/frontend/src/Pages/Leaderboard.tsx +++ b/frontend/src/Pages/Leaderboard.tsx @@ -26,6 +26,7 @@ import { GamificationEvent, } from "@/services/gamificationService"; import BadgeUnlocked from "@/components/BadgeUnlocked"; +import Top10Celebration from "@/components/Top10Celebration"; import { useUser } from "@/hooks/useUser"; interface Debater { @@ -86,7 +87,16 @@ const Leaderboard: React.FC = () => { }); const [sortCategory, setSortCategory] = useState("score"); const wsRef = useRef(null); + const previousRankRef = useRef(null); + const isInitialRenderRef = useRef(true); const { user } = useUser(); + const [top10Celebration, setTop10Celebration] = useState<{ + rank: number; + isOpen: boolean; + }>({ + rank: 0, + isOpen: false, + }); // Load initial leaderboard data useEffect(() => { @@ -226,6 +236,33 @@ const Leaderboard: React.FC = () => { (debater) => debater.currentUser ); + // Check if user entered top 10 and trigger celebration + useEffect(() => { + // Skip celebration on initial page load + if (isInitialRenderRef.current) { + if (currentUserIndex !== -1) { + previousRankRef.current = currentUserIndex + 1; + } + isInitialRenderRef.current = false; + return; + } + + if (currentUserIndex !== -1 && currentUserIndex < 10) { + const currentRank = currentUserIndex + 1; + // Only celebrate if user wasn't in top 10 before or improved their rank + if (previousRankRef.current !== null && previousRankRef.current > 10) { + // User just entered top 10! + setTop10Celebration({ rank: currentRank, isOpen: true }); + } else if (previousRankRef.current !== null && previousRankRef.current > currentRank && currentRank <= 3) { + // User improved to top 3! + setTop10Celebration({ rank: currentRank, isOpen: true }); + } + previousRankRef.current = currentRank; + } else if (currentUserIndex >= 10) { + previousRankRef.current = currentUserIndex + 1; + } + }, [currentUserIndex, sortedDebaters]); + const getVisibleDebaters = () => { if (!sortedDebaters.length) return []; const initialList = sortedDebaters @@ -268,6 +305,11 @@ const Leaderboard: React.FC = () => { isOpen={badgeUnlocked.isOpen} onClose={() => setBadgeUnlocked({ badgeName: "", isOpen: false })} /> + setTop10Celebration({ rank: 0, isOpen: false })} + />

Hone your skills and see how you stack up against top debaters! 🏆 @@ -315,9 +357,8 @@ const Leaderboard: React.FC = () => { {visibleDebaters.map((debater) => (

void; +} + +const Top10Celebration: React.FC = ({ rank, isOpen, onClose }) => { + const [showConfetti, setShowConfetti] = useState(false); + const [windowSize, setWindowSize] = useState({ width: 0, height: 0 }); + + useEffect(() => { + if (isOpen) { + setShowConfetti(true); + setWindowSize({ width: window.innerWidth, height: window.innerHeight }); + + // Hide confetti after 6 seconds + const timer = setTimeout(() => { + setShowConfetti(false); + }, 6000); + + return () => clearTimeout(timer); + } + }, [isOpen]); + + useEffect(() => { + const handleResize = () => { + setWindowSize({ width: window.innerWidth, height: window.innerHeight }); + }; + + window.addEventListener("resize", handleResize); + return () => window.removeEventListener("resize", handleResize); + }, []); + + const getRankContent = () => { + if (rank === 1) { + return { + icon: , + title: "🥇 You're #1!", + message: "You've reached the top of the leaderboard! You're the champion!", + confettiColors: ['#FFD700', '#FFA500', '#FF8C00', '#FF6347'], + }; + } else if (rank === 2) { + return { + icon: , + title: "🥈 Silver Position!", + message: "Amazing! You're in 2nd place on the leaderboard!", + confettiColors: ['#C0C0C0', '#A8A8A8', '#909090', '#B8B8B8'], + }; + } else if (rank === 3) { + return { + icon: , + title: "🥉 Bronze Achievement!", + message: "Fantastic! You've claimed 3rd place on the leaderboard!", + confettiColors: ['#CD7F32', '#B87333', '#A0522D', '#D2691E'], + }; + } else { + return { + icon: , + title: `🌟 Top 10! Rank #${rank}`, + message: "Congratulations! You've made it to the Top 10!", + confettiColors: ['#6366F1', '#8B5CF6', '#A855F7', '#D946EF'], + }; + } + }; + + const content = getRankContent(); + + return ( + <> + {showConfetti && ( + + )} + + + + {content.title} + + +
+
{content.icon}
+

{content.message}

+
+
+
+ +
+
+
+ + ); +}; + +export default Top10Celebration;