Vendor webhooks, free kids through 12, multi-origin lookup CORS
Children now free through age 12: - Scannable/paid ticket total = adults + youth 13-16 only; kids 12 & under (0-4, 5-9, 10-12) are stored but not counted (charging starts at 13). computeTotal + freeKidsCount in fields.ts, webhook guard, scan/admin badges, event-report labels, docs, and personas updated. Vendor booth webhooks (vendors.beartariacampgrounds.com): - New /vendor-webhook/food (2 named pass-holders) and /vendor-webhook/non-food (1 pass-holder), reusing WEBHOOK_SECRET. Booth name -> ticket title; each named person = one entry pass; tagged with a "Food Vendor"/"Vendor" Ticket Type (badge on scan + event-report rollup). Idempotent + QR email like the attendee hook. - Extracted shared FluentForms parsing (nameGroup/qty/selected/ addressLine/readDonor) into fluentforms.ts; attendee webhook now imports it. 13 new unit tests. Public lookup CORS is now a comma-separated allowlist; the caller's Origin is echoed only if it matches. tickets + vendors both allowed on donor-eligibility and ticket-vouchers. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
43fddec286
commit
7296555964
15 changed files with 368 additions and 91 deletions
|
|
@ -209,7 +209,7 @@ function TicketCard({ ticket, onAdjust }: { ticket: TicketView; onAdjust: (t: Ti
|
|||
if (e.rvParking) tags.push("🚐 RV");
|
||||
if (e.utv) tags.push("🏍️ UTV");
|
||||
if (e.iceAccess || ticket.ice.total > 0) tags.push(`🧊 ${ticket.ice.remaining}/${ticket.ice.total}`);
|
||||
if (e.freeUnder5 > 0) tags.push(`👶 ${e.freeUnder5} free`);
|
||||
if (e.freeKids > 0) tags.push(`👶 ${e.freeKids} free kids`);
|
||||
|
||||
return (
|
||||
<View style={styles.card}>
|
||||
|
|
|
|||
|
|
@ -346,7 +346,7 @@ function ExtrasRow({ ticket }: { ticket: TicketView }) {
|
|||
if (e.rvParking) tags.push("🚐 RV parking");
|
||||
if (e.utv) tags.push("🏍️ UTV/ATV");
|
||||
if (e.iceAccess || ticket.ice.total > 0) tags.push(`🧊 ${ticket.ice.remaining}/${ticket.ice.total} ice`);
|
||||
if (e.freeUnder5 > 0) tags.push(`👶 ${e.freeUnder5} under 5 (free)`);
|
||||
if (e.freeKids > 0) tags.push(`👶 ${e.freeKids} ${e.freeKids === 1 ? "kid" : "kids"} 12 & under (free)`);
|
||||
if (!tags.length) return null;
|
||||
return (
|
||||
<View style={styles.tags}>
|
||||
|
|
@ -365,6 +365,8 @@ const TYPE_ICON: Record<string, string> = {
|
|||
Performer: "🎭",
|
||||
Volunteer: "🙌",
|
||||
Speaker: "🎤",
|
||||
"Food Vendor": "🍔",
|
||||
Vendor: "🛒",
|
||||
};
|
||||
|
||||
function TypeBadge({ type }: { type: string }) {
|
||||
|
|
|
|||
|
|
@ -13,6 +13,8 @@ const TYPE_ICON: Record<string, string> = {
|
|||
Performer: "🎭",
|
||||
Volunteer: "🙌",
|
||||
Speaker: "🎤",
|
||||
"Food Vendor": "🍔",
|
||||
Vendor: "🛒",
|
||||
};
|
||||
const MEDAL = ["🥇", "🥈", "🥉"];
|
||||
|
||||
|
|
@ -138,9 +140,9 @@ export default function StatsScreen() {
|
|||
<View style={styles.card}>
|
||||
<Text style={styles.cardTitle}>Who's coming</Text>
|
||||
<View style={styles.tileRow}>
|
||||
<Tile value={stats.people.adults} label="adults" />
|
||||
<Tile value={stats.people.youth} label="youth 13-16" />
|
||||
<Tile value={stats.people.kids12 + stats.people.kids9} label="kids 5-12" />
|
||||
<Tile value={stats.people.adults} label="adults (paid)" />
|
||||
<Tile value={stats.people.youth} label="youth 13-16 (paid)" />
|
||||
<Tile value={stats.people.kids12 + stats.people.kids9} label="kids 5-12 (free)" />
|
||||
<Tile value={stats.people.kids4Free} label="under 5 (free)" />
|
||||
</View>
|
||||
</View>
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ export interface TicketView {
|
|||
isDonor: boolean;
|
||||
donorTier: string;
|
||||
vouchers: number;
|
||||
freeUnder5: number;
|
||||
freeKids: number;
|
||||
};
|
||||
ages: { bracket: string; count: number; free: boolean }[];
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue