"""Public render functions called from bp routes.""" from __future__ import annotations from typing import Any from shared.sx.parser import SxExpr from shared.sx.helpers import ( sx_call, post_header_sx as _post_header_sx, oob_header_sx as _oob_header_sx, full_page_sx, oob_page_sx, ) from .utils import _clear_deeper_oob, _product_detail_sx, _product_meta_sx from .cards import _product_cards_sx, _market_cards_sx from .filters import _desktop_filter_sx, _mobile_filter_summary_sx from .layouts import ( _market_header_sx, _product_header_sx, _product_admin_header_sx, _mobile_nav_panel_sx, ) from .helpers import _markets_admin_panel_sx # --------------------------------------------------------------------------- # Browse page # --------------------------------------------------------------------------- def _product_grid(cards_sx: str) -> str: """Wrap product cards in a grid as sx.""" return sx_call("market-product-grid", cards=SxExpr(cards_sx)) async def render_browse_page(ctx: dict) -> str: """Full page: product browse with filters.""" cards = _product_cards_sx(ctx) content = _product_grid(cards) from shared.sx.helpers import render_to_sx_with_env hdr = await render_to_sx_with_env("market-browse-layout-full", {}) menu = _mobile_nav_panel_sx(ctx) filter_sx = await _mobile_filter_summary_sx(ctx) aside_sx = await _desktop_filter_sx(ctx) return await full_page_sx(ctx, header_rows=hdr, content=content, menu=menu, filter=filter_sx, aside=aside_sx) async def render_browse_oob(ctx: dict) -> str: """OOB response: product browse.""" cards = _product_cards_sx(ctx) content = _product_grid(cards) # Layout handles all OOB headers via auto-fetch macros oobs = sx_call("market-browse-layout-oob") menu = _mobile_nav_panel_sx(ctx) filter_sx = await _mobile_filter_summary_sx(ctx) aside_sx = await _desktop_filter_sx(ctx) return await oob_page_sx(oobs=oobs, content=content, menu=menu, filter=filter_sx, aside=aside_sx) def render_browse_cards(ctx: dict) -> str: """Pagination fragment: product cards -- sx wire format.""" return _product_cards_sx(ctx) # --------------------------------------------------------------------------- # Product detail # --------------------------------------------------------------------------- async def render_product_page(ctx: dict, d: dict) -> str: """Full page: product detail.""" content = _product_detail_sx(d, ctx) meta = _product_meta_sx(d, ctx) from shared.sx.helpers import render_to_sx_with_env hdr = await render_to_sx_with_env("market-product-layout-full", {}, post_header=await _post_header_sx(ctx), market_header=_market_header_sx(ctx), product_header=_product_header_sx(ctx, d)) return await full_page_sx(ctx, header_rows=hdr, content=content, meta=meta) async def render_product_oob(ctx: dict, d: dict) -> str: """OOB response: product detail.""" content = _product_detail_sx(d, ctx) oobs = sx_call("market-oob-wrap", parts=SxExpr("(<> " + _market_header_sx(ctx, oob=True) + " " + await _oob_header_sx("market-header-child", "product-header-child", _product_header_sx(ctx, d)) + " " + _clear_deeper_oob("post-row", "post-header-child", "market-row", "market-header-child", "product-row", "product-header-child") + ")")) menu = _mobile_nav_panel_sx(ctx) return await oob_page_sx(oobs=oobs, content=content, menu=menu) # --------------------------------------------------------------------------- # Product admin # --------------------------------------------------------------------------- async def render_product_admin_page(ctx: dict, d: dict) -> str: """Full page: product admin.""" content = _product_detail_sx(d, ctx) from shared.sx.helpers import render_to_sx_with_env hdr = await render_to_sx_with_env("market-product-admin-layout-full", {}, post_header=await _post_header_sx(ctx), market_header=_market_header_sx(ctx), product_header=_product_header_sx(ctx, d), admin_header=_product_admin_header_sx(ctx, d)) return await full_page_sx(ctx, header_rows=hdr, content=content) async def render_product_admin_oob(ctx: dict, d: dict) -> str: """OOB response: product admin.""" content = _product_detail_sx(d, ctx) oobs = sx_call("market-oob-wrap", parts=SxExpr("(<> " + _product_header_sx(ctx, d, oob=True) + " " + await _oob_header_sx("product-header-child", "product-admin-header-child", _product_admin_header_sx(ctx, d)) + " " + _clear_deeper_oob("post-row", "post-header-child", "market-row", "market-header-child", "product-row", "product-header-child", "product-admin-row", "product-admin-header-child") + ")")) return await oob_page_sx(oobs=oobs, content=content) # --------------------------------------------------------------------------- # Market admin list panel # --------------------------------------------------------------------------- async def render_markets_admin_list_panel(ctx: dict) -> str: """Render the markets admin panel HTML for POST/DELETE response.""" return await _markets_admin_panel_sx(ctx) # --------------------------------------------------------------------------- # Public API: POST handler fragment renderers # --------------------------------------------------------------------------- def render_all_markets_cards(markets: list, has_more: bool, page_info: dict, page: int) -> str: """Pagination fragment: all markets cards.""" from quart import url_for from shared.utils import route_prefix prefix = route_prefix() next_url = prefix + url_for("all_markets.markets_fragment", page=page + 1) return _market_cards_sx(markets, page_info, page, has_more, next_url) def render_page_markets_cards(markets: list, has_more: bool, page: int, post_slug: str) -> str: """Pagination fragment: page-scoped markets cards.""" from quart import url_for from shared.utils import route_prefix prefix = route_prefix() next_url = prefix + url_for("page_markets.markets_fragment", page=page + 1) return _market_cards_sx(markets, {}, page, has_more, next_url, show_page_badge=False, post_slug=post_slug) def render_like_toggle_button(slug: str, liked: bool, *, like_url: str | None = None, item_type: str = "product") -> str: """Render a standalone like toggle button for HTMX POST response.""" from shared.browser.app.csrf import generate_csrf_token from quart import url_for from shared.utils import host_url csrf = generate_csrf_token() if not like_url: like_url = host_url(url_for("market.browse.product.like_toggle", product_slug=slug)) if liked: colour = "text-red-600" icon = "fa-solid fa-heart" label = f"Unlike this {item_type}" else: colour = "text-stone-300" icon = "fa-regular fa-heart" label = f"Like this {item_type}" return sx_call( "market-like-toggle-button", colour=colour, action=like_url, hx_headers={"X-CSRFToken": csrf}, label=label, icon_cls=icon, ) def render_cart_added_response(cart: list, item: Any, d: dict) -> str: """Render the HTMX response after add-to-cart. Returns OOB fragments: cart-mini icon + product add/remove buttons + cart item row. """ from shared.browser.app.csrf import generate_csrf_token from quart import url_for from shared.infrastructure.urls import cart_url as _cart_url csrf = generate_csrf_token() slug = d.get("slug", "") count = sum(getattr(ci, "quantity", 0) for ci in cart) # 1. Cart mini icon OOB if count > 0: cart_href = _cart_url("/") cart_mini = sx_call("market-cart-mini-count", href=cart_href, count=str(count)) else: from shared.config import config blog_href = config().get("blog_url", "/") logo = config().get("logo", "") cart_mini = sx_call("market-cart-mini-empty", href=blog_href, logo=logo) # 2. Add/remove buttons OOB action = url_for("market.browse.product.cart", product_slug=slug) quantity = getattr(item, "quantity", 0) if item else 0 if not quantity: cart_add = sx_call( "market-cart-add-empty", cart_id=f"cart-{slug}", action=action, csrf=csrf, ) else: cart_href = _cart_url("/") if callable(_cart_url) else "/" cart_add = sx_call( "market-cart-add-quantity", cart_id=f"cart-{slug}", action=action, csrf=csrf, minus_val=str(quantity - 1), plus_val=str(quantity + 1), quantity=str(quantity), cart_href=cart_href, ) add_sx = sx_call( "market-cart-add-oob", id=f"cart-add-{slug}", inner=cart_add, ) return "(<> " + cart_mini + " " + add_sx + ")"