"""Price helpers, product detail/meta data builders.""" from __future__ import annotations from shared.sx.helpers import sx_call # --------------------------------------------------------------------------- # Price helpers # --------------------------------------------------------------------------- _SYM = {"GBP": "\u00a3", "EUR": "\u20ac", "USD": "$"} def _price_str(val, raw, cur) -> str: if raw: return str(raw) if isinstance(val, (int, float)): return f"{_SYM.get(cur, '')}{val:.2f}" return str(val or "") def _set_prices(item: dict) -> dict: """Extract price values from product dict (mirrors prices.html set_prices macro).""" oe = item.get("oe_list_price") or {} sp_val = item.get("special_price") or (oe.get("special") if oe else None) sp_raw = item.get("special_price_raw") or (oe.get("special_raw") if oe else None) sp_cur = item.get("special_price_currency") or (oe.get("special_currency") if oe else None) rp_val = item.get("regular_price") or item.get("rrp") or (oe.get("rrp") if oe else None) rp_raw = item.get("regular_price_raw") or item.get("rrp_raw") or (oe.get("rrp_raw") if oe else None) rp_cur = item.get("regular_price_currency") or item.get("rrp_currency") or (oe.get("rrp_currency") if oe else None) return dict(sp_val=sp_val, sp_raw=sp_raw, sp_cur=sp_cur, rp_val=rp_val, rp_raw=rp_raw, rp_cur=rp_cur) # --------------------------------------------------------------------------- # Product detail data extraction # --------------------------------------------------------------------------- def _product_detail_data(d: dict, ctx: dict) -> dict: """Extract product detail page data for .sx composition.""" from shared.browser.app.csrf import generate_csrf_token from .cards import _like_button_data asset_url_fn = ctx.get("asset_url") user = ctx.get("user") liked_by_current_user = ctx.get("liked_by_current_user", False) csrf = generate_csrf_token() images = d.get("images", []) labels = d.get("labels", []) stickers = d.get("stickers", []) brand = d.get("brand", "") slug = d.get("slug", "") # Like button data like_data = None if user: like_data = _like_button_data(slug, liked_by_current_user, csrf, ctx) # Label overlay URLs label_urls = [] if callable(asset_url_fn): label_urls = [asset_url_fn("labels/" + l + ".svg") for l in labels] # Image data image_data = [{"src": u, "alt": d.get("title", "")} for u in images] if images else [] # Thumbnail data thumb_data = [] if len(images) > 1: for i, u in enumerate(images): thumb_data.append({"title": f"Image {i+1}", "src": u, "alt": f"thumb {i+1}"}) # Sticker items sticker_items = [] if stickers and callable(asset_url_fn): for s in stickers: sticker_items.append({"src": asset_url_fn("stickers/" + s + ".svg"), "name": s}) # Extras (unit price, case size) extras = [] ppu = d.get("price_per_unit") or d.get("price_per_unit_raw") if ppu: extras.append({ "type": "unit-price", "value": _price_str(d.get("price_per_unit"), d.get("price_per_unit_raw"), d.get("price_per_unit_currency")), }) if d.get("case_size_raw"): extras.append({"type": "case-size", "value": d["case_size_raw"]}) return { "images": image_data or None, "labels": label_urls or None, "brand": brand, "like-data": like_data, "has-nav-buttons": len(images) > 1, "thumbs": thumb_data or None, "sticker-items": sticker_items or None, "extras": extras or None, "desc-short": d.get("description_short") or None, "desc-html": d.get("description_html") or None, "sections": d.get("sections") or None, } def _product_detail_sx(d: dict, ctx: dict) -> str: """Build product detail content — delegates to .sx defcomp.""" data = _product_detail_data(d, ctx) return sx_call("market-product-detail-from-data", **data) # --------------------------------------------------------------------------- # Product meta data extraction # --------------------------------------------------------------------------- def _product_meta_data(d: dict, ctx: dict) -> dict: """Extract product meta/SEO data for .sx composition.""" import json from quart import request title = d.get("title", "") desc_source = d.get("description_short") or "" if not desc_source and d.get("description_html"): import re desc_source = re.sub(r"<[^>]+>", "", d.get("description_html", "")) description = desc_source.strip().replace("\n", " ")[:160] image_url = d.get("image") or (d.get("images", [None])[0] if d.get("images") else None) canonical = request.url if request else "" brand = d.get("brand", "") sku = d.get("sku", "") price = d.get("special_price") or d.get("regular_price") or d.get("rrp") price_currency = (d.get("special_price_currency") or d.get("regular_price_currency") or d.get("rrp_currency")) # JSON-LD jsonld = { "@context": "https://schema.org", "@type": "Product", "name": title, "image": image_url, "description": description, "sku": sku, "url": canonical, } if brand: jsonld["brand"] = {"@type": "Brand", "name": brand} if price and price_currency: jsonld["offers"] = { "@type": "Offer", "price": price, "priceCurrency": price_currency, "url": canonical, "availability": "https://schema.org/InStock", } return { "title": title, "description": description, "canonical": canonical or None, "image-url": image_url or None, "site-title": ctx.get("base_title", ""), "brand": brand or None, "price": f"{price:.2f}" if price and price_currency else None, "price-currency": price_currency if price else None, "jsonld-json": json.dumps(jsonld), } def _product_meta_sx(d: dict, ctx: dict) -> str: """Build product meta tags — delegates to .sx defcomp.""" data = _product_meta_data(d, ctx) return sx_call("market-product-meta-from-data", **data)