"""Customer self-service manage-booking page (#18), reached via the token-linked link sent in the confirmation email (booking_mail.py). The signed token (booking_api.verify_manage_token) is the only source of identity here -- customers have no account, so this route needs no login. The actual cancel/reschedule mutations still go through booking_api.py's JSON API (#16); this route only resolves the token, decides which of the three states (invalid/expired, already used, active) to render, and lets the page's own JS drive the API from there. """ from flask import Blueprint, render_template from zoneinfo import ZoneInfo import booking_db as bdb from booking_api import verify_manage_token bp = Blueprint("manage_booking", __name__) @bp.get("/manage/") def manage_page(token): resolved = verify_manage_token(token) if resolved is None: # Distinct from "already used" per #18's acceptance criteria -- an # expired/malformed token never resolved to a booking at all. return render_template("manage.html", state="invalid"), 400 client_id, booking_id = resolved booking = bdb.get_booking(client_id, booking_id) client = bdb.get_client(client_id) if booking is None or client is None: return render_template("manage.html", state="invalid"), 404 if booking["status"] == "cancelled": return render_template("manage.html", state="used") tz = ZoneInfo(client.get("timezone") or "Europe/Berlin") duration_minutes = int( (booking["end_time"] - booking["start_time"]).total_seconds() // 60) return render_template( "manage.html", state="active", token=token, client_id=client_id, booking_id=booking_id, resource_id=booking["resource_id"], service=booking["service"], duration_minutes=duration_minutes, start_local=booking["start_time"].astimezone(tz), status=booking["status"], )