feat: gate registration behind 16 March 2026 19:00 opening date
- Add REGISTRATION_OPENS_AT const in lib/opening.ts as single source of truth - Add useRegistrationOpen hook with live 1s countdown ticker - Add CountdownBanner component (DD/HH/MM/SS) with canvas-confetti on open - EventRegistrationForm shows countdown instead of form while closed - Login page hides/disables signup while closed; login always available - WatcherForm AccountModal guards signUp call as defence-in-depth Closes #2
This commit is contained in:
119
apps/web/src/components/homepage/CountdownBanner.tsx
Normal file
119
apps/web/src/components/homepage/CountdownBanner.tsx
Normal file
@@ -0,0 +1,119 @@
|
||||
import confetti from "canvas-confetti";
|
||||
import { useEffect, useRef } from "react";
|
||||
import { REGISTRATION_OPENS_AT } from "@/lib/opening";
|
||||
import { useRegistrationOpen } from "@/lib/useRegistrationOpen";
|
||||
|
||||
function pad(n: number): string {
|
||||
return String(n).padStart(2, "0");
|
||||
}
|
||||
|
||||
interface UnitBoxProps {
|
||||
value: string;
|
||||
label: string;
|
||||
}
|
||||
|
||||
function UnitBox({ value, label }: UnitBoxProps) {
|
||||
return (
|
||||
<div className="flex flex-col items-center gap-1">
|
||||
<div className="flex items-center justify-center rounded-sm bg-white/10 px-4 py-3 font-['Intro',sans-serif] text-5xl text-white tabular-nums md:text-6xl lg:text-7xl">
|
||||
{value}
|
||||
</div>
|
||||
<span className="font-['Intro',sans-serif] text-white/50 text-xs uppercase tracking-widest">
|
||||
{label}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
|
||||
const openDate = REGISTRATION_OPENS_AT.toLocaleDateString("nl-BE", {
|
||||
weekday: "long",
|
||||
day: "numeric",
|
||||
month: "long",
|
||||
year: "numeric",
|
||||
});
|
||||
const openTime = REGISTRATION_OPENS_AT.toLocaleTimeString("nl-BE", {
|
||||
hour: "2-digit",
|
||||
minute: "2-digit",
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="flex flex-col items-center gap-8 py-4 text-center">
|
||||
<div>
|
||||
<p className="font-['Intro',sans-serif] text-lg text-white/70 md:text-xl">
|
||||
Inschrijvingen openen op
|
||||
</p>
|
||||
<p className="mt-1 font-['Intro',sans-serif] text-white text-xl capitalize md:text-2xl">
|
||||
{openDate} om {openTime}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Countdown boxes */}
|
||||
<div className="flex flex-wrap items-center justify-center gap-3 md:gap-6">
|
||||
<UnitBox value={String(days)} label="dagen" />
|
||||
<span className="mb-6 font-['Intro',sans-serif] text-4xl text-white/40">
|
||||
:
|
||||
</span>
|
||||
<UnitBox value={pad(hours)} label="uren" />
|
||||
<span className="mb-6 font-['Intro',sans-serif] text-4xl text-white/40">
|
||||
:
|
||||
</span>
|
||||
<UnitBox value={pad(minutes)} label="minuten" />
|
||||
<span className="mb-6 font-['Intro',sans-serif] text-4xl text-white/40">
|
||||
:
|
||||
</span>
|
||||
<UnitBox value={pad(seconds)} label="seconden" />
|
||||
</div>
|
||||
|
||||
<p className="max-w-md text-sm text-white/50">
|
||||
Kom snel terug! Zodra de inschrijvingen openen kun je je hier
|
||||
registreren als toeschouwer of als artiest.
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user