"""
Events service s-expression page components.
Renders all events, page summary, calendars, calendar month, day, day admin,
calendar admin, tickets, ticket admin, markets, and payments pages.
Called from route handlers in place of ``render_template()``.
"""
from __future__ import annotations
from typing import Any
from markupsafe import escape
from shared.sexp.jinja_bridge import sexp
from shared.sexp.helpers import (
call_url, get_asset_url, root_header_html,
search_mobile_html, search_desktop_html,
full_page, oob_page,
)
# ---------------------------------------------------------------------------
# OOB header helper (same pattern as market)
# ---------------------------------------------------------------------------
def _oob_header_html(parent_id: str, child_id: str, row_html: str) -> str:
"""Wrap a header row in OOB div with child placeholder."""
return (
f'
'
)
# ---------------------------------------------------------------------------
# Post header helpers (mirrors events/templates/_types/post/header/_header.html)
# ---------------------------------------------------------------------------
def _post_header_html(ctx: dict, *, oob: bool = False) -> str:
"""Build the post-level header row."""
post = ctx.get("post") or {}
slug = post.get("slug", "")
title = (post.get("title") or "")[:160]
feature_image = post.get("feature_image")
label_parts = []
if feature_image:
label_parts.append(
f' '
)
label_parts.append(f"{escape(title)} ")
label_html = "".join(label_parts)
nav_parts = []
page_cart_count = ctx.get("page_cart_count", 0)
if page_cart_count and page_cart_count > 0:
cart_href = call_url(ctx, "cart_url", f"/{slug}/")
nav_parts.append(
f''
f' '
f'{page_cart_count} '
)
# Post nav: calendar links + admin
nav_parts.append(_post_nav_html(ctx))
nav_html = "".join(nav_parts)
link_href = call_url(ctx, "blog_url", f"/{slug}/")
return sexp(
'(~menu-row :id "post-row" :level 1'
' :link-href lh :link-label-html llh'
' :nav-html nh :child-id "post-header-child" :oob oob)',
lh=link_href,
llh=label_html,
nh=nav_html,
oob=oob,
)
def _post_nav_html(ctx: dict) -> str:
"""Post desktop nav: calendar links + admin gear."""
from quart import url_for
calendars = ctx.get("calendars") or []
rights = ctx.get("rights") or {}
is_admin = getattr(rights, "admin", False) if hasattr(rights, "admin") else rights.get("admin", False)
hx_select = ctx.get("hx_select_search", "#main-panel")
select_colours = ctx.get("select_colours", "")
post = ctx.get("post") or {}
slug = post.get("slug", "")
parts = []
for cal in calendars:
cal_slug = getattr(cal, "slug", "") if hasattr(cal, "slug") else cal.get("slug", "")
cal_name = getattr(cal, "name", "") if hasattr(cal, "name") else cal.get("name", "")
href = url_for("calendars.calendar.get", calendar_slug=cal_slug)
parts.append(sexp(
'(~nav-link :href h :icon "fa fa-calendar" :label l :select-colours sc)',
h=href,
l=cal_name,
sc=select_colours,
))
if is_admin:
admin_href = call_url(ctx, "blog_url", f"/{slug}/admin/")
parts.append(
f''
f' '
)
return "".join(parts)
# ---------------------------------------------------------------------------
# Post admin header
# ---------------------------------------------------------------------------
def _post_admin_header_html(ctx: dict, *, oob: bool = False) -> str:
"""Build the post-admin-level header row."""
post = ctx.get("post") or {}
slug = post.get("slug", "")
link_href = call_url(ctx, "blog_url", f"/{slug}/admin/")
return sexp(
'(~menu-row :id "post-admin-row" :level 2'
' :link-href lh :link-label "admin" :icon "fa fa-cog"'
' :child-id "post-admin-header-child" :oob oob)',
lh=link_href,
oob=oob,
)
# ---------------------------------------------------------------------------
# Calendars header
# ---------------------------------------------------------------------------
def _calendars_header_html(ctx: dict, *, oob: bool = False) -> str:
"""Build the calendars section header row."""
from quart import url_for
link_href = url_for("calendars.home")
return sexp(
'(~menu-row :id "calendars-row" :level 3'
' :link-href lh :link-label-html llh'
' :child-id "calendars-header-child" :oob oob)',
lh=link_href,
llh='Calendars
',
oob=oob,
)
# ---------------------------------------------------------------------------
# Calendar header
# ---------------------------------------------------------------------------
def _calendar_header_html(ctx: dict, *, oob: bool = False) -> str:
"""Build a single calendar's header row."""
from quart import url_for
calendar = ctx.get("calendar")
if not calendar:
return ""
cal_slug = getattr(calendar, "slug", "")
cal_name = getattr(calendar, "name", "")
cal_desc = getattr(calendar, "description", "") or ""
link_href = url_for("calendars.calendar.get", calendar_slug=cal_slug)
label_html = (
''
'
'
f'
'
f'
{escape(cal_name)}
'
'
'
f'
{escape(cal_desc)}
'
'
'
)
# Desktop nav: slots + admin
nav_html = _calendar_nav_html(ctx)
return sexp(
'(~menu-row :id "calendar-row" :level 3'
' :link-href lh :link-label-html llh'
' :nav-html nh :child-id "calendar-header-child" :oob oob)',
lh=link_href,
llh=label_html,
nh=nav_html,
oob=oob,
)
def _calendar_nav_html(ctx: dict) -> str:
"""Calendar desktop nav: Slots + admin link."""
from quart import url_for
calendar = ctx.get("calendar")
if not calendar:
return ""
cal_slug = getattr(calendar, "slug", "")
rights = ctx.get("rights") or {}
is_admin = getattr(rights, "admin", False) if hasattr(rights, "admin") else rights.get("admin", False)
select_colours = ctx.get("select_colours", "")
parts = []
slots_href = url_for("calendars.calendar.slots.get", calendar_slug=cal_slug)
parts.append(sexp(
'(~nav-link :href h :icon "fa fa-clock" :label "Slots" :select-colours sc)',
h=slots_href,
sc=select_colours,
))
if is_admin:
admin_href = url_for("calendars.calendar.admin.admin", calendar_slug=cal_slug)
parts.append(
f''
f' '
)
return "".join(parts)
# ---------------------------------------------------------------------------
# Day header
# ---------------------------------------------------------------------------
def _day_header_html(ctx: dict, *, oob: bool = False) -> str:
"""Build day detail header row."""
from quart import url_for
calendar = ctx.get("calendar")
if not calendar:
return ""
cal_slug = getattr(calendar, "slug", "")
day_date = ctx.get("day_date")
if not day_date:
return ""
link_href = url_for(
"calendars.calendar.day.show_day",
calendar_slug=cal_slug,
year=day_date.year,
month=day_date.month,
day=day_date.day,
)
label_html = (
''
f' '
f' {escape(day_date.strftime("%A %d %B %Y"))}'
'
'
)
nav_html = _day_nav_html(ctx)
return sexp(
'(~menu-row :id "day-row" :level 4'
' :link-href lh :link-label-html llh'
' :nav-html nh :child-id "day-header-child" :oob oob)',
lh=link_href,
llh=label_html,
nh=nav_html,
oob=oob,
)
def _day_nav_html(ctx: dict) -> str:
"""Day desktop nav: confirmed entries scrolling menu + admin link."""
from quart import url_for
calendar = ctx.get("calendar")
if not calendar:
return ""
cal_slug = getattr(calendar, "slug", "")
day_date = ctx.get("day_date")
confirmed_entries = ctx.get("confirmed_entries") or []
rights = ctx.get("rights") or {}
is_admin = getattr(rights, "admin", False) if hasattr(rights, "admin") else rights.get("admin", False)
parts = []
# Confirmed entries nav (scrolling menu)
if confirmed_entries:
parts.append(
'')
if is_admin and day_date:
admin_href = url_for(
"calendars.calendar.day.admin.admin",
calendar_slug=cal_slug,
year=day_date.year,
month=day_date.month,
day=day_date.day,
)
parts.append(
f''
f' '
)
return "".join(parts)
# ---------------------------------------------------------------------------
# Day admin header
# ---------------------------------------------------------------------------
def _day_admin_header_html(ctx: dict, *, oob: bool = False) -> str:
"""Build day admin header row."""
from quart import url_for
calendar = ctx.get("calendar")
if not calendar:
return ""
cal_slug = getattr(calendar, "slug", "")
day_date = ctx.get("day_date")
if not day_date:
return ""
link_href = url_for(
"calendars.calendar.day.admin.admin",
calendar_slug=cal_slug,
year=day_date.year,
month=day_date.month,
day=day_date.day,
)
return sexp(
'(~menu-row :id "day-admin-row" :level 5'
' :link-href lh :link-label "admin" :icon "fa fa-cog"'
' :child-id "day-admin-header-child" :oob oob)',
lh=link_href,
oob=oob,
)
# ---------------------------------------------------------------------------
# Calendar admin header
# ---------------------------------------------------------------------------
def _calendar_admin_header_html(ctx: dict, *, oob: bool = False) -> str:
"""Build calendar admin header row."""
return sexp(
'(~menu-row :id "calendar-admin-row" :level 4'
' :link-label "admin" :icon "fa fa-cog"'
' :child-id "calendar-admin-header-child" :oob oob)',
oob=oob,
)
# ---------------------------------------------------------------------------
# Markets header
# ---------------------------------------------------------------------------
def _markets_header_html(ctx: dict, *, oob: bool = False) -> str:
"""Build the markets section header row."""
from quart import url_for
link_href = url_for("markets.home")
return sexp(
'(~menu-row :id "markets-row" :level 3'
' :link-href lh :link-label-html llh'
' :child-id "markets-header-child" :oob oob)',
lh=link_href,
llh='Markets
',
oob=oob,
)
# ---------------------------------------------------------------------------
# Payments header
# ---------------------------------------------------------------------------
def _payments_header_html(ctx: dict, *, oob: bool = False) -> str:
"""Build the payments section header row."""
from quart import url_for
link_href = url_for("payments.home")
return sexp(
'(~menu-row :id "payments-row" :level 3'
' :link-href lh :link-label-html llh'
' :child-id "payments-header-child" :oob oob)',
lh=link_href,
llh='Payments
',
oob=oob,
)
# ---------------------------------------------------------------------------
# Calendars main panel
# ---------------------------------------------------------------------------
def _calendars_main_panel_html(ctx: dict) -> str:
"""Render the calendars list + create form panel."""
from quart import url_for
rights = ctx.get("rights") or {}
is_admin = getattr(rights, "admin", False) if hasattr(rights, "admin") else rights.get("admin", False)
has_access = ctx.get("has_access")
can_create = has_access("calendars.create_calendar") if callable(has_access) else is_admin
csrf_token = ctx.get("csrf_token")
csrf = csrf_token() if callable(csrf_token) else (csrf_token or "")
calendars = ctx.get("calendars") or []
hx_select = ctx.get("hx_select_search", "#main-panel")
parts = ['']
if can_create:
create_url = url_for("calendars.create_calendar")
parts.append(
'
'
f''
)
parts.append('')
parts.append(_calendars_list_html(ctx, calendars))
parts.append('
')
return "".join(parts)
def _calendars_list_html(ctx: dict, calendars: list) -> str:
"""Render the calendars list items."""
from quart import url_for
from shared.utils import route_prefix
hx_select = ctx.get("hx_select_search", "#main-panel")
csrf_token = ctx.get("csrf_token")
csrf = csrf_token() if callable(csrf_token) else (csrf_token or "")
prefix = route_prefix()
if not calendars:
return 'No calendars yet. Create one above.
'
parts = []
for cal in calendars:
cal_slug = getattr(cal, "slug", "")
cal_name = getattr(cal, "name", "")
href = prefix + url_for("calendars.calendar.get", calendar_slug=cal_slug)
del_url = url_for("calendars.calendar.delete", calendar_slug=cal_slug)
parts.append(
f''
)
return "".join(parts)
# ---------------------------------------------------------------------------
# Calendar month grid
# ---------------------------------------------------------------------------
def _calendar_main_panel_html(ctx: dict) -> str:
"""Render the calendar month grid."""
from quart import url_for
from quart import session as qsession
calendar = ctx.get("calendar")
if not calendar:
return ""
cal_slug = getattr(calendar, "slug", "")
hx_select = ctx.get("hx_select_search", "#main-panel")
styles = ctx.get("styles") or {}
pill_cls = getattr(styles, "pill", "") if hasattr(styles, "pill") else styles.get("pill", "")
year = ctx.get("year", 2024)
month = ctx.get("month", 1)
month_name = ctx.get("month_name", "")
weekday_names = ctx.get("weekday_names", [])
weeks = ctx.get("weeks", [])
prev_month = ctx.get("prev_month", 1)
prev_month_year = ctx.get("prev_month_year", year)
next_month = ctx.get("next_month", 1)
next_month_year = ctx.get("next_month_year", year)
prev_year = ctx.get("prev_year", year - 1)
next_year = ctx.get("next_year", year + 1)
month_entries = ctx.get("month_entries") or []
user = ctx.get("user")
qs = qsession if "qsession" not in ctx else ctx["qsession"]
def nav_link(y, m):
href = url_for("calendars.calendar.get", calendar_slug=cal_slug, year=y, month=m)
return href
# Month navigation header
parts = ['']
parts.append('')
# Calendar grid
parts.append('')
# Weekday headers
parts.append('
')
for wd in weekday_names:
parts.append(f'
{wd}
')
parts.append('
')
# Weeks grid
parts.append('
')
for week in weeks:
for day_cell in week:
in_month = getattr(day_cell, "in_month", True)
is_today = getattr(day_cell, "is_today", False)
day_date = getattr(day_cell, "date", None)
cell_cls = "min-h-20 sm:min-h-24 bg-white px-3 py-2 text-xs"
if not in_month:
cell_cls += " bg-stone-50 text-stone-400"
if is_today:
cell_cls += " ring-2 ring-blue-500 z-10 relative"
parts.append(f'
')
parts.append('
')
parts.append(f'
{day_date.strftime("%a")} ')
# Clickable day number
if day_date:
day_href = url_for(
"calendars.calendar.day.show_day",
calendar_slug=cal_slug,
year=day_date.year,
month=day_date.month,
day=day_date.day,
)
parts.append(
f'
{day_date.day} '
)
parts.append('
')
# Entries for this day
parts.append('
')
if day_date:
for e in month_entries:
if e.start_at.date() == day_date:
is_mine = (
(user and e.user_id == user.id)
or (not user and e.session_id == qs.get("calendar_sid"))
)
if e.state == "confirmed":
bg_cls = "bg-emerald-200 text-emerald-900" if is_mine else "bg-emerald-100 text-emerald-800"
else:
bg_cls = "bg-sky-100 text-sky-800" if is_mine else "bg-stone-100 text-stone-700"
state_label = (e.state or "pending").replace("_", " ")
parts.append(
f'
'
f'{escape(e.name)} '
f'{state_label} '
f'
'
)
parts.append('
')
parts.append('
')
return "".join(parts)
# ---------------------------------------------------------------------------
# Day main panel
# ---------------------------------------------------------------------------
def _day_main_panel_html(ctx: dict) -> str:
"""Render the day entries table + add button."""
from quart import url_for
calendar = ctx.get("calendar")
if not calendar:
return ""
cal_slug = getattr(calendar, "slug", "")
day_entries = ctx.get("day_entries") or []
day = ctx.get("day")
month = ctx.get("month")
year = ctx.get("year")
hx_select = ctx.get("hx_select_search", "#main-panel")
styles = ctx.get("styles") or {}
list_container = getattr(styles, "list_container", "") if hasattr(styles, "list_container") else styles.get("list_container", "")
pill_cls = getattr(styles, "pill", "") if hasattr(styles, "pill") else styles.get("pill", "")
tr_cls = getattr(styles, "tr", "") if hasattr(styles, "tr") else styles.get("tr", "")
pre_action = getattr(styles, "pre_action_button", "") if hasattr(styles, "pre_action_button") else styles.get("pre_action_button", "")
parts = [f'']
parts.append(
''
'Name '
'Slot/Time '
'State '
'Cost '
'Tickets '
'Actions '
' '
)
if day_entries:
for entry in day_entries:
parts.append(_day_row_html(ctx, entry))
else:
parts.append('No entries yet. ')
parts.append('
')
# Add entry button
add_url = url_for(
"calendars.calendar.day.calendar_entries.add_form",
calendar_slug=cal_slug,
day=day, month=month, year=year,
)
parts.append(
f''
f''
f'+ Add entry
'
)
parts.append(' ')
return "".join(parts)
def _day_row_html(ctx: dict, entry) -> str:
"""Render a single day table row."""
from quart import url_for
calendar = ctx.get("calendar")
cal_slug = getattr(calendar, "slug", "")
day = ctx.get("day")
month = ctx.get("month")
year = ctx.get("year")
hx_select = ctx.get("hx_select_search", "#main-panel")
styles = ctx.get("styles") or {}
pill_cls = getattr(styles, "pill", "") if hasattr(styles, "pill") else styles.get("pill", "")
tr_cls = getattr(styles, "tr", "") if hasattr(styles, "tr") else styles.get("tr", "")
entry_href = url_for(
"calendars.calendar.day.calendar_entries.calendar_entry.get",
calendar_slug=cal_slug, day=day, month=month, year=year, entry_id=entry.id,
)
# Name
name_html = (
f' '
)
# Slot/Time
slot = getattr(entry, "slot", None)
if slot:
slot_href = url_for("calendars.calendar.slots.slot.get", calendar_slug=cal_slug, slot_id=slot.id)
time_start = slot.time_start.strftime("%H:%M") if slot.time_start else ""
time_end = f" → {slot.time_end.strftime('%H:%M')}" if slot.time_end else ""
slot_html = (
f''
f'
'
f'{escape(slot.name)} '
f'
({time_start}{time_end}) '
f'
'
)
else:
start = entry.start_at.strftime("%H:%M") if entry.start_at else ""
end = f" → {entry.end_at.strftime('%H:%M')}" if entry.end_at else ""
slot_html = f'{start}{end}
'
# State
state = getattr(entry, "state", "pending") or "pending"
state_html = _entry_state_badge_html(state)
state_td = f'{state_html}
'
# Cost
cost = getattr(entry, "cost", None)
cost_str = f"£{cost:.2f}" if cost is not None else "£0.00"
cost_td = f'{cost_str} '
# Tickets
tp = getattr(entry, "ticket_price", None)
if tp is not None:
tc = getattr(entry, "ticket_count", None)
tc_str = f"{tc} tickets" if tc is not None else "Unlimited"
tickets_td = (
f''
f'
£{tp:.2f}
'
f'
{tc_str}
'
)
else:
tickets_td = 'No tickets '
# Actions (entry options) - keep simple, just link to entry
actions_td = f' '
return f'{name_html}{slot_html}{state_td}{cost_td}{tickets_td}{actions_td} '
def _entry_state_badge_html(state: str) -> str:
"""Render an entry state badge."""
state_classes = {
"confirmed": "bg-emerald-100 text-emerald-800",
"provisional": "bg-amber-100 text-amber-800",
"ordered": "bg-sky-100 text-sky-800",
"pending": "bg-stone-100 text-stone-700",
"declined": "bg-red-100 text-red-800",
}
cls = state_classes.get(state, "bg-stone-100 text-stone-700")
label = state.replace("_", " ").capitalize()
return f'{label} '
# ---------------------------------------------------------------------------
# Day admin main panel
# ---------------------------------------------------------------------------
def _day_admin_main_panel_html(ctx: dict) -> str:
"""Render day admin panel (placeholder nav)."""
return 'Admin options
'
# ---------------------------------------------------------------------------
# Calendar admin main panel
# ---------------------------------------------------------------------------
def _calendar_admin_main_panel_html(ctx: dict) -> str:
"""Render calendar admin config panel with description editor."""
from quart import url_for
calendar = ctx.get("calendar")
if not calendar:
return ""
csrf_token = ctx.get("csrf_token")
csrf = csrf_token() if callable(csrf_token) else (csrf_token or "")
cal_slug = getattr(calendar, "slug", "")
desc = getattr(calendar, "description", "") or ""
hx_select = ctx.get("hx_select_search", "#main-panel")
desc_edit_url = url_for("calendars.calendar.admin.calendar_description_edit", calendar_slug=cal_slug)
description_html = _calendar_description_display_html(calendar, desc_edit_url)
parts = ['']
parts.append('Calendar configuration ')
parts.append('
')
parts.append(f'
Description ')
parts.append(description_html)
parts.append('
')
# Hidden form for direct PUT
parts.append(
f'
'
)
parts.append('
')
return "".join(parts)
def _calendar_description_display_html(calendar, edit_url: str) -> str:
"""Render calendar description display with edit button."""
desc = getattr(calendar, "description", "") or ""
if desc:
desc_html = f'{escape(desc)}
'
else:
desc_html = 'No description yet.
'
return (
f'{desc_html}'
f''
f'
'
)
# ---------------------------------------------------------------------------
# Markets main panel
# ---------------------------------------------------------------------------
def _markets_main_panel_html(ctx: dict) -> str:
"""Render markets list + create form panel."""
from quart import url_for
rights = ctx.get("rights") or {}
is_admin = getattr(rights, "admin", False) if hasattr(rights, "admin") else rights.get("admin", False)
has_access = ctx.get("has_access")
can_create = has_access("markets.create_market") if callable(has_access) else is_admin
csrf_token = ctx.get("csrf_token")
csrf = csrf_token() if callable(csrf_token) else (csrf_token or "")
markets = ctx.get("markets") or []
post = ctx.get("post") or {}
parts = ['']
if can_create:
create_url = url_for("markets.create_market")
parts.append(
'
'
f'"""
f' '
'Name '
'
'
'Add market '
)
parts.append('')
parts.append(_markets_list_html(ctx, markets))
parts.append('
')
return "".join(parts)
def _markets_list_html(ctx: dict, markets: list) -> str:
"""Render markets list items."""
from quart import url_for
csrf_token = ctx.get("csrf_token")
csrf = csrf_token() if callable(csrf_token) else (csrf_token or "")
post = ctx.get("post") or {}
slug = post.get("slug", "")
if not markets:
return 'No markets yet. Create one above.
'
parts = []
for m in markets:
m_slug = getattr(m, "slug", "") if hasattr(m, "slug") else m.get("slug", "")
m_name = getattr(m, "name", "") if hasattr(m, "name") else m.get("name", "")
market_href = call_url(ctx, "market_url", f"/{slug}/{m_slug}/")
del_url = url_for("markets.delete_market", market_slug=m_slug)
parts.append(
f''
)
return "".join(parts)
# ---------------------------------------------------------------------------
# Payments main panel
# ---------------------------------------------------------------------------
def _payments_main_panel_html(ctx: dict) -> str:
"""Render SumUp payment config form."""
from quart import url_for
csrf_token = ctx.get("csrf_token")
csrf = csrf_token() if callable(csrf_token) else (csrf_token or "")
sumup_configured = ctx.get("sumup_configured", False)
merchant_code = ctx.get("sumup_merchant_code", "")
checkout_prefix = ctx.get("sumup_checkout_prefix", "")
update_url = url_for("payments.update_sumup")
placeholder = "--------" if sumup_configured else "sup_sk_..."
key_note = 'Key is set. Leave blank to keep current key.
' if sumup_configured else ""
connected = (''
' Connected ') if sumup_configured else ""
return (
''
)
# ---------------------------------------------------------------------------
# Ticket state badge helper
# ---------------------------------------------------------------------------
def _ticket_state_badge_html(state: str) -> str:
"""Render a ticket state badge."""
cls_map = {
"confirmed": "bg-emerald-100 text-emerald-800",
"checked_in": "bg-blue-100 text-blue-800",
"reserved": "bg-amber-100 text-amber-800",
"cancelled": "bg-red-100 text-red-800",
}
cls = cls_map.get(state, "bg-stone-100 text-stone-700")
label = (state or "").replace("_", " ").capitalize()
return f'{label} '
# ---------------------------------------------------------------------------
# Tickets main panel (my tickets)
# ---------------------------------------------------------------------------
def _tickets_main_panel_html(ctx: dict, tickets: list) -> str:
"""Render my tickets list."""
from quart import url_for
parts = [f'']
parts.append('My Tickets ')
if tickets:
parts.append('')
for ticket in tickets:
href = url_for("tickets.ticket_detail", code=ticket.code)
entry = getattr(ticket, "entry", None)
entry_name = entry.name if entry else "Unknown event"
tt = getattr(ticket, "ticket_type", None)
state = getattr(ticket, "state", "")
parts.append(
f'
'
''
f'
{escape(entry_name)}
'
)
if tt:
parts.append(f'
{escape(tt.name)}
')
if entry:
parts.append(
'
'
f'{entry.start_at.strftime("%A, %B %d, %Y at %H:%M")}'
)
if entry.end_at:
parts.append(f' – {entry.end_at.strftime("%H:%M")}')
parts.append('
')
cal = getattr(entry, "calendar", None)
if cal:
parts.append(f'
{escape(cal.name)}
')
parts.append('
')
parts.append(_ticket_state_badge_html(state))
parts.append(f'{ticket.code[:8]}... ')
parts.append('
')
parts.append('
')
else:
parts.append(
''
'
'
'
No tickets yet
'
'
Tickets will appear here after you purchase them.
'
)
parts.append(' ')
return "".join(parts)
# ---------------------------------------------------------------------------
# Ticket detail panel
# ---------------------------------------------------------------------------
def _ticket_detail_panel_html(ctx: dict, ticket) -> str:
"""Render a single ticket detail with QR code."""
from quart import url_for
entry = getattr(ticket, "entry", None)
tt = getattr(ticket, "ticket_type", None)
state = getattr(ticket, "state", "")
code = ticket.code
# Background color for header
bg_map = {"confirmed": "bg-emerald-50", "checked_in": "bg-blue-50", "reserved": "bg-amber-50"}
header_bg = bg_map.get(state, "bg-stone-50")
entry_name = entry.name if entry else "Ticket"
back_href = url_for("tickets.my_tickets")
parts = [f'']
parts.append(
f''
' Back to my tickets '
)
parts.append('')
# Header
parts.append(f'')
# QR code
parts.append(
f'
'
)
# Event details
parts.append('
')
if entry:
parts.append(
'
'
f'
{entry.start_at.strftime("%A, %B %d, %Y")}
'
f'
{entry.start_at.strftime("%H:%M")}'
)
if entry.end_at:
parts.append(f' – {entry.end_at.strftime("%H:%M")}')
parts.append('
')
cal = getattr(entry, "calendar", None)
if cal:
parts.append(
'
'
)
if tt and getattr(tt, "cost", None):
parts.append(
'
'
f'
{escape(tt.name)} — £{tt.cost:.2f}
'
)
checked_in_at = getattr(ticket, "checked_in_at", None)
if checked_in_at:
parts.append(
'
'
f'
Checked in: {checked_in_at.strftime("%B %d, %Y at %H:%M")}
'
)
parts.append('
')
# QR code script
parts.append(
''
''
)
parts.append(' ')
return "".join(parts)
# ---------------------------------------------------------------------------
# Ticket admin main panel
# ---------------------------------------------------------------------------
def _ticket_admin_main_panel_html(ctx: dict, tickets: list, stats: dict) -> str:
"""Render ticket admin dashboard."""
from quart import url_for
csrf_token = ctx.get("csrf_token")
csrf = csrf_token() if callable(csrf_token) else (csrf_token or "")
lookup_url = url_for("ticket_admin.lookup")
parts = [f'']
parts.append('Ticket Admin ')
# Stats
parts.append('')
for label, key, border, bg, text_cls in [
("Total", "total", "border-stone-200", "", "text-stone-900"),
("Confirmed", "confirmed", "border-emerald-200", "bg-emerald-50", "text-emerald-700"),
("Checked In", "checked_in", "border-blue-200", "bg-blue-50", "text-blue-700"),
("Reserved", "reserved", "border-amber-200", "bg-amber-50", "text-amber-700"),
]:
val = stats.get(key, 0)
lbl_cls = text_cls.replace("700", "600").replace("900", "500") if "stone" not in text_cls else "text-stone-500"
parts.append(
f'
'
)
parts.append('
')
# Scanner
parts.append(
''
'
Scan / Look Up Ticket'
'
'
f' '
'"""
'
'
'
'
'Enter a ticket code to look it up
'
)
# Recent tickets table
parts.append('')
parts.append('
Recent Tickets ')
if tickets:
parts.append('
')
for col in ["Code", "Event", "Type", "State", "Actions"]:
parts.append(f'{col} ')
parts.append(' ')
for ticket in tickets:
entry = getattr(ticket, "entry", None)
tt = getattr(ticket, "ticket_type", None)
state = getattr(ticket, "state", "")
code = ticket.code
parts.append(f'')
parts.append(f'{code[:12]}... ')
parts.append(f'{escape(entry.name) if entry else "—"}
')
if entry and entry.start_at:
parts.append(f'{entry.start_at.strftime("%d %b %Y, %H:%M")}
')
parts.append(' ')
parts.append(f'{escape(tt.name) if tt else "—"} ')
parts.append(f'{_ticket_state_badge_html(state)} ')
# Actions
parts.append('')
if state in ("confirmed", "reserved"):
checkin_url = url_for("ticket_admin.do_checkin", code=code)
parts.append(
f''
f' '
''
' Check in '
)
elif state == "checked_in":
checked_in_at = getattr(ticket, "checked_in_at", None)
t_str = checked_in_at.strftime("%H:%M") if checked_in_at else ""
parts.append(f' {t_str} ')
parts.append(' ')
parts.append('
')
else:
parts.append('
No tickets yet
')
parts.append('
')
return "".join(parts)
# ---------------------------------------------------------------------------
# All events / page summary entry cards
# ---------------------------------------------------------------------------
def _entry_card_html(entry, page_info: dict, pending_tickets: dict,
ticket_url: str, events_url_fn, *, is_page_scoped: bool = False,
post: dict | None = None) -> str:
"""Render a list card for one event entry."""
pi = page_info.get(getattr(entry, "calendar_container_id", 0), {})
if is_page_scoped and post:
page_slug = pi.get("slug", post.get("slug", ""))
else:
page_slug = pi.get("slug", "")
page_title = pi.get("title")
day_href = ""
if page_slug:
day_href = events_url_fn(f"/{page_slug}/calendars/{entry.calendar_slug}/day/{entry.start_at.strftime('%Y/%-m/%-d')}/")
entry_href = f"{day_href}entries/{entry.id}/" if day_href else ""
parts = ['']
parts.append('')
parts.append('
')
if entry_href:
parts.append(f'
')
parts.append(f'{escape(entry.name)} ')
if entry_href:
parts.append(' ')
# Badges
parts.append('
')
if page_title and (not is_page_scoped or page_title != (post or {}).get("title")):
page_href = events_url_fn(f"/{page_slug}/")
parts.append(
f'
'
f'{escape(page_title)} '
)
cal_name = getattr(entry, "calendar_name", "")
if cal_name:
parts.append(f'
{escape(cal_name)} ')
parts.append('
')
# Time
parts.append('
')
if day_href and not is_page_scoped:
parts.append(f'
{entry.start_at.strftime("%a %-d %b")} · ')
elif not is_page_scoped:
parts.append(f'{entry.start_at.strftime("%a %-d %b")} · ')
parts.append(entry.start_at.strftime("%H:%M"))
if entry.end_at:
parts.append(f' – {entry.end_at.strftime("%H:%M")}')
parts.append('
')
cost = getattr(entry, "cost", None)
if cost:
parts.append(f'
£{cost:.2f}
')
parts.append('
')
# Ticket widget
tp = getattr(entry, "ticket_price", None)
if tp is not None:
qty = pending_tickets.get(entry.id, 0)
parts.append('
')
parts.append(_ticket_widget_html(entry, qty, ticket_url, ctx={}))
parts.append('
')
parts.append('
')
return "".join(parts)
def _entry_card_tile_html(entry, page_info: dict, pending_tickets: dict,
ticket_url: str, events_url_fn, *, is_page_scoped: bool = False,
post: dict | None = None) -> str:
"""Render a tile card for one event entry."""
pi = page_info.get(getattr(entry, "calendar_container_id", 0), {})
if is_page_scoped and post:
page_slug = pi.get("slug", post.get("slug", ""))
else:
page_slug = pi.get("slug", "")
page_title = pi.get("title")
day_href = ""
if page_slug:
day_href = events_url_fn(f"/{page_slug}/calendars/{entry.calendar_slug}/day/{entry.start_at.strftime('%Y/%-m/%-d')}/")
entry_href = f"{day_href}entries/{entry.id}/" if day_href else ""
parts = ['']
if entry_href:
parts.append(f'
')
parts.append(f'{escape(entry.name)} ')
if entry_href:
parts.append(' ')
parts.append('
')
if page_title and (not is_page_scoped or page_title != (post or {}).get("title")):
page_href = events_url_fn(f"/{page_slug}/")
parts.append(
f'
'
f'{escape(page_title)} '
)
cal_name = getattr(entry, "calendar_name", "")
if cal_name:
parts.append(f'
{escape(cal_name)} ')
parts.append('
')
parts.append('
')
if day_href:
parts.append(f'
{entry.start_at.strftime("%a %-d %b")} ')
else:
parts.append(entry.start_at.strftime("%a %-d %b"))
parts.append(f' · {entry.start_at.strftime("%H:%M")}')
if entry.end_at:
parts.append(f' – {entry.end_at.strftime("%H:%M")}')
parts.append('
')
cost = getattr(entry, "cost", None)
if cost:
parts.append(f'
£{cost:.2f}
')
parts.append('
')
tp = getattr(entry, "ticket_price", None)
if tp is not None:
qty = pending_tickets.get(entry.id, 0)
parts.append('')
parts.append(_ticket_widget_html(entry, qty, ticket_url, ctx={}))
parts.append('
')
parts.append(' ')
return "".join(parts)
def _ticket_widget_html(entry, qty: int, ticket_url: str, *, ctx: dict) -> str:
"""Render the inline +/- ticket widget."""
csrf_token_val = ""
if ctx:
ct = ctx.get("csrf_token")
csrf_token_val = ct() if callable(ct) else (ct or "")
else:
from quart import g as _g
ct = getattr(_g, "_csrf_token", None)
try:
from quart import current_app
with current_app.app_context():
pass
except Exception:
pass
# Use a deferred approach - get CSRF from template context
csrf_token_val = ""
# For the ticket widget, we need to get csrf token from the app
try:
from flask_wtf.csrf import generate_csrf
csrf_token_val = generate_csrf()
except Exception:
pass
if not csrf_token_val:
try:
from quart import current_app
csrf_token_val = current_app.config.get("WTF_CSRF_SECRET_KEY", "")
except Exception:
pass
eid = entry.id
tp = getattr(entry, "ticket_price", 0) or 0
cart_url_fn = None
parts = [f'']
parts.append(f'£{tp:.2f} ')
if qty == 0:
parts.append(
f'
'
f' '
f' '
' '
''
' '
)
else:
# Minus button
parts.append(
f''
f' '
f' '
f' '
'- '
)
# Cart icon with count
parts.append(
''
''
' '
''
f'{qty} '
' '
)
# Plus button
parts.append(
f''
f' '
f' '
f' '
'+ '
)
parts.append('')
return "".join(parts)
def _entry_cards_html(entries, page_info, pending_tickets, ticket_url,
events_url_fn, view, page, has_more, next_url,
*, is_page_scoped=False, post=None) -> str:
"""Render entry cards (list or tile) with sentinel."""
parts = []
last_date = None
for entry in entries:
if view == "tile":
parts.append(_entry_card_tile_html(
entry, page_info, pending_tickets, ticket_url, events_url_fn,
is_page_scoped=is_page_scoped, post=post,
))
else:
entry_date = entry.start_at.strftime("%A %-d %B %Y")
if entry_date != last_date:
parts.append(
f'
'
f'{entry_date} '
)
last_date = entry_date
parts.append(_entry_card_html(
entry, page_info, pending_tickets, ticket_url, events_url_fn,
is_page_scoped=is_page_scoped, post=post,
))
if has_more:
parts.append(
f''
)
return "".join(parts)
# ---------------------------------------------------------------------------
# All events / page summary main panels
# ---------------------------------------------------------------------------
_LIST_SVG = ' '
_TILE_SVG = ' '
def _view_toggle_html(ctx: dict, view: str) -> str:
"""Render the list/tile view toggle bar."""
from shared.utils import route_prefix
prefix = route_prefix()
clh = ctx.get("current_local_href", "/")
qs_fn = ctx.get("qs")
hx_select = ctx.get("hx_select_search", "#main-panel")
# Build hrefs - list removes view param, tile sets view=tile
list_href = prefix + str(clh)
tile_href = prefix + str(clh)
# Use simple query parameter manipulation
if "?" in list_href:
list_href = list_href.split("?")[0]
if "?" in tile_href:
tile_href = tile_href.split("?")[0] + "?view=tile"
else:
tile_href = tile_href + "?view=tile"
list_active = 'bg-stone-200 text-stone-800' if view != 'tile' else 'text-stone-400 hover:text-stone-600'
tile_active = 'bg-stone-200 text-stone-800' if view == 'tile' else 'text-stone-400 hover:text-stone-600'
return (
'"""
)
def _events_main_panel_html(ctx: dict, entries, has_more, pending_tickets, page_info,
page, view, ticket_url, next_url, events_url_fn,
*, is_page_scoped=False, post=None) -> str:
"""Render the events main panel with view toggle + cards."""
parts = [_view_toggle_html(ctx, view)]
if entries:
cards = _entry_cards_html(
entries, page_info, pending_tickets, ticket_url, events_url_fn,
view, page, has_more, next_url,
is_page_scoped=is_page_scoped, post=post,
)
if view == "tile":
parts.append(f'{cards}
')
else:
parts.append(f'{cards}
')
else:
parts.append(
''
)
parts.append('
')
return "".join(parts)
# ---------------------------------------------------------------------------
# Utility
# ---------------------------------------------------------------------------
def _list_container(ctx: dict) -> str:
styles = ctx.get("styles") or {}
return getattr(styles, "list_container", "") if hasattr(styles, "list_container") else styles.get("list_container", "")
# ===========================================================================
# PUBLIC API
# ===========================================================================
# ---------------------------------------------------------------------------
# All events
# ---------------------------------------------------------------------------
async def render_all_events_page(ctx: dict, entries, has_more, pending_tickets,
page_info, page, view) -> str:
"""Full page: all events listing."""
from quart import url_for
from shared.utils import route_prefix, events_url
prefix = route_prefix()
view_param = f"&view={view}" if view != "list" else ""
ticket_url = url_for("all_events.adjust_ticket")
next_url = prefix + url_for("all_events.entries_fragment", page=page + 1) + (f"?view={view}" if view != "list" else "")
content = _events_main_panel_html(
ctx, entries, has_more, pending_tickets, page_info,
page, view, ticket_url, next_url, events_url,
)
hdr = root_header_html(ctx)
return full_page(ctx, header_rows_html=hdr, content_html=content)
async def render_all_events_oob(ctx: dict, entries, has_more, pending_tickets,
page_info, page, view) -> str:
"""OOB response: all events listing (htmx nav)."""
from quart import url_for
from shared.utils import route_prefix, events_url
prefix = route_prefix()
ticket_url = url_for("all_events.adjust_ticket")
next_url = prefix + url_for("all_events.entries_fragment", page=page + 1) + (f"?view={view}" if view != "list" else "")
content = _events_main_panel_html(
ctx, entries, has_more, pending_tickets, page_info,
page, view, ticket_url, next_url, events_url,
)
return oob_page(ctx, content_html=content)
async def render_all_events_cards(entries, has_more, pending_tickets,
page_info, page, view) -> str:
"""Pagination fragment: all events cards only."""
from quart import url_for
from shared.utils import route_prefix, events_url
prefix = route_prefix()
ticket_url = url_for("all_events.adjust_ticket")
next_url = prefix + url_for("all_events.entries_fragment", page=page + 1) + (f"?view={view}" if view != "list" else "")
return _entry_cards_html(
entries, page_info, pending_tickets, ticket_url, events_url,
view, page, has_more, next_url,
)
# ---------------------------------------------------------------------------
# Page summary
# ---------------------------------------------------------------------------
async def render_page_summary_page(ctx: dict, entries, has_more, pending_tickets,
page_info, page, view) -> str:
"""Full page: page-scoped events listing."""
from quart import url_for
from shared.utils import route_prefix, events_url
prefix = route_prefix()
post = ctx.get("post") or {}
ticket_url = url_for("page_summary.adjust_ticket")
next_url = prefix + url_for("page_summary.entries_fragment", page=page + 1) + (f"?view={view}" if view != "list" else "")
content = _events_main_panel_html(
ctx, entries, has_more, pending_tickets, page_info,
page, view, ticket_url, next_url, events_url,
is_page_scoped=True, post=post,
)
hdr = root_header_html(ctx)
hdr += sexp(
'(div :id "root-header-child" :class "w-full" (raw! ph))',
ph=_post_header_html(ctx),
)
return full_page(ctx, header_rows_html=hdr, content_html=content)
async def render_page_summary_oob(ctx: dict, entries, has_more, pending_tickets,
page_info, page, view) -> str:
"""OOB response: page-scoped events (htmx nav)."""
from quart import url_for
from shared.utils import route_prefix, events_url
prefix = route_prefix()
post = ctx.get("post") or {}
ticket_url = url_for("page_summary.adjust_ticket")
next_url = prefix + url_for("page_summary.entries_fragment", page=page + 1) + (f"?view={view}" if view != "list" else "")
content = _events_main_panel_html(
ctx, entries, has_more, pending_tickets, page_info,
page, view, ticket_url, next_url, events_url,
is_page_scoped=True, post=post,
)
oobs = _post_header_html(ctx, oob=True)
return oob_page(ctx, oobs_html=oobs, content_html=content)
async def render_page_summary_cards(entries, has_more, pending_tickets,
page_info, page, view, post) -> str:
"""Pagination fragment: page-scoped events cards only."""
from quart import url_for
from shared.utils import route_prefix, events_url
prefix = route_prefix()
ticket_url = url_for("page_summary.adjust_ticket")
next_url = prefix + url_for("page_summary.entries_fragment", page=page + 1) + (f"?view={view}" if view != "list" else "")
return _entry_cards_html(
entries, page_info, pending_tickets, ticket_url, events_url,
view, page, has_more, next_url,
is_page_scoped=True, post=post,
)
# ---------------------------------------------------------------------------
# Calendars home
# ---------------------------------------------------------------------------
async def render_calendars_page(ctx: dict) -> str:
"""Full page: calendars listing."""
content = _calendars_main_panel_html(ctx)
hdr = root_header_html(ctx)
hdr += sexp(
'(div :id "root-header-child" :class "w-full" (raw! ph (raw! pah (raw! ch))))',
ph=_post_header_html(ctx),
pah=_post_admin_header_html(ctx),
ch=_calendars_header_html(ctx),
)
return full_page(ctx, header_rows_html=hdr, content_html=content)
async def render_calendars_oob(ctx: dict) -> str:
"""OOB response: calendars listing."""
content = _calendars_main_panel_html(ctx)
oobs = _post_admin_header_html(ctx, oob=True)
oobs += _oob_header_html("post-admin-header-child", "calendars-header-child",
_calendars_header_html(ctx))
return oob_page(ctx, oobs_html=oobs, content_html=content)
# ---------------------------------------------------------------------------
# Calendar month view
# ---------------------------------------------------------------------------
async def render_calendar_page(ctx: dict) -> str:
"""Full page: calendar month view."""
content = _calendar_main_panel_html(ctx)
hdr = root_header_html(ctx)
hdr += sexp(
'(div :id "root-header-child" :class "w-full" (raw! ph (raw! pah (raw! ch))))',
ph=_post_header_html(ctx),
pah=_post_admin_header_html(ctx),
ch=_calendar_header_html(ctx),
)
return full_page(ctx, header_rows_html=hdr, content_html=content)
async def render_calendar_oob(ctx: dict) -> str:
"""OOB response: calendar month view."""
content = _calendar_main_panel_html(ctx)
oobs = _post_header_html(ctx, oob=True)
oobs += _oob_header_html("post-header-child", "calendar-header-child",
_calendar_header_html(ctx))
return oob_page(ctx, oobs_html=oobs, content_html=content)
# ---------------------------------------------------------------------------
# Day detail
# ---------------------------------------------------------------------------
async def render_day_page(ctx: dict) -> str:
"""Full page: day detail."""
content = _day_main_panel_html(ctx)
hdr = root_header_html(ctx)
hdr += sexp(
'(div :id "root-header-child" :class "w-full" (raw! ph (raw! pah (raw! ch (raw! dh)))))',
ph=_post_header_html(ctx),
pah=_post_admin_header_html(ctx),
ch=_calendar_header_html(ctx),
dh=_day_header_html(ctx),
)
return full_page(ctx, header_rows_html=hdr, content_html=content)
async def render_day_oob(ctx: dict) -> str:
"""OOB response: day detail."""
content = _day_main_panel_html(ctx)
oobs = _calendar_header_html(ctx, oob=True)
oobs += _oob_header_html("calendar-header-child", "day-header-child",
_day_header_html(ctx))
return oob_page(ctx, oobs_html=oobs, content_html=content)
# ---------------------------------------------------------------------------
# Day admin
# ---------------------------------------------------------------------------
async def render_day_admin_page(ctx: dict) -> str:
"""Full page: day admin."""
content = _day_admin_main_panel_html(ctx)
hdr = root_header_html(ctx)
hdr += sexp(
'(div :id "root-header-child" :class "w-full" (raw! ph (raw! pah (raw! ch (raw! dh (raw! dah))))))',
ph=_post_header_html(ctx),
pah=_post_admin_header_html(ctx),
ch=_calendar_header_html(ctx),
dh=_day_header_html(ctx),
dah=_day_admin_header_html(ctx),
)
return full_page(ctx, header_rows_html=hdr, content_html=content)
async def render_day_admin_oob(ctx: dict) -> str:
"""OOB response: day admin."""
content = _day_admin_main_panel_html(ctx)
oobs = _calendar_header_html(ctx, oob=True)
oobs += _oob_header_html("day-header-child", "day-admin-header-child",
_day_admin_header_html(ctx))
return oob_page(ctx, oobs_html=oobs, content_html=content)
# ---------------------------------------------------------------------------
# Calendar admin
# ---------------------------------------------------------------------------
async def render_calendar_admin_page(ctx: dict) -> str:
"""Full page: calendar admin."""
content = _calendar_admin_main_panel_html(ctx)
hdr = root_header_html(ctx)
hdr += sexp(
'(div :id "root-header-child" :class "w-full" (raw! ph (raw! pah (raw! ch (raw! cah)))))',
ph=_post_header_html(ctx),
pah=_post_admin_header_html(ctx),
ch=_calendar_header_html(ctx),
cah=_calendar_admin_header_html(ctx),
)
return full_page(ctx, header_rows_html=hdr, content_html=content)
async def render_calendar_admin_oob(ctx: dict) -> str:
"""OOB response: calendar admin."""
content = _calendar_admin_main_panel_html(ctx)
oobs = _calendar_header_html(ctx, oob=True)
oobs += _oob_header_html("calendar-header-child", "calendar-admin-header-child",
_calendar_admin_header_html(ctx))
return oob_page(ctx, oobs_html=oobs, content_html=content)
# ---------------------------------------------------------------------------
# Tickets
# ---------------------------------------------------------------------------
async def render_tickets_page(ctx: dict, tickets: list) -> str:
"""Full page: my tickets."""
content = _tickets_main_panel_html(ctx, tickets)
hdr = root_header_html(ctx)
return full_page(ctx, header_rows_html=hdr, content_html=content)
async def render_tickets_oob(ctx: dict, tickets: list) -> str:
"""OOB response: my tickets."""
content = _tickets_main_panel_html(ctx, tickets)
return oob_page(ctx, content_html=content)
async def render_ticket_detail_page(ctx: dict, ticket) -> str:
"""Full page: ticket detail with QR."""
content = _ticket_detail_panel_html(ctx, ticket)
hdr = root_header_html(ctx)
return full_page(ctx, header_rows_html=hdr, content_html=content)
async def render_ticket_detail_oob(ctx: dict, ticket) -> str:
"""OOB response: ticket detail."""
content = _ticket_detail_panel_html(ctx, ticket)
return oob_page(ctx, content_html=content)
# ---------------------------------------------------------------------------
# Ticket admin
# ---------------------------------------------------------------------------
async def render_ticket_admin_page(ctx: dict, tickets: list, stats: dict) -> str:
"""Full page: ticket admin dashboard."""
content = _ticket_admin_main_panel_html(ctx, tickets, stats)
hdr = root_header_html(ctx)
return full_page(ctx, header_rows_html=hdr, content_html=content)
async def render_ticket_admin_oob(ctx: dict, tickets: list, stats: dict) -> str:
"""OOB response: ticket admin dashboard."""
content = _ticket_admin_main_panel_html(ctx, tickets, stats)
return oob_page(ctx, content_html=content)
# ---------------------------------------------------------------------------
# Markets
# ---------------------------------------------------------------------------
async def render_markets_page(ctx: dict) -> str:
"""Full page: markets listing."""
content = _markets_main_panel_html(ctx)
hdr = root_header_html(ctx)
hdr += sexp(
'(div :id "root-header-child" :class "w-full" (raw! ph (raw! pah (raw! mh))))',
ph=_post_header_html(ctx),
pah=_post_admin_header_html(ctx),
mh=_markets_header_html(ctx),
)
return full_page(ctx, header_rows_html=hdr, content_html=content)
async def render_markets_oob(ctx: dict) -> str:
"""OOB response: markets listing."""
content = _markets_main_panel_html(ctx)
oobs = _post_admin_header_html(ctx, oob=True)
oobs += _oob_header_html("post-admin-header-child", "markets-header-child",
_markets_header_html(ctx))
return oob_page(ctx, oobs_html=oobs, content_html=content)
# ---------------------------------------------------------------------------
# Payments
# ---------------------------------------------------------------------------
async def render_payments_page(ctx: dict) -> str:
"""Full page: payments admin."""
content = _payments_main_panel_html(ctx)
hdr = root_header_html(ctx)
hdr += sexp(
'(div :id "root-header-child" :class "w-full" (raw! ph (raw! pah (raw! pyh))))',
ph=_post_header_html(ctx),
pah=_post_admin_header_html(ctx),
pyh=_payments_header_html(ctx),
)
return full_page(ctx, header_rows_html=hdr, content_html=content)
async def render_payments_oob(ctx: dict) -> str:
"""OOB response: payments admin."""
content = _payments_main_panel_html(ctx)
oobs = _post_admin_header_html(ctx, oob=True)
oobs += _oob_header_html("post-admin-header-child", "payments-header-child",
_payments_header_html(ctx))
return oob_page(ctx, oobs_html=oobs, content_html=content)