- Waitlist per session: entries for pick-sessions tickets name a session; offers use that session's free places, never run past its start, and claim links tick it. Held offers count towards session capacity. - At the door: the Select items pop-up has the session picker, with today's session preselected for new attendees. - On pick-sessions events every ticket shows the session list straight away. "All sessions" tickets record the ticked sessions (RegistrationOption.sessionIds) at one price; scanning, attendance, certificates, reminders and capacity follow them. - Fix: per-session stock limits weren't enforced (takenCount filtered WaitlistEntry by a sessionId column that didn't exist, and the error was swallowed), and the public event page lost its stock info. - Fix: editing a door registration with picked sessions couldn't be saved. - Fix: the offline-scanning service worker served /_next/static cache-first forever, so dev browsers that had opened the scanner ran stale code. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
32 lines
1.9 KiB
TypeScript
32 lines
1.9 KiB
TypeScript
// Pure helpers shared by server and client components. Must not be "use client",
|
|
// or server pages calling these get a client reference instead of the function.
|
|
|
|
// sessionId: one session of a "pick sessions" option (those are waited for per session).
|
|
export type WaitlistLine = { eventOptionId: string; variantId?: string | null; sessionId?: string | null; label: string };
|
|
|
|
/** "Drop-in (Adult)" or "Drop-in — Class 2, Wed 4 Nov" for a waitlist entry. */
|
|
export function entryLineName(e: { eventOption: { name: string }; variant?: { name: string } | null; session?: { label?: string | null; startsAt: string } | null }): string {
|
|
const base = `${e.eventOption.name}${e.variant ? ` (${e.variant.name})` : ""}`;
|
|
if (!e.session) return base;
|
|
const when = new Date(e.session.startsAt).toLocaleDateString(undefined, { weekday: "short", day: "numeric", month: "short" });
|
|
return `${base} — ${e.session.label ? `${e.session.label}, ` : ""}${when}`;
|
|
}
|
|
|
|
/** The sold-out lines of an event, for the waitlist button. */
|
|
export function soldOutLines(options: { id: string; name: string; stockLimit?: number; availableCount?: number | null; variants?: { id: string; name: string; stockLimit?: number; availableCount?: number | null }[] | null }[]): WaitlistLine[] {
|
|
const out: WaitlistLine[] = [];
|
|
for (const o of options || []) {
|
|
const optOut = (o.stockLimit ?? 0) > 0 && o.availableCount !== undefined && o.availableCount !== null && o.availableCount <= 0;
|
|
const variants = o.variants || [];
|
|
if (variants.length > 0) {
|
|
for (const v of variants) {
|
|
const vOut = (v.stockLimit ?? 0) > 0 && v.availableCount !== undefined && v.availableCount !== null && v.availableCount <= 0;
|
|
if (optOut || vOut) out.push({ eventOptionId: o.id, variantId: v.id, label: `${o.name} (${v.name})` });
|
|
}
|
|
} else if (optOut) {
|
|
out.push({ eventOptionId: o.id, label: o.name });
|
|
}
|
|
}
|
|
return out;
|
|
}
|