Rework webhook + model for the 2026 Tickets form
New form schema: up to 10 named adults, youth 13-16, kids 10-12 / 5-9 / 0-4, donor tier/vouchers (from the lookups), parking/RV/UTV/ice payment fields. - Scannable total = adults + youth + kids 10-12 + kids 5-9 (kids 0-4 free). - Store + display adult names on a good scan; show donor tier, UTV, vouchers. - Ice: payment_ice = 1-4 tickets ($20 each), 1 ticket = 3 bags. - New NocoDB 2026 schema (with Id PK); webhook parses compound names, quantity/payment fields (nested objects or money strings). - Our webhook keeps sending the QR ticket email (FluentForms sends the receipt); from address is now info@beartariacampgrounds.com. Updated /test personas, /webhook-doc, tests, and the app display. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
718d1515b0
commit
b7c77afe02
14 changed files with 360 additions and 210 deletions
|
|
@ -201,11 +201,14 @@ function TicketCard({ ticket, onAdjust }: { ticket: TicketView; onAdjust: (t: Ti
|
|||
}, [ticket.redeemed, showHistory]);
|
||||
|
||||
const tags: string[] = [];
|
||||
if (ticket.extras.carParking) tags.push("🚗 Car");
|
||||
if (ticket.extras.rvParking) tags.push("🚐 RV");
|
||||
if (ticket.extras.iceAccess) tags.push("🧊 Ice");
|
||||
if (ticket.extras.isDonor) tags.push("⭐ Donor");
|
||||
if (ticket.extras.freeUnder4 > 0) tags.push(`👶 ${ticket.extras.freeUnder4} free`);
|
||||
const e = ticket.extras;
|
||||
if (e.donorTier === "member") tags.push("🐻 Member");
|
||||
else if (e.isDonor) tags.push("⭐ Donor");
|
||||
if (e.carParking) tags.push("🚗 Car");
|
||||
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`);
|
||||
|
||||
return (
|
||||
<View style={styles.card}>
|
||||
|
|
|
|||
|
|
@ -259,6 +259,7 @@ export default function ScannerScreen() {
|
|||
<Text style={styles.counts}>
|
||||
{ticket.redeemed} of {ticket.total} redeemed · {ticket.remaining} remaining
|
||||
</Text>
|
||||
<AdultNames names={ticket.adultNames} />
|
||||
<ExtrasRow ticket={ticket} />
|
||||
</>
|
||||
)}
|
||||
|
|
@ -345,11 +346,14 @@ function BanquetResult({ donor, ticketName }: { donor: DonorLookup | null; ticke
|
|||
|
||||
function ExtrasRow({ ticket }: { ticket: TicketView }) {
|
||||
const tags: string[] = [];
|
||||
if (ticket.extras.carParking) tags.push("🚗 Car parking");
|
||||
if (ticket.extras.rvParking) tags.push("🚐 RV parking");
|
||||
if (ticket.extras.iceAccess || ticket.ice.total > 0) tags.push(`🧊 ${ticket.ice.remaining}/${ticket.ice.total} ice`);
|
||||
if (ticket.extras.isDonor) tags.push("⭐ Donor");
|
||||
if (ticket.extras.freeUnder4 > 0) tags.push(`👶 ${ticket.extras.freeUnder4} under 4 (free)`);
|
||||
const e = ticket.extras;
|
||||
if (e.donorTier === "member") tags.push("🐻 Member");
|
||||
else if (e.isDonor) tags.push("⭐ Donor");
|
||||
if (e.carParking) tags.push("🚗 Car parking");
|
||||
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 (!tags.length) return null;
|
||||
return (
|
||||
<View style={styles.tags}>
|
||||
|
|
@ -362,6 +366,19 @@ function ExtrasRow({ ticket }: { ticket: TicketView }) {
|
|||
);
|
||||
}
|
||||
|
||||
function AdultNames({ names }: { names: string[] }) {
|
||||
if (!names.length) return null;
|
||||
return (
|
||||
<View style={styles.namesBox}>
|
||||
{names.map((n, i) => (
|
||||
<Text key={i} style={styles.nameLine}>
|
||||
{n}
|
||||
</Text>
|
||||
))}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
function ConfirmCard({
|
||||
ticket,
|
||||
isIce,
|
||||
|
|
@ -393,6 +410,7 @@ function ConfirmCard({
|
|||
<Text style={{ color: theme.successBright, fontWeight: "800" }}>{remaining}</Text> of {total} {unit} remaining
|
||||
</Text>
|
||||
<Text style={styles.cardSub}>{redeemed} already redeemed</Text>
|
||||
{!isIce && <AdultNames names={ticket.adultNames} />}
|
||||
{!isIce && <ExtrasRow ticket={ticket} />}
|
||||
{isIce && total === 0 && <Text style={styles.exhausted}>This ticket did not prepay for ice.</Text>}
|
||||
|
||||
|
|
@ -489,6 +507,8 @@ const styles = StyleSheet.create({
|
|||
donorFigureDivider: { width: 1, alignSelf: "stretch", backgroundColor: "rgba(255,255,255,0.35)", marginVertical: 8 },
|
||||
donorEmail: { color: "rgba(255,255,255,0.85)", fontSize: 14, marginTop: 18 },
|
||||
|
||||
namesBox: { marginTop: 12, alignItems: "center", gap: 3 },
|
||||
nameLine: { color: "#fff", fontSize: 18, fontWeight: "600", textAlign: "center" },
|
||||
tags: { flexDirection: "row", flexWrap: "wrap", justifyContent: "center", gap: 8, marginTop: 14 },
|
||||
tag: { color: "#fff", backgroundColor: "rgba(255,255,255,0.18)", paddingHorizontal: 10, paddingVertical: 5, borderRadius: 999, fontSize: 13, overflow: "hidden" },
|
||||
|
||||
|
|
|
|||
|
|
@ -25,12 +25,16 @@ export interface TicketView {
|
|||
redeemed: number;
|
||||
remaining: number;
|
||||
ice: ResourceCount;
|
||||
adultNames: string[];
|
||||
extras: {
|
||||
carParking: boolean;
|
||||
rvParking: boolean;
|
||||
utv: boolean;
|
||||
iceAccess: boolean;
|
||||
isDonor: boolean;
|
||||
freeUnder4: number;
|
||||
donorTier: string;
|
||||
vouchers: number;
|
||||
freeUnder5: number;
|
||||
};
|
||||
ages: { bracket: string; count: number; free: boolean }[];
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue