Modules
PhoenixKit plugin module for universal bookings.
Thin wrapper around PhoenixKit.Activity.log/1 so callers don't need to
duplicate the Code.ensure_loaded?/1 guard and rescue clause everywhere.
Safe to call from any LiveView — never crashes the caller.
Booking lifecycle: create (race-proof), confirm, cancel — plus holds, waitlist, unit auto-assignment, pricing, and the queries that feed pickers and admin lists.
Adapter between the Bookings domain and phoenix_live_calendar's booking
rules engine (Layer 3: BookingConfig / Availability / Constraints /
TimeSlots), plus the day/night path (DayEngine) the lib doesn't cover.
Validation + per-date capacity for day/night services — the date-granular
path phoenix_live_calendar's minute-based engine doesn't cover.
Maps engine/context error atoms to user-facing messages in one place, so LiveViews never show a raw atom or an internal engine string.
Module-owned gettext backend for Bookings domain strings (hybrid
convention: generic strings already translated workspace-wide stay on
core's PhoenixKitWeb.Gettext).
Minimal iCalendar (RFC 5545) rendering for booking confirmations —
enough for Google/Outlook/Apple to import the event from the email
attachment. Timed bookings emit UTC datetimes; day/night bookings emit
VALUE=DATE pairs (exclusive end, exactly the iCal convention).
Versioned migration for the Bookings module.
Transactional email for the booking lifecycle, via core's
PhoenixKit.Mailer (routes through Integrations → parent mailer →
built-in, honors the blocklist). Every send is best-effort — a mail
failure must never fail the booking operation (the newsletters worker
convention). Confirmation emails attach an .ics calendar file.
Every path this module navigates to, in one place, all routed through
PhoenixKit.Utils.Routes.path/1 for prefix/locale handling. Use
url/1-based helpers for absolute links (emails).
Authorization + self-service rules for the admin surface, in one server-side module (every admin LiveView routes its reads and mutations through here — buttons are hidden AND actions re-checked).
Total computation for a booking request: price × price_per units.
One availability rule of a service. Maps 1:1 onto
PhoenixLiveCalendar.Availability
A reservation of one seat of a service.
A short-lived capacity hold — created when a public visitor advances to
the details step, so the seat they picked can't be sold out from under
them while they type (the Cal.com SelectedSlots pattern).
A bookable offer — the universal unit of configuration.
A named bookable unit of a service — Room 101, Chair 2, Court A.
A waitlist signup: "email me when date frees up on this service".
CRUD for bookable services and their availability rules.
Admin reservations list: status filter tabs (pending is the approval queue), confirm/cancel actions, live updates via the admin PubSub topic.
Create/edit form for a bookable service. The form is mode-aware: picking
a time_unit (and, for minute services, toggling free-form) swaps the
relevant rule fields in and out. Availability rules are managed inline
on the edit page (a service must exist before rules can reference it).
Admin list of bookable services: create, activate/deactivate, trash / restore / permanently delete, jump to the edit form. Live — subscribes to the services topic so config changes from other sessions re-render.
Module settings: the self-service policy. Only bookings.manage_all
holders may view or save (the settings decide what everyone ELSE may
do, so base-permission holders have no business here).
Presentation helpers shared by the admin and public LiveViews: booking ranges in the site frame, service mode summaries, status badge classes.
The routed public booking page (/book/:slug) — a thin shell around
BookingFlow, wrapped in the host layout via LayoutWrapper.app_layout.
The shared public booking flow: state, events, and markup used by BOTH
Public.BookLive (the routed page) and Public.BookingWidgetLive (the
live_render/3 embeddable). The two LiveViews stay thin shells that
delegate handle_event/3 + handle_info/2 here — one flow, two mounts,
because an embeddable LiveView must not export handle_params/3
(Phoenix refuses to mount it outside a router live route).
The embeddable booking widget — the same BookingFlow as the routed
page, mountable on ANY host page via live_render/3
Guest self-service page (/bookings/manage/:token): view a booking via
its signed manage token and cancel it while it hasn't started yet. No
account needed — the token (mailed/shown at booking time) is the
credential.
Public index of active bookable services (/bookings) — name,
description, shape summary, and a Book link per service. Live: service
config changes re-render the list.
Public route definitions for the Bookings module.
Sends the reminder email at starts_at - reminder_minutes (scheduled at
booking creation via scheduled_at: — the :default queue, which every
Oban install runs). The job re-checks state at fire time: a cancelled
booking or a service whose reminder was turned off sends nothing, so
stale jobs are harmless and no cancellation bookkeeping is needed.