"""Public booking page blueprint (#17): the customer-facing /book/ page, embedded via iframe into a client's landing page. This route only resolves a slug to a client and renders the slot-grid/booking-form page around it -- all booking mutations still go through booking_api.py's JSON API (#16), which the page's own JS calls via fetch. IP rate limiting on these public routes is enforced at the Caddy layer (per #17's acceptance criteria), not in Flask -- see deploy/booking/RATE_LIMIT.md for the Caddy config to apply by hand on the homelab. """ from flask import Blueprint, abort, render_template, request import booking_db as bdb bp = Blueprint("public_booking", __name__) DEFAULT_BRAND_COLOR = "#0f8a7e" @bp.get("/book/") def book_page(slug): client = bdb.get_client_by_slug(slug) if client is None: abort(404) resources = bdb.list_active_resources(client["client_id"]) services = bdb.list_active_services(client["client_id"]) if not resources or not services: # No bookable services/resources configured yet -- nothing to show a # customer rather than a broken/empty booking form. abort(404) return render_template( "book.html", client_id=client["client_id"], business_name=client.get("business_name") or slug, resources=[{"resource_id": r["resource_id"], "name": r["name"]} for r in resources], services=[{"service_id": s["service_id"], "name": s["name"], "duration_minutes": s["duration_minutes"], "price": float(s["price"]) if s["price"] is not None else None} for s in services], brand_color=request.args.get("color") or DEFAULT_BRAND_COLOR, )