Skip to content

Commit e212196

Browse files
authored
Merge pull request #376 from Calebux/feat/confetti-winning
Feat/confetti winning
2 parents 180d5bc + d50a672 commit e212196

2 files changed

Lines changed: 46 additions & 2 deletions

File tree

client/package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
"@stellar/stellar-sdk": "^14.4.3",
2222
"@supabase/supabase-js": "^2.76.1",
2323
"@tailwindcss/vite": "^4.1.13",
24+
"canvas-confetti": "^1.9.4",
2425
"lucide-react": "^0.544.0",
2526
"react": "^19.2.0",
2627
"react-dom": "^19.2.0",
@@ -35,6 +36,7 @@
3536
"@playwright/test": "^1.43.0",
3637
"@testing-library/jest-dom": "^5.16.5",
3738
"@testing-library/react": "^13.4.0",
39+
"@types/canvas-confetti": "^1.9.0",
3840
"@types/node": "^25.0.9",
3941
"@types/react": "^19.1.10",
4042
"@types/react-dom": "^19.1.7",
@@ -51,4 +53,4 @@
5153
"vite": "^7.1.2",
5254
"vitest": "^1.4.1"
5355
}
54-
}
56+
}

client/src/pages/RaffleDetails.tsx

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
import { useEffect } from "react";
1+
import { useEffect, useRef } from "react";
2+
import confetti from "canvas-confetti";
3+
import { useWalletContext } from "../providers/WalletProvider";
24
import { useParams, useSearchParams } from "react-router-dom";
35
import { toast } from "sonner";
46
import { useRaffle } from "../hooks/useRaffles";
@@ -16,6 +18,8 @@ const RaffleDetails = () => {
1618
const [searchParams] = useSearchParams();
1719
const raffleId = id ?? searchParams.get("raffle");
1820
const parsedRaffleId = raffleId ? parseInt(raffleId, 10) : 0;
21+
const { address } = useWalletContext();
22+
const hasCelebrated = useRef(false);
1923

2024
const { raffle, error, isLoading, refetch } = useRaffle(parsedRaffleId);
2125

@@ -25,6 +29,30 @@ const RaffleDetails = () => {
2529
return () => window.removeEventListener("focus", handleFocus);
2630
}, [refetch]);
2731

32+
useEffect(() => {
33+
if (raffle?.winner && address === raffle.winner && !hasCelebrated.current) {
34+
const duration = 3 * 1000;
35+
const animationEnd = Date.now() + duration;
36+
const defaults = { startVelocity: 30, spread: 360, ticks: 60, zIndex: 0 };
37+
38+
const randomInRange = (min: number, max: number) => Math.random() * (max - min) + min;
39+
40+
const interval: any = setInterval(function () {
41+
const timeLeft = animationEnd - Date.now();
42+
43+
if (timeLeft <= 0) {
44+
return clearInterval(interval);
45+
}
46+
47+
const particleCount = 50 * (timeLeft / duration);
48+
confetti({ ...defaults, particleCount, origin: { x: randomInRange(0.1, 0.3), y: Math.random() - 0.2 } });
49+
confetti({ ...defaults, particleCount, origin: { x: randomInRange(0.7, 0.9), y: Math.random() - 0.2 } });
50+
}, 250);
51+
52+
hasCelebrated.current = true;
53+
}
54+
}, [raffle?.winner, address]);
55+
2856
const statusLabel = raffle?.status?.toUpperCase() || "UNKNOWN";
2957
const lowerStatus = raffle?.status?.toLowerCase() || "";
3058
const isFinalized = lowerStatus === "closed" || lowerStatus === "finalized" || lowerStatus === "cancelled";
@@ -81,6 +109,20 @@ const RaffleDetails = () => {
81109
/>
82110
</div>
83111

112+
{raffle?.winner && address === raffle.winner && (
113+
<div className="bg-gradient-to-r from-yellow-400 via-pink-500 to-purple-600 p-1 rounded-3xl mb-6 animate-pulse">
114+
<div className="bg-white dark:bg-[#11172E] rounded-[22px] p-8 text-center shadow-2xl">
115+
<div className="text-5xl mb-4">🏆</div>
116+
<h2 className="text-3xl md:text-4xl font-black text-gray-900 dark:text-white mb-2 tracking-tight">
117+
CONGRATULATIONS!
118+
</h2>
119+
<p className="text-lg text-gray-600 dark:text-pink-200 font-medium">
120+
You are the winner of this raffle! 🎉
121+
</p>
122+
</div>
123+
</div>
124+
)}
125+
84126
<RaffleDetailsCard
85127
image={raffle.image || detailimage}
86128
images={raffle.metadata?.images}

0 commit comments

Comments
 (0)