Commit graph

10 commits

Author SHA1 Message Date
e87be49ebd Fix web camera stuck on "Starting camera…"
getUserMedia (and the awaited play()) could hang indefinitely on iOS when the
scanner mounts after navigation (not in a user-gesture context), leaving the
UI stuck with no recovery. Add a 12s watchdog that surfaces an error + Retry
button (Retry is a fresh gesture iOS honors), stop awaiting play() (fire and
forget), and stop any prior stream before re-requesting.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-08 17:26:00 +00:00
f14e45349e CI: cap runner parallelism; play success chime immediately on scan
CI: limit Gradle to 2 worker JVMs (org.gradle.workers.max=2, --max-workers=2,
parallel=false), cap native compiler jobs (CMAKE_BUILD_PARALLEL_LEVEL=2), and
build only the arm64-v8a ABI — so the build no longer spawns a JVM/compiler per
core and swamps the host (which also serves Forgejo).

App: the success chime now fires the instant a scan resolves to a valid ticket
(ticket/ice modes), matching Banquet, instead of after the check-in round-trip.
The failure sound is unchanged. Removed the duplicate chime at check-in.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-08 17:04:52 +00:00
34c7e23179 Make the success sound a warm ascending major arpeggio
Replace the two-note chirp with a bell-like C–E–G–C arpeggio (soft attack,
gentle decay, light harmonics) for a more pleasing check-in confirmation.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-08 16:57:03 +00:00
0e8fe3bb9a 4-digit auto-submit PIN + operator name in audit logs
Login: fixed-length 4-digit PIN that auto-submits on the 4th digit (no submit
button to scroll to on small iPhone screens) and clears on a wrong PIN.
Compact, vertically-centered keypad so it fits without scrolling.

Operator tracking: after PIN auth, staff enter their name (new /operator
screen, persisted per device). The name is sent as X-Operator on every authed
request and recorded on each check-in/undo/ice audit entry (new Operator
column), so logs show who did what. Shown in the scanner header and the admin
audit view.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-08 16:45:23 +00:00
336d2a5c83 Fix banquet mode ignored on web: use latest onScan in camera loop
The web scanner's camera loop starts once in useEffect and closed over the
mount-time onScan handler, so switching modes (e.g. to Banquet) kept invoking
the original ticket-check-in handler. Route onScan through a ref updated each
render so the loop always calls the current handler. Native was unaffected.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-08 16:31:25 +00:00
1ca2c38fbf Fix login hang after PIN + enrich Banquet mode
Some checks failed
Build Android APK / build-apk (push) Failing after 57m31s
- Auth: introduce a shared AuthProvider/useAuth so entering the PIN updates
  reactive state and the layout's gate navigates immediately (previously it
  cached the token check at startup, so login appeared to hang until a manual
  refresh). login/scanner now use signIn/signOut.
- Banquet: donor lookup now returns lifetime giving, last-12-months giving
  (computed from dated transactions), member/donor status, bear name, and tags.
  The banquet result screen shows status badge + lifetime and last-year figures.
  Tickets are irrelevant in banquet mode.
- Admin: fix "‹ Scanner" back link wrapping (remove fixed width).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-08 16:01:41 +00:00
dc39c41428 Fix blank web page: align react and react-dom versions
react resolved to 19.2.7 (via react-native) while react-dom was pinned to
19.2.3, causing React error #527 (version mismatch) and a blank page on web.
Pin both to 19.2.7. Native APK was unaffected.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-08 05:37:45 +00:00
d293e53ee6 Add Check-in / Ice / Banquet scan modes
- Ice mode: prepaid ice bags (Ice Total / Ice Redeemed columns) redeemed
  independently of ticket check-ins; grab all bags at once or some now.
- Banquet mode: donor total (online + offline) looked up by the ticket's
  email via the Donors Master List, with a manual email override. New
  DonorService + POST /api/banquet.
- Redeem generalized over a resource (tickets|ice); audit records ice actions.
- App gains a mode selector; webhook maps ice_bags (defaults to
  ICE_BAGS_DEFAULT when only a boolean ice option is present).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-08 04:07:51 +00:00
b36d63a6a4 Add check-in audit logging + in-app history view
Every successful check-in/undo writes a row to the "2026 Ticket Audit Logs"
NocoDB table (timestamp, people, code, action, name, remaining-after).
Non-fatal: audit failures never block a gate check-in. New GET /api/audit
endpoint (global or per-code). Admin panel gains a global "Recent check-ins"
panel and per-ticket history. Audit table is optional via NOCODB_AUDIT_TABLE_ID.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-08 03:53:30 +00:00
3397e3e3ec Initial Camp Scan ticketing system
Backend (Fastify + TS): FluentForms webhook -> NocoDB row + QR + MailerSend
email; PIN auth; scan/lookup/redeem with per-code serialization; reusable QR
codes with count-based check-in; admin search.

App (Expo, one codebase): Android APK + iPhone PWA. Login, camera scanner
(native + web barcode-detector split), green/red overlay with sound + haptics,
admin lookup/redeem. Session token persisted per device.

Ops: multi-stage Dockerfile serving API + PWA same-origin, compose bound to
127.0.0.1; Forgejo Actions runner + tag-triggered signed APK build for Obtainium.
Docs in README.md and INSTALL.md.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-08 03:47:59 +00:00