Event finance
- Finance tab on the event page: P&L summary with a revenue-to-result
waterfall, costs and other income in one ledger, and the partner split
with payouts. Lifecycle stepper (Selling, Adding costs, Ready to close,
Finalized) with what is left to do; closing the books goes through a
checklist dialog that freezes the numbers.
- Expense modal shows only the fields each calculation type needs, a live
preview with the event's real counts, and a category suggested from the
description. Date inputs follow the UI language.
- Global Finance page: profit per event with outliers clipped and labelled,
and a "Ready to close" list of past events whose books are still open.
- Calculation service (integer PYG, basis points) with pinned regression
scenarios; PYG formatting centralised in lib/money with locale-aware
separators.
Event team permissions
- Per-event members with role presets and requireEventPermission; the
header stat "Confirmed" is relabelled "Not checked in yet", which is
what it counts.
Public sales state
- One sales state (online, door, sold out, ended, external, cancelled) for
the event page, listings and JSON-LD, with the door price and tenders
shown only while people can still pay at the door.
Frontend unit tests run with vitest (npm test in frontend/).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Events can now set an optional walk-in price (events.walk_in_price):
null falls back to the ticket price, 0 is a free walk-in. It is set in
the Create/Edit Event modal (EN/ES) and carried through create, update
and duplicate, but it is internal: public event responses and the
attendee-facing ticket/dashboard endpoints drop it, and only admin,
organizer and staff callers receive it.
The door screen no longer trusts the amount the client sends. It sends
a quantity and the server prices the charge from the event record:
walk-ins pay the walk-in price, existing tickets the ticket price. A
typed amount is only honoured with amountOverride from admin/organizer
and is written to audit_logs in the same transaction. The charged
amount stays snapshotted on the payment row.
POS is a new door tender for the physical card terminal. Staff pick
POS, see the amount to key in, charge the card, then confirm with
"Mark as paid"; it is then recorded like any other door payment
(provider and method 'pos'). It can be switched off globally or per
event via payment options, and shows up in the payments filters,
bookings, revenue summaries and door takings. PosChargePanel is where
an automatic terminal push would go later; nothing calls a terminal
today.
tickets.booking_source (online | walk_in | admin) records how a booking
was made. The migration backfills walk-ins from the door screen's
idempotency records; everything else stays online.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Events gain nullable presale_closure_enabled / presale_close_minutes_before
overrides; null inherits the new site_settings defaults (enabled, 120 min).
A shared resolver computes the effective cutoff, which the public event API
exposes as presaleClosesAt and the public booking endpoint enforces. Door and
admin ticket creation are not gated.
Admin: toggle + duration picker in the event modal (pre-filled from the site
default) and a matching default card on Settings › General. Public: the event
page shows "Registration Closed" after the cutoff and the checkout page
redirects back.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Phones cannot land downloads in the photo library, so the gallery opens a share sheet for a cacheable preview while streaming via a dedicated download endpoint and recording preview sizes.
Co-authored-by: Cursor <cursoragent@cursor.com>
Signing in showed the "Welcome back!" toast but never left /login. The
session cookie was host-only on the API subdomain, so the Next middleware
guard on the site origin saw no cookie and bounced /dashboard straight
back to /login?redirect=/dashboard.
- Add AUTH_COOKIE_DOMAIN, wiring Better Auth's crossSubDomainCookies so
the cookie also reaches the site origin. Unset in dev, where localhost
is single-host and must stay host-only.
- Navigate after authentication with a full page load, via a shared
authRedirect helper: only a top-level request carries the httpOnly
cookie. Used by the login, register, magic-link and Google flows.
- Show "Redirecting..." on the login and register pages and keep the
submit button disabled until the browser replaces the page, instead of
re-enabling it mid-navigation.
- Guard against a redirect loop with a sessionStorage marker. A React ref
cannot do this: the full page load resets component state. If the
destination bounces back, explain it rather than navigating again.
- Middleware: accept any *.session_token cookie so a cookiePrefix change
cannot lock everyone out, and preserve the destination's query string.
- Trust any loopback port in dev, so reaching the dev server through a
forwarded port does not fail Better Auth's CSRF origin check.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Replace the three Attendees-tab modals (Manual Ticket / Add at Door /
Invite Guest) with a single Add Ticket modal: Paid/Unpaid/Guest segmented
control, shared fields, "Check in now" for all types, and a live
"what happens" preview, backed by one POST /api/tickets/admin/add.
- Add tickets.payment_status (paid | unpaid | comp) with a backfill
migration; keep it in sync on every payment-settlement path (mark-paid,
admin approval, Lightning, free bookings, hold recovery).
- Show Paid/Unpaid/Comp badges in the attendee list, count only paid
tickets toward revenue, let unpaid tickets be resolved via Mark Paid,
and flag unpaid tickets with their balance due in the door scanner.
- Replace the per-page useStatsPrivacy hook with an admin-wide
PrivacyContext + SensitiveValue mask, toggled from the admin layout.
- Add server-side pagination with page-size options to the users page.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Introduces the Go photo-api service, nginx/systemd deploy wiring, and Next.js gallery/lightbox pages so event photos can be managed and browsed.
Co-authored-by: Cursor <cursoragent@cursor.com>
Require an explicit payment method on booking, hide stale release banners for past or inactive events, open event editing in place on the detail page, and auto-reject unconfirmed payments after events end without sending email.
Co-authored-by: Cursor <cursoragent@cursor.com>
Consolidate profile and security into AccountTab, add shared dashboard components, and move awaiting-approval payment messages to translations.
Co-authored-by: Cursor <cursoragent@cursor.com>
Wire legal pages to LanguageContext, pass locale on footer/booking links,
translate layout chrome, and restore SSR content when switching back to English.
Display past event photos in an auto-playing carousel between the
"What is Spanglish?" and "Stay Updated" sections. Images are loaded
dynamically from /images/carrousel/ folder with support for jpg, png,
and webp formats.