in cart.html
+ and returned on its own by /cart/update and /cart/remove. #}
+{% if items | length > 0 %}
+
+
+
+
+ | {{ t(key="product", lang=lang | default(value='sk')) }} |
+ {{ t(key="price", lang=lang | default(value='sk')) }} |
+ {{ t(key="quantity", lang=lang | default(value='sk')) }} |
+ {{ t(key="cart-total", lang=lang | default(value='sk')) }} |
+ |
+
+
+
+ {% for item in items %}
+
+ |
+ {{ item.name }}
+ |
+ {{ item.price }} {{ item.currency }} |
+
+ {# Changing the quantity posts via htmx and swaps only #cart-body. #}
+
+ |
+ {{ item.line_total }} {{ item.currency }} |
+
+
+ |
+
+ {% endfor %}
+
+
+
+ | {{ t(key="cart-total", lang=lang | default(value='sk')) }} |
+ {{ total }} {{ currency }} |
+ |
+
+
+
+
+
+
+{% else %}
+
+
{{ t(key="cart-empty", lang=lang | default(value='sk')) }}
+
{{ t(key="cart-continue", lang=lang | default(value='sk')) }}
+
+{% endif %}
diff --git a/assets/views/shop/cart.html b/assets/views/shop/cart.html
index 6e4cc8c..07c8a06 100644
--- a/assets/views/shop/cart.html
+++ b/assets/views/shop/cart.html
@@ -6,62 +6,8 @@
{{ t(key="cart-title", lang=lang | default(value='sk')) }}
- {% if items | length > 0 %}
-
-
-
-
- | {{ t(key="product", lang=lang | default(value='sk')) }} |
- {{ t(key="price", lang=lang | default(value='sk')) }} |
- {{ t(key="quantity", lang=lang | default(value='sk')) }} |
- {{ t(key="cart-total", lang=lang | default(value='sk')) }} |
- |
-
-
-
- {% for item in items %}
-
- |
- {{ item.name }}
- |
- {{ item.price }} {{ item.currency }} |
-
-
- |
- {{ item.line_total }} {{ item.currency }} |
-
-
- |
-
- {% endfor %}
-
-
-
- | {{ t(key="cart-total", lang=lang | default(value='sk')) }} |
- {{ total }} {{ currency }} |
- |
-
-
-
+
+ {% include "shop/_cart_body.html" %}
-
-
- {% else %}
-
-
{{ t(key="cart-empty", lang=lang | default(value='sk')) }}
-
{{ t(key="cart-continue", lang=lang | default(value='sk')) }}
-
- {% endif %}
{% endblock content %}
diff --git a/assets/views/shop/checkout.html b/assets/views/shop/checkout.html
index e38f9d5..265abfe 100644
--- a/assets/views/shop/checkout.html
+++ b/assets/views/shop/checkout.html
@@ -46,18 +46,32 @@
@@ -85,15 +99,32 @@
-
+
diff --git a/src/controllers/cart.rs b/src/controllers/cart.rs
index 5407f66..c3cdc62 100644
--- a/src/controllers/cart.rs
+++ b/src/controllers/cart.rs
@@ -1,4 +1,5 @@
use crate::{controllers::i18n::current_lang, shared::money::format_price, models::products};
+use axum::{http::HeaderMap, response::Redirect};
use axum_extra::extract::cookie::{Cookie, CookieJar, SameSite};
use loco_rs::prelude::*;
use sea_orm::{ColumnTrait, EntityTrait, QueryFilter};
@@ -96,6 +97,8 @@ async fn add(
async fn update(
jar: CookieJar,
State(ctx): State
,
+ ViewEngine(v): ViewEngine,
+ headers: HeaderMap,
Form(form): Form,
) -> Result {
let stock = published_product(&ctx, form.product_id)
@@ -110,19 +113,57 @@ async fn update(
}
items.retain(|(_, qty)| *qty > 0);
- format::render()
- .cookies(&[cart_cookie(serialize_cart(&items))])?
- .redirect("/cart")
+ let jar = jar.add(cart_cookie(serialize_cart(&items)));
+ cart_response(&ctx, &v, jar, &headers).await
}
#[debug_handler]
-async fn remove(jar: CookieJar, Form(form): Form) -> Result {
+async fn remove(
+ jar: CookieJar,
+ State(ctx): State,
+ ViewEngine(v): ViewEngine,
+ headers: HeaderMap,
+ Form(form): Form,
+) -> Result {
let mut items = parse_cart(&jar);
items.retain(|(id, _)| *id != form.product_id);
- format::render()
- .cookies(&[cart_cookie(serialize_cart(&items))])?
- .redirect("/cart")
+ let jar = jar.add(cart_cookie(serialize_cart(&items)));
+ cart_response(&ctx, &v, jar, &headers).await
+}
+
+/// Response after a cart mutation: for an htmx request, just the `#cart-body`
+/// fragment (so the page never fully reloads); otherwise a redirect back to
+/// `/cart` for no-JS fallback. `jar` must already hold the updated cart cookie.
+async fn cart_response(
+ ctx: &AppContext,
+ v: &TeraView,
+ jar: CookieJar,
+ headers: &HeaderMap,
+) -> Result {
+ if !headers.contains_key("HX-Request") {
+ return Ok((jar, Redirect::to("/cart")).into_response());
+ }
+
+ let (lines, valid, total) = resolve_cart(ctx, &jar).await?;
+ let currency = lines
+ .first()
+ .and_then(|line| line["currency"].as_str())
+ .unwrap_or("EUR")
+ .to_string();
+ // Persist the re-validated cookie (drops now-invalid lines).
+ let jar = jar.add(cart_cookie(serialize_cart(&valid)));
+ let response = format::view(
+ v,
+ "shop/_cart_body.html",
+ json!({
+ "items": lines,
+ "total": format_price(total),
+ "currency": currency,
+ "lang": current_lang(&jar),
+ }),
+ )?;
+ Ok((jar, response).into_response())
}
/// Resolve the cart cookie into priced line items, dropping anything that is no