Remove 47 identical market template overrides of shared templates
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 45s
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 45s
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,7 +0,0 @@
|
||||
{% import "macros/links.html" as links %}
|
||||
{% if g.rights.admin %}
|
||||
{% from 'macros/admin_nav.html' import admin_nav_item %}
|
||||
{{admin_nav_item(
|
||||
url_for('market.browse.product.admin', slug=slug)
|
||||
)}}
|
||||
{% endif %}
|
||||
@@ -1,5 +0,0 @@
|
||||
|
||||
<div class="grid grid-cols-1 sm:grid-cols-3 md:grid-cols-6 gap-3">
|
||||
{% include "_types/browse/_product_cards.html" %}
|
||||
</div>
|
||||
<div class="pb-8"></div>
|
||||
@@ -1,104 +0,0 @@
|
||||
{% import 'macros/stickers.html' as stick %}
|
||||
{% import '_types/product/prices.html' as prices %}
|
||||
{% set prices_ns = namespace() %}
|
||||
{{ prices.set_prices(p, prices_ns) }}
|
||||
{% set item_href = url_for('market.browse.product.product_detail', slug=p.slug)|host %}
|
||||
<div class="flex flex-col rounded-xl bg-white shadow hover:shadow-md transition overflow-hidden relative">
|
||||
{# ❤️ like button overlay - OUTSIDE the link #}
|
||||
{% if g.user %}
|
||||
<div class="absolute top-2 right-2 z-10 text-6xl md:text-xl">
|
||||
{% set slug = p.slug %}
|
||||
{% set liked = p.is_liked or False %}
|
||||
{% include "_types/browse/like/button.html" %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<a
|
||||
href="{{ item_href }}"
|
||||
hx-get="{{ item_href }}"
|
||||
hx-target="#main-panel"
|
||||
hx-select ="{{hx_select_search}}"
|
||||
hx-swap="outerHTML"
|
||||
hx-push-url="true"
|
||||
class=""
|
||||
>
|
||||
|
||||
{# Make this relative so we can absolutely position children #}
|
||||
<div class="w-full aspect-square bg-stone-100 relative">
|
||||
{% if p.image %}
|
||||
<figure class="inline-block w-full h-full">
|
||||
<div class="relative w-full h-full">
|
||||
<img
|
||||
src="{{ p.image }}"
|
||||
alt="no image"
|
||||
class="absolute inset-0 w-full h-full object-contain object-top"
|
||||
loading="lazy" decoding="async" fetchpriority="low"
|
||||
/>
|
||||
|
||||
{% for l in p.labels %}
|
||||
<img
|
||||
src="{{ asset_url('labels/' + l + '.svg') }}"
|
||||
alt=""
|
||||
class="pointer-events-none absolute inset-0 w-full h-full object-contain object-top"
|
||||
/>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
<figcaption class="
|
||||
mt-2 text-sm text-center
|
||||
{{ 'bg-yellow-200' if p.brand in selected_brands else '' }}
|
||||
text-stone-600
|
||||
">
|
||||
{{ p.brand }}
|
||||
</figcaption>
|
||||
</figure>
|
||||
|
||||
{% else %}
|
||||
<div class="p-2 flex flex-col items-center justify-center gap-2 text-red-500 h-full relative">
|
||||
<div class="text-stone-400 text-xs">No image</div>
|
||||
<ul class="flex flex-row gap-1">
|
||||
{% for l in p.labels %}
|
||||
<li>{{ l }}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
<div class="text-stone-900 text-center line-clamp-3 break-words [overflow-wrap:anywhere]">
|
||||
{{ p.brand }}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
</div>
|
||||
{# <div>{{ prices.rrp(prices_ns) }}</div> #}
|
||||
{{ prices.card_price(p)}}
|
||||
|
||||
{% import '_types/product/_cart.html' as _cart %}
|
||||
</a>
|
||||
<div class="flex justify-center">
|
||||
{{ _cart.add(p.slug, cart)}}
|
||||
</div>
|
||||
|
||||
|
||||
<a
|
||||
href="{{ item_href }}"
|
||||
hx-get="{{ item_href }}"
|
||||
hx-target="#main-panel"
|
||||
hx-select ="{{hx_select_search}}"
|
||||
hx-swap="outerHTML"
|
||||
hx-push-url="true"
|
||||
>
|
||||
<div class="flex flex-row justify-center gap-2 p-2">
|
||||
{% for s in p.stickers %}
|
||||
{{ stick.sticker(
|
||||
asset_url('stickers/' + s + '.svg'),
|
||||
s,
|
||||
True,
|
||||
size=24,
|
||||
found=s in selected_stickers
|
||||
) }}
|
||||
{% endfor %}
|
||||
</div>
|
||||
<div class="text-sm font-medium text-stone-800 text-center line-clamp-3 break-words [overflow-wrap:anywhere]">
|
||||
{{ p.title | highlight(search) }}
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
@@ -1,107 +0,0 @@
|
||||
{% for p in products %}
|
||||
{% include "_types/browse/_product_card.html" %}
|
||||
{% endfor %}
|
||||
{% if page < total_pages|int %}
|
||||
|
||||
|
||||
<div
|
||||
id="sentinel-{{ page }}-m"
|
||||
class="block md:hidden h-[60vh] opacity-0 pointer-events-none js-mobile-sentinel"
|
||||
hx-get="{{ (current_local_href ~ {'page': page + 1}|qs)|host }}"
|
||||
hx-trigger="intersect once delay:250ms, sentinelmobile:retry"
|
||||
hx-swap="outerHTML"
|
||||
_="
|
||||
init
|
||||
if not me.dataset.retryMs then set me.dataset.retryMs to 1000 end
|
||||
if window.matchMedia('(min-width: 768px)').matches then set @hx-disabled to '' end
|
||||
|
||||
on resize from window
|
||||
if window.matchMedia('(min-width: 768px)').matches then set @hx-disabled to '' else remove @hx-disabled end
|
||||
|
||||
on htmx:beforeRequest
|
||||
if window.matchMedia('(min-width: 768px)').matches then halt end
|
||||
add .hidden to .js-neterr in me
|
||||
remove .hidden from .js-loading in me
|
||||
remove .opacity-100 from me
|
||||
add .opacity-0 to me
|
||||
|
||||
def backoff()
|
||||
set ms to me.dataset.retryMs
|
||||
if ms > 30000 then set ms to 30000 end
|
||||
-- show big SVG panel & make sentinel visible
|
||||
add .hidden to .js-loading in me
|
||||
remove .hidden from .js-neterr in me
|
||||
remove .opacity-0 from me
|
||||
add .opacity-100 to me
|
||||
wait ms ms
|
||||
trigger sentinelmobile:retry
|
||||
set ms to ms * 2
|
||||
if ms > 30000 then set ms to 30000 end
|
||||
set me.dataset.retryMs to ms
|
||||
end
|
||||
|
||||
on htmx:sendError call backoff()
|
||||
on htmx:responseError call backoff()
|
||||
on htmx:timeout call backoff()
|
||||
"
|
||||
role="status"
|
||||
aria-live="polite"
|
||||
aria-hidden="true"
|
||||
>
|
||||
{% include "sentinel/mobile_content.html" %}
|
||||
</div>
|
||||
<!-- DESKTOP sentinel (custom scroll container) -->
|
||||
<div
|
||||
id="sentinel-{{ page }}-d"
|
||||
class="hidden md:block h-4 opacity-0 pointer-events-none"
|
||||
hx-get="{{ (current_local_href ~ {'page': page + 1}|qs)|host}}"
|
||||
hx-trigger="intersect once delay:250ms, sentinel:retry"
|
||||
hx-swap="outerHTML"
|
||||
_="
|
||||
init
|
||||
if not me.dataset.retryMs then set me.dataset.retryMs to 1000 end
|
||||
|
||||
on htmx:beforeRequest(event)
|
||||
add .hidden to .js-neterr in me
|
||||
remove .hidden from .js-loading in me
|
||||
remove .opacity-100 from me
|
||||
add .opacity-0 to me
|
||||
|
||||
set trig to null
|
||||
if event.detail and event.detail.triggeringEvent then
|
||||
set trig to event.detail.triggeringEvent
|
||||
end
|
||||
if trig and trig.type is 'intersect'
|
||||
set scroller to the closest .js-grid-viewport
|
||||
if scroller is null then halt end
|
||||
if scroller.scrollTop < 20 then halt end
|
||||
end
|
||||
|
||||
def backoff()
|
||||
set ms to me.dataset.retryMs
|
||||
if ms > 30000 then set ms to 30000 end
|
||||
add .hidden to .js-loading in me
|
||||
remove .hidden from .js-neterr in me
|
||||
remove .opacity-0 from me
|
||||
add .opacity-100 to me
|
||||
wait ms ms
|
||||
trigger sentinel:retry
|
||||
set ms to ms * 2
|
||||
if ms > 30000 then set ms to 30000 end
|
||||
set me.dataset.retryMs to ms
|
||||
end
|
||||
|
||||
on htmx:sendError call backoff()
|
||||
on htmx:responseError call backoff()
|
||||
on htmx:timeout call backoff()
|
||||
"
|
||||
role="status"
|
||||
aria-live="polite"
|
||||
aria-hidden="true"
|
||||
>
|
||||
{% include "sentinel/desktop_content.html" %}
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="col-span-full mt-4 text-center text-xs text-stone-400">End of results</div>
|
||||
{% endif %}
|
||||
|
||||
@@ -1,40 +0,0 @@
|
||||
{# Categories #}
|
||||
<nav aria-label="Categories"
|
||||
class="rounded-xl border bg-white shadow-sm min-h-0">
|
||||
<ul class="divide-y">
|
||||
{% set top_active = (current_local_href == top_local_href) %}
|
||||
{% set href = (url_for('market.browse.browse_top', top_slug=top_slug) ~ qs)|host %}
|
||||
<li>
|
||||
<a
|
||||
href="{{ href }}"
|
||||
hx-get="{{ href }}"
|
||||
hx-target="#main-panel"
|
||||
hx-select="{{hx_select_search}}"
|
||||
hx-swap="outerHTML"
|
||||
hx-push-url="true"
|
||||
aria-selected="{{ 'true' if top_active else 'false' }}"
|
||||
class="block px-4 py-3 text-[15px] transition {{select_colours}}">
|
||||
<div class="prose prose-stone max-w-none">All products</div>
|
||||
</a>
|
||||
</li>
|
||||
|
||||
{% for sub in subs_local %}
|
||||
{% set active = (current_local_href == sub.local_href) %}
|
||||
{% set href = (url_for('market.browse.browse_sub', top_slug=top_slug, sub_slug=sub.slug) ~ qs)|host %}
|
||||
<li>
|
||||
<a
|
||||
href="{{ href }}"
|
||||
hx-get="{{ href }}"
|
||||
hx-target="#main-panel"
|
||||
hx-select="{{hx_select_search}}"
|
||||
hx-swap="outerHTML"
|
||||
hx-push-url="true"
|
||||
aria-selected="{{ 'true' if active else 'false' }}"
|
||||
class="block px-4 py-3 text-[15px] border-l-4 transition {{select_colours}}"
|
||||
>
|
||||
<div class="prose prose-stone max-w-none">{{ (sub.html_label or sub.name) | safe }}</div>
|
||||
</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</nav>
|
||||
@@ -1,40 +0,0 @@
|
||||
{# Brand filter (desktop, single-select) #}
|
||||
|
||||
{# Brands #}
|
||||
<nav aria-label="Brands"
|
||||
class="rounded-xl border bg-white shadow-sm">
|
||||
<h2 class="text-md mt-2 font-semibold">Brands</h2>
|
||||
<ul class="divide-y">
|
||||
{% for b in brands %}
|
||||
{% set is_selected = (b.name in selected_brands) %}
|
||||
{% if is_selected %}
|
||||
{% set brand_href = (current_local_href ~ {"remove_brand": b.name, "page": None}|qs)|host %}
|
||||
{% else %}
|
||||
{% set brand_href = (current_local_href ~ {"add_brand": b.name, "page": None}|qs)|host %}
|
||||
{% endif %}
|
||||
<li>
|
||||
<a
|
||||
href="{{ brand_href }}"
|
||||
hx-get="{{ brand_href }}"
|
||||
hx-target="#main-panel"
|
||||
hx-select="{{hx_select_search}}"
|
||||
hx-swap="outerHTML" hx-push-url="true" hx-on:htmx:afterSwap="this.closest('details')?.removeAttribute('open')"
|
||||
class="flex items-center gap-2 px-2 py-2 rounded transition {% if is_selected %} bg-stone-900 text-white {% else %} hover:bg-stone-50 {% endif %}">
|
||||
<span class="inline-flex items-center justify-center w-5 h-5 rounded border {% if is_selected %} border-stone-900 bg-stone-900 text-white {% else %} border-stone-300 {% endif %}">
|
||||
{% if is_selected %}
|
||||
<svg viewBox="0 0 24 24" class="w-4 h-4" aria-hidden="true">
|
||||
<path d="M5 13l4 4L19 7" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
{% endif %}
|
||||
</span>
|
||||
<span class="flex-1 text-sm">{{ b.name }}</span>
|
||||
|
||||
{% if b.count is not none %}
|
||||
<span class="{% if b.count==0 %}text-lg text-red-500{% else %}text-sm{% endif %} {% if is_selected %}opacity-90{% else %}text-stone-500{% endif %}">{{ b.count }}</span>
|
||||
{% endif %}
|
||||
</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
|
||||
</ul>
|
||||
</nav>
|
||||
@@ -1,44 +0,0 @@
|
||||
|
||||
|
||||
|
||||
{% import 'macros/stickers.html' as stick %}
|
||||
|
||||
<ul
|
||||
id="labels-details-desktop"
|
||||
class="flex justify-center p-0 m-0 gap-2"
|
||||
>
|
||||
{% for s in labels %}
|
||||
{% set is_on = (selected_labels and (s.name|lower in selected_labels)) %}
|
||||
{% set qs = {"remove_label": s.name, "page":None}|qs if is_on
|
||||
else {"add_label": s.name, "page":None}|qs %}
|
||||
{% set href = (current_local_href ~ qs)|host %}
|
||||
<li>
|
||||
<a
|
||||
href="{{ href }}"
|
||||
hx-get="{{ href }}"
|
||||
hx-target="#main-panel"
|
||||
hx-select="{{hx_select_search}}"
|
||||
hx-swap="outerHTML"
|
||||
hx-push-url="true"
|
||||
role="button"
|
||||
aria-pressed="{{ 'true' if is_on else 'false' }}"
|
||||
title="{{ s.name }}" aria-label="{{ s.name }}"
|
||||
class="flex w-full h-full flex-col items-center justify-center py-2"
|
||||
>
|
||||
<!-- col 1: icon -->
|
||||
{{ stick.sticker(asset_url('nav-labels/' + s.name + '.svg'), s.name, is_on)}}
|
||||
|
||||
|
||||
<!-- col 3: count (right aligned) -->
|
||||
{% if s.count is not none %}
|
||||
<span class="
|
||||
{{'text-xs text-stone-500' if s.count != 0 else 'text-md text-red-500 font-bold'}}
|
||||
leading-none justify-self-end tabular-nums">
|
||||
{{ s.count }}
|
||||
</span>
|
||||
{% endif %}
|
||||
</a>
|
||||
</li>
|
||||
|
||||
{% endfor %}
|
||||
</ul>
|
||||
@@ -1,38 +0,0 @@
|
||||
{% import 'macros/stickers.html' as stick %}
|
||||
{% set qs = {"liked": None if liked else True, "page": None}|qs %}
|
||||
{% set href = (current_local_href ~ qs)|host %}
|
||||
<a
|
||||
href="{{ href }}"
|
||||
hx-get="{{ href }}"
|
||||
hx-target="#main-panel"
|
||||
hx-select="{{hx_select_search}}"
|
||||
hx-swap="outerHTML"
|
||||
hx-push-url="true"
|
||||
role="button"
|
||||
aria-pressed="{{ 'true' if liked else 'false' }}"
|
||||
title="liked" aria-label="liked"
|
||||
class="flex flex-col items-center justify-start p-1 rounded hover:bg-stone-50"
|
||||
{% if liked %}
|
||||
aria-label="liked and unliked"
|
||||
{% else %}
|
||||
aria-label="just liked"
|
||||
{% endif %}
|
||||
>
|
||||
{% if liked %}
|
||||
<i aria-hidden="true"
|
||||
class="fa-solid fa-heart text-red-500 text-[40px] leading-none"
|
||||
></i>
|
||||
{% else %}
|
||||
<i aria-hidden="true"
|
||||
class="fa-solid fa-heart text-stone-300 text-[40px] leading-none"
|
||||
></i>
|
||||
{% endif %}
|
||||
<span class="
|
||||
{{'text-[10px] text-stone-500' if liked_count != 0 else 'text-md text-red-500 font-bold'}}
|
||||
mt-1 leading-none tabular-nums
|
||||
"
|
||||
aria_label="liked count"
|
||||
>
|
||||
{{ liked_count }}
|
||||
</span>
|
||||
</a>
|
||||
@@ -1,44 +0,0 @@
|
||||
|
||||
{% macro search(current_local_href,search, search_count, hx_select) -%}
|
||||
<!-- Search (1/3 width → 4/12 columns) -->
|
||||
<!-- nb this does NOT oob itself!! -->
|
||||
<div
|
||||
id="search-desktop-wrapper"
|
||||
class="flex flex-row gap-2 items-center"
|
||||
>
|
||||
<input
|
||||
id="search-desktop"
|
||||
type="text"
|
||||
name="search"
|
||||
aria-label="search"
|
||||
class="w-full mx-1 my-3 px-3 py-2 text-md rounded-xl border-2 shadow-sm border-white placeholder-shown:border-white [&:not(:placeholder-shown)]:border-yellow-200"
|
||||
hx-preserve
|
||||
value="{{ search|default('', true) }}"
|
||||
placeholder="search"
|
||||
hx-trigger="input changed delay:300ms"
|
||||
hx-target="#main-panel"
|
||||
|
||||
hx-select="{{hx_select}}, #search-mobile-wrapper, #search-desktop-wrapper"
|
||||
hx-get="{{ (current_local_href ~ {'search': None}|qs)|host}}"
|
||||
hx-swap="outerHTML"
|
||||
hx-push-url="true"
|
||||
hx-headers='{"X-Origin":"search-desktop", "X-Search":"true"}'
|
||||
hx-sync="this:replace"
|
||||
|
||||
autocomplete="off"
|
||||
>
|
||||
|
||||
<div
|
||||
id="search-count-desktop"
|
||||
aria-label="search count"
|
||||
{% if not search_count %}
|
||||
class="text-xl text-red-500"
|
||||
{% endif %}
|
||||
>
|
||||
{% if search %}
|
||||
{{search_count}}
|
||||
{% endif %}
|
||||
{{zap_filter}}
|
||||
</div>
|
||||
</div>
|
||||
{% endmacro %}
|
||||
@@ -1,34 +0,0 @@
|
||||
|
||||
|
||||
|
||||
|
||||
{% import 'macros/stickers.html' as stick %}
|
||||
{% set sort_val = sort|default('az', true) %}
|
||||
|
||||
<ul
|
||||
id="sort-details-desktop"
|
||||
class="flex w-full p-0 m-0 border bg-white shadow-sm rounded-xl gap-0 [&>li]:list-none [&>li]:flex-1"
|
||||
>
|
||||
{% for key,label,icon in sort_options %}
|
||||
{% set is_on = (sort_val == key) %}
|
||||
{% set qs = {"sort": None, "page": None}|qs if is_on
|
||||
else {"sort": key, "page": None}|qs %}
|
||||
{% set href = (current_local_href ~ qs)|host %}
|
||||
|
||||
<li>
|
||||
<a
|
||||
href="{{ href }}"
|
||||
hx-get="{{ href }}"
|
||||
hx-target="#main-panel"
|
||||
hx-select="{{hx_select_search}}"
|
||||
hx-swap="outerHTML"
|
||||
hx-push-url="true"
|
||||
role="button"
|
||||
aria-pressed="{{ 'true' if is_on else 'false' }}"
|
||||
class="flex flex-col items-center justify-center w-full h-full py-2 m-0"
|
||||
>
|
||||
{{ stick.sticker(asset_url(icon), label, is_on) }}
|
||||
</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
@@ -1,46 +0,0 @@
|
||||
|
||||
|
||||
|
||||
|
||||
{% import 'macros/stickers.html' as stick %}
|
||||
|
||||
<ul
|
||||
id="stickers-details-desktop"
|
||||
class="flex flex-wrap justify-center w-full p-0 m-0 border bg-white shadow-sm rounded-xl gap-1 [&>li]:list-none [&>li]:basis-[20%] [&>li]:max-w-[20%] [&>li]:grow-0"
|
||||
>
|
||||
{% for s in stickers %}
|
||||
{% set is_on = (selected_stickers and (s.name|lower in selected_stickers)) %}
|
||||
{% set qs = {"remove_sticker": s.name, "page": None}|qs if is_on
|
||||
else {"add_sticker": s.name, "page": None}|qs %}
|
||||
{% set href = (current_local_href ~ qs)|host%}
|
||||
<li>
|
||||
<a
|
||||
href="{{ href }}"
|
||||
hx-get="{{ href }}"
|
||||
hx-target="#main-panel"
|
||||
hx-select="{{hx_select_search}}"
|
||||
hx-swap="outerHTML"
|
||||
hx-push-url="true"
|
||||
role="button"
|
||||
aria-pressed="{{ 'true' if is_on else 'false' }}"
|
||||
title="{{ s.name }}" aria-label="{{ s.name }}"
|
||||
class="flex w-full h-full flex-col items-center justify-center py-2"
|
||||
>
|
||||
<span class="text-[11px]">{{s.name|capitalize if s.name|lower != 'sugarfree' else 'Sugar'}}</span>
|
||||
<!-- col 1: icon -->
|
||||
{{ stick.sticker(asset_url('stickers/' + s.name + '.svg'), s.name, is_on)}}
|
||||
|
||||
|
||||
<!-- col 3: count (right aligned) -->
|
||||
{% if s.count is not none %}
|
||||
<span class="
|
||||
{{'text-xs text-stone-500' if s.count != 0 else 'text-md text-red-500 font-bold'}}
|
||||
leading-none justify-self-end tabular-nums">
|
||||
{{ s.count }}
|
||||
</span>
|
||||
{% endif %}
|
||||
</a>
|
||||
</li>
|
||||
|
||||
{% endfor %}
|
||||
</ul>
|
||||
@@ -1,37 +0,0 @@
|
||||
{% import '_types/browse/desktop/_filter/search.html' as s %}
|
||||
{{ s.search(current_local_href, search, search_count, hx_select) }}
|
||||
|
||||
<div
|
||||
id="category-summary-desktop"
|
||||
hxx-swap-oob="outerHTML"
|
||||
>
|
||||
<div class="mb-4">
|
||||
<div class="text-2xl uppercase tracking-wide text-black-500">{{ category_label }}</div>
|
||||
</div>
|
||||
{% include "_types/browse/desktop/_filter/sort.html" %}
|
||||
<nav aria-label="like" class="flex flex-row justify-center w-full p-0 m-0 border bg-white shadow-sm rounded-xl gap-1">
|
||||
{% include "_types/browse/desktop/_filter/like.html" %}
|
||||
{% if labels %}
|
||||
{% include "_types/browse/desktop/_filter/labels.html" %}
|
||||
{% endif %}
|
||||
</nav>
|
||||
|
||||
{% if stickers %}
|
||||
{% include "_types/browse/desktop/_filter/stickers.html" %}
|
||||
{% endif %}
|
||||
|
||||
|
||||
{% if subs_local and top_local_href %}
|
||||
{% include "_types/browse/desktop/_category_selector.html" %}
|
||||
{% endif %}
|
||||
|
||||
</div>
|
||||
|
||||
<div
|
||||
id="filter-summary-desktop"
|
||||
hxx-swap-oob="outerHTML"
|
||||
>
|
||||
|
||||
{% include "_types/browse/desktop/_filter/brand.html" %}
|
||||
|
||||
</div>
|
||||
@@ -1,13 +0,0 @@
|
||||
{% extends '_types/market/index.html' %}
|
||||
|
||||
{% block filter %}
|
||||
{% include "_types/browse/mobile/_filter/summary.html" %}
|
||||
{% endblock %}
|
||||
|
||||
{% block aside %}
|
||||
{% include "_types/browse/desktop/menu.html" %}
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
{% include "_types/browse/_main_panel.html" %}
|
||||
{% endblock %}
|
||||
@@ -1,20 +0,0 @@
|
||||
<button
|
||||
class="flex items-center gap-1 {% if liked %} text-red-600 {% else %} text-stone-300 {% endif %} hover:text-red-600 transition-colors w-[1em] h-[1em]"
|
||||
hx-post="{{ like_url if like_url else url_for('market.browse.product.like_toggle', slug=slug)|host }}"
|
||||
hx-target="this"
|
||||
hx-swap="outerHTML"
|
||||
hx-push-url="false"
|
||||
hx-headers='{"X-CSRFToken": "{{ csrf_token() }}"}'
|
||||
hx-swap-settle="0ms"
|
||||
{% if liked %}
|
||||
aria-label="Unlike this {{ item_type if item_type else 'product' }}"
|
||||
{% else %}
|
||||
aria-label="Like this {{ item_type if item_type else 'product' }}"
|
||||
{% endif %}
|
||||
>
|
||||
{% if liked %}
|
||||
<i aria-hidden="true" class="fa-solid fa-heart"></i>
|
||||
{% else %}
|
||||
<i aria-hidden="true" class="fa-regular fa-heart"></i>
|
||||
{% endif %}
|
||||
</button>
|
||||
@@ -1,40 +0,0 @@
|
||||
<nav aria-label="Brands" class="px-4 pb-3" >
|
||||
{% if brands|length %}
|
||||
<h2 class="text-md mt-2 font-semibold">Brands</h2>
|
||||
<ul class="space-y-1 pr-1" >
|
||||
{% for b in brands %}
|
||||
{% set is_selected = (b.name in selected_brands) %}
|
||||
<li>
|
||||
{{current_local_href}}
|
||||
<a
|
||||
{% if is_selected %}
|
||||
{% set href = (current_local_href ~ {"remove_brand": b.name, "page": None}|qs)|host %}
|
||||
{% else %}
|
||||
{% set href = (current_local_href ~ {"add_brand": b.name, "page": None}|qs)|host %}
|
||||
{%endif%}
|
||||
href="{{ href }}"
|
||||
hx-get="{{ href }}"
|
||||
|
||||
hx-target="#main-panel"
|
||||
hx-select="{{hx_select_search}}"
|
||||
hx-swap="outerHTML"
|
||||
hx-push-url="true"
|
||||
|
||||
class="flex items-center gap-2 my-3 px-2 py-2 rounded transition {% if is_selected %} bg-stone-900 text-white {% else %} hover:bg-stone-50 {% endif %}">
|
||||
<span class="inline-flex items-center justify-center w-5 h-5 rounded border {% if is_selected %} border-stone-900 bg-stone-900 text-white {% else %} border-stone-300 {% endif %}">
|
||||
{% if is_selected %}
|
||||
<svg viewBox="0 0 24 24" class="w-4 h-4" aria-hidden="true">
|
||||
<path d="M5 13l4 4L19 7" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
{% endif %}
|
||||
</span>
|
||||
<span class="flex-1 text-sm">{{ b.name }}</span>
|
||||
{% if b.count is not none %}
|
||||
<span class="text-xs {% if is_selected %}opacity-90{% else %}text-stone-500{% endif %}">{{ b.count }}</span>
|
||||
{% endif %}
|
||||
</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endif %}
|
||||
</nav>
|
||||
@@ -1,30 +0,0 @@
|
||||
|
||||
{% include "_types/browse/mobile/_filter/sort_ul.html" %}
|
||||
{% if search or selected_labels|length or selected_stickers|length or selected_brands|length %}
|
||||
{% set href = (current_local_href ~ {"clear_filters": True}|qs)|host %}
|
||||
<div class = "flex flex-row justify-center">
|
||||
<a
|
||||
href="{{ href }}"
|
||||
hx-get="{{ href }}"
|
||||
hx-target="#main-panel"
|
||||
hx-select="{{hx_select_search}}"
|
||||
hx-swap="outerHTML"
|
||||
hx-push-url="true"
|
||||
role="button"
|
||||
title="clear filters"
|
||||
aria-label="clear filters"
|
||||
class="flex flex-col items-center justify-start p-1 rounded bg-stone-200 text-black cursor-pointer">
|
||||
<span class="mt-1 leading-none tabular-nums"
|
||||
>
|
||||
clear filters
|
||||
</span>
|
||||
</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="flex flex-row gap-2 justify-center items center">
|
||||
{% include "_types/browse/mobile/_filter/like.html" %}
|
||||
{% include "_types/browse/mobile/_filter/labels.html" %}
|
||||
</div>
|
||||
{% include "_types/browse/mobile/_filter/stickers.html" %}
|
||||
{% include "_types/browse/mobile/_filter/brand_ul.html" %}
|
||||
|
||||
@@ -1,47 +0,0 @@
|
||||
{% import 'macros/stickers.html' as stick %}
|
||||
<nav aria-label="labels" class="px-4 pb-3">
|
||||
{# One row only; center when not overflowing; horizontal scroll when needed #}
|
||||
<ul
|
||||
class="flex w-full items-start justify-center gap-3 overflow-x-auto overflow-y-visible pr-1 no-scrollbar"
|
||||
>
|
||||
|
||||
{% for s in labels %}
|
||||
{% set is_on = (selected_labels and (s.name|lower in selected_labels)) %}
|
||||
|
||||
{% set qs = {"remove_label": s.name, "page": None}|qs if is_on
|
||||
else {"add_label": s.name, "page": None}|qs %}
|
||||
{% set href = (current_local_href ~ qs)|host %}
|
||||
|
||||
<li class="list-none shrink-0">
|
||||
<a
|
||||
href="{{ href }}"
|
||||
hx-get="{{ href }}"
|
||||
hx-target="#main-panel"
|
||||
hx-select="{{hx_select_search}}"
|
||||
hx-swap="outerHTML"
|
||||
hx-push-url="true"
|
||||
role="button"
|
||||
aria-pressed="{{ 'true' if is_on else 'false' }}"
|
||||
title="{{ s.name }}" aria-label="{{ s.name }}"
|
||||
class="flex flex-col items-center justify-start p-1 rounded hover:bg-stone-50">
|
||||
{{ stick.sticker(asset_url('nav-labels/' + s.name + '.svg'), s.name, is_on)}}
|
||||
{% if s.count is not none %}
|
||||
<span class="
|
||||
{{'text-[10px] text-stone-500' if s.count != 0 else 'text-md text-red-500 font-bold'}}
|
||||
mt-1 leading-none tabular-nums
|
||||
"
|
||||
>
|
||||
{{ s.count }}
|
||||
</span>
|
||||
{% endif %}
|
||||
</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</nav>
|
||||
|
||||
{# Optional: hide horizontal scrollbar on mobile while keeping scrollable #}
|
||||
<style>
|
||||
.no-scrollbar::-webkit-scrollbar { display: none; }
|
||||
.no-scrollbar { -ms-overflow-style: none; scrollbar-width: none; }
|
||||
</style>
|
||||
@@ -1,40 +0,0 @@
|
||||
{% import 'macros/stickers.html' as stick %}
|
||||
<nav aria-label="like" class="px-4 pb-3">
|
||||
{% set qs = {"liked": None if liked else True, "page": None}|qs%}
|
||||
{% set href = (current_local_href ~ qs)|host %}
|
||||
<a
|
||||
href="{{ href }}"
|
||||
hx-get="{{ href }}"
|
||||
hx-target="#main-panel"
|
||||
hx-select="{{hx_select_search}}"
|
||||
hx-swap="outerHTML"
|
||||
hx-push-url="true"
|
||||
role="button"
|
||||
aria-pressed="{{ 'true' if liked else 'false' }}"
|
||||
title="liked" aria-label="liked"
|
||||
class="flex flex-col items-center justify-start p-1 rounded hover:bg-stone-50"
|
||||
{% if liked %}
|
||||
aria-label="liked and unliked"
|
||||
{% else %}
|
||||
aria-label="just liked"
|
||||
{% endif %}
|
||||
>
|
||||
{% if liked %}
|
||||
<i aria-hidden="true"
|
||||
class="fa-solid fa-heart text-red-500 text-[40px] leading-none"
|
||||
></i>
|
||||
{% else %}
|
||||
<i aria-hidden="true"
|
||||
class="fa-solid fa-heart text-stone-500 text-[40px] leading-none"
|
||||
></i>
|
||||
{% endif %}
|
||||
<span class="
|
||||
{{'text-[10px] text-stone-500' if liked_count != 0 else 'text-md text-red-500 font-bold'}}
|
||||
mt-1 leading-none tabular-nums
|
||||
"
|
||||
aria_label="liked count"
|
||||
>
|
||||
{{ liked_count }}
|
||||
</span>
|
||||
</a>
|
||||
</nav>
|
||||
@@ -1,40 +0,0 @@
|
||||
{% macro search(current_local_href, search, search_count, hx_select) -%}
|
||||
|
||||
<div
|
||||
id="search-mobile-wrapper"
|
||||
class="flex flex-row gap-2 items-center flex-1 min-w-0 pr-2"
|
||||
>
|
||||
<input
|
||||
id="search-mobile"
|
||||
type="text"
|
||||
name="search"
|
||||
aria-label="search"
|
||||
class="text-base md:text-sm col-span-5 rounded-md px-3 py-2 mb-2 w-full min-w-0 max-w-full border-2 border-stone-200 placeholder-shown:border-stone-200 [&:not(:placeholder-shown)]:border-yellow-200"
|
||||
hx-preserve
|
||||
value="{{ search|default('', true) }}"
|
||||
placeholder="search"
|
||||
hx-trigger="input changed delay:300ms"
|
||||
hx-target="#main-panel"
|
||||
|
||||
hx-select="{{hx_select}}, #search-mobile-wrapper, #search-desktop-wrapper"
|
||||
hx-get="{{ (current_local_href ~ {'search': None}|qs)|host }}"
|
||||
hx-swap="outerHTML"
|
||||
hx-push-url="true"
|
||||
hx-headers='{"X-Origin":"search-mobile", "X-Search":"true"}'
|
||||
hx-sync="this:replace"
|
||||
autocomplete="off"
|
||||
>
|
||||
|
||||
<div
|
||||
id="search-count-mobile"
|
||||
aria-label="search count"
|
||||
{% if not search_count %}
|
||||
class="text-xl text-red-500"
|
||||
{% endif %}
|
||||
>
|
||||
{% if search %}
|
||||
{{search_count}}
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endmacro %}
|
||||
@@ -1,33 +0,0 @@
|
||||
|
||||
|
||||
|
||||
{% import 'macros/stickers.html' as stick %}
|
||||
|
||||
|
||||
<nav aria-label="sort" class="px-4 pb-3" >
|
||||
<ul class="flex w-full items-start justify-center gap-3 overflow-x-auto overflow-y-visible pr-1 no-scrollbar">
|
||||
|
||||
{% for key,label,icon in sort_options %}
|
||||
<li class="list-none">
|
||||
<div class="flex flex-col items-center justify-center w-full">
|
||||
<a
|
||||
{% if sort == key %}
|
||||
{% set href= (current_local_href, {"sort": None, "page": None}|qs )|host %}
|
||||
{% else %}
|
||||
{% set href= (current_local_href ~ {"sort": key, "page": None}|qs )|host %}
|
||||
{% endif %}
|
||||
href="{{ href }}"
|
||||
hx-get="{{ href }}"
|
||||
hx-target="#main-panel"
|
||||
hx-select="{{hx_select_search}}"
|
||||
hx-swap="outerHTML"
|
||||
hx-push-url="true"
|
||||
>
|
||||
{{ stick.sticker(asset_url(icon), label, sort==key) }}
|
||||
</a>
|
||||
|
||||
</div>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</nav>
|
||||
@@ -1,50 +0,0 @@
|
||||
{% import 'macros/stickers.html' as stick %}
|
||||
|
||||
<nav aria-label="stickers" class="px-4 pb-3">
|
||||
{# One row only; center when not overflowing; horizontal scroll when needed #}
|
||||
<ul
|
||||
class="flex w-full items-start justify-center gap-3 overflow-x-auto overflow-y-visible pr-1 no-scrollbar"
|
||||
>
|
||||
|
||||
{% for s in stickers %}
|
||||
{% set is_on = (selected_stickers and (s.name|lower in selected_stickers)) %}
|
||||
{% set qs = {"remove_sticker": s.name, "page": None}|qs if is_on
|
||||
else {"add_sticker": s.name, "page": None}|qs %}
|
||||
|
||||
{% set href = (current_local_href ~ qs)|host %}
|
||||
|
||||
<li class="list-none shrink-0">
|
||||
<a
|
||||
href="{{ href }}"
|
||||
hx-get="{{ href }}"
|
||||
hx-target="#main-panel"
|
||||
hx-select="{{hx_select_search}}"
|
||||
hx-swap="outerHTML"
|
||||
hx-push-url="true"
|
||||
role="button"
|
||||
aria-pressed="{{ 'true' if is_on else 'false' }}"
|
||||
title="{{ s.name }}" aria-label="{{ s.name }}"
|
||||
class="flex flex-col items-center justify-start p-1 rounded hover:bg-stone-50">
|
||||
<span class="text-sm">{{s.name|capitalize if s.name|lower != 'sugarfree' else 'Sugar'}}</span>
|
||||
{{ stick.sticker(asset_url('stickers/' + s.name + '.svg'), s.name, is_on) }}
|
||||
|
||||
{% if s.count is not none %}
|
||||
<span class="
|
||||
{{'text-[10px] text-stone-500' if s.count != 0 else 'text-md text-red-500 font-bold'}}
|
||||
mt-1 leading-none tabular-nums
|
||||
"
|
||||
>
|
||||
{{ s.count }}
|
||||
</span>
|
||||
{% endif %}
|
||||
</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</nav>
|
||||
|
||||
{# Optional: hide horizontal scrollbar on mobile while keeping scrollable #}
|
||||
<style>
|
||||
.no-scrollbar::-webkit-scrollbar { display: none; }
|
||||
.no-scrollbar { -ms-overflow-style: none; scrollbar-width: none; }
|
||||
</style>
|
||||
@@ -1,120 +0,0 @@
|
||||
{% import 'macros/stickers.html' as stick %}
|
||||
{% import 'macros/layout.html' as layout %}
|
||||
|
||||
|
||||
|
||||
|
||||
{% call layout.details('/filter', 'md:hidden') %}
|
||||
{% call layout.filter_summary("filter-summary-mobile", current_local_href, search, search_count, hx_select) %}
|
||||
<div
|
||||
class="col-span-12 min-w-0 grid grid-cols-1 gap-1 bg-gray-100 px-2"
|
||||
role="list">
|
||||
|
||||
|
||||
<div class="flex flex-row items-start gap-2">
|
||||
{% if sort %}
|
||||
<ul class="relative inline-flex items-center justify-center gap-2">
|
||||
<!-- sticker icon -->
|
||||
{% for k,l,i in sort_options %}
|
||||
{% if k == sort %}
|
||||
{% set key = k %}
|
||||
{% set label = l %}
|
||||
{% set icon = i %}
|
||||
<li role="listitem">
|
||||
{{ stick.sticker(asset_url(icon), label, True)}}
|
||||
</li>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endif %}
|
||||
{% if liked %}
|
||||
<div class="flex flex-col items-center gap-1 pb-1">
|
||||
<i aria-hidden="true"
|
||||
class="fa-solid fa-heart text-red-500 text-[40px] leading-none"
|
||||
></i>
|
||||
{% if liked_count is not none %}
|
||||
<div class="
|
||||
{{'text-[10px] text-stone-500' if liked_count != 0 else 'text-md text-red-500 font-bold'}}
|
||||
mt-1 leading-none tabular-nums"
|
||||
>
|
||||
{{ liked_count }}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if selected_labels and selected_labels|length %}
|
||||
<ul class="relative inline-flex items-center justify-center gap-2">
|
||||
{% for st in selected_labels %}
|
||||
{% for s in labels %}
|
||||
{% if st == s.name %}
|
||||
<li role="listitem" class="flex flex-col items-center gap-1 pb-1">
|
||||
{{ stick.sticker(asset_url('nav-labels/' + s.name + '.svg'), s.name, True)}}
|
||||
{% if s.count is not none %}
|
||||
<div class="
|
||||
{{'text-[10px] text-stone-500' if s.count != 0 else 'text-md text-red-500 font-bold'}}
|
||||
mt-1 leading-none tabular-nums
|
||||
"
|
||||
>
|
||||
{{ s.count }}
|
||||
</div>
|
||||
{% endif %}
|
||||
</li>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endif %}
|
||||
{% if selected_stickers and selected_stickers|length %}
|
||||
<ul class="relative inline-flex items-center justify-center gap-2">
|
||||
{% for st in selected_stickers %}
|
||||
{% for s in stickers %}
|
||||
{% if st == s.name %}
|
||||
<li role="listitem" class="flex flex-col items-center gap-1 pb-1">
|
||||
<!-- sticker icon -->
|
||||
{{ stick.sticker(asset_url('stickers/' + s.name + '.svg'), s.name, True)}}
|
||||
{% if s.count is not none %}
|
||||
<span class="
|
||||
{{'text-[10px] text-stone-500' if s.count != 0 else 'text-md text-red-500 font-bold'}}
|
||||
mt-1 leading-none tabular-nums
|
||||
"
|
||||
>
|
||||
{{ s.count }}
|
||||
</span>
|
||||
{% endif %}
|
||||
</li>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
{% if selected_brands and selected_brands|length %}
|
||||
<ul class_="w-full grid grid-cols-12 items-center gap-3 px-4 py-3">
|
||||
{% for b in selected_brands %}
|
||||
<li role="listitem" class="flex flex-row items-center gap-2">
|
||||
{% set ns = namespace(count=0) %}
|
||||
{% for brand in brands %}
|
||||
{% if brand.name == b %}
|
||||
{% set ns.count = brand.count %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% if ns.count %}
|
||||
<div class="text-md">{{ b }}</div>
|
||||
<div class="text-md">{{ ns.count }}</div>
|
||||
{% else %}
|
||||
<div class="text-md text-red-500">{{ b }}</div>
|
||||
<div class="text-xl text-red-500">0</div>
|
||||
{% endif %}
|
||||
</li>
|
||||
{% endfor %}
|
||||
|
||||
|
||||
</ul>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endcall %}
|
||||
<div id="filter-details-mobile" style="display:contents">
|
||||
{% include "_types/browse/mobile/_filter/index.html" %}
|
||||
</div>
|
||||
{% endcall %}
|
||||
@@ -1,7 +0,0 @@
|
||||
{% import "macros/links.html" as links %}
|
||||
{% if g.rights.admin %}
|
||||
{% from 'macros/admin_nav.html' import admin_nav_item %}
|
||||
{{admin_nav_item(
|
||||
url_for('market.admin.admin')
|
||||
)}}
|
||||
{% endif %}
|
||||
@@ -1,23 +0,0 @@
|
||||
{# Main panel fragment for HTMX navigation - market landing page #}
|
||||
<article class="relative w-full">
|
||||
{% if post.custom_excerpt %}
|
||||
<div class="w-full text-center italic text-3xl p-2">
|
||||
{{post.custom_excerpt|safe}}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if post.feature_image %}
|
||||
<div class="mb-3 flex justify-center">
|
||||
<img
|
||||
src="{{ post.feature_image }}"
|
||||
alt=""
|
||||
class="rounded-lg w-full md:w-3/4 object-cover"
|
||||
>
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="blog-content p-2">
|
||||
{% if post.html %}
|
||||
{{post.html|safe}}
|
||||
{% endif %}
|
||||
</div>
|
||||
</article>
|
||||
<div class="pb-8"></div>
|
||||
@@ -1,17 +0,0 @@
|
||||
<div
|
||||
class="font-bold text-xl flex-shrink-0 flex gap-2 items-center">
|
||||
<div>
|
||||
<i class="fa fa-shop"></i>
|
||||
{{ coop_title }}
|
||||
</div>
|
||||
<div class="flex flex-col md:flex-row md:gap-2 text-xs">
|
||||
<div>
|
||||
{{top_slug or ''}}
|
||||
</div>
|
||||
{% if sub_slug %}
|
||||
<div>
|
||||
{{sub_slug}}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
@@ -1 +0,0 @@
|
||||
market admin
|
||||
@@ -1,2 +0,0 @@
|
||||
{% from 'macros/admin_nav.html' import placeholder_nav %}
|
||||
{{ placeholder_nav() }}
|
||||
@@ -1,29 +0,0 @@
|
||||
{% extends 'oob_elements.html' %}
|
||||
|
||||
{# OOB elements for HTMX navigation - all elements that need updating #}
|
||||
|
||||
{# Import shared OOB macros #}
|
||||
{% from '_types/root/_oob_menu.html' import mobile_menu with context %}
|
||||
|
||||
{# Header with app title - includes cart-mini, navigation, and market-specific header #}
|
||||
|
||||
{% block oobs %}
|
||||
|
||||
{% from '_types/root/_n/macros.html' import oob_header with context %}
|
||||
{{oob_header('market-header-child', 'market-admin-header-child', '_types/market/admin/header/_header.html')}}
|
||||
|
||||
{% from '_types/market/header/_header.html' import header_row with context %}
|
||||
{{ header_row(oob=True) }}
|
||||
{% endblock %}
|
||||
|
||||
|
||||
{% block mobile_menu %}
|
||||
{% include '_types/market/admin/_nav.html' %}
|
||||
{% endblock %}
|
||||
|
||||
|
||||
{% block content %}
|
||||
{% include "_types/market/admin/_main_panel.html" %}
|
||||
{% endblock %}
|
||||
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
{% import 'macros/links.html' as links %}
|
||||
{% macro header_row(oob=False) %}
|
||||
{% call links.menu_row(id='market-admin-row', oob=oob) %}
|
||||
{% call links.link(url_for('market.admin.admin'), hx_select_search) %}
|
||||
{{ links.admin() }}
|
||||
{% endcall %}
|
||||
{% call links.desktop_nav() %}
|
||||
{% include '_types/market/admin/_nav.html' %}
|
||||
{% endcall %}
|
||||
{% endcall %}
|
||||
{% endmacro %}
|
||||
@@ -1,19 +0,0 @@
|
||||
{% extends '_types/market/index.html' %}
|
||||
|
||||
|
||||
{% block market_header_child %}
|
||||
{% from '_types/root/_n/macros.html' import index_row with context %}
|
||||
{% call index_row('market-admin-header-child', '_types/market/admin/header/_header.html') %}
|
||||
{% block market_admin_header_child %}
|
||||
{% endblock %}
|
||||
{% endcall %}
|
||||
{% endblock %}
|
||||
|
||||
{% block _main_mobile_menu %}
|
||||
{% include '_types/market/admin/_nav.html' %}
|
||||
{% endblock %}
|
||||
|
||||
|
||||
{% block content %}
|
||||
{% include '_types/market/admin/_main_panel.html' %}
|
||||
{% endblock %}
|
||||
@@ -1,38 +0,0 @@
|
||||
<!-- Desktop nav -->
|
||||
<nav class="hidden md:flex gap-4 text-sm ml-2 w-full justify-end items-center">
|
||||
{% set all_href = (url_for('market.browse.browse_all') ~ qs)|host %}
|
||||
{% set all_active = (category_label == 'All Products') %}
|
||||
<div class="relative nav-group">
|
||||
<a
|
||||
href="{{ all_href }}"
|
||||
hx-get="{{ all_href }}"
|
||||
hx-target="#main-panel"
|
||||
hx-select="{{hx_select_search}}"
|
||||
hx-swap="outerHTML"
|
||||
hx-push-url="true"
|
||||
aria-selected="{{ 'true' if all_active else 'false' }}"
|
||||
class="block px-2 py-1 rounded text-center whitespace-normal break-words leading-snug bg-stone-200 text-black {{select_colours}}">
|
||||
All
|
||||
</a>
|
||||
</div>
|
||||
|
||||
{% for cat, data in categories.items() %}
|
||||
{% set cat_href = (url_for('market.browse.browse_top', top_slug=data.slug) ~ qs)|host%}
|
||||
{% set cat_active = (cat == category_label) %}
|
||||
<div class="relative nav-group">
|
||||
<a
|
||||
href="{{ cat_href }}"
|
||||
hx-get="{{ cat_href }}"
|
||||
hx-target="#main-panel"
|
||||
hx-select="{{hx_select_search}}"
|
||||
hx-swap="outerHTML"
|
||||
hx-push-url="true"
|
||||
aria-selected="{{ 'true' if cat_active else 'false' }}"
|
||||
class="block px-2 py-1 rounded text-center whitespace-normal break-words leading-snug bg-stone-200 text-black {{select_colours}}"
|
||||
>
|
||||
{{ cat }}
|
||||
</a>
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% include '_types/market/_admin.html' %}
|
||||
</nav>
|
||||
@@ -1,11 +0,0 @@
|
||||
{% import 'macros/links.html' as links %}
|
||||
{% macro header_row(oob=False) %}
|
||||
{% call links.menu_row(id='market-row', oob=oob) %}
|
||||
{% call links.link(url_for('market.browse.home'), hx_select_search ) %}
|
||||
{% include '_types/market/_title.html' %}
|
||||
{% endcall %}
|
||||
{% call links.desktop_nav() %}
|
||||
{% include '_types/market/desktop/_nav.html' %}
|
||||
{% endcall %}
|
||||
{% endcall %}
|
||||
{% endmacro %}
|
||||
@@ -1,110 +0,0 @@
|
||||
{% from 'macros/glyphs.html' import opener %}
|
||||
<div class="px-4 py-2">
|
||||
<div class="divide-y">
|
||||
{% set all_href = (url_for('market.browse.browse_all') ~ qs)|host %}
|
||||
{% set all_active = (category_label == 'All Products') %}
|
||||
<a role="option"
|
||||
href="{{ all_href }}"
|
||||
hx-get="{{ all_href }}"
|
||||
hx-target="#main-panel"
|
||||
hx-select="{{hx_select_search}}"
|
||||
hx-swap="outerHTML"
|
||||
hx-push-url="true"
|
||||
aria-selected="{{ 'true' if all_active else 'false' }}"
|
||||
class="block rounded-lg px-3 py-3 text-base hover:bg-stone-50 {{select_colours}}">
|
||||
<div class="prose prose-stone max-w-none">
|
||||
All
|
||||
</div>
|
||||
</a>
|
||||
{% for cat, data in categories.items() %}
|
||||
<details
|
||||
class="group/cat py-1"
|
||||
{% if top_slug == (data.slug | lower) %}open{% endif %}
|
||||
>
|
||||
<summary class="flex items-center justify-between cursor-pointer select-none block rounded-lg px-3 py-3 text-base hover:bg-stone-50 {% if top_slug==(data.slug | lower) %} bg-stone-900 text-white hover:bg-stone-900 {% endif %}">
|
||||
|
||||
{% set href = (url_for('market.browse.browse_top', top_slug=data.slug) ~ qs)|host %}
|
||||
|
||||
<a
|
||||
href="{{ href }}"
|
||||
hx-get="{{ href }}"
|
||||
hx-target="#main-panel"
|
||||
hx-select="{{ hx_select_search }}"
|
||||
hx-swap="outerHTML"
|
||||
hx-push-url="true"
|
||||
aria-selected="{{ 'true' if top_slug==(data.slug | lower) else 'false' }}"
|
||||
class="font-medium {{ select_colours }} flex flex-row gap-2"
|
||||
>
|
||||
<div>{{ cat }}</div>
|
||||
<div aria-label="{{ data.count }} products">{{ data.count }}</div>
|
||||
</a>
|
||||
{{ opener('cat')}}
|
||||
|
||||
</summary>
|
||||
|
||||
<div class="pb-3 pl-2">
|
||||
{% if data.subs %}
|
||||
<!-- Viewport -->
|
||||
<div
|
||||
data-peek-viewport
|
||||
data-peek-size-px="18"
|
||||
data-peek-edge="bottom"
|
||||
data-peek-mask="true"
|
||||
class="m-2 bg-stone-100">
|
||||
<!-- Inner list (no negative margin by default) -->
|
||||
<div data-peek-inner class="grid grid-cols-1 gap-1 snap-y snap-mandatory pr-1" aria-label="Subcategories">
|
||||
{% for sub in data.subs %}
|
||||
{% set href = (url_for('market.browse.browse_sub', top_slug=data.slug, sub_slug=sub.slug) ~qs)|host%}
|
||||
{% if top_slug==(data.slug | lower) and sub_slug == sub.slug %}
|
||||
<a
|
||||
class="snap-start px-2 py-3 rounded {{select_colours}} flex flex-row gap-2"
|
||||
aria-selected="{{ 'true' if top_slug==(data.slug | lower) and sub_slug == sub.slug else 'false' }}"
|
||||
href="{{ href }}"
|
||||
hx-get="{{ href }}"
|
||||
hx-target="#main-panel"
|
||||
hx-select="{{hx_select_search}}"
|
||||
hx-swap="outerHTML"
|
||||
hx-push-url="true"
|
||||
>
|
||||
<div>{{ sub.html_label or sub.name }}</div>
|
||||
<div aria-label="{{ sub.count }} products">{{ sub.count }}</div>
|
||||
</a>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% for sub in data.subs %}
|
||||
{% if not (top_slug==(data.slug | lower) and sub_slug == sub.slug) %}
|
||||
{% set href = (url_for('market.browse.browse_sub', top_slug=data.slug, sub_slug=sub.slug) ~ qs)|host%}
|
||||
<a
|
||||
class="snap-start px-2 py-3 rounded {{select_colours}} flex flex-row gap-2"
|
||||
aria-selected="{{ 'true' if top_slug==(data.slug | lower) and sub_slug == sub.slug else 'false' }}"
|
||||
href="{{ href }}"
|
||||
hx-get="{{ href }}"
|
||||
hx-target="#main-panel"
|
||||
hx-select="{{hx_select_search}}"
|
||||
hx-swap="outerHTML"
|
||||
hx-push-url="true"
|
||||
>
|
||||
<div>{{ sub.name }}</div>
|
||||
<div aria-label="{{ sub.count }} products">{{ sub.count }}</div>
|
||||
</a>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
{% else %}
|
||||
{% set href = (url_for('market.browse.browse_top', top_slug=data.slug) ~ qs)|host%}
|
||||
<a class="px-2 py-1 rounded hover:bg-stone-100 block"
|
||||
href="{{ href }}"
|
||||
hx-get="{{ href }}"
|
||||
hx-target="#main-panel"
|
||||
hx-select="{{hx_select_search}}"
|
||||
hx-swap="outerHTML"
|
||||
hx-push-url="true"
|
||||
>View all</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</details>
|
||||
{% endfor %}
|
||||
{% include '_types/market/_admin.html' %}
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,6 +0,0 @@
|
||||
{% extends 'mobile/menu.html' %}
|
||||
{% block menu %}
|
||||
{% block mobile_menu %}
|
||||
{% endblock %}
|
||||
{% include '_types/market/mobile/_nav_panel.html' %}
|
||||
{% endblock %}
|
||||
@@ -1,25 +0,0 @@
|
||||
{% set oob='true' %}
|
||||
{% import '_types/product/_cart.html' as _cart %}
|
||||
{% from '_types/cart/_mini.html' import mini with context %}
|
||||
{{mini()}}
|
||||
|
||||
{{ _cart.add(d.slug, cart, oob='true')}}
|
||||
|
||||
{% from '_types/product/_cart.html' import cart_item with context %}
|
||||
|
||||
{% if cart | sum(attribute="quantity") > 0 %}
|
||||
{% if item.quantity > 0 %}
|
||||
{{ cart_item(oob='true')}}
|
||||
{% else %}
|
||||
{{ cart_item(oob='delete')}}
|
||||
{% endif %}
|
||||
{% from '_types/cart/_cart.html' import summary %}
|
||||
|
||||
{{ summary(cart, total,calendar_total, calendar_cart_entries, oob='true')}}
|
||||
|
||||
{% else %}
|
||||
{% set cart=[] %}
|
||||
{% from '_types/cart/_cart.html' import show_cart with context %}
|
||||
{{ show_cart( oob='true') }}
|
||||
|
||||
{% endif %}
|
||||
@@ -1,131 +0,0 @@
|
||||
{# Main panel fragment for HTMX navigation - product detail content #}
|
||||
{% import 'macros/stickers.html' as stick %}
|
||||
{% import '_types/product/prices.html' as prices %}
|
||||
{% set prices_ns = namespace() %}
|
||||
{{ prices.set_prices(d, prices_ns)}}
|
||||
|
||||
{# Product detail grid from content block #}
|
||||
<div class="mt-3 grid grid-cols-1 md:grid-cols-5 gap-6" data-gallery-root>
|
||||
<div class="md:col-span-2">
|
||||
{% if d.images and d.images|length > 0 %}
|
||||
<div class="relative rounded-xl overflow-hidden bg-stone-100">
|
||||
{# --- like button overlay in top-right --- #}
|
||||
{% if g.user %}
|
||||
<div class="absolute top-3 right-5 z-10 text-6xl md:text-xl">
|
||||
{% set slug = d.slug %}
|
||||
{% set liked = liked_by_current_user %}
|
||||
{% include "_types/browse/like/button.html" %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<figure class="inline-block">
|
||||
<div class="relative w-full aspect-square">
|
||||
<img
|
||||
data-main-img
|
||||
src="{{ d.images[0] }}"
|
||||
alt="{{ d.title }}"
|
||||
class="w-full h-full object-contain object-top"
|
||||
loading="eager" decoding="async"
|
||||
/>
|
||||
|
||||
{% for l in d.labels %}
|
||||
<img
|
||||
src="{{ asset_url('labels/' + l + '.svg') }}"
|
||||
alt=""
|
||||
class="pointer-events-none absolute inset-0 w-full h-full object-contain object-top"
|
||||
/>
|
||||
{% endfor %}
|
||||
</div>
|
||||
<figcaption class="mt-2 text-sm text-stone-600 text-center">
|
||||
{{ d.brand }}
|
||||
</figcaption>
|
||||
</figure>
|
||||
|
||||
{% if d.images|length > 1 %}
|
||||
<button type="button" data-prev
|
||||
class="absolute left-2 top-1/2 -translate-y-1/2 z-10 grid place-items-center w-12 h-12 md:w-14 md:h-14 rounded-full bg-white/90 hover:bg-white shadow-lg focus:outline-none focus:ring-2 focus:ring-stone-300 text-3xl md:text-4xl"
|
||||
title="Previous">‹</button>
|
||||
<button type="button" data-next
|
||||
class="absolute right-2 top-1/2 -translate-y-1/2 z-10 grid place-items-center w-12 h-12 md:w-14 md:h-14 rounded-full bg-white/90 hover:bg-white shadow-lg focus:outline-none focus:ring-2 focus:ring-stone-300 text-3xl md:text-4xl"
|
||||
title="Next">›</button>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div class="flex flex-row justify-center">
|
||||
<div class="mt-3 flex gap-2 overflow-x-auto no-scrollbar">
|
||||
{% for u in d.images %}
|
||||
<button type="button" data-thumb
|
||||
class="shrink-0 rounded-lg overflow-hidden bg-stone-100 hover:opacity-90 ring-offset-2"
|
||||
title="Image {{ loop.index }}">
|
||||
<img src="{{ u }}" class="h-16 w-16 object-contain" alt="thumb {{ loop.index }}" loading="lazy" decoding="async">
|
||||
</button>
|
||||
<span data-image-src="{{ u }}" class="hidden"></span>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="relative aspect-square bg-stone-100 rounded-xl flex items-center justify-center text-stone-400">
|
||||
{# Even if no image, still render the like button in the corner for consistency #}
|
||||
{% if g.user %}
|
||||
<div class="absolute top-2 right-2 z-10">
|
||||
{% set slug = d.slug %}
|
||||
{% set liked = liked_by_current_user %}
|
||||
{% include "_types/browse/like/button.html" %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
No image
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="p-2 flex flex-row justify-center gap-2">
|
||||
{% for s in d.stickers %}
|
||||
{{ stick.sticker(asset_url('stickers/' + s + '.svg'), s, True, size=40) }}
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="md:col-span-3">
|
||||
{# Optional extras shown quietly #}
|
||||
<div class="mt-2 space-y-1 text-sm text-stone-600">
|
||||
{% if d.price_per_unit or d.price_per_unit_raw %}
|
||||
<div>Unit price: {{ prices.price_str(d.price_per_unit, d.price_per_unit_raw, d.price_per_unit_currency) }}</div>
|
||||
{% endif %}
|
||||
{% if d.case_size_raw %}
|
||||
<div>Case size: {{ d.case_size_raw }}</div>
|
||||
{% endif %}
|
||||
|
||||
</div>
|
||||
|
||||
{% if d.description_short or d.description_html %}
|
||||
<div class="mt-4 text-stone-800 space-y-3">
|
||||
{% if d.description_short %}
|
||||
<p class="leading-relaxed text-lg">{{ d.description_short }}</p>
|
||||
{% endif %}
|
||||
{% if d.description_html %}
|
||||
<div class="max-w-none text-sm leading-relaxed">
|
||||
{{ d.description_html | safe }}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if d.sections and d.sections|length %}
|
||||
<div class="mt-8 space-y-3">
|
||||
{% for sec in d.sections %}
|
||||
<details class="group rounded-xl border bg-white shadow-sm open:shadow p-0">
|
||||
<summary class="cursor-pointer select-none px-4 py-3 flex items-center justify-between">
|
||||
<span class="font-medium">{{ sec.title }}</span>
|
||||
<span class="ml-2 text-xl transition-transform group-open:rotate-180">⌄</span>
|
||||
</summary>
|
||||
<div class="px-4 pb-4 max-w-none text-sm leading-relaxed">
|
||||
{{ sec.html | safe }}
|
||||
</div>
|
||||
</details>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div class="pb-8"></div>
|
||||
@@ -1,106 +0,0 @@
|
||||
{# --- social/meta_product.html --- #}
|
||||
{# Context expected:
|
||||
site, d (Product), request
|
||||
#}
|
||||
|
||||
{# Visibility → robots: index unless soft-deleted #}
|
||||
{% set robots_here = 'noindex,nofollow' if d.deleted_at else 'index,follow' %}
|
||||
|
||||
{# Compute canonical #}
|
||||
{% set _site_url = site().url.rstrip('/') if site and site().url else '' %}
|
||||
{% set _product_path = request.path if request else ('/products/' ~ (d.slug or '')) %}
|
||||
{% set canonical = _site_url ~ _product_path if _site_url else (request.url if request else None) %}
|
||||
|
||||
{# Include common base (charset, viewport, robots default, RSS, Org/WebSite JSON-LD) #}
|
||||
{% set robots_override = robots_here %}
|
||||
{% include 'social/meta_base.html' %}
|
||||
|
||||
{# ---- Titles / descriptions ---- #}
|
||||
{% set base_product_title = d.title or base_title %}
|
||||
{% set og_title = base_product_title %}
|
||||
{% set tw_title = base_product_title %}
|
||||
|
||||
{# Description: prefer short, then HTML stripped #}
|
||||
{% set desc_source = d.description_short
|
||||
or (d.description_html|striptags if d.description_html else '') %}
|
||||
{% set description = (desc_source|trim|replace('\n',' ')|replace('\r',' ')|striptags)|truncate(160, True, '…') %}
|
||||
|
||||
{# ---- Image priority: product image, then first gallery image, then site default ---- #}
|
||||
{% set image_url = d.image
|
||||
or ((d.images|first).url if d.images and (d.images|first).url else None)
|
||||
or (site().default_image if site and site().default_image else None) %}
|
||||
|
||||
{# ---- Price / offer helpers ---- #}
|
||||
{% set price = d.special_price or d.regular_price or d.rrp %}
|
||||
{% set price_currency = d.special_price_currency or d.regular_price_currency or d.rrp_currency %}
|
||||
|
||||
{# ---- Basic meta ---- #}
|
||||
<title>{{ base_product_title }}</title>
|
||||
<meta name="description" content="{{ description }}">
|
||||
{% if canonical %}<link rel="canonical" href="{{ canonical }}">{% endif %}
|
||||
|
||||
{# ---- Open Graph ---- #}
|
||||
<meta property="og:site_name" content="{{ site().title if site and site().title else '' }}">
|
||||
<meta property="og:type" content="product">
|
||||
<meta property="og:title" content="{{ og_title }}">
|
||||
<meta property="og:description" content="{{ description }}">
|
||||
{% if canonical %}<meta property="og:url" content="{{ canonical }}">{% endif %}
|
||||
{% if image_url %}<meta property="og:image" content="{{ image_url }}">{% endif %}
|
||||
|
||||
{# Optional product OG price tags #}
|
||||
{% if price and price_currency %}
|
||||
<meta property="product:price:amount" content="{{ '%.2f'|format(price) }}">
|
||||
<meta property="product:price:currency" content="{{ price_currency }}">
|
||||
{% endif %}
|
||||
{% if d.brand %}
|
||||
<meta property="product:brand" content="{{ d.brand }}">
|
||||
{% endif %}
|
||||
{% if d.sku %}
|
||||
<meta property="product:retailer_item_id" content="{{ d.sku }}">
|
||||
{% endif %}
|
||||
|
||||
{# ---- Twitter ---- #}
|
||||
<meta name="twitter:card" content="{{ 'summary_large_image' if image_url else 'summary' }}">
|
||||
{% if site and site().twitter_site %}<meta name="twitter:site" content="{{ site().twitter_site }}">{% endif %}
|
||||
<meta name="twitter:title" content="{{ tw_title }}">
|
||||
<meta name="twitter:description" content="{{ description }}">
|
||||
{% if image_url %}<meta name="twitter:image" content="{{ image_url }}">{% endif %}
|
||||
|
||||
{# ---- JSON-LD Product ---- #}
|
||||
{% set jsonld = {
|
||||
"@context": "https://schema.org",
|
||||
"@type": "Product",
|
||||
"name": d.title,
|
||||
"image": image_url,
|
||||
"description": description,
|
||||
"sku": d.sku,
|
||||
"brand": d.brand,
|
||||
"url": canonical
|
||||
} %}
|
||||
|
||||
{# Brand as proper object if present #}
|
||||
{% if d.brand %}
|
||||
{% set jsonld = jsonld | combine({
|
||||
"brand": {
|
||||
"@type": "Brand",
|
||||
"name": d.brand
|
||||
}
|
||||
}) %}
|
||||
{% endif %}
|
||||
|
||||
{# Offers if price available #}
|
||||
{% if price and price_currency %}
|
||||
{% set jsonld = jsonld | combine({
|
||||
"offers": {
|
||||
"@type": "Offer",
|
||||
"price": price,
|
||||
"priceCurrency": price_currency,
|
||||
"url": canonical,
|
||||
"availability": "https://schema.org/InStock"
|
||||
}
|
||||
}) %}
|
||||
{% endif %}
|
||||
|
||||
<script type="application/ld+json">
|
||||
{{ jsonld | tojson }}
|
||||
</script>
|
||||
@@ -1,49 +0,0 @@
|
||||
{% extends 'oob_elements.html' %}
|
||||
{# OOB elements for HTMX navigation - product extends browse so use similar structure #}
|
||||
{% import 'macros/layout.html' as layout %}
|
||||
{% import 'macros/stickers.html' as stick %}
|
||||
{% import '_types/product/prices.html' as prices %}
|
||||
{% set prices_ns = namespace() %}
|
||||
{{ prices.set_prices(d, prices_ns)}}
|
||||
|
||||
{# Import shared OOB macros #}
|
||||
{% from '_types/root/header/_oob.html' import root_header_start, root_header_end with context %}
|
||||
{% from '_types/root/_oob_menu.html' import mobile_menu with context %}
|
||||
|
||||
|
||||
|
||||
{% block oobs %}
|
||||
{% from '_types/market/header/_header.html' import header_row with context %}
|
||||
{{ header_row(oob=True) }}
|
||||
|
||||
{% from '_types/root/_n/macros.html' import oob_header with context %}
|
||||
{{oob_header('market-header-child', 'product-header-child', '_types/product/header/_header.html')}}
|
||||
|
||||
{% endblock %}
|
||||
|
||||
|
||||
|
||||
{% block mobile_menu %}
|
||||
{% include '_types/market/mobile/_nav_panel.html' %}
|
||||
{% include '_types/browse/_admin.html' %}
|
||||
{% endblock %}
|
||||
|
||||
{% block filter %}
|
||||
{% call layout.details() %}
|
||||
{% call layout.summary('coop-child-header') %}
|
||||
{% endcall %}
|
||||
{% call layout.menu('blog-child-menu') %}
|
||||
{% endcall %}
|
||||
{% endcall %}
|
||||
|
||||
{% call layout.details() %}
|
||||
{% call layout.summary('product-child-header') %}
|
||||
{% endcall %}
|
||||
{% call layout.menu('item-child-menu') %}
|
||||
{% endcall %}
|
||||
{% endcall %}
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
{% include '_types/product/_main_panel.html' %}
|
||||
{% endblock %}
|
||||
@@ -1,33 +0,0 @@
|
||||
{% import '_types/product/_cart.html' as _cart %}
|
||||
{# ---- Price block ---- #}
|
||||
{% import '_types/product/prices.html' as prices %}
|
||||
{% set prices_ns = namespace() %}
|
||||
{{ prices.set_prices(d, prices_ns)}}
|
||||
|
||||
<div class="flex flex-row items-center justify-between md:gap-2 md:px-2">
|
||||
{{ _cart.add(d.slug, cart)}}
|
||||
|
||||
{% if prices_ns.sp_val %}
|
||||
<div class="text-md font-bold text-emerald-700">
|
||||
Special price
|
||||
</div>
|
||||
<div class="text-xl font-semibold text-emerald-700">
|
||||
{{ prices.price_str(prices_ns.sp_val, prices_ns.sp_raw, prices_ns.sp_cur) }}
|
||||
</div>
|
||||
{% if prices_ns.sp_val and prices_ns.rp_val %}
|
||||
<div class="text-base text-md line-through text-stone-500">
|
||||
{{ prices.price_str(prices_ns.rp_val, prices_ns.rp_raw, prices_ns.rp_cur) }}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% elif prices_ns.rp_val %}
|
||||
<div class="hidden md:block text-xl font-bold">
|
||||
Our price
|
||||
</div>
|
||||
<div class="text-xl font-semibold">
|
||||
{{ prices.price_str(prices_ns.rp_val, prices_ns.rp_raw, prices_ns.rp_cur) }}
|
||||
</div>
|
||||
{% endif %}
|
||||
{{ prices.rrp(prices_ns) }}
|
||||
|
||||
</div>
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
<i class="fa fa-shopping-bag" aria-hidden="true"></i>
|
||||
<div>{{ d.title }}</div>
|
||||
@@ -1,2 +0,0 @@
|
||||
{% from 'macros/admin_nav.html' import placeholder_nav %}
|
||||
{{ placeholder_nav() }}
|
||||
@@ -1,40 +0,0 @@
|
||||
{% extends 'oob_elements.html' %}
|
||||
|
||||
|
||||
{# OOB elements for HTMX navigation - all elements that need updating #}
|
||||
{# Import shared OOB macros #}
|
||||
{% from '_types/root/header/_oob.html' import root_header_start, root_header_end with context %}
|
||||
{% from '_types/root/_oob_menu.html' import mobile_menu with context %}
|
||||
|
||||
|
||||
|
||||
{% block oobs %}
|
||||
|
||||
{% from '_types/root/_n/macros.html' import oob_header with context %}
|
||||
{{oob_header('product-header-child', 'product-admin-header-child', '_types/product/admin/header/_header.html')}}
|
||||
|
||||
{% from '_types/product/header/_header.html' import header_row with context %}
|
||||
{{ header_row(oob=True) }}
|
||||
{% endblock %}
|
||||
|
||||
|
||||
{% from '_types/root/_n/macros.html' import header with context %}
|
||||
{% call header(id='product-header-child', oob=True) %}
|
||||
{% call header() %}
|
||||
{% from '_types/product/admin/header/_header.html' import header_row with context %}
|
||||
{{header_row()}}
|
||||
<div id="product-admin-header-header-child">
|
||||
|
||||
</div>
|
||||
{% endcall %}
|
||||
{% endcall %}
|
||||
|
||||
|
||||
{% block mobile_menu %}
|
||||
{% include '_types/product/admin/_nav.html' %}
|
||||
{% endblock %}
|
||||
|
||||
|
||||
{% block content %}
|
||||
{% include '_types/product/_main_panel.html' %}
|
||||
{% endblock %}
|
||||
@@ -1,11 +0,0 @@
|
||||
{% import 'macros/links.html' as links %}
|
||||
{% macro header_row(oob=False) %}
|
||||
{% call links.menu_row(id='product-admin-row', oob=oob) %}
|
||||
{% call links.link(url_for('market.browse.product.admin', slug=d.slug), hx_select_search ) %}
|
||||
admin!!
|
||||
{% endcall %}
|
||||
{% call links.desktop_nav() %}
|
||||
{% include '_types/product/admin/_nav.html' %}
|
||||
{% endcall %}
|
||||
{% endcall %}
|
||||
{% endmacro %}
|
||||
@@ -1,39 +0,0 @@
|
||||
{% extends '_types/product/index.html' %}
|
||||
|
||||
{% import 'macros/layout.html' as layout %}
|
||||
|
||||
{% block product_header_child %}
|
||||
{% from '_types/root/_n/macros.html' import index_row with context %}
|
||||
{% call index_row('market-header-child', '_types/product/admin/header/_header.html') %}
|
||||
{% block product_admin_header_child %}
|
||||
{% endblock %}
|
||||
{% endcall %}
|
||||
{% endblock %}
|
||||
|
||||
|
||||
|
||||
{% block ___app_title %}
|
||||
{% import 'macros/links.html' as links %}
|
||||
{% call links.menu_row() %}
|
||||
{% call links.link(url_for('market.browse.product.admin', slug=slug), hx_select_search) %}
|
||||
{{ links.admin() }}
|
||||
{% endcall %}
|
||||
{% call links.desktop_nav() %}
|
||||
{% include '_types/product/admin/_nav.html' %}
|
||||
{% endcall %}
|
||||
{% endcall %}
|
||||
{% endblock %}
|
||||
|
||||
|
||||
|
||||
{% block _main_mobile_menu %}
|
||||
{% include '_types/product/admin/_nav.html' %}
|
||||
{% endblock %}
|
||||
|
||||
{% block aside %}
|
||||
{% endblock %}
|
||||
|
||||
|
||||
{% block content %}
|
||||
{% include '_types/product/_main_panel.html' %}
|
||||
{% endblock %}
|
||||
@@ -1,15 +0,0 @@
|
||||
{% import 'macros/links.html' as links %}
|
||||
{% macro header_row(oob=False) %}
|
||||
{% call links.menu_row(id='product-row', oob=oob) %}
|
||||
{% call links.link(url_for('market.browse.product.product_detail', slug=d.slug), hx_select_search ) %}
|
||||
{% include '_types/product/_title.html' %}
|
||||
{% endcall %}
|
||||
{% include '_types/product/_prices.html' %}
|
||||
{% call links.desktop_nav() %}
|
||||
{% include '_types/browse/_admin.html' %}
|
||||
{% endcall %}
|
||||
{% endcall %}
|
||||
{% endmacro %}
|
||||
|
||||
|
||||
|
||||
@@ -1,61 +0,0 @@
|
||||
{% extends '_types/browse/index.html' %}
|
||||
|
||||
{% block meta %}
|
||||
{% include '_types/product/_meta.html' %}
|
||||
{% endblock %}
|
||||
|
||||
|
||||
{% import 'macros/stickers.html' as stick %}
|
||||
{% import '_types/product/prices.html' as prices %}
|
||||
{% set prices_ns = namespace() %}
|
||||
{{ prices.set_prices(d, prices_ns)}}
|
||||
|
||||
|
||||
|
||||
{% block market_header_child %}
|
||||
{% from '_types/root/_n/macros.html' import index_row with context %}
|
||||
{% call index_row('market-header-child', '_types/product/header/_header.html') %}
|
||||
{% block product_header_child %}
|
||||
{% endblock %}
|
||||
{% endcall %}
|
||||
{% endblock %}
|
||||
|
||||
|
||||
{% block _main_mobile_menu %}
|
||||
{% include '_types/browse/_admin.html' %}
|
||||
{% endblock %}
|
||||
|
||||
|
||||
|
||||
{% block filter %}
|
||||
|
||||
{% call layout.details() %}
|
||||
{% call layout.summary('coop-child-header') %}
|
||||
{% block coop_child_summary %}
|
||||
{% endblock %}
|
||||
{% endcall %}
|
||||
{% call layout.menu('blog-child-menu') %}
|
||||
{% block post_child_menu %}
|
||||
{% endblock %}
|
||||
{% endcall %}
|
||||
{% endcall %}
|
||||
|
||||
{% call layout.details() %}
|
||||
{% call layout.summary('product-child-header') %}
|
||||
{% block item_child_summary %}
|
||||
{% endblock %}
|
||||
{% endcall %}
|
||||
{% call layout.menu('item-child-menu') %}
|
||||
{% block item_child_menu %}
|
||||
{% endblock %}
|
||||
{% endcall %}
|
||||
{% endcall %}
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block aside %}
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
{% include '_types/product/_main_panel.html' %}
|
||||
{% endblock %}
|
||||
@@ -1,66 +0,0 @@
|
||||
{# ---- Price formatting helpers ---- #}
|
||||
{% set _sym = {'GBP':'£','EUR':'€','USD':'$'} %}
|
||||
{% macro price_str(val, raw, cur) -%}
|
||||
{%- if raw -%}
|
||||
{{ raw }}
|
||||
{%- elif val is number -%}
|
||||
{{ (_sym.get(cur) or '') ~ ('%.2f'|format(val)) }}
|
||||
{%- else -%}
|
||||
{{ val or '' }}
|
||||
{%- endif -%}
|
||||
{%- endmacro %}
|
||||
|
||||
|
||||
{% macro set_prices(item, ns) -%}
|
||||
|
||||
{% set ns.sp_val = item.special_price or (item.oe_list_price and item.oe_list_price.special) %}
|
||||
{% set ns.sp_raw = item.special_price_raw or (item.oe_list_price and item.oe_list_price.special_raw) %}
|
||||
{% set ns.sp_cur = item.special_price_currency or (item.oe_list_price and item.oe_list_price.special_currency) %}
|
||||
|
||||
{% set ns.rp_val = item.regular_price or item.rrp or (item.oe_list_price and item.oe_list_price.rrp) %}
|
||||
{% set ns.rp_raw = item.regular_price_raw or item.rrp_raw or (item.oe_list_price and item.oe_list_price.rrp_raw) %}
|
||||
{% set ns.rp_cur = item.regular_price_currency or item.rrp_currency or (item.oe_list_price and item.oe_list_price.rrp_currency) %}
|
||||
|
||||
{% set ns.case_size_count = (item.case_size_count or 1) %}
|
||||
{% set ns.rrp = item.rrp_raw[0] ~ "%.2f"|format(item.rrp * (ns.case_size_count)) %}
|
||||
{% set ns.rrp_raw = item.rrp_raw %}
|
||||
|
||||
{%- endmacro %}
|
||||
|
||||
|
||||
{% macro rrp(ns) -%}
|
||||
{% if ns.rrp %}
|
||||
<div class="text-base md:text-lgtext-stone-400">
|
||||
<span>rrp:</span>
|
||||
<span>
|
||||
{{ ns.rrp }}
|
||||
</span>
|
||||
</div>
|
||||
{% endif %}
|
||||
{%- endmacro %}
|
||||
|
||||
|
||||
{% macro card_price(item) %}
|
||||
|
||||
|
||||
{# price block unchanged #}
|
||||
{% set _sym = {'GBP':'£','EUR':'€','USD':'$'} %}
|
||||
{% set sp_val = item.special_price or (item.oe_list_price and item.oe_list_price.special) %}
|
||||
{% set sp_raw = item.special_price_raw or (item.oe_list_price and item.oe_list_price.special_raw) %}
|
||||
{% set sp_cur = item.special_price_currency or (item.oe_list_price and item.oe_list_price.special_currency) %}
|
||||
{% set rp_val = item.regular_price or item.rrp or (item.oe_list_price and item.oe_list_price.rrp) %}
|
||||
{% set rp_raw = item.regular_price_raw or item.rrp_raw or (item.oe_list_price and item.oe_list_price.rrp_raw) %}
|
||||
{% set rp_cur = item.regular_price_currency or item.rrp_currency or (item.oe_list_price and item.oe_list_price.rrp_currency) %}
|
||||
{% set sp_str = sp_raw if sp_raw else ( (_sym.get(sp_cur, '') ~ ('%.2f'|format(sp_val))) if sp_val is number else (sp_val or '')) %}
|
||||
{% set rp_str = rp_raw if rp_raw else ( (_sym.get(rp_cur, '') ~ ('%.2f'|format(rp_val))) if rp_val is number else (rp_val or '')) %}
|
||||
<div class="mt-1 flex items-baseline gap-2 justify-center">
|
||||
{% if sp_val %}
|
||||
<div class="text-lg font-semibold text-emerald-700">{{ sp_str }}</div>
|
||||
{% if rp_val %}
|
||||
<div class="text-sm line-through text-stone-500">{{ rp_str }}</div>
|
||||
{% endif %}
|
||||
{% elif rp_val %}
|
||||
<div class="mt-1 text-lg font-semibold">{{ rp_str }}</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endmacro %}
|
||||
Reference in New Issue
Block a user