Six site improvements picked from a "what could be better" review, plus a Jest
test suite covering the two areas with the trickiest money-handling history
in this project (early-bird pricing tranches, donation-leg accounting):
- "Add to calendar" .ics download on event pages and in confirmation emails
- sitemap.xml, robots.txt, and Open Graph/Twitter metadata for public pages
- Sentry error monitoring (backend + frontend), a no-op until SENTRY_DSN is set
- Nightly local pg_dump backups with a Site Settings tab to browse/trigger/download
- Admin audit trail for refunds, donations, manual registrations, event and
settings changes, and staff-initiated cancellations
- Jest tests reproducing and guarding against the 1.8.0 tranche-pricing bug
and the 1.4.2 donation-balance-inflation bug
Wallet passes (Google/Apple) were scoped out of this round — Apple Wallet
needs a paid Apple Developer account the project doesn't have yet, and the
user preferred shipping both together later rather than Google alone now.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- Registration confirmations attach an invoice PDF (itemized breakdown,
early-bird discount, balance due, Yoco pay-now link/QR) whenever a
balance is outstanding; payment/donation confirmations attach a
payment receipt PDF. Sent as an email attachment and, over WhatsApp,
as the PDF itself with the existing message as its caption.
- Users can also (re)send either document on demand: an "Invoice"
button on the registration detail popup, and a "Receipt" button next
to each payment there and on the Payment history page, each opening
an Email/WhatsApp choice popup, via two new endpoints restricted to
the registration/payment's own owner.
- Fix: editing an event option's early-bird tiers deleted and
recreated every tier for that option with brand-new ids, silently
severing the appliedTierId link on all historical purchases (losing
early-bird attribution and undercounting stock-limit usage) even for
tiers the admin didn't touch. Tiers are now upserted by id.
- Update the "My Events" help content and the API docs index for the
new endpoints.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Site Settings -> Branding now supports a Primary/Secondary/Accent brand color
system applied site-wide (buttons, nav, hover states, links) and to outgoing
email header/CTA colors, plus a favicon upload alongside the existing logo
upload, a live preview panel (website/email x desktop/mobile), and
logo-based color suggestions. The setup wizard's Branding step got the same
treatment. Fixes two related bugs found along the way: the setup wizard's
logo/favicon upload was missing its auth token, and a static favicon.ico in
Next's special app/ convention path was silently overriding the dynamic one.
Also replaces every "Hope Events"/"Hope Family Church" default (org name,
email subjects, WhatsApp messages, report metadata, API docs) with a neutral
"Cross Code" placeholder, and the optional legal settings (operator name, IO
details, website URL, effective date) with obviously-generic placeholders
instead of defaulting to real personal/organisational details -- since this
platform is deployed for multiple organisations. Adds SETTINGS.md documenting
every setting's default behaviour.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Assigning a donation to a registration was one-way: the refund flow
could reverse the money but left the donation's "leg" payment in
place, permanently locking that portion of the donation as used even
though it had been refunded back out. Adds POST
/api/payments/unassign-donation, which deletes the leg, reverts the
registration's status/tickets the same way a refund downgrade already
does, and notifies the registrant. New "Assigned donations" list on
the supervisor Payments page surfaces existing legs with an Unassign
action, since no such list existed before.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Multi-phase visual facelift (design tokens, dashboards, sidebar/navbar
shell, per-page help guides, and a layout/content pass across every
remaining page) plus backend fixes to the dashboard KPI stats:
- Admin/Supervisor dashboard KPIs (revenue, donations, registrations,
tickets sold) now use a rolling trailing-month window (today back one
calendar month, e.g. 9 May - 8 June if today is 8 June) instead of
calendar month-to-date, which under-counted for most of the month.
The comparison window shifts the same way, so like is still compared
with like.
- Reports deep-links from those stat tiles now match the same window
(range=trailing_month, replacing range=this_month).
- Design tokens (brand-* Tailwind scale + shadcn CSS variables), a
site-wide contextual help button, fixed dashboard sidebar/navbar,
Admin/Supervisor/Staff/User dashboard rebuilds backed by a new
GET /api/stats/overview endpoint, a dedicated Contact page, Site
Settings restyle with WhatsApp config folded in, and an Account
activity feed backed by a new SecurityEvent model.
- Every remaining page (home, events, registration flow, auth, legal,
payment results, and every Admin/Supervisor/Staff/User tool page)
restyled onto the same design tokens, several with real layout
upgrades (home hero, events list/detail, donate page, auth pages).
- 20+ new dedicated help guides so the whole site has page-specific
help content instead of falling back to a generic guide.
- Assorted fixes surfaced along the way: donation-leg double-counting
in payment stats, donations not counting toward revenue, refund
netting in per-method report breakdowns, and donation
over-allocation after a refund.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Financial correctness (donation-leg model):
- Donations are no longer mutated when assigned to a registration; assignment now
creates an immutable "leg" record referencing the original donation instead.
- Fixed several places where money was double-counted once a donation was partially
or fully assigned (Payments, Revenue summary, Cashup reconciliation, Finance
report, Profit report, Master Orders, Revenue Detailed).
- Payments now record who recorded them (recordedBy), separate from who they're for.
Cashup:
- Per-user cash denomination counting (optional, any time) replaces the single
event-wide manual entry; the event's cash actual is the live sum of these counts.
- New "Payment accountability by staff member" breakdown across all methods, and a
read-only "Report" tab that opens automatically once an event is closed.
Reports page redesign:
- New shell: sidebar of universal filters (events, date range, past/inactive/closed
toggles), searchable/categorized report grid, and a popup viewer with
Print/Email/Excel/WhatsApp actions plus an in-app Reporting Guide.
- Visual pass: colored stat tiles and bar charts on most reports, matching mockups.
- PDF exports (download/Print/Email/WhatsApp) now share a branded design mirroring
the web report — colored header, stat tiles, bar chart, highlighted totals.
- Excel export now produces a styled .xlsx (via exceljs) instead of a plain CSV.
- Master Orders' "Donations made" table is now included in every export channel.
Bug fixes discovered while testing exports:
- Report emails now go through the shared, DB-configurable mail utility instead of
a one-off transporter that ignored Site Settings SMTP config.
- WhatsApp report sends now surface the actual WAWP API error and auto-recover a
disconnected session, instead of a bare axios status-code message.
Also: Admin-editable notification preference, richer Admin Registrations dashboard,
{{payment.link}} placeholder for Email/WhatsApp Attendees, and background
email/WhatsApp attendee sending.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Per feedback, drop the dynamic /mypayments/methods lookup (wasn't
loading reliably) in favor of a static cash/card/eft/voucher/other
dropdown. Server-side normalization now folds any gateway-reported
method outside those four manual-entry values (apple_pay, google_pay,
yoco, etc.) into "other" instead of "card", both in the returned data
and in the filter query.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The previous fix hardcoded apple_pay/google_pay as extra filter options,
but Payment.method is free-text set by whatever the gateway reports, so
guessing at literal values was fragile and still didn't surface them for
this user. Add GET /api/payments/mypayments/methods returning the
distinct method values actually present in the user's payments, and have
the dashboard filter build its options from that instead. Also switch
the method filter from a startsWith match to an exact match, since the
values now come straight from the same column being filtered.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>