Unify post admin nav across all services
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 1m56s

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

@@ -11,7 +11,7 @@ from sqlalchemy import select
from shared.infrastructure.factory import create_base_app
from shared.config import config
from bp import register_market_bp, register_all_markets, register_page_markets, register_fragments, register_actions, register_data
from bp import register_market_bp, register_all_markets, register_page_markets, register_page_admin, register_fragments, register_actions, register_data
async def market_context() -> dict:
@@ -111,6 +111,12 @@ def create_app() -> "Quart":
url_prefix="/<slug>",
)
# Page admin: /<slug>/admin/ — post-level admin for markets
app.register_blueprint(
register_page_admin(),
url_prefix="/<slug>/admin",
)
# Market blueprint nested under post slug: /<page_slug>/<market_slug>/
app.register_blueprint(
register_market_bp(

View File

@@ -2,6 +2,7 @@ from .market.routes import register as register_market_bp
from .product.routes import register as register_product
from .all_markets.routes import register as register_all_markets
from .page_markets.routes import register as register_page_markets
from .page_admin.routes import register as register_page_admin
from .fragments import register_fragments
from .actions import register_actions
from .data import register_data

View File

View File

@@ -0,0 +1,25 @@
from __future__ import annotations
from quart import make_response, Blueprint
from shared.browser.app.authz import require_admin
from shared.browser.app.utils.htmx import is_htmx_request
def register():
bp = Blueprint("page_admin", __name__)
@bp.get("/")
@require_admin
async def admin(**kwargs):
from shared.sexp.page import get_template_context
from sexp.sexp_components import render_page_admin_page, render_page_admin_oob
tctx = await get_template_context()
if not is_htmx_request():
html = await render_page_admin_page(tctx)
else:
html = await render_page_admin_oob(tctx)
return await make_response(html)
return bp

View File

@@ -15,6 +15,7 @@ from shared.sexp.jinja_bridge import render, load_service_components
from shared.sexp.helpers import (
call_url, get_asset_url, root_header_html,
post_header_html as _post_header_html,
post_admin_header_html,
oob_header_html as _oob_header_html,
search_mobile_html, search_desktop_html,
full_page, oob_page,
@@ -1420,7 +1421,7 @@ async def render_market_admin_page(ctx: dict) -> str:
content = "market admin"
hdr = root_header_html(ctx)
child = _post_header_html(ctx) + _market_header_html(ctx) + _market_admin_header_html(ctx)
child = _post_header_html(ctx) + _market_header_html(ctx) + _market_admin_header_html(ctx, selected="markets")
hdr += render("header-child", inner_html=child)
return full_page(ctx, header_rows_html=hdr, content_html=content)
@@ -1431,21 +1432,37 @@ async def render_market_admin_oob(ctx: dict) -> str:
oobs = _market_header_html(ctx, oob=True)
oobs += _oob_header_html("market-header-child", "market-admin-header-child",
_market_admin_header_html(ctx))
_market_admin_header_html(ctx, selected="markets"))
return oob_page(ctx, oobs_html=oobs, content_html=content)
def _market_admin_header_html(ctx: dict, *, oob: bool = False) -> str:
"""Build market admin header row."""
from quart import url_for
def _market_admin_header_html(ctx: dict, *, oob: bool = False, selected: str = "") -> str:
"""Build market admin header row — delegates to shared helper."""
slug = (ctx.get("post") or {}).get("slug", "")
return post_admin_header_html(ctx, slug, oob=oob, selected=selected)
link_href = url_for("market.admin.admin")
return render(
"menu-row",
id="market-admin-row", level=3,
link_href=link_href, link_label="admin", icon="fa fa-cog",
child_id="market-admin-header-child", oob=oob,
)
# ---------------------------------------------------------------------------
# Page admin (/<slug>/admin/) — post-level admin for markets
# ---------------------------------------------------------------------------
async def render_page_admin_page(ctx: dict) -> str:
"""Full page: page-level market admin."""
slug = (ctx.get("post") or {}).get("slug", "")
admin_hdr = post_admin_header_html(ctx, slug, selected="markets")
hdr = root_header_html(ctx)
child = _post_header_html(ctx) + admin_hdr
hdr += render("header-child", inner_html=child)
content = '<div id="main-panel"><div class="p-4 text-stone-500">Market admin</div></div>'
return full_page(ctx, header_rows_html=hdr, content_html=content)
async def render_page_admin_oob(ctx: dict) -> str:
"""OOB response: page-level market admin."""
slug = (ctx.get("post") or {}).get("slug", "")
oobs = post_admin_header_html(ctx, slug, oob=True, selected="markets")
content = '<div id="main-panel"><div class="p-4 text-stone-500">Market admin</div></div>'
return oob_page(ctx, oobs_html=oobs, content_html=content)
# ---------------------------------------------------------------------------