diff --git a/src/components/AssignmentView.tsx b/src/components/AssignmentView.tsx index 32fed52..5dedc53 100644 --- a/src/components/AssignmentView.tsx +++ b/src/components/AssignmentView.tsx @@ -1,4 +1,4 @@ -import { useState, useEffect, useCallback } from 'react' +import { useState, useEffect, useCallback, useRef } from 'react' import { Button } from '@/components/ui/button' import { Card } from '@/components/ui/card' import { Badge } from '@/components/ui/badge' @@ -53,6 +53,21 @@ interface AssignmentViewProps { emailConfigured?: boolean } +const CONFETTI_COLORS = ['#F43F5E', '#F59E0B', '#10B981', '#3B82F6', '#A855F7', '#EC4899'] +// Using a prime factor avoids repeating columns too early and helps spread 24 pieces across the viewport. +const CONFETTI_HORIZONTAL_SPACING_FACTOR = 11 +const CONFETTI_PIECES = Array.from({ length: 24 }, (_, index) => ({ + id: index, + color: CONFETTI_COLORS[index % CONFETTI_COLORS.length], + left: `${(index * CONFETTI_HORIZONTAL_SPACING_FACTOR) % 100}%`, + delay: (index % 6) * 0.08, + duration: 1.8 + (index % 5) * 0.2, + drift: ((index % 7) - 3) * 14 +})) +const CONFETTI_DISPLAY_DURATION_MS = ( + Math.max(...CONFETTI_PIECES.map((piece) => piece.delay + piece.duration)) * 1000 +) + export function AssignmentView({ game, participant, @@ -75,6 +90,8 @@ export function AssignmentView({ const [isConfirming, setIsConfirming] = useState(false) const [isRefreshing, setIsRefreshing] = useState(false) const [giverHasConfirmed, setGiverHasConfirmed] = useState(false) + const [showConfetti, setShowConfetti] = useState(false) + const lastConfettiReceiverId = useRef(null) // Refresh game data from API const refreshGameData = useCallback(async () => { @@ -142,6 +159,15 @@ export function AssignmentView({ return () => clearTimeout(timer) }, [currentReceiver]) + useEffect(() => { + if (!currentReceiver || lastConfettiReceiverId.current === currentReceiver.id) return + + lastConfettiReceiverId.current = currentReceiver.id + setShowConfetti(true) + const timer = setTimeout(() => setShowConfetti(false), CONFETTI_DISPLAY_DURATION_MS) + return () => clearTimeout(timer) + }, [currentReceiver]) + // Note: No mount-time refresh needed - game data is already loaded when entering this view // refreshGameData is available for manual refresh via the refresh button only @@ -454,6 +480,34 @@ export function AssignmentView({ return (
+ {showConfetti && ( + + )} +