"""Product/market card builders.""" from __future__ import annotations from typing import Any from shared.sx.parser import SxExpr from shared.sx.helpers import sx_call from .utils import _set_prices, _price_str from .filters import _MOBILE_SENTINEL_HS, _DESKTOP_SENTINEL_HS # --------------------------------------------------------------------------- # Product card (browse grid item) # --------------------------------------------------------------------------- def _product_card_sx(p: dict, ctx: dict) -> str: """Build a single product card for browse grid as sx call.""" from quart import url_for from shared.browser.app.csrf import generate_csrf_token from shared.utils import route_prefix prefix = route_prefix() slug = p.get("slug", "") item_href = prefix + url_for("market.browse.product.product_detail", product_slug=slug) hx_select = ctx.get("hx_select_search", "#main-panel") asset_url_fn = ctx.get("asset_url") cart = ctx.get("cart", []) selected_brands = ctx.get("selected_brands", []) selected_stickers = ctx.get("selected_stickers", []) search = ctx.get("search", "") user = ctx.get("user") csrf = generate_csrf_token() # Price data pr = _set_prices(p) sp_str = _price_str(pr["sp_val"], pr["sp_raw"], pr["sp_cur"]) rp_str = _price_str(pr["rp_val"], pr["rp_raw"], pr["rp_cur"]) # Image labels as src URLs labels = p.get("labels", []) label_srcs = [] if p.get("image") and callable(asset_url_fn): label_srcs = [asset_url_fn("labels/" + l + ".svg") for l in labels] # Stickers as data raw_stickers = p.get("stickers", []) sticker_data = [] if raw_stickers and callable(asset_url_fn): for s in raw_stickers: ring = " ring-2 ring-emerald-500 rounded" if s in selected_stickers else "" sticker_data.append({"src": asset_url_fn(f"stickers/{s}.svg"), "name": s, "ring-cls": ring}) # Title highlighting title = p.get("title", "") has_highlight = False search_pre = search_mid = search_post = "" if search and search.lower() in title.lower(): idx = title.lower().index(search.lower()) has_highlight = True search_pre = title[:idx] search_mid = title[idx:idx + len(search)] search_post = title[idx + len(search):] # Cart quantity = sum(ci.quantity for ci in cart if ci.product.slug == slug) if cart else 0 cart_action = url_for("market.browse.product.cart", product_slug=slug) cart_url_fn = ctx.get("cart_url") cart_href = cart_url_fn("/") if callable(cart_url_fn) else "/" brand = p.get("brand", "") brand_highlight = " bg-yellow-200" if brand in selected_brands else "" kwargs = dict( href=item_href, hx_select=hx_select, slug=slug, image=p.get("image", ""), brand=brand, brand_highlight=brand_highlight, special_price=sp_str, regular_price=rp_str, cart_action=cart_action, quantity=quantity, cart_href=cart_href, csrf=csrf, title=title, has_like=bool(user), ) if label_srcs: kwargs["labels"] = label_srcs elif labels: kwargs["labels"] = labels if user: kwargs["liked"] = p.get("is_liked", False) kwargs["like_action"] = url_for("market.browse.product.like_toggle", product_slug=slug) if sticker_data: kwargs["stickers"] = sticker_data if has_highlight: kwargs["has_highlight"] = True kwargs["search_pre"] = search_pre kwargs["search_mid"] = search_mid kwargs["search_post"] = search_post return sx_call("market-product-card", **kwargs) def _product_cards_sx(ctx: dict) -> str: """S-expression wire format for product cards (client renders).""" from shared.utils import route_prefix prefix = route_prefix() products = ctx.get("products", []) page = ctx.get("page", 1) total_pages = ctx.get("total_pages", 1) current_local_href = ctx.get("current_local_href", "/") qs_fn = ctx.get("qs_filter") parts = [] for p in products: parts.append(_product_card_sx(p, ctx)) if page < total_pages: if callable(qs_fn): next_qs = qs_fn({"page": page + 1}) else: next_qs = f"?page={page + 1}" next_url = prefix + current_local_href + next_qs parts.append(sx_call("sentinel-mobile", id=f"sentinel-{page}-m", next_url=next_url, hyperscript=_MOBILE_SENTINEL_HS)) parts.append(sx_call("sentinel-desktop", id=f"sentinel-{page}-d", next_url=next_url, hyperscript=_DESKTOP_SENTINEL_HS)) else: parts.append(sx_call("end-of-results")) return "(<> " + " ".join(parts) + ")" def _like_button_sx(slug: str, liked: bool, csrf: str, ctx: dict) -> str: """Build the like/unlike heart button overlay as sx.""" from quart import url_for action = url_for("market.browse.product.like_toggle", product_slug=slug) icon_cls = "fa-solid fa-heart text-red-500" if liked else "fa-regular fa-heart text-stone-400" return sx_call( "market-like-button", form_id=f"like-{slug}", action=action, slug=slug, csrf=csrf, icon_cls=icon_cls, ) # --------------------------------------------------------------------------- # Market cards (all markets / page markets) # --------------------------------------------------------------------------- def _market_card_sx(market: Any, page_info: dict, *, show_page_badge: bool = True, post_slug: str = "") -> str: """Build a single market card as sx.""" from shared.infrastructure.urls import market_url name = getattr(market, "name", "") description = getattr(market, "description", "") slug = getattr(market, "slug", "") container_id = getattr(market, "container_id", None) if show_page_badge and page_info: pi = page_info.get(container_id, {}) p_slug = pi.get("slug", "") p_title = pi.get("title", "") market_href = market_url(f"/{p_slug}/{slug}/") if p_slug else "" else: p_slug = post_slug p_title = "" market_href = market_url(f"/{post_slug}/{slug}/") if post_slug else "" title_sx = "" if market_href: title_sx = sx_call("market-market-card-title-link", href=market_href, name=name) else: title_sx = sx_call("market-market-card-title", name=name) desc_sx = "" if description: desc_sx = sx_call("market-market-card-desc", description=description) badge_sx = "" if show_page_badge and p_title: badge_href = market_url(f"/{p_slug}/") badge_sx = sx_call("market-market-card-badge", href=badge_href, title=p_title) return sx_call( "market-market-card", title_content=SxExpr(title_sx) if title_sx else None, desc_content=SxExpr(desc_sx) if desc_sx else None, badge_content=SxExpr(badge_sx) if badge_sx else None, ) def _market_cards_sx(markets: list, page_info: dict, page: int, has_more: bool, next_url: str, *, show_page_badge: bool = True, post_slug: str = "") -> str: """Build market cards with infinite scroll sentinel as sx.""" parts = [] for m in markets: parts.append(_market_card_sx(m, page_info, show_page_badge=show_page_badge, post_slug=post_slug)) if has_more: parts.append(sx_call( "sentinel-simple", id=f"sentinel-{page}", next_url=next_url, )) return "(<> " + " ".join(parts) + ")" def _markets_grid(cards_sx: str) -> str: """Wrap market cards in a grid as sx.""" return sx_call("market-markets-grid", cards=SxExpr(cards_sx)) def _no_markets_sx(message: str = "No markets available") -> str: """Empty state for markets as sx.""" return sx_call("empty-state", icon="fa fa-store", message=message, cls="px-3 py-12 text-center text-stone-400") # --------------------------------------------------------------------------- # Market landing page # --------------------------------------------------------------------------- def _market_landing_content_sx(post: dict) -> str: """Build market landing page content as sx.""" parts: list[str] = [] if post.get("custom_excerpt"): parts.append(sx_call("market-landing-excerpt", text=post["custom_excerpt"])) if post.get("feature_image"): parts.append(sx_call("market-landing-image", src=post["feature_image"])) if post.get("html"): parts.append(sx_call("market-landing-html", html=post["html"])) inner = "(<> " + " ".join(parts) + ")" if parts else "(<>)" return sx_call("market-landing-content", inner=SxExpr(inner))