From e59a588a9d99cf7f73a34e0ab00f64f33402535c Mon Sep 17 00:00:00 2001 From: zias Date: Wed, 11 Mar 2026 10:39:51 +0100 Subject: [PATCH] feat: confetti Closes #5 --- .../components/homepage/CountdownBanner.tsx | 40 +------------------ .../homepage/EventRegistrationForm.tsx | 39 +++++++++++++++++- 2 files changed, 39 insertions(+), 40 deletions(-) diff --git a/apps/web/src/components/homepage/CountdownBanner.tsx b/apps/web/src/components/homepage/CountdownBanner.tsx index 230946e..fcbc7b7 100644 --- a/apps/web/src/components/homepage/CountdownBanner.tsx +++ b/apps/web/src/components/homepage/CountdownBanner.tsx @@ -1,5 +1,4 @@ -import confetti from "canvas-confetti"; -import { useEffect, useRef, useState } from "react"; +import { useState } from "react"; import { REGISTRATION_OPENS_AT } from "@/lib/opening"; import { useRegistrationOpen } from "@/lib/useRegistrationOpen"; import { client } from "@/utils/orpc"; @@ -26,34 +25,6 @@ function UnitBox({ value, label }: UnitBoxProps) { ); } -function fireConfetti() { - const colors = ["#d82560", "#52979b", "#d09035", "#214e51", "#ffffff"]; - - // Three bursts from different origins - confetti({ - particleCount: 120, - spread: 80, - origin: { x: 0.3, y: 0.6 }, - colors, - }); - setTimeout(() => { - confetti({ - particleCount: 120, - spread: 80, - origin: { x: 0.7, y: 0.6 }, - colors, - }); - }, 200); - setTimeout(() => { - confetti({ - particleCount: 80, - spread: 100, - origin: { x: 0.5, y: 0.5 }, - colors, - }); - }, 400); -} - // Reminder opt-in form — sits at the very bottom of the banner function ReminderForm() { const [email, setEmail] = useState(""); @@ -231,18 +202,9 @@ function ReminderForm() { /** * Shown in place of the registration form while registration is not yet open. - * Fires confetti the moment the gate opens (without a page reload). */ export function CountdownBanner() { const { isOpen, days, hours, minutes, seconds } = useRegistrationOpen(); - const confettiFired = useRef(false); - - useEffect(() => { - if (isOpen && !confettiFired.current) { - confettiFired.current = true; - fireConfetti(); - } - }, [isOpen]); // Once open the parent component will unmount this — but render nothing just in case if (isOpen) return null; diff --git a/apps/web/src/components/homepage/EventRegistrationForm.tsx b/apps/web/src/components/homepage/EventRegistrationForm.tsx index 2170746..9ca20b0 100644 --- a/apps/web/src/components/homepage/EventRegistrationForm.tsx +++ b/apps/web/src/components/homepage/EventRegistrationForm.tsx @@ -1,5 +1,6 @@ import { Link } from "@tanstack/react-router"; -import { useState } from "react"; +import confetti from "canvas-confetti"; +import { useEffect, useRef, useState } from "react"; import { CountdownBanner } from "@/components/homepage/CountdownBanner"; import { PerformerForm } from "@/components/registration/PerformerForm"; import { SuccessScreen } from "@/components/registration/SuccessScreen"; @@ -8,6 +9,33 @@ import { WatcherForm } from "@/components/registration/WatcherForm"; import { authClient } from "@/lib/auth-client"; import { useRegistrationOpen } from "@/lib/useRegistrationOpen"; +function fireConfetti() { + const colors = ["#d82560", "#52979b", "#d09035", "#214e51", "#ffffff"]; + + confetti({ + particleCount: 120, + spread: 80, + origin: { x: 0.3, y: 0.6 }, + colors, + }); + setTimeout(() => { + confetti({ + particleCount: 120, + spread: 80, + origin: { x: 0.7, y: 0.6 }, + colors, + }); + }, 200); + setTimeout(() => { + confetti({ + particleCount: 80, + spread: 100, + origin: { x: 0.5, y: 0.5 }, + colors, + }); + }, 400); +} + type RegistrationType = "performer" | "watcher"; interface SuccessState { @@ -19,6 +47,15 @@ interface SuccessState { export default function EventRegistrationForm() { const { data: session } = authClient.useSession(); const { isOpen } = useRegistrationOpen(); + const confettiFired = useRef(false); + + useEffect(() => { + if (isOpen && !confettiFired.current) { + confettiFired.current = true; + fireConfetti(); + } + }, [isOpen]); + const [selectedType, setSelectedType] = useState( null, );