11-Mar-2026 — Lightweight Booking System, Maps cleanup unused components, mail & walks fixes #175
build 517 — commit 6edae09
Note that during the development of this ticket, it transpired that there was a sizeable amount of clean up work that was required following issue #12, as navigation from the walk summary → detail → edit and back was broken. The same issues were found with the same social event navigations. Several old components were deleted and the social-event* objects were renamed to group-event* in order to be more consistent with Ramblers data model.
Lightweight Booking System
Events can now accept bookings directly from any events page. When an event has bookingEnabled: true and a maxCapacity set on its fields, a Book a Place panel appears below the event details showing remaining capacity.
Admin — Booking (/admin/bookings)
To enable this feature, visit the above URL. A bookings button has also been added to the admin menu.

- Configuration tab: Enable Booking on Events and choose which event types are enabled for bookings, default max capacity, max attendees per booking and member priority.
- Per-Event Detail tab is the event-level booking admin view which lets you:
- select an upcoming or existing bookable event
- see each booking for that event in a table
- view attendee names, email addresses, phone numbers, booking date, status, and places booked
- spot waitlisted bookings separately from active ones
- delete individual bookings if needed
- download the selected event’s booking data as CSV
- It is also used to prepare events before bookings open:
- find an upcoming event from the selector
- set or update that event’s max booking capacity even if nobody has booked yet
- Select an event to see individual bookings with attendee names, emails, phone numbers, and booking dates — also downloadable as CSV
- Admin can delete bookings directly from the detail view
- Summary tab: table of all bookable events with booked/capacity counts
Booking flow
- Visitors enter their name and email (required) plus optional phone number
- Additional attendees can be added (up to
maxGroupSize, default 3) - Clicking Book Now creates a booking and confirms with a success message
- The capacity counter updates in real time — once all places are taken the panel shows Fully booked
- An email is sent to the visitor and optionally the BCC address specified within the email configuration for Bookings.


Cancellation flow
- After booking, a Need to cancel a booking? link appears
- The visitor enters their email to look up active bookings
- Each matching booking can be individually cancelled
- Cancellation is verified server-side: the email must match an attendee on the booking, preventing unauthorised cancellations
- An email is sent to the visitor and optionally the BCC address specified within the email configuration for Bookings.


Booking Follow-up Enhancements
Booking email notifications
- Booking workflows can now send confirmation, cancellation, waitlisted, and restored emails using the configured booking notification template
- Booking emails include attendee lists, event dates, salutation, and a direct link back to the website event page
- The event page link is passed from the current walk/group event view so the email uses the same deep link already exposed in the UI
Booking configuration defaults
- Added booking settings to walks config with defaults for enabled state, maximum capacity, maximum attendees per booking, and member priority days
- Added the walks config admin tab to manage those defaults centrally
- Updated the unreleased walks config migration to seed booking defaults and to
move notification configuration role copies from
ccRolestobccRoles
Member priority and waitlist handling
- During member priority periods, authenticated member bookings can displace non-member bookings onto the waiting list when an event is full
- Waitlisted bookings are restored automatically when places are freed by a cancellation and restored attendees are notified by email
- Eligibility responses now expose member priority state and waiting list totals
Booking safeguards and lookup fixes
- Prevent duplicate active or waitlisted bookings on the same event by attendee email address
- Fixed booking lookup responses to return API objects with
id, allowing cancellation to work reliably after lookup - Added explicit validation for missing booking ids on cancellation requests
- Cancellation entry points are available whenever an event already has bookings
Booking email configuration and workflow alignment
- Booking notification configurations now behave as workflow-managed email configs instead of generic mailing-list selections
- Booking notifications now use BCC recipient roles consistently, with support
for migrated legacy
ccRolesdata during the transition - Booking emails now send through the shared transactional mail sender logic so recipient handling, BCC behaviour, template substitution, and Brevo request construction are maintained in one place
Walk and event view integration
- Booking panels now render correctly on standalone deep-link walk pages after walks config has loaded
- Walk and group event views pass their canonical website links into booking workflows so correspondence uses the same event URLs shown in related links
- Booking form layout was refined for stacked narrow layouts and single-row wide layouts, with panel spacing aligned to surrounding event panels
Migration consistency
- Corrected booking-related config migrations to use the
configcollection, matching the live mongoose model and runtime code
Global booking configuration and admin controls
- Booking configuration is now stored in its own global
bookingconfig rather than underwalks, with environment setup and the unreleased migration moved to seed and migrate the new config cleanly before release /admin/bookingsnow owns booking configuration through a dedicated Configuration tab instead of the walks config area- Booking configuration now supports per-event-type enablement so booking can be turned on for walks, group events, and wellbeing walks independently
Per-event booking preparation
- Booking admins can now search upcoming events from the existing autocomplete pattern without typing before results appear
- Upcoming event choices now include event type in the label for clarity
- Admins can set event-specific maximum capacity before any bookings exist, allowing events to be prepared ahead of opening booking
Booking UX refinements
- Cancellation lookup guidance now uses the same alert treatment as the rest of the booking panel
- Booking success and waiting-list messaging now use shared pluralisation helpers for consistent wording
- Group event and walk views now own their own booking panel spacing so page- specific layout tuning no longer leaks through the shared booking component
GPX Parser: Support Route Elements
- Root cause: GPX parser only handled
<trk>/<trkpt>(track) elements but ignored<rte>/<rtept>(route) elements. 37 of 39 Sevenoaks walking route GPX files use<rte>format, so only 2 routes were rendering on the map - Fix: added
parseRoute()andparseRoutePoints()methods to convert<rte>/<rtept>elements into renderableGpxTrackobjects alongside existing<trk>parsing - Waypoint dedup: changed
parseWaypoints()from selecting"wpt, rtept"to just"wpt"—<rtept>elements are now parsed as route tracks, not waypoints, preventing duplicate rendering
Map Viewport Hang Fix
- Root cause: Dragging/zooming the map triggered an infinite feedback
loop —
moveend→loadRoutes()→ layer reassignment +fitBounds→ Leaflet re-render →moveendagain - Debounced viewport handler: 300ms debounce on
moveend/zoomendprevents rapid-fire reloads during panning - Re-entrancy guard:
loadRoutesInProgressflag skips concurrentloadRoutes()calls - Suppress programmatic events:
suppressViewportHandlerflag prevents layer/bounds assignment from re-triggering the viewport handler (cleared after 200ms) - Skip fitBounds on viewport reloads:
loadRoutes(skipFitBounds)parameter preventscalculateFitBoundson user-initiated pan/zoom, which was the primary loop trigger - Removed cache eviction on viewport change: the viewport filtering
already uses
currentBounds.intersects()on cached data, so deleting and re-fetching GPX on every drag was unnecessary
Mail API Resilience (No API Key / Invalid Key)
- Short-circuit when no API key:
MailMessagingService.initialise()checksmailConfig.apiKeyafter loading config — if absent, skips all Brevo API calls (account, lists, folders, templates) and sets safe empty defaults. Zero 401 errors in the console - Short-circuit on account error: if the account query fails (invalid key), remaining Brevo calls are skipped with empty defaults
- Senders list guard:
MailSendersListComponentwaits for themailMessagingConfigvia theevents()observable before calling senders/domain APIs — skips ifaccountErroris set - Create-or-amend sender guard: checks
brevoAccountConfigured()before callingquerySenders() - Mail provider stats guard:
calculateMailProviderStats()handlesthis.listbeing undefined when Brevo lists fail to load, showing "No Brevo lists available" instead of crashing - List key values null safety: added
?.and|| []fallback whenbrevo.lists.listsis undefined - Alert icon: changed error alert icon from
faCircleChecktofaExclamationTrianglefor the account error panel
CommonDataService Double HTTP Request Fix
- Root cause:
responseFrom()used.subscribe()+.toPromise()on a shared observable, causing two subscriptions and duplicate HTTP requests - Fix: replaced with single
firstValueFrom()call — one subscription, one HTTP request, proper error propagation viathrow
Config Save: Allow Clearing Sensitive Fields
- Root cause:
restoreSensitiveFields()in the backend config controller treated both missing keys AND falsy values ("",null) as "restore from DB", making it impossible to clear the API key - Fix: changed condition from
!(k in incoming) || !incoming[k]to!(k in incoming)— only restores when the key is truly absent (redacted), not when explicitly cleared
Mail Settings: Prevent Config Overwrite During Editing
- Root cause:
MailMessagingService.initialise()created new subscriptions tocommitteeConfigandsystemConfigServiceon every call, causing accumulated subscriptions that re-emitted config and overwrote unsaved edits - Fix: moved one-time subscriptions to the constructor;
acceptNextConfigEmissionflag inMailSettingsComponentprevents subscription emissions from overwriting local edits except after save or undo
Domain API URL Refactor
- Changed domain endpoints from path parameters
(
/domains/:domainName/configuration) to query parameters (/domains/configuration?domainName=...) across backend routes and frontend service
Walk Search Layout
- Alert inline when default filtering, own row with pagination/filters
- Clear filters resets to default preset
- Bug button hidden by default
Events
- Bug button visibility fix
Server API for #175
POST /api/database/booking— create bookingPUT /api/database/booking/:id— update bookingDELETE /api/database/booking/:id— delete bookingGET /api/database/booking/all— list bookings (with query criteria)GET /api/database/booking/capacity/:eventId— public capacity checkPOST /api/database/booking/lookup— find bookings by event + emailPUT /api/database/booking/cancel/:id— cancel with email verification
Contact Us Persistence
Contact form submissions are now persisted to the database via
ContactInteractionService and a new /api/database/contact-interaction
endpoint. Each interaction records: name, email, subject, message, whether
the sender was anonymous, the recipient role, and a status
(new/read/archived).
Social → Group Events Rename
All social event components, services, models, and routes have been renamed
from social-* to group-event-* to align with the Ramblers API naming
convention. Key renames:
SocialEditComponent→GroupEventEditSocialDisplayService→GroupEventDisplayServiceSocialViewPage→GroupEventViewPageSocialCard→GroupEventCardsocial-events.model.ts→group-events.model.tssocial-auth-guard.ts→group-event-auth-guard.ts
Removed Orphaned Code
Deleted components and services no longer referenced anywhere:
WalkList(723 lines) — replaced byEventsFullCMS componentGroupEventList+ sassGroupEventViewSelectorSocialViewSelectorSocialList+ sassSocialRoutingModuleSocialPopulationLocalGuard- Hardcoded
PAGE_HEADERandACTION_BUTTONSanchors — walks and social pages are now 100% content-managed
Event Navigation Fixes
Deep path dispatch (fixes wrong event for generic slugs)
For paths with 3+ segments (e.g. /walks/weekends-away/swanage/day-2),
page content is now checked before event lookup. This prevents generic
slugs like day-2 from regex-matching unrelated events. Two-segment paths
(e.g. /walks/some-event-slug) still try event lookup first.
Walk card title links (fixes pre-existing non-navigation)
walkRouterLink()now generates relative URLs sorouterLinkUrl()returns a valid path instead of null (absolute URLs were rejected as "remote")- Added
stopPropagationon the title<a>to prevent the parent card'stoggleView()handler from intercepting the routerLink click
Edit/cancel navigation symmetry
close()strips/editfrom the current path instead of reconstructing the URL viagroupEventLink(), which was reading the still-dirty URL- Added explicit
editandnewroutes towalks-routing.module.tsbefore the**catch-all to prevent/edit/edit/editURL corruption
Cleanup
walksArea()simplified tourlService.area()— removed stale fallbacks topageService.walksPage()?.hrefand hardcoded"walks"