Delete 391 dead Jinja templates replaced by sx_components/defpage
All app-level templates have been replaced by native sx component builders and defpage declarative routes. Removes ~15,200 lines of dead HTML. Kept: shared/browser templates (errors, ap_social, macros, root layout), account + federation _email/magic_link, federation profile.html chain. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,12 +0,0 @@
|
||||
{% import 'macros/links.html' as links %}
|
||||
{% macro header_row(oob=False) %}
|
||||
{% call links.menu_row(id='auth-row', oob=oob) %}
|
||||
{% call links.link(account_url('/'), hx_select_search ) %}
|
||||
<i class="fa-solid fa-user"></i>
|
||||
<div>account</div>
|
||||
{% endcall %}
|
||||
{% call links.desktop_nav() %}
|
||||
{% include "_types/auth/_nav.html" %}
|
||||
{% endcall %}
|
||||
{% endcall %}
|
||||
{% endmacro %}
|
||||
@@ -1,18 +0,0 @@
|
||||
{% extends oob.extends %}
|
||||
|
||||
|
||||
{% block root_header_child %}
|
||||
{% from '_types/root/_n/macros.html' import index_row with context %}
|
||||
{% call index_row(oob.child_id, oob.header) %}
|
||||
{% block auth_header_child %}
|
||||
{% endblock %}
|
||||
{% endcall %}
|
||||
{% endblock %}
|
||||
|
||||
{% block _main_mobile_menu %}
|
||||
{% include oob.nav %}
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
{% include oob.main %}
|
||||
{% endblock %}
|
||||
@@ -1,260 +0,0 @@
|
||||
{% macro show_cart(oob=False) %}
|
||||
<div id="cart" {% if oob %} sx-swap-oob="{{oob}}" {% endif%}>
|
||||
{# Empty cart #}
|
||||
{% if not cart and not calendar_cart_entries and not ticket_cart_entries %}
|
||||
<div class="rounded-2xl border border-dashed border-stone-300 bg-white/80 p-6 sm:p-8 text-center">
|
||||
<div class="inline-flex h-10 w-10 sm:h-12 sm:w-12 items-center justify-center rounded-full bg-stone-100 mb-3">
|
||||
<i class="fa fa-shopping-cart text-stone-500 text-sm sm:text-base" aria-hidden="true"></i>
|
||||
</div>
|
||||
<p class="text-base sm:text-lg font-medium text-stone-800">
|
||||
Your cart is empty
|
||||
</p>
|
||||
{#
|
||||
<p class="mt-1 text-xs sm:text-sm text-stone-600">
|
||||
Add some items from the shop to see them here.
|
||||
</p>
|
||||
<div class="mt-4">
|
||||
<a
|
||||
href="{{ market_url('/') }}"
|
||||
class="inline-flex items-center px-4 py-2 text-sm font-semibold rounded-full bg-emerald-600 text-white hover:bg-emerald-700"
|
||||
>
|
||||
Browse products
|
||||
</a>
|
||||
</div> #}
|
||||
</div>
|
||||
|
||||
{% else %}
|
||||
|
||||
<div _class="grid gap-y-6 lg:gap-8 lg:grid-cols-[minmax(0,2fr),minmax(0,1fr)]">
|
||||
{# Items list #}
|
||||
<section class="space-y-3 sm:space-y-4">
|
||||
{% for item in cart %}
|
||||
{% from '_types/product/_cart.html' import cart_item with context %}
|
||||
{{ cart_item()}}
|
||||
{% endfor %}
|
||||
{% if calendar_cart_entries %}
|
||||
<div class="mt-6 border-t border-stone-200 pt-4">
|
||||
<h2 class="text-base font-semibold mb-2">
|
||||
Calendar bookings
|
||||
</h2>
|
||||
|
||||
<ul class="space-y-2">
|
||||
{% for entry in calendar_cart_entries %}
|
||||
<li class="flex items-start justify-between text-sm">
|
||||
<div>
|
||||
<div class="font-medium">
|
||||
{{ entry.name or entry.calendar_name }}
|
||||
</div>
|
||||
<div class="text-xs text-stone-500">
|
||||
{{ entry.start_at }}
|
||||
{% if entry.end_at %}
|
||||
– {{ entry.end_at }}
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="ml-4 font-medium">
|
||||
£{{ "%.2f"|format(entry.cost or 0) }}
|
||||
</div>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if ticket_groups is defined and ticket_groups %}
|
||||
<div class="mt-6 border-t border-stone-200 pt-4">
|
||||
<h2 class="text-base font-semibold mb-2">
|
||||
<i class="fa fa-ticket mr-1" aria-hidden="true"></i>
|
||||
Event tickets
|
||||
</h2>
|
||||
|
||||
<div class="space-y-3">
|
||||
{% for tg in ticket_groups %}
|
||||
<article class="flex flex-col sm:flex-row gap-3 sm:gap-4 rounded-2xl bg-white shadow-sm border border-stone-200 p-3 sm:p-4">
|
||||
<div class="flex-1 min-w-0">
|
||||
<div class="flex flex-col sm:flex-row sm:items-start justify-between gap-2 sm:gap-3">
|
||||
<div class="min-w-0">
|
||||
<h3 class="text-sm sm:text-base font-semibold text-stone-900">
|
||||
{{ tg.entry_name }}
|
||||
</h3>
|
||||
{% if tg.ticket_type_name %}
|
||||
<p class="mt-0.5 text-[0.7rem] sm:text-xs text-stone-500">
|
||||
{{ tg.ticket_type_name }}
|
||||
</p>
|
||||
{% endif %}
|
||||
<p class="mt-0.5 text-[0.7rem] sm:text-xs text-stone-500">
|
||||
{{ tg.entry_start_at.strftime('%-d %b %Y, %H:%M') }}
|
||||
{% if tg.entry_end_at %}
|
||||
– {{ tg.entry_end_at.strftime('%-d %b %Y, %H:%M') }}
|
||||
{% endif %}
|
||||
</p>
|
||||
</div>
|
||||
<div class="text-left sm:text-right">
|
||||
<p class="text-sm sm:text-base font-semibold text-stone-900">
|
||||
£{{ "%.2f"|format(tg.price or 0) }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-3 flex flex-col sm:flex-row sm:items-center justify-between gap-2 sm:gap-4">
|
||||
<div class="flex items-center gap-2 text-xs sm:text-sm text-stone-700">
|
||||
<span class="text-[0.65rem] sm:text-xs uppercase tracking-wide text-stone-500">Quantity</span>
|
||||
{% set qty_url = url_for('cart_global.update_ticket_quantity') %}
|
||||
|
||||
<form
|
||||
action="{{ qty_url }}"
|
||||
method="post"
|
||||
sx-post="{{ qty_url }}"
|
||||
sx-swap="none"
|
||||
>
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
|
||||
<input type="hidden" name="entry_id" value="{{ tg.entry_id }}">
|
||||
{% if tg.ticket_type_id %}
|
||||
<input type="hidden" name="ticket_type_id" value="{{ tg.ticket_type_id }}">
|
||||
{% endif %}
|
||||
<input type="hidden" name="count" value="{{ [tg.quantity - 1, 0] | max }}">
|
||||
<button
|
||||
type="submit"
|
||||
class="inline-flex items-center justify-center w-8 h-8 text-sm font-medium rounded-full border border-emerald-600 text-emerald-700 hover:bg-emerald-50 text-xl"
|
||||
>
|
||||
-
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<span class="inline-flex items-center justify-center px-2 py-1 rounded-full bg-stone-100 text-[0.7rem] sm:text-xs font-medium">
|
||||
{{ tg.quantity }}
|
||||
</span>
|
||||
|
||||
<form
|
||||
action="{{ qty_url }}"
|
||||
method="post"
|
||||
sx-post="{{ qty_url }}"
|
||||
sx-swap="none"
|
||||
>
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
|
||||
<input type="hidden" name="entry_id" value="{{ tg.entry_id }}">
|
||||
{% if tg.ticket_type_id %}
|
||||
<input type="hidden" name="ticket_type_id" value="{{ tg.ticket_type_id }}">
|
||||
{% endif %}
|
||||
<input type="hidden" name="count" value="{{ tg.quantity + 1 }}">
|
||||
<button
|
||||
type="submit"
|
||||
class="inline-flex items-center justify-center w-8 h-8 text-sm font-medium rounded-full border border-emerald-600 text-emerald-700 hover:bg-emerald-50 text-xl"
|
||||
>
|
||||
+
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center justify-between sm:justify-end gap-3">
|
||||
<p class="text-sm sm:text-base font-semibold text-stone-900">
|
||||
Line total:
|
||||
£{{ "%.2f"|format(tg.line_total) }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</section>
|
||||
{{summary(cart, total, calendar_total, calendar_cart_entries, ticket_total, ticket_cart_entries)}}
|
||||
|
||||
</div>
|
||||
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endmacro %}
|
||||
|
||||
|
||||
{% macro summary(cart, total, calendar_total, calendar_cart_entries, ticket_total, ticket_cart_entries, oob=False) %}
|
||||
<aside id="cart-summary" class="lg:pl-2" {% if oob %} sx-swap-oob="{{oob}}" {% endif %}>
|
||||
<div class="rounded-2xl bg-white shadow-sm border border-stone-200 p-4 sm:p-5">
|
||||
<h2 class="text-sm sm:text-base font-semibold text-stone-900 mb-3 sm:mb-4">
|
||||
Order summary
|
||||
</h2>
|
||||
|
||||
<dl class="space-y-2 text-xs sm:text-sm">
|
||||
<div class="flex items-center justify-between">
|
||||
<dt class="text-stone-600">Items</dt>
|
||||
<dd class="text-stone-900">
|
||||
{% set product_qty = cart | sum(attribute="quantity") %}
|
||||
{% set ticket_qty = ticket_cart_entries | length if ticket_cart_entries else 0 %}
|
||||
{{ product_qty + ticket_qty }}
|
||||
</dd>
|
||||
</div>
|
||||
<div class="flex items-center justify-between">
|
||||
<dt class="text-stone-600">Subtotal</dt>
|
||||
<dd class="text-stone-900">
|
||||
{{ cart_grand_total(cart, total, calendar_total, calendar_cart_entries, ticket_total, ticket_cart_entries) }}
|
||||
</dd>
|
||||
</div>
|
||||
</dl>
|
||||
<div class="flex flex-col items-center w-full">
|
||||
<h1 class="text-5xl mt-2">
|
||||
This is a test - it will not take actual money
|
||||
</h1>
|
||||
<div>
|
||||
use dummy card number: 5555 5555 5555 4444
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-4 sm:mt-5">
|
||||
{% if g.user %}
|
||||
<form
|
||||
method="post"
|
||||
action="{{ url_for('page_cart.page_checkout')|host if page_post is defined and page_post else url_for('cart_global.checkout')|host }}"
|
||||
class="w-full"
|
||||
>
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
|
||||
<button
|
||||
type="submit"
|
||||
class="w-full inline-flex items-center justify-center px-4 py-2 text-xs sm:text-sm rounded-full border border-emerald-600 bg-emerald-600 text-white hover:bg-emerald-700 transition"
|
||||
>
|
||||
<i class="fa-solid fa-credit-card mr-2" aria-hidden="true"></i>
|
||||
Checkout as {{g.user.email}}
|
||||
</button>
|
||||
</form>
|
||||
{% else %}
|
||||
{% set href=login_url(request.url) %}
|
||||
<div class="w-full flex">
|
||||
<a
|
||||
href="{{ href }}"
|
||||
class="w-full cursor-pointer flex flex-row items-center justify-center p-3 gap-2 rounded bg-stone-200 text-black hover:bg-stone-300 transition"
|
||||
>
|
||||
<i class="fa-solid fa-key"></i>
|
||||
<span>sign in or register to checkout</span>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
{% endmacro %}
|
||||
|
||||
{% macro cart_total(cart, total) %}
|
||||
{% set cart_total = total(cart) %}
|
||||
{% if cart_total %}
|
||||
{% set symbol = "£" if cart[0].product.regular_price_currency == "GBP" else cart[0].product.regular_price_currency %}
|
||||
{{ symbol }}{{ "%.2f"|format(cart_total) }}
|
||||
{% else %}
|
||||
–
|
||||
{% endif %}
|
||||
{% endmacro %}
|
||||
|
||||
|
||||
{% macro cart_grand_total(cart, total, calendar_total, calendar_cart_entries, ticket_total, ticket_cart_entries) %}
|
||||
{% set product_total = total(cart) or 0 %}
|
||||
{% set cal_total = calendar_total(calendar_cart_entries) or 0 %}
|
||||
{% set tk_total = ticket_total(ticket_cart_entries) or 0 %}
|
||||
{% set grand = product_total + cal_total + tk_total %}
|
||||
|
||||
{% if cart and cart[0].product.regular_price_currency %}
|
||||
{% set symbol = "£" if cart[0].product.regular_price_currency == "GBP" else cart[0].product.regular_price_currency %}
|
||||
{% else %}
|
||||
{% set symbol = "£" %}
|
||||
{% endif %}
|
||||
|
||||
{{ symbol }}{{ "%.2f"|format(grand) }}
|
||||
{% endmacro %}
|
||||
@@ -1,4 +0,0 @@
|
||||
<div class="max-w-full px-3 py-3 space-y-3">
|
||||
{% from '_types/cart/_cart.html' import show_cart with context %}
|
||||
{{ show_cart() }}
|
||||
</div>
|
||||
@@ -1,45 +0,0 @@
|
||||
{% macro mini(oob=False, count=None) %}
|
||||
<div id="cart-mini" {% if oob %}sx-swap-oob="{{oob}}"{% endif %} >
|
||||
{# cart_count is set by the context processor in all apps.
|
||||
Cart app computes it from g.cart + calendar_cart_entries;
|
||||
other apps get it from the cart internal API.
|
||||
count param allows explicit override when macro is imported without context. #}
|
||||
{% if count is not none %}
|
||||
{% set _count = count %}
|
||||
{% elif cart_count is defined and cart_count is not none %}
|
||||
{% set _count = cart_count %}
|
||||
{% elif cart is defined and cart is not none %}
|
||||
{% set _count = (cart | sum(attribute="quantity")) + ((calendar_cart_entries | length) if calendar_cart_entries else 0) %}
|
||||
{% else %}
|
||||
{% set _count = 0 %}
|
||||
{% endif %}
|
||||
|
||||
{% if _count == 0 %}
|
||||
<div class="h-12 w-12 rounded-full overflow-hidden border border-stone-300 flex-shrink-0">
|
||||
<a
|
||||
href="{{ blog_url('/') }}"
|
||||
class="h-full w-full font-bold text-5xl flex-shrink-0 flex flex-row items-center gap-1"
|
||||
>
|
||||
<img
|
||||
src="{{ site().logo }}"
|
||||
class="h-full w-full rounded-full object-cover border border-stone-300 flex-shrink-0"
|
||||
>
|
||||
</a>
|
||||
</div>
|
||||
{% else %}
|
||||
|
||||
<a
|
||||
href="{{ cart_url('/') }}"
|
||||
class="relative inline-flex items-center justify-center text-stone-700 hover:text-emerald-700"
|
||||
>
|
||||
<i class="fa fa-shopping-cart text-5xl" aria-hidden="true"></i>
|
||||
|
||||
<span
|
||||
class="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 inline-flex items-center justify-center rounded-full bg-emerald-600 text-white text-sm w-5 h-5"
|
||||
>
|
||||
{{ _count }}
|
||||
</span>
|
||||
</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endmacro %}
|
||||
@@ -1,2 +0,0 @@
|
||||
{% from 'macros/admin_nav.html' import placeholder_nav %}
|
||||
{{ placeholder_nav() }}
|
||||
@@ -1,28 +0,0 @@
|
||||
{% extends 'oob_elements.html' %}
|
||||
|
||||
{# OOB elements for HTMX navigation - all elements that need updating #}
|
||||
|
||||
{% 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('root-header-child', 'cart-header-child', '_types/cart/header/_header.html')}}
|
||||
|
||||
{% from '_types/root/header/_header.html' import header_row with context %}
|
||||
{{ header_row(oob=True) }}
|
||||
{% endblock %}
|
||||
|
||||
|
||||
{% block mobile_menu %}
|
||||
{% include '_types/cart/_nav.html' %}
|
||||
{% endblock %}
|
||||
|
||||
|
||||
{% block content %}
|
||||
{% include "_types/cart/_main_panel.html" %}
|
||||
{% endblock %}
|
||||
|
||||
|
||||
@@ -1,38 +0,0 @@
|
||||
{% extends '_types/root/index.html' %}
|
||||
|
||||
{% block filter %}
|
||||
<header class="mb-6 sm:mb-8">
|
||||
<h1 class="text-xl sm:text-2xl md:text-3xl font-semibold tracking-tight">
|
||||
Checkout error
|
||||
</h1>
|
||||
<p class="text-xs sm:text-sm text-stone-600">
|
||||
We tried to start your payment with SumUp but hit a problem.
|
||||
</p>
|
||||
</header>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="max-w-full px-3 py-3 space-y-4">
|
||||
<div class="rounded-2xl border border-rose-200 bg-rose-50/80 p-4 sm:p-6 text-sm text-rose-900 space-y-2">
|
||||
<p class="font-medium">Something went wrong.</p>
|
||||
<p>
|
||||
{{ error or "Unexpected error while creating the hosted checkout session." }}
|
||||
</p>
|
||||
{% if order %}
|
||||
<p class="text-xs text-rose-800/80">
|
||||
Order ID: <span class="font-mono">#{{ order.id }}</span>
|
||||
</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<a
|
||||
href="{{ cart_url('/') }}"
|
||||
class="inline-flex items-center px-3 py-2 text-xs sm:text-sm rounded-full border border-stone-300 bg-white hover:bg-stone-50 transition"
|
||||
>
|
||||
<i class="fa fa-shopping-cart mr-2" aria-hidden="true"></i>
|
||||
Back to cart
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -1,68 +0,0 @@
|
||||
{% extends '_types/root/index.html' %}
|
||||
|
||||
{% block filter %}
|
||||
<header class="mb-1 sm:mb-2 flex flex-col sm:flex-row sm:items-center justify-between gap-3 sm:gap-4">
|
||||
<div class="space-y-1">
|
||||
<h1 class="text-xl sm:text-2xl md:text-3xl font-semibold tracking-tight">
|
||||
{% if order.status == 'paid' %}
|
||||
Payment received
|
||||
{% elif order.status == 'failed' %}
|
||||
Payment failed
|
||||
{% elif order.status == 'missing' %}
|
||||
Order not found
|
||||
{% else %}
|
||||
Payment status: {{ order.status|default('pending')|capitalize }}
|
||||
{% endif %}
|
||||
</h1>
|
||||
<p class="text-xs sm:text-sm text-stone-600">
|
||||
{% if order.status == 'paid' %}
|
||||
Thanks for your order.
|
||||
{% elif order.status == 'failed' %}
|
||||
Something went wrong while processing your payment. You can try again below.
|
||||
{% elif order.status == 'missing' %}
|
||||
We couldn't find that order – it may have expired or never been created.
|
||||
{% else %}
|
||||
We’re still waiting for a final confirmation from SumUp.
|
||||
{% endif %}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
</header>
|
||||
{% endblock %}
|
||||
|
||||
{% block aside %}
|
||||
{# no aside content for now #}
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="max-w-full px-1 py-1">
|
||||
{% if order %}
|
||||
<div class="rounded-2xl border border-stone-200 bg-white/80 p-4 sm:p-6 space-y-2">
|
||||
{% include '_types/order/_summary.html' %}
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="rounded-2xl border border-dashed border-rose-300 bg-rose-50/80 p-4 sm:p-6 text-sm text-rose-800">
|
||||
We couldn’t find that order. If you reached this page from an old link, please start a new order.
|
||||
</div>
|
||||
{% endif %}
|
||||
{% include '_types/order/_items.html' %}
|
||||
{% include '_types/order/_calendar_items.html' %}
|
||||
{% include '_types/order/_ticket_items.html' %}
|
||||
|
||||
{% if order.status == 'failed' and order %}
|
||||
<div class="rounded-2xl border border-rose-200 bg-rose-50/80 p-4 sm:p-6 text-sm text-rose-900 space-y-2">
|
||||
<p class="font-medium">Your payment was not completed.</p>
|
||||
<p>
|
||||
You can go back to your cart and try checkout again. If the problem persists,
|
||||
please contact us and mention order <span class="font-mono">#{{ order.id }}</span>.
|
||||
</p>
|
||||
</div>
|
||||
{% elif order.status == 'paid' %}
|
||||
<div class="rounded-2xl border border-emerald-200 bg-emerald-50/80 p-4 sm:p-6 text-sm text-emerald-900 space-y-2">
|
||||
<p class="font-medium">All done!</p>
|
||||
<p>We’ll start processing your order shortly.</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -1,12 +0,0 @@
|
||||
{% import 'macros/links.html' as links %}
|
||||
{% macro header_row(oob=False) %}
|
||||
{% call links.menu_row(id='cart-row', oob=oob) %}
|
||||
{% call links.link(cart_url('/'), hx_select_search ) %}
|
||||
<i class="fa fa-shopping-cart"></i>
|
||||
<h2 class="text-xl font-bold">cart</h2>
|
||||
{% endcall %}
|
||||
{% call links.desktop_nav() %}
|
||||
{% include '_types/cart/_nav.html' %}
|
||||
{% endcall %}
|
||||
{% endcall %}
|
||||
{% endmacro %}
|
||||
@@ -1,22 +0,0 @@
|
||||
{% extends '_types/root/_index.html' %}
|
||||
|
||||
{% block root_header_child %}
|
||||
{% from '_types/root/_n/macros.html' import index_row with context %}
|
||||
{% call index_row('cart-header-child', '_types/cart/header/_header.html') %}
|
||||
{% block cart_header_child %}
|
||||
{% endblock %}
|
||||
{% endcall %}
|
||||
{% endblock %}
|
||||
|
||||
|
||||
{% block _main_mobile_menu %}
|
||||
{% include '_types/cart/_nav.html' %}
|
||||
{% endblock %}
|
||||
|
||||
|
||||
{% block aside %}
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
{% include '_types/cart/_main_panel.html' %}
|
||||
{% endblock %}
|
||||
@@ -1,147 +0,0 @@
|
||||
<div class="max-w-full px-3 py-3 space-y-3">
|
||||
{% if not page_groups or (page_groups | length == 0) %}
|
||||
<div class="rounded-2xl border border-dashed border-stone-300 bg-white/80 p-6 sm:p-8 text-center">
|
||||
<div class="inline-flex h-10 w-10 sm:h-12 sm:w-12 items-center justify-center rounded-full bg-stone-100 mb-3">
|
||||
<i class="fa fa-shopping-cart text-stone-500 text-sm sm:text-base" aria-hidden="true"></i>
|
||||
</div>
|
||||
<p class="text-base sm:text-lg font-medium text-stone-800">
|
||||
Your cart is empty
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{% else %}
|
||||
{# Check if there are any items at all across all groups #}
|
||||
{% set ns = namespace(has_items=false) %}
|
||||
{% for grp in page_groups %}
|
||||
{% if grp.cart_items or grp.calendar_entries or grp.get('tickets') %}
|
||||
{% set ns.has_items = true %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
|
||||
{% if not ns.has_items %}
|
||||
<div class="rounded-2xl border border-dashed border-stone-300 bg-white/80 p-6 sm:p-8 text-center">
|
||||
<div class="inline-flex h-10 w-10 sm:h-12 sm:w-12 items-center justify-center rounded-full bg-stone-100 mb-3">
|
||||
<i class="fa fa-shopping-cart text-stone-500 text-sm sm:text-base" aria-hidden="true"></i>
|
||||
</div>
|
||||
<p class="text-base sm:text-lg font-medium text-stone-800">
|
||||
Your cart is empty
|
||||
</p>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="space-y-4">
|
||||
{% for grp in page_groups %}
|
||||
{% if grp.cart_items or grp.calendar_entries or grp.get('tickets') %}
|
||||
|
||||
{% if grp.post %}
|
||||
{# Market / page cart card #}
|
||||
<a
|
||||
href="{{ cart_url('/' + grp.post.slug + '/') }}"
|
||||
class="block rounded-2xl border border-stone-200 bg-white shadow-sm hover:shadow-md hover:border-stone-300 transition p-4 sm:p-5"
|
||||
>
|
||||
<div class="flex items-start gap-4">
|
||||
{% if grp.post.feature_image %}
|
||||
<img
|
||||
src="{{ grp.post.feature_image }}"
|
||||
alt="{{ grp.post.title }}"
|
||||
class="h-16 w-16 rounded-xl object-cover border border-stone-200 flex-shrink-0"
|
||||
>
|
||||
{% else %}
|
||||
<div class="h-16 w-16 rounded-xl bg-stone-100 flex items-center justify-center flex-shrink-0">
|
||||
<i class="fa fa-store text-stone-400 text-xl" aria-hidden="true"></i>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="flex-1 min-w-0">
|
||||
<h3 class="text-base sm:text-lg font-semibold text-stone-900 truncate">
|
||||
{% if grp.market_place %}
|
||||
{{ grp.market_place.name }}
|
||||
{% else %}
|
||||
{{ grp.post.title }}
|
||||
{% endif %}
|
||||
</h3>
|
||||
{% if grp.market_place %}
|
||||
<p class="text-xs text-stone-500 truncate">{{ grp.post.title }}</p>
|
||||
{% endif %}
|
||||
|
||||
<div class="mt-1 flex flex-wrap gap-2 text-xs text-stone-600">
|
||||
{% if grp.product_count > 0 %}
|
||||
<span class="inline-flex items-center gap-1 px-2 py-0.5 rounded-full bg-stone-100">
|
||||
<i class="fa fa-box-open" aria-hidden="true"></i>
|
||||
{{ grp.product_count }} item{{ 's' if grp.product_count != 1 }}
|
||||
</span>
|
||||
{% endif %}
|
||||
{% if grp.calendar_count > 0 %}
|
||||
<span class="inline-flex items-center gap-1 px-2 py-0.5 rounded-full bg-stone-100">
|
||||
<i class="fa fa-calendar" aria-hidden="true"></i>
|
||||
{{ grp.calendar_count }} booking{{ 's' if grp.calendar_count != 1 }}
|
||||
</span>
|
||||
{% endif %}
|
||||
{% if grp.ticket_count is defined and grp.ticket_count > 0 %}
|
||||
<span class="inline-flex items-center gap-1 px-2 py-0.5 rounded-full bg-stone-100">
|
||||
<i class="fa fa-ticket" aria-hidden="true"></i>
|
||||
{{ grp.ticket_count }} ticket{{ 's' if grp.ticket_count != 1 }}
|
||||
</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="text-right flex-shrink-0">
|
||||
<div class="text-lg font-bold text-stone-900">
|
||||
£{{ "%.2f"|format(grp.total) }}
|
||||
</div>
|
||||
<div class="mt-1 text-xs text-emerald-700 font-medium">
|
||||
View cart →
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
|
||||
{% else %}
|
||||
{# Orphan bucket (items without a page) #}
|
||||
<div class="rounded-2xl border border-dashed border-amber-300 bg-amber-50/60 p-4 sm:p-5">
|
||||
<div class="flex items-start gap-4">
|
||||
<div class="h-16 w-16 rounded-xl bg-amber-100 flex items-center justify-center flex-shrink-0">
|
||||
<i class="fa fa-shopping-cart text-amber-500 text-xl" aria-hidden="true"></i>
|
||||
</div>
|
||||
|
||||
<div class="flex-1 min-w-0">
|
||||
<h3 class="text-base sm:text-lg font-semibold text-stone-900">
|
||||
Other items
|
||||
</h3>
|
||||
<div class="mt-1 flex flex-wrap gap-2 text-xs text-stone-600">
|
||||
{% if grp.product_count > 0 %}
|
||||
<span class="inline-flex items-center gap-1 px-2 py-0.5 rounded-full bg-amber-100">
|
||||
<i class="fa fa-box-open" aria-hidden="true"></i>
|
||||
{{ grp.product_count }} item{{ 's' if grp.product_count != 1 }}
|
||||
</span>
|
||||
{% endif %}
|
||||
{% if grp.calendar_count > 0 %}
|
||||
<span class="inline-flex items-center gap-1 px-2 py-0.5 rounded-full bg-amber-100">
|
||||
<i class="fa fa-calendar" aria-hidden="true"></i>
|
||||
{{ grp.calendar_count }} booking{{ 's' if grp.calendar_count != 1 }}
|
||||
</span>
|
||||
{% endif %}
|
||||
{% if grp.ticket_count is defined and grp.ticket_count > 0 %}
|
||||
<span class="inline-flex items-center gap-1 px-2 py-0.5 rounded-full bg-amber-100">
|
||||
<i class="fa fa-ticket" aria-hidden="true"></i>
|
||||
{{ grp.ticket_count }} ticket{{ 's' if grp.ticket_count != 1 }}
|
||||
</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="text-right flex-shrink-0">
|
||||
<div class="text-lg font-bold text-stone-900">
|
||||
£{{ "%.2f"|format(grp.total) }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</div>
|
||||
@@ -1,24 +0,0 @@
|
||||
{% extends 'oob_elements.html' %}
|
||||
|
||||
{# OOB elements for cart overview HTMX navigation #}
|
||||
|
||||
{% 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('root-header-child', 'cart-header-child', '_types/cart/header/_header.html')}}
|
||||
|
||||
{% from '_types/root/header/_header.html' import header_row with context %}
|
||||
{{ header_row(oob=True) }}
|
||||
{% endblock %}
|
||||
|
||||
|
||||
{% block mobile_menu %}
|
||||
{% include '_types/cart/_nav.html' %}
|
||||
{% endblock %}
|
||||
|
||||
|
||||
{% block content %}
|
||||
{% include "_types/cart/overview/_main_panel.html" %}
|
||||
{% endblock %}
|
||||
@@ -1,22 +0,0 @@
|
||||
{% extends '_types/root/_index.html' %}
|
||||
|
||||
{% block root_header_child %}
|
||||
{% from '_types/root/_n/macros.html' import index_row with context %}
|
||||
{% call index_row('cart-header-child', '_types/cart/header/_header.html') %}
|
||||
{% block cart_header_child %}
|
||||
{% endblock %}
|
||||
{% endcall %}
|
||||
{% endblock %}
|
||||
|
||||
|
||||
{% block _main_mobile_menu %}
|
||||
{% include '_types/cart/_nav.html' %}
|
||||
{% endblock %}
|
||||
|
||||
|
||||
{% block aside %}
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
{% include '_types/cart/overview/_main_panel.html' %}
|
||||
{% endblock %}
|
||||
@@ -1,4 +0,0 @@
|
||||
<div class="max-w-full px-3 py-3 space-y-3">
|
||||
{% from '_types/cart/_cart.html' import show_cart with context %}
|
||||
{{ show_cart() }}
|
||||
</div>
|
||||
@@ -1,27 +0,0 @@
|
||||
{% extends 'oob_elements.html' %}
|
||||
|
||||
{# OOB elements for page cart HTMX navigation #}
|
||||
|
||||
{% 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('root-header-child', 'cart-header-child', '_types/cart/header/_header.html')}}
|
||||
|
||||
{% from '_types/cart/page/header/_header.html' import page_header_row with context %}
|
||||
{{ page_header_row(oob=True) }}
|
||||
|
||||
{% from '_types/root/header/_header.html' import header_row with context %}
|
||||
{{ header_row(oob=True) }}
|
||||
{% endblock %}
|
||||
|
||||
|
||||
{% block mobile_menu %}
|
||||
{% include '_types/cart/_nav.html' %}
|
||||
{% endblock %}
|
||||
|
||||
|
||||
{% block content %}
|
||||
{% include "_types/cart/page/_main_panel.html" %}
|
||||
{% endblock %}
|
||||
@@ -1,25 +0,0 @@
|
||||
{% import 'macros/links.html' as links %}
|
||||
{% macro page_header_row(oob=False) %}
|
||||
{% call links.menu_row(id='page-cart-row', oob=oob) %}
|
||||
{% call links.link(cart_url('/' + page_post.slug + '/'), hx_select_search) %}
|
||||
{% if page_post.feature_image %}
|
||||
<img
|
||||
src="{{ page_post.feature_image }}"
|
||||
class="h-8 w-8 rounded-full object-cover border border-stone-300 flex-shrink-0"
|
||||
>
|
||||
{% endif %}
|
||||
<span>
|
||||
{{ page_post.title | truncate(160, True, '...') }}
|
||||
</span>
|
||||
{% endcall %}
|
||||
{% call links.desktop_nav() %}
|
||||
<a
|
||||
href="{{ cart_url('/') }}"
|
||||
class="inline-flex items-center gap-1.5 px-3 py-1.5 text-sm rounded-full border border-stone-300 bg-white hover:bg-stone-50 transition"
|
||||
>
|
||||
<i class="fa fa-arrow-left text-xs" aria-hidden="true"></i>
|
||||
All carts
|
||||
</a>
|
||||
{% endcall %}
|
||||
{% endcall %}
|
||||
{% endmacro %}
|
||||
@@ -1,24 +0,0 @@
|
||||
{% extends '_types/root/_index.html' %}
|
||||
|
||||
{% block root_header_child %}
|
||||
{% from '_types/root/_n/macros.html' import index_row with context %}
|
||||
{% call index_row('cart-header-child', '_types/cart/header/_header.html') %}
|
||||
{% block cart_header_child %}
|
||||
{% from '_types/cart/page/header/_header.html' import page_header_row with context %}
|
||||
{{ page_header_row() }}
|
||||
{% endblock %}
|
||||
{% endcall %}
|
||||
{% endblock %}
|
||||
|
||||
|
||||
{% block _main_mobile_menu %}
|
||||
{% include '_types/cart/_nav.html' %}
|
||||
{% endblock %}
|
||||
|
||||
|
||||
{% block aside %}
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
{% include '_types/cart/page/_main_panel.html' %}
|
||||
{% endblock %}
|
||||
@@ -1,43 +0,0 @@
|
||||
{# --- NEW: calendar bookings in this order --- #}
|
||||
{% if order and calendar_entries %}
|
||||
<section class="mt-6 space-y-3">
|
||||
<h2 class="text-base sm:text-lg font-semibold">
|
||||
Calendar bookings in this order
|
||||
</h2>
|
||||
|
||||
<ul class="divide-y divide-stone-200 rounded-2xl border border-stone-200 bg-white/80">
|
||||
{% for entry in calendar_entries %}
|
||||
<li class="px-4 py-3 flex items-start justify-between text-sm">
|
||||
<div>
|
||||
<div class="font-medium flex items-center gap-2">
|
||||
{{ entry.name }}
|
||||
{# Small status pill #}
|
||||
<span class="inline-flex items-center rounded-full px-2 py-0.5 text-[11px] font-medium
|
||||
{% if entry.state == 'confirmed' %}
|
||||
bg-emerald-100 text-emerald-800
|
||||
{% elif entry.state == 'provisional' %}
|
||||
bg-amber-100 text-amber-800
|
||||
{% elif entry.state == 'ordered' %}
|
||||
bg-blue-100 text-blue-800
|
||||
{% else %}
|
||||
bg-stone-100 text-stone-700
|
||||
{% endif %}
|
||||
">
|
||||
{{ entry.state|capitalize }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="text-xs text-stone-500">
|
||||
{{ entry.start_at.strftime('%-d %b %Y, %H:%M') }}
|
||||
{% if entry.end_at %}
|
||||
– {{ entry.end_at.strftime('%-d %b %Y, %H:%M') }}
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="ml-4 font-medium">
|
||||
£{{ "%.2f"|format(entry.cost or 0) }}
|
||||
</div>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</section>
|
||||
{% endif %}
|
||||
@@ -1,51 +0,0 @@
|
||||
{# Items list #}
|
||||
{% if order and order.items %}
|
||||
<div class="rounded-2xl border border-stone-200 bg-white/80 p-4 sm:p-6">
|
||||
<h2 class="text-sm sm:text-base font-semibold mb-3">
|
||||
Items
|
||||
</h2>
|
||||
<ul class="divide-y divide-stone-100 text-xs sm:text-sm">
|
||||
{% for item in order.items %}
|
||||
<li>
|
||||
<a class="w-full py-2 flex gap-3" href="{{ market_product_url(item.product_slug) }}">
|
||||
{# Thumbnail #}
|
||||
<div class="w-12 h-12 sm:w-14 sm:h-14 rounded-md bg-stone-100 flex-shrink-0 overflow-hidden">
|
||||
{% if item.product_image %}
|
||||
<img
|
||||
src="{{ item.product_image }}"
|
||||
alt="{{ item.product_title or 'Product image' }}"
|
||||
class="w-full h-full object-contain object-center"
|
||||
loading="lazy"
|
||||
decoding="async"
|
||||
>
|
||||
{% else %}
|
||||
<div class="w-full h-full flex items-center justify-center text-[9px] text-stone-400">
|
||||
No image
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
{# Text + pricing #}
|
||||
<div class="flex-1 flex justify-between gap-3">
|
||||
<div>
|
||||
<p class="font-medium">
|
||||
{{ item.product_title or 'Unknown product' }}
|
||||
</p>
|
||||
<p class="text-[11px] text-stone-500">
|
||||
Product ID: {{ item.product_id }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="text-right whitespace-nowrap">
|
||||
<p>Qty: {{ item.quantity }}</p>
|
||||
<p>
|
||||
{{ item.currency or order.currency or 'GBP' }}
|
||||
{{ '%.2f'|format(item.unit_price or 0) }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
{% endif %}
|
||||
@@ -1,7 +0,0 @@
|
||||
<div class="max-w-full px-3 py-3 space-y-4">
|
||||
{# Order summary card #}
|
||||
{% include '_types/order/_summary.html' %}
|
||||
{% include '_types/order/_items.html' %}
|
||||
{% include '_types/order/_calendar_items.html' %}
|
||||
|
||||
</div>
|
||||
@@ -1,2 +0,0 @@
|
||||
{% from 'macros/admin_nav.html' import placeholder_nav %}
|
||||
{{ placeholder_nav() }}
|
||||
@@ -1,30 +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 %}
|
||||
|
||||
{# 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('orders-header-child', 'order-header-child', '_types/order/header/_header.html')}}
|
||||
|
||||
{% from '_types/order/header/_header.html' import header_row with context %}
|
||||
{{ header_row(oob=True) }}
|
||||
{% endblock %}
|
||||
|
||||
|
||||
{% block mobile_menu %}
|
||||
{% include '_types/order/_nav.html' %}
|
||||
{% endblock %}
|
||||
|
||||
|
||||
{% block content %}
|
||||
{% include "_types/order/_main_panel.html" %}
|
||||
{% endblock %}
|
||||
|
||||
|
||||
@@ -1,52 +0,0 @@
|
||||
<div class="rounded-2xl border border-stone-200 bg-white/80 p-4 sm:p-6 space-y-2 text-xs sm:text-sm text-stone-800">
|
||||
<p>
|
||||
<span class="font-medium">Order ID:</span>
|
||||
<span class="font-mono">#{{ order.id }}</span>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<span class="font-medium">Created:</span>
|
||||
{% if order.created_at %}
|
||||
{{ order.created_at.strftime('%-d %b %Y, %H:%M') }}
|
||||
{% else %}
|
||||
—
|
||||
{% endif %}
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<span class="font-medium">Description:</span>
|
||||
{{ order.description or '–' }}
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<span class="font-medium">Status:</span>
|
||||
<span class="inline-flex items-center rounded-full px-2 py-1 text-[11px] font-medium
|
||||
{% if order.status == 'paid' %}
|
||||
bg-emerald-50 text-emerald-700 border border-emerald-200
|
||||
{% elif order.status == 'failed' %}
|
||||
bg-rose-50 text-rose-700 border border-rose-200
|
||||
{% else %}
|
||||
bg-stone-50 text-stone-700 border border-stone-200
|
||||
{% endif %}
|
||||
">
|
||||
{{ order.status or 'pending' }}
|
||||
</span>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<span class="font-medium">Currency:</span>
|
||||
{{ order.currency or 'GBP' }}
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<span class="font-medium">Total:</span>
|
||||
{% if order.total_amount %}
|
||||
{{ order.currency or 'GBP' }} {{ '%.2f'|format(order.total_amount) }}
|
||||
{% else %}
|
||||
–
|
||||
{% endif %}
|
||||
</p>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
@@ -1,49 +0,0 @@
|
||||
{# --- Tickets in this order --- #}
|
||||
{% if order and order_tickets %}
|
||||
<section class="mt-6 space-y-3">
|
||||
<h2 class="text-base sm:text-lg font-semibold">
|
||||
Event tickets in this order
|
||||
</h2>
|
||||
|
||||
<ul class="divide-y divide-stone-200 rounded-2xl border border-stone-200 bg-white/80">
|
||||
{% for tk in order_tickets %}
|
||||
<li class="px-4 py-3 flex items-start justify-between text-sm">
|
||||
<div>
|
||||
<div class="font-medium flex items-center gap-2">
|
||||
{{ tk.entry_name }}
|
||||
{# Small status pill #}
|
||||
<span class="inline-flex items-center rounded-full px-2 py-0.5 text-[11px] font-medium
|
||||
{% if tk.state == 'confirmed' %}
|
||||
bg-emerald-100 text-emerald-800
|
||||
{% elif tk.state == 'reserved' %}
|
||||
bg-amber-100 text-amber-800
|
||||
{% elif tk.state == 'checked_in' %}
|
||||
bg-blue-100 text-blue-800
|
||||
{% else %}
|
||||
bg-stone-100 text-stone-700
|
||||
{% endif %}
|
||||
">
|
||||
{{ tk.state|replace('_', ' ')|capitalize }}
|
||||
</span>
|
||||
</div>
|
||||
{% if tk.ticket_type_name %}
|
||||
<div class="text-xs text-stone-500">{{ tk.ticket_type_name }}</div>
|
||||
{% endif %}
|
||||
<div class="text-xs text-stone-500">
|
||||
{{ tk.entry_start_at.strftime('%-d %b %Y, %H:%M') }}
|
||||
{% if tk.entry_end_at %}
|
||||
– {{ tk.entry_end_at.strftime('%-d %b %Y, %H:%M') }}
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="text-xs text-stone-400 font-mono mt-0.5">
|
||||
{{ tk.code }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="ml-4 font-medium">
|
||||
£{{ "%.2f"|format(tk.price or 0) }}
|
||||
</div>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</section>
|
||||
{% endif %}
|
||||
@@ -1,17 +0,0 @@
|
||||
{% import 'macros/links.html' as links %}
|
||||
{% macro header_row(oob=False) %}
|
||||
{% call links.menu_row(id='order-row', oob=oob) %}
|
||||
{% call links.link(url_for('orders.order.order_detail', order_id=order.id), hx_select_search ) %}
|
||||
<i class="fa fa-gbp" aria-hidden="true"></i>
|
||||
<div>
|
||||
Order
|
||||
</div>
|
||||
<div>
|
||||
{{ order.id }}
|
||||
</div>
|
||||
{% endcall %}
|
||||
{% call links.desktop_nav() %}
|
||||
{% include '_types/order/_nav.html' %}
|
||||
{% endcall %}
|
||||
{% endcall %}
|
||||
{% endmacro %}
|
||||
@@ -1,68 +0,0 @@
|
||||
{% extends '_types/orders/index.html' %}
|
||||
|
||||
|
||||
{% block orders_header_child %}
|
||||
{% from '_types/root/_n/macros.html' import index_row with context %}
|
||||
{% call index_row('order-header-child', '_types/order/header/_header.html') %}
|
||||
{% block order_header_child %}
|
||||
{% endblock %}
|
||||
{% endcall %}
|
||||
{% endblock %}
|
||||
|
||||
{% block _main_mobile_menu %}
|
||||
{% include '_types/order/_nav.html' %}
|
||||
{% endblock %}
|
||||
|
||||
|
||||
|
||||
{% block filter %}
|
||||
<header class="mb-6 sm:mb-8 flex flex-col sm:flex-row sm:items-center justify-between gap-3 sm:gap-4">
|
||||
<div class="space-y-1">
|
||||
<p class="text-xs sm:text-sm text-stone-600">
|
||||
Placed {% if order.created_at %}{{ order.created_at.strftime('%-d %b %Y, %H:%M') }}{% else %}—{% endif %} · Status: {{ order.status or 'pending' }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="flex w-full sm:w-auto justify-start sm:justify-end gap-2">
|
||||
<a
|
||||
href="{{ url_for('orders.list_orders')|host }}"
|
||||
class="inline-flex items-center px-3 py-2 text-xs sm:text-sm rounded-full border border-stone-300 bg-white hover:bg-stone-50 transition"
|
||||
>
|
||||
<i class="fa-solid fa-list mr-2" aria-hidden="true"></i>
|
||||
All orders
|
||||
</a>
|
||||
|
||||
{# Re-check status button #}
|
||||
<form
|
||||
method="post"
|
||||
action="{{ url_for('orders.order.order_recheck', order_id=order.id)|host }}"
|
||||
class="inline"
|
||||
>
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
|
||||
<button
|
||||
type="submit"
|
||||
class="inline-flex items-center px-3 py-2 text-xs sm:text-sm rounded-full border border-stone-300 bg-white hover:bg-stone-50 transition"
|
||||
>
|
||||
<i class="fa-solid fa-rotate mr-2" aria-hidden="true"></i>
|
||||
Re-check status
|
||||
</button>
|
||||
</form>
|
||||
|
||||
{% if order.status != 'paid' %}
|
||||
<a
|
||||
href="{{ url_for('orders.order.order_pay', order_id=order.id)|host }}"
|
||||
class="inline-flex items-center px-3 py-2 text-xs sm:text-sm rounded-full border border-emerald-600 bg-emerald-600 text-white hover:bg-emerald-700 transition"
|
||||
>
|
||||
<i class="fa fa-credit-card mr-2" aria-hidden="true"></i>
|
||||
Open payment page
|
||||
</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</header>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
{% include '_types/order/_main_panel.html' %}
|
||||
{% endblock %}
|
||||
|
||||
{% block aside %}
|
||||
{% endblock %}
|
||||
@@ -1,26 +0,0 @@
|
||||
<div class="max-w-full px-3 py-3 space-y-3">
|
||||
{% if not orders %}
|
||||
<div class="rounded-2xl border border-dashed border-stone-300 bg-white/80 p-4 sm:p-6 text-sm text-stone-700">
|
||||
No orders yet.
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="overflow-x-auto rounded-2xl border border-stone-200 bg-white/80">
|
||||
<table class="min-w-full text-xs sm:text-sm">
|
||||
<thead class="bg-stone-50 border-b border-stone-200 text-stone-600">
|
||||
<tr>
|
||||
<th class="px-3 py-2 text-left font-medium">Order</th>
|
||||
<th class="px-3 py-2 text-left font-medium">Created</th>
|
||||
<th class="px-3 py-2 text-left font-medium">Description</th>
|
||||
<th class="px-3 py-2 text-left font-medium">Total</th>
|
||||
<th class="px-3 py-2 text-left font-medium">Status</th>
|
||||
<th class="px-3 py-2 text-left font-medium"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{# rows + infinite-scroll sentinel #}
|
||||
{% include "_types/orders/_rows.html" %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
@@ -1,2 +0,0 @@
|
||||
{% from 'macros/admin_nav.html' import placeholder_nav %}
|
||||
{{ placeholder_nav() }}
|
||||
@@ -1,38 +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 %}
|
||||
|
||||
{# 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('auth-header-child', 'orders-header-child', '_types/orders/header/_header.html')}}
|
||||
|
||||
{% from '_types/auth/header/_header.html' import header_row with context %}
|
||||
{{ header_row(oob=True) }}
|
||||
{% endblock %}
|
||||
|
||||
{% block aside %}
|
||||
{% from 'macros/search.html' import search_desktop %}
|
||||
{{ search_desktop(current_local_href, search, search_count, hx_select) }}
|
||||
{% endblock %}
|
||||
|
||||
{% block filter %}
|
||||
{% include '_types/orders/_summary.html' %}
|
||||
{% endblock %}
|
||||
|
||||
{% block mobile_menu %}
|
||||
{% include '_types/orders/_nav.html' %}
|
||||
{% endblock %}
|
||||
|
||||
|
||||
{% block content %}
|
||||
{% include "_types/orders/_main_panel.html" %}
|
||||
{% endblock %}
|
||||
|
||||
|
||||
@@ -1,131 +0,0 @@
|
||||
{# suma_browser/templates/_types/order/_orders_rows.html #}
|
||||
|
||||
{# --- existing rows, but split into desktop/tablet vs mobile --- #}
|
||||
{% for order in orders %}
|
||||
{# Desktop / tablet table row #}
|
||||
<tr class="hidden sm:table-row border-t border-stone-100 hover:bg-stone-50/60">
|
||||
<td class="px-3 py-2 align-top">
|
||||
<span class="font-mono text-[11px] sm:text-xs">#{{ order.id }}</span>
|
||||
</td>
|
||||
<td class="px-3 py-2 align-top text-stone-700 text-xs sm:text-sm">
|
||||
{% if order.created_at %}
|
||||
{{ order.created_at.strftime('%-d %b %Y, %H:%M') }}
|
||||
{% else %}
|
||||
—
|
||||
{% endif %}
|
||||
</td>
|
||||
<td class="px-3 py-2 align-top text-stone-700 text-xs sm:text-sm">
|
||||
{{ order.description or '' }}
|
||||
</td>
|
||||
|
||||
<td class="px-3 py-2 align-top text-stone-700 text-xs sm:text-sm">
|
||||
{{ order.currency or 'GBP' }}
|
||||
{{ '%.2f'|format(order.total_amount or 0) }}
|
||||
</td>
|
||||
<td class="px-3 py-2 align-top">
|
||||
{# status pill, roughly matching existing styling #}
|
||||
<span
|
||||
class="
|
||||
inline-flex items-center rounded-full border px-2 py-0.5
|
||||
text-[11px] sm:text-xs
|
||||
{% if (order.status or '').lower() == 'paid' %}
|
||||
border-emerald-300 bg-emerald-50 text-emerald-700
|
||||
{% elif (order.status or '').lower() in ['failed', 'cancelled'] %}
|
||||
border-rose-300 bg-rose-50 text-rose-700
|
||||
{% else %}
|
||||
border-stone-300 bg-stone-50 text-stone-700
|
||||
{% endif %}
|
||||
"
|
||||
>
|
||||
{{ order.status or 'pending' }}
|
||||
</span>
|
||||
</td>
|
||||
<td class="px-3 py-0.5 align-top text-right">
|
||||
<a
|
||||
href="{{ url_for('orders.order.order_detail', order_id=order.id)|host }}"
|
||||
class="inline-flex items-center px-3 py-1.5 text-xs sm:text-sm rounded-full border border-stone-300 bg-white hover:bg-stone-50 transition"
|
||||
>
|
||||
View
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
{# Mobile card row #}
|
||||
<tr class="sm:hidden border-t border-stone-100">
|
||||
<td colspan="5" class="px-3 py-3">
|
||||
<div class="flex flex-col gap-2 text-xs">
|
||||
<div class="flex items-center justify-between gap-2">
|
||||
<span class="font-mono text-[11px] text-stone-700">
|
||||
#{{ order.id }}
|
||||
</span>
|
||||
|
||||
<span
|
||||
class="
|
||||
inline-flex items-center rounded-full border px-2 py-0.5
|
||||
text-[11px]
|
||||
{% if (order.status or '').lower() == 'paid' %}
|
||||
border-emerald-300 bg-emerald-50 text-emerald-700
|
||||
{% elif (order.status or '').lower() in ['failed', 'cancelled'] %}
|
||||
border-rose-300 bg-rose-50 text-rose-700
|
||||
{% else %}
|
||||
border-stone-300 bg-stone-50 text-stone-700
|
||||
{% endif %}
|
||||
"
|
||||
>
|
||||
{{ order.status or 'pending' }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="text-[11px] text-stone-500 break-words">
|
||||
{{ order.created_at or '' }}
|
||||
</div>
|
||||
|
||||
<div class="flex items-center justify-between gap-2">
|
||||
<div class="font-medium text-stone-800">
|
||||
{{ order.currency or 'GBP' }}
|
||||
{{ '%.2f'|format(order.total_amount or 0) }}
|
||||
</div>
|
||||
|
||||
<a
|
||||
href="{{ url_for('orders.order.order_detail', order_id=order.id)|host }}"
|
||||
class="inline-flex items-center px-2 py-1 text-[11px] rounded-full border border-stone-300 bg-white hover:bg-stone-50 transition shrink-0"
|
||||
>
|
||||
View
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
|
||||
{# --- sentinel / end-of-results --- #}
|
||||
{% if page < total_pages|int %}
|
||||
<tr
|
||||
id="orders-sentinel-{{ page }}"
|
||||
sx-get="{{ (current_local_href ~ {'page': page + 1}|qs)|host }}"
|
||||
sx-trigger="intersect once delay:250ms"
|
||||
sx-swap="outerHTML"
|
||||
sx-retry="exponential:1000:30000"
|
||||
role="status"
|
||||
aria-live="polite"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<td colspan="5" class="px-3 py-4">
|
||||
{# Mobile sentinel content #}
|
||||
<div class="block md:hidden h-[60vh] js-mobile-sentinel">
|
||||
{% include "sentinel/mobile_content.html" %}
|
||||
</div>
|
||||
|
||||
{# Desktop sentinel content #}
|
||||
<div class="hidden md:block h-[30vh] js-desktop-sentinel">
|
||||
{% include "sentinel/desktop_content.html" %}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
{% else %}
|
||||
<tr>
|
||||
<td colspan="5" class="px-3 py-4 text-center text-xs text-stone-400">
|
||||
End of results
|
||||
</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
@@ -1,11 +0,0 @@
|
||||
<header class="mb-6 sm:mb-8 flex flex-col sm:flex-row sm:items-center justify-between gap-3 sm:gap-4">
|
||||
<div class="space-y-1">
|
||||
<p class="text-xs sm:text-sm text-stone-600">
|
||||
Recent orders placed via the checkout.
|
||||
</p>
|
||||
</div>
|
||||
<div class="md:hidden">
|
||||
{% from 'macros/search.html' import search_mobile %}
|
||||
{{ search_mobile(current_local_href, search, search_count, hx_select) }}
|
||||
</div>
|
||||
</header>
|
||||
@@ -1,14 +0,0 @@
|
||||
{% import 'macros/links.html' as links %}
|
||||
{% macro header_row(oob=False) %}
|
||||
{% call links.menu_row(id='orders-row', oob=oob) %}
|
||||
{% call links.link(url_for('orders.list_orders'), hx_select_search, ) %}
|
||||
<i class="fa fa-gbp" aria-hidden="true"></i>
|
||||
<div>
|
||||
Orders
|
||||
</div>
|
||||
{% endcall %}
|
||||
{% call links.desktop_nav() %}
|
||||
{% include '_types/orders/_nav.html' %}
|
||||
{% endcall %}
|
||||
{% endcall %}
|
||||
{% endmacro %}
|
||||
@@ -1,29 +0,0 @@
|
||||
{% extends '_types/auth/index.html' %}
|
||||
|
||||
|
||||
{% block auth_header_child %}
|
||||
{% from '_types/root/_n/macros.html' import index_row with context %}
|
||||
{% call index_row('orders-header-child', '_types/orders/header/_header.html') %}
|
||||
{% block orders_header_child %}
|
||||
{% endblock %}
|
||||
{% endcall %}
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block _main_mobile_menu %}
|
||||
{% include '_types/orders/_nav.html' %}
|
||||
{% endblock %}
|
||||
|
||||
{% block aside %}
|
||||
{% from 'macros/search.html' import search_desktop %}
|
||||
{{ search_desktop(current_local_href, search, search_count, hx_select) }}
|
||||
{% endblock %}
|
||||
|
||||
|
||||
{% block filter %}
|
||||
{% include '_types/orders/_summary.html' %}
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
{% include '_types/orders/_main_panel.html' %}
|
||||
{% endblock %}
|
||||
@@ -1,249 +0,0 @@
|
||||
{% macro add(slug, cart, oob='false') %}
|
||||
{% set quantity = cart
|
||||
| selectattr('product.slug', 'equalto', slug)
|
||||
| sum(attribute='quantity') %}
|
||||
|
||||
<div id="cart-{{ slug }}" {% if oob=='true' %} sx-swap-oob="{{oob}}" {% endif %}>
|
||||
|
||||
{% if not quantity %}
|
||||
<form
|
||||
action="{{ url_for('market.browse.product.cart', product_slug=slug) }}"
|
||||
method="post"
|
||||
sx-post="{{ url_for('market.browse.product.cart', product_slug=slug) }}"
|
||||
sx-target="#cart-mini"
|
||||
sx-swap="outerHTML"
|
||||
class="rounded flex items-center"
|
||||
>
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
|
||||
<input
|
||||
type="hidden"
|
||||
name="count"
|
||||
value="1"
|
||||
>
|
||||
|
||||
<button
|
||||
type="submit"
|
||||
class="relative inline-flex items-center justify-center text-sm font-medium text-stone-500 hover:bg-emerald-50"
|
||||
>
|
||||
<span class="relative inline-flex items-center justify-center">
|
||||
<i class="fa fa-cart-plus text-4xl" aria-hidden="true"></i>
|
||||
|
||||
<!-- black + overlaid in the center -->
|
||||
|
||||
</span>
|
||||
</button>
|
||||
</form>
|
||||
|
||||
{% else %}
|
||||
<div class="rounded flex items-center gap-2">
|
||||
<!-- minus -->
|
||||
<form
|
||||
action="{{ url_for('market.browse.product.cart', product_slug=slug) }}"
|
||||
method="post"
|
||||
sx-post="{{ url_for('market.browse.product.cart', product_slug=slug) }}"
|
||||
sx-target="#cart-mini"
|
||||
sx-swap="outerHTML"
|
||||
>
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
|
||||
<input
|
||||
type="hidden"
|
||||
name="count"
|
||||
value="{{ quantity - 1 }}"
|
||||
>
|
||||
<button
|
||||
type="submit"
|
||||
class="inline-flex items-center justify-center w-8 h-8 text-sm font-medium rounded-full border border-emerald-600 text-emerald-700 hover:bg-emerald-50 text-xl"
|
||||
>
|
||||
-
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<!-- basket with quantity badge -->
|
||||
<a
|
||||
class="relative inline-flex items-center justify-center text-emerald-700"
|
||||
href="{{ cart_url('/') }}"
|
||||
>
|
||||
<span class="relative inline-flex items-center justify-center">
|
||||
<i class="fa-solid fa-shopping-cart text-2xl" aria-hidden="true"></i>
|
||||
|
||||
<span
|
||||
class="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 pointer-events-none"
|
||||
>
|
||||
<span
|
||||
class="flex items-center justify-center bg-black text-white rounded-full w-4 h-4 text-xs font-bold"
|
||||
>
|
||||
{{ quantity }}
|
||||
</span>
|
||||
</span>
|
||||
</span>
|
||||
</a>
|
||||
|
||||
<!-- plus -->
|
||||
<form
|
||||
action="{{ url_for('market.browse.product.cart', product_slug=slug) }}"
|
||||
method="post"
|
||||
sx-post="{{ url_for('market.browse.product.cart', product_slug=slug) }}"
|
||||
sx-target="#cart-mini"
|
||||
sx-swap="outerHTML"
|
||||
>
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
|
||||
<input
|
||||
type="hidden"
|
||||
name="count"
|
||||
value="{{ quantity + 1 }}"
|
||||
>
|
||||
<button
|
||||
type="submit"
|
||||
class="inline-flex items-center justify-center w-8 h-8 text-sm font-medium rounded-full border border-emerald-600 text-emerald-700 hover:bg-emerald-50 text-xl"
|
||||
>
|
||||
+
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endmacro %}
|
||||
|
||||
|
||||
|
||||
{% macro cart_item(oob=False) %}
|
||||
|
||||
{% set p = item.product %}
|
||||
{% set unit_price = p.special_price or p.regular_price %}
|
||||
<article
|
||||
id="cart-item-{{p.slug}}"
|
||||
{% if oob %}
|
||||
sx-swap-oob="{{oob}}"
|
||||
{% endif %}
|
||||
class="flex flex-col sm:flex-row gap-3 sm:gap-4 rounded-2xl bg-white shadow-sm border border-stone-200 p-3 sm:p-4 md:p-5"
|
||||
>
|
||||
<div class="w-full sm:w-32 shrink-0 flex justify-center sm:block">
|
||||
{% if p.image %}
|
||||
<img
|
||||
src="{{ p.image }}"
|
||||
alt="{{ p.title }}"
|
||||
class="w-24 h-24 sm:w-32 sm:h-28 object-cover rounded-xl border border-stone-100"
|
||||
loading="lazy"
|
||||
>
|
||||
{% else %}
|
||||
<div
|
||||
class="w-24 h-24 sm:w-32 sm:h-28 rounded-xl border border-dashed border-stone-300 flex items-center justify-center text-xs text-stone-400"
|
||||
>
|
||||
No image
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
{# Details #}
|
||||
<div class="flex-1 min-w-0">
|
||||
<div class="flex flex-col sm:flex-row sm:items-start justify-between gap-2 sm:gap-3">
|
||||
<div class="min-w-0">
|
||||
<h2 class="text-sm sm:text-base md:text-lg font-semibold text-stone-900">
|
||||
{% set href=market_product_url(p.slug) %}
|
||||
<a
|
||||
href="{{ href }}"
|
||||
hx_get="{{href}}"
|
||||
sx-target="#main-panel"
|
||||
sx-select ="{{hx_select_search}}"
|
||||
sx-swap="outerHTML"
|
||||
sx-push-url="true"
|
||||
class="hover:text-emerald-700"
|
||||
>
|
||||
{{ p.title }}
|
||||
</a>
|
||||
</h2>
|
||||
|
||||
{% if p.brand %}
|
||||
<p class="mt-0.5 text-[0.7rem] sm:text-xs text-stone-500">
|
||||
{{ p.brand }}
|
||||
</p>
|
||||
{% endif %}
|
||||
|
||||
{% if item.is_deleted %}
|
||||
<p class="mt-2 inline-flex items-center gap-1 text-[0.65rem] sm:text-xs font-medium text-amber-700 bg-amber-50 border border-amber-200 rounded-full px-2 py-0.5">
|
||||
<i class="fa-solid fa-triangle-exclamation text-[0.6rem]" aria-hidden="true"></i>
|
||||
This item is no longer available or price has changed
|
||||
</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
{# Unit price #}
|
||||
<div class="text-left sm:text-right">
|
||||
{% if unit_price %}
|
||||
{% set symbol = "£" if p.regular_price_currency == "GBP" else p.regular_price_currency %}
|
||||
<p class="text-sm sm:text-base font-semibold text-stone-900">
|
||||
{{ symbol }}{{ "%.2f"|format(unit_price) }}
|
||||
</p>
|
||||
{% if p.special_price and p.special_price != p.regular_price %}
|
||||
<p class="text-xs text-stone-400 line-through">
|
||||
{{ symbol }}{{ "%.2f"|format(p.regular_price) }}
|
||||
</p>
|
||||
{% endif %}
|
||||
{% else %}
|
||||
<p class="text-xs text-stone-500">No price</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-3 flex flex-col sm:flex-row sm:items-center justify-between gap-2 sm:gap-4">
|
||||
<div class="flex items-center gap-2 text-xs sm:text-sm text-stone-700">
|
||||
<span class="text-[0.65rem] sm:text-xs uppercase tracking-wide text-stone-500">Quantity</span>
|
||||
{% set qty_url = url_for('cart_global.update_quantity', product_id=p.id) %}
|
||||
<form
|
||||
action="{{ qty_url }}"
|
||||
method="post"
|
||||
sx-post="{{ qty_url }}"
|
||||
sx-swap="none"
|
||||
>
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
|
||||
<input
|
||||
type="hidden"
|
||||
name="count"
|
||||
value="{{ item.quantity - 1 }}"
|
||||
>
|
||||
<button
|
||||
type="submit"
|
||||
class="inline-flex items-center justify-center w-8 h-8 text-sm font-medium rounded-full border border-emerald-600 text-emerald-700 hover:bg-emerald-50 text-xl"
|
||||
>
|
||||
-
|
||||
</button>
|
||||
</form>
|
||||
<span class="inline-flex items-center justify-center px-2 py-1 rounded-full bg-stone-100 text-[0.7rem] sm:text-xs font-medium">
|
||||
{{ item.quantity }}
|
||||
</span>
|
||||
<form
|
||||
action="{{ qty_url }}"
|
||||
method="post"
|
||||
sx-post="{{ qty_url }}"
|
||||
sx-swap="none"
|
||||
>
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
|
||||
<input
|
||||
type="hidden"
|
||||
name="count"
|
||||
value="{{ item.quantity + 1 }}"
|
||||
>
|
||||
<button
|
||||
type="submit"
|
||||
class="inline-flex items-center justify-center w-8 h-8 text-sm font-medium rounded-full border border-emerald-600 text-emerald-700 hover:bg-emerald-50 text-xl"
|
||||
>
|
||||
+
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center justify-between sm:justify-end gap-3">
|
||||
{% if unit_price %}
|
||||
{% set line_total = unit_price * item.quantity %}
|
||||
{% set symbol = "£" if p.regular_price_currency == "GBP" else p.regular_price_currency %}
|
||||
<p class="text-sm sm:text-base font-semibold text-stone-900">
|
||||
Line total:
|
||||
{{ symbol }}{{ "%.2f"|format(line_total) }}
|
||||
</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
|
||||
{% endmacro %}
|
||||
Reference in New Issue
Block a user