Unify post admin nav across all services

Move post admin header into shared/sexp/helpers.py so blog, cart,
events, and market all render the same admin row with identical nav:
calendars | markets | payments | entries | data | edit | settings.

All links are external (cross-service). The selected item shows
highlighted on the right and as white text next to "admin" on the left.

- blog: delegates to shared helper, removes blog-specific nav builder
- cart: delegates to shared helper for payments admin
- events: adds shared admin row (selected=calendars) to calendar admin
- market: adds /<slug>/admin/ route + page_admin blueprint, delegates
  to shared helper (selected=markets). Fixes 404 on page-level admin.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-28 22:01:56 +00:00
parent 2d08d6f787
commit b47ad6224b
9 changed files with 143 additions and 101 deletions

View File

@@ -8,6 +8,8 @@ from __future__ import annotations
from typing import Any
from markupsafe import escape
from .jinja_bridge import render
from .page import SEARCH_HEADERS_MOBILE, SEARCH_HEADERS_DESKTOP
@@ -104,6 +106,62 @@ def post_header_html(ctx: dict, *, oob: bool = False) -> str:
)
def post_admin_header_html(ctx: dict, slug: str, *, oob: bool = False,
selected: str = "", admin_href: str = "") -> str:
"""Shared post admin header row with unified nav across all services.
Shows: calendars | markets | payments | entries | data | edit | settings
All links are external (cross-service). The *selected* item is
highlighted on the nav and shown in white next to the admin label.
"""
# Label: shield icon + "admin" + optional selected sub-page in white
label_html = '<i class="fa fa-shield-halved" aria-hidden="true"></i> admin'
if selected:
label_html += f' <span class="text-white">{escape(selected)}</span>'
# Nav items — all external links to the appropriate service
select_colours = ctx.get("select_colours", "")
base_cls = ("justify-center cursor-pointer flex flex-row items-center"
" gap-2 rounded bg-stone-200 text-black p-3")
selected_cls = ("justify-center cursor-pointer flex flex-row items-center"
" gap-2 rounded !bg-stone-500 !text-white p-3")
nav_parts: list[str] = []
items = [
("events_url", f"/{slug}/admin/", "calendars"),
("market_url", f"/{slug}/admin/", "markets"),
("cart_url", f"/{slug}/admin/payments/", "payments"),
("blog_url", f"/{slug}/admin/entries/", "entries"),
("blog_url", f"/{slug}/admin/data/", "data"),
("blog_url", f"/{slug}/admin/edit/", "edit"),
("blog_url", f"/{slug}/admin/settings/", "settings"),
]
for url_key, path, label in items:
url_fn = ctx.get(url_key)
if not callable(url_fn):
continue
href = url_fn(path)
is_sel = label == selected
cls = selected_cls if is_sel else base_cls
aria = ' aria-selected="true"' if is_sel else ""
nav_parts.append(
f'<div class="relative nav-group">'
f'<a href="{escape(href)}"{aria}'
f' class="{cls} {escape(select_colours)}">'
f'{escape(label)}</a></div>'
)
nav_html = "".join(nav_parts)
if not admin_href:
blog_fn = ctx.get("blog_url")
admin_href = blog_fn(f"/{slug}/admin/") if callable(blog_fn) else f"/{slug}/admin/"
return render("menu-row",
id="post-admin-row", level=2,
link_href=admin_href, link_label_html=label_html,
nav_html=nav_html, child_id="post-admin-header-child", oob=oob,
)
def oob_header_html(parent_id: str, child_id: str, row_html: str) -> str:
"""Wrap a header row in an OOB swap div with child placeholder."""
return render("oob-header",