Sell a set of tickets together for one price (e.g. 5 Adult + 3 Child for R500).
Bundles apply automatically, as many times as a registration's tickets allow, and
only when cheaper than the prices actually charged. Coupons come off afterwards.
- EventBundle/EventBundleItem/RegistrationBundle models and Registration.bundleDiscount
- Supervisor "Bundle deals" page, quote endpoint, bundle lines on invoices,
receipts, emails, WhatsApp and Yoco checkout, Discounts report rows
- Register form shows started bundles with "Complete bundle" and "+" buttons
- Demo: Family Fun Day sells a Family deal bundle instead of the Family pass ticket
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
set-demo now also stages ten past events with door sales, staff cash
counts, check-ins, refunds, donations and feedback. Their cashups run
through the real cashup code after the insert and cover every path:
quick close, full settlements (card fees, deposit fee, shortfall, cash
on hand, an unaccounted amount), close-reopen-close, and pending EFT
money received later or still outstanding. One recent event is left
open with the cash counted for a live demo. Funnel sessions older than
90 days are rolled up into daily totals, as the app does.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A bank charge on a cash deposit comes out of cash that was counted and
banked, so cash on hand is now the count less what reached the bank and
any bank-charge write-off. Before, a deposit fee showed as money "more
than expected".
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A full cashup now records, per payment method, how much of the expected
takings is in the bank, still on hand (cash) or pending (on the card
report or with a POP), and writes off what was lost to card fees, bank
charges or a shortfall. Whatever is left is shown as unaccounted; closing
with an unaccounted amount is allowed after a warning.
Pending money can be marked received or written off after the event is
closed, without reopening it. Reopening starts the next cashup from the
last one. Write-offs come off net profit and show as "Bank & card
charges" in the cashup, finance and profit reports.
The reports page also moves to the shared back link.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Every dashboard page now uses the shared BackLink, the small
"← Back to dashboard" link above the page title that the Waitlist page
already had, instead of a mix of grey Back buttons in different places.
Pages that had no way back (site settings, profile, donate, manual
registration) get one too.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
npm run set-demo wipes the database, keeping the site settings and the
admin account, and stages six upcoming events with registrations,
payments, tickets, a waitlist, camp cabins, coupons, costs and funnel
stats for showing the platform to other churches. Documented in
backend/DEMO_DATA.md.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ReportsV2 gets typed API responses (funnel, feedback summary, coupon
redemptions, cashup audit, consent) and derived rows. Master-order
quantities move into a per-row map, so an option name can no longer
overwrite a fixed column. Consent row types move to src/types. Removes
unused export helpers and state.
With the frontend now at zero lint errors and warnings, next build runs
ESLint again (eslint.ignoreDuringBuilds is gone).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Each door panel and modal gets typed props; unused setError/setInfo/
onCreated props are dropped. The current early-bird price helpers move
to src/lib/pricing.ts (shared with manual registration), dead fallbacks
for a legacy registration 'options' field are removed, and the refund
history now shows a refund's reason (stored in its status field) instead
of a 'reason' field that never exists.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Moves the duplicated registration-option pricing helper into
src/lib/pricing.ts and drops an unused users list from payments.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Adds typed payloads and results for the email/WhatsApp attendee and
broadcast endpoints. The WhatsApp page's messageDirty flag becomes a ref
(it only gated the template effect), and a no-op branch in the email
template effect is removed.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Cashup, users, profile, consent, allocations, event options, forms,
event tickets, ticket scanning and manual registration now use real
types. Data loaders are wrapped in useCallback and listed as effect
dependencies. Also adds shared Attendee, Section and form response
types.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Expand src/types with User, Event, EventOption, Registration, Payment,
Ticket, paginated results and dashboard stats, modelled on the Prisma
schema. apiFetch now defaults to unknown instead of any. Dashboard
loaders are wrapped in useCallback so their effects can list them as
dependencies.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Replace `catch (e: any)` with `catch (e)` and read the message through a
new errorMessage() helper. ApiError.data gets a real type, and
useDismissingState's setter keeps one identity across renders.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Internal links use next/link instead of <a> (no full page reload).
- Remove dead code: RegisterForm's unreachable Yoco checkout branch, the
unused AttendeeForms on the registration success page, unused Geist
fonts, and assorted unused variables and catch bindings.
- Escape quotes in JSX text; replace @ts-ignore in apiFetch with a cast.
- Keep the intentional raw <img> elements (data URLs, runtime API host)
with a documented lint exception; the event editor uses ApiImage.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Prisma 5.22 -> 7.10 using the @prisma/adapter-pg driver adapter; the
datasource URL moves to backend/prisma.config.js. All clients are built
in src/config/db.js, and index.js reuses the shared client.
- Backend: patch follow-redirects, drop nodemon for `node --watch`, and use
js-yaml 4 for the Jest coverage loader. npm audit reports 0.
- Frontend: override postcss-selector-parser to 7.1.7 (Tailwind 3 output
is unchanged). braces has no patched release and stays, build/lint only.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The cookie consent banner was fixed at bottom-0 under the z-50 BottomNav, so
on mobile it was hidden. It now sits above the nav (h-16 + safe-area) and
publishes its height as --cookie-banner-h, which HelpFab adds to its offset
so the banner no longer covers the help button either.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Yoco secret key and webhook secret now live in AppSetting (AES-256-GCM
encrypted), with .env as a fallback. A new Site Settings → Yoco tab shows
them masked card-style, registers a webhook with Yoco (storing the secret it
returns) or accepts one entered by hand, and lists/deletes the account's
webhooks. Generic settings endpoints never read or write these keys.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Transfers now send the new holder a link (to the email/phone typed) and
only complete once they accept; they can decline and the sender can
cancel. No response reveals whether contact details belong to an
account, and attendee account links are no longer sent to payers.
Terms of Use and Privacy Policy are updated for all features since
1.12, with a full cookie policy and a "Cookie settings" control. The
/docs route list now covers every mounted route, and account
activation auto-login is fixed.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CP62L9JxHomA36aYP1ekCe
Admins (with 2FA) can view the site as a non-admin user for up to 30
minutes, with a required reason and a "Viewing as" banner. It is
strictly read-only: a guard mounted before every route refuses any
write made with an impersonation token, protect/optionalAuth check the
session is live, and GET handlers skip the price-refresh writes. Start,
end, reason and every page/API call (including blocked attempts) are
audited; optional email to the user.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CP62L9JxHomA36aYP1ekCe
First-party, consent-based funnel tracking: a cookie notice asks once,
and only visitors who allow it get an anonymous visit id. Event page
views and registration starts are tracked from the browser; submit and
paid come from the registration. Adds the Registration funnel report
(conversion per stage, daily trend, by source/device/ticket option), a
conversion tile on the supervisor/admin dashboards, short-link source
tagging, and a daily rollup of raw rows older than 90 days.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CP62L9JxHomA36aYP1ekCe
A Feedback step in the event wizard builds a survey that is sent at a
chosen time to everyone who checked in (or everyone registered), with
personal links and one reminder after 3 days. Answers can be anonymous.
Adds rating (1-5 stars) and multiple-choice field types, also usable on
attendee forms, and a Feedback summary report with response rate,
average ratings, choice breakdowns, comments and event comparison.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Events can allow sponsorships: a "Sponsor a spot" button lets anyone
(guests included) pay through Yoco for whole spots or a custom amount,
anonymously or with a name and message. Payments land as event
donations in a sponsorship pool that supervisors assign with the
existing donation flow. Adds optional public progress towards a goal,
a sponsor receipt and Automations thank-you, pool columns in the
Donations report, and sponsorship labels/filter in Payments.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>