feat(registration): add watcher capacity limits and update pricing

Add 70-person capacity limit for watchers with real-time availability checks.
Update drink card pricing to €5 per person (was €5 base + €2 per guest).
Add feature flag to bypass registration countdown.
Show under-review notice for performer registrations.
Update FAQ performance duration from 5-7 to 5 minutes.
This commit is contained in:
2026-03-14 19:36:16 +01:00
parent 3a2e403ee9
commit 0d99f7c5f5
11 changed files with 162 additions and 39 deletions

View File

@@ -62,9 +62,11 @@ const REMINDER_24H_WINDOW_END = new Date(
// Shared helpers
// ---------------------------------------------------------------------------
/** Drink card price in euros: €5 base + €2 per extra guest. */
const MAX_WATCHERS = 70; // max total watcher spots (people, not registrations)
/** Drink card price in euros: €5 per person (primary + guests). */
function drinkCardEuros(guestCount: number): number {
return 5 + guestCount * 2;
return 5 + guestCount * 5;
}
/** Drink card price in cents for payment processing. */
@@ -301,6 +303,28 @@ export const appRouter = {
healthCheck: publicProcedure.handler(() => "OK"),
drinkkaart: drinkkaartRouter,
getWatcherCapacity: publicProcedure.handler(async () => {
const rows = await db
.select({ guests: registration.guests })
.from(registration)
.where(
and(
eq(registration.registrationType, "watcher"),
isNull(registration.cancelledAt),
),
);
const takenSpots = rows.reduce((sum, r) => {
const g = r.guests ? (JSON.parse(r.guests) as unknown[]) : [];
return sum + 1 + g.length;
}, 0);
return {
total: MAX_WATCHERS,
taken: takenSpots,
available: Math.max(0, MAX_WATCHERS - takenSpots),
isFull: takenSpots >= MAX_WATCHERS,
};
}),
privateData: protectedProcedure.handler(({ context }) => ({
message: "This is private",
user: context.session?.user,
@@ -313,6 +337,29 @@ export const appRouter = {
const isPerformer = input.registrationType === "performer";
const guests = isPerformer ? [] : (input.guests ?? []);
// Enforce max watcher capacity (70 people total, counting guests)
if (!isPerformer) {
const rows = await db
.select({ guests: registration.guests })
.from(registration)
.where(
and(
eq(registration.registrationType, "watcher"),
isNull(registration.cancelledAt),
),
);
const takenSpots = rows.reduce((sum, r) => {
const g = r.guests ? (JSON.parse(r.guests) as unknown[]) : [];
return sum + 1 + g.length;
}, 0);
const newSpots = 1 + guests.length;
if (takenSpots + newSpots > MAX_WATCHERS) {
throw new Error(
`Er zijn helaas niet genoeg plaatsen meer beschikbaar. Nog ${MAX_WATCHERS - takenSpots} ${MAX_WATCHERS - takenSpots === 1 ? "plaats" : "plaatsen"} vrij.`,
);
}
}
await db.insert(registration).values({
id: randomUUID(),
firstName: input.firstName,