From 454d5cb3491aa402a49899d478104ca35f8d6996 Mon Sep 17 00:00:00 2001 From: Priec Date: Fri, 19 Jun 2026 13:59:31 +0200 Subject: [PATCH] navbar profile --- assets/views/base.html | 14 ++++---- assets/views/partials/profile_menu.html | 46 +++++++++++++++++++++++++ src/controllers/account.rs | 22 ++++++++---- src/controllers/cart.rs | 8 +++-- src/controllers/checkout.rs | 8 +++-- src/controllers/home.rs | 8 +++-- src/controllers/shop.rs | 24 ++++++++----- src/shared/guard.rs | 31 +++++++++++++---- 8 files changed, 124 insertions(+), 37 deletions(-) create mode 100644 assets/views/partials/profile_menu.html diff --git a/assets/views/base.html b/assets/views/base.html index 6a3245a..040d33d 100644 --- a/assets/views/base.html +++ b/assets/views/base.html @@ -86,12 +86,7 @@ {% elif logged_in_customer %} -
  • {{ ui::nav_link(label=t(key="nav-profile", lang=lang | default(value='sk')), href="/account/profile", data_nav="/account") }}
  • -
  • -
    - -
    -
  • + {# customer account links live in the profile dropdown next to the cart #} {% else %}
  • {{ ui::nav_link(label=t(key="nav-login", lang=lang | default(value='sk')), href="/login", data_nav="/login") }}
  • {{ ui::nav_link(label=t(key="nav-register", lang=lang | default(value='sk')), href="/register", data_nav="/register") }}
  • @@ -111,6 +106,13 @@ + + {% if logged_in_customer %} +
    + {% include "partials/profile_menu.html" %} +
    + {% endif %} +
    {% include "partials/settings_dropdown.html" %} diff --git a/assets/views/partials/profile_menu.html b/assets/views/partials/profile_menu.html new file mode 100644 index 0000000..fabe4fe --- /dev/null +++ b/assets/views/partials/profile_menu.html @@ -0,0 +1,46 @@ +{# Customer profile dropdown shown in the storefront navbar next to the cart. + Trigger shows an initials avatar, the customer's name and their account type + (personal / company); clicking opens a quick-navigation menu. + + The host template provides the wrapper +
    . #} + +{# initials from the name, e.g. "Filip Priec" -> "FP" #} +{% set _name = customer_name | default(value='') | trim %} +{% set _parts = _name | split(pat=' ') %} +{% set _initials = _parts.0 | truncate(length=1, end='') | upper %} +{% if _parts | length > 1 %}{% set _second = _parts | last | truncate(length=1, end='') | upper %}{% set _initials = _initials ~ _second %}{% endif %} +{% if customer_account_type == "company" %}{% set _type_label = t(key="account-company", lang=lang | default(value='sk')) %}{% else %}{% set _type_label = t(key="account-personal", lang=lang | default(value='sk')) %}{% endif %} + + + + diff --git a/src/controllers/account.rs b/src/controllers/account.rs index 3b59e3b..c13a892 100644 --- a/src/controllers/account.rs +++ b/src/controllers/account.rs @@ -126,6 +126,8 @@ fn profile_view( "logged_in_admin": false, "logged_in_customer": true, "account_nav": true, + "customer_name": user.name, + "customer_account_type": user.account_type, "saved": saved, "error": error, "name": user.name, @@ -232,6 +234,8 @@ async fn orders_page( "logged_in_admin": false, "logged_in_customer": true, "account_nav": true, + "customer_name": user.name, + "customer_account_type": user.account_type, "active_orders": shape(active), "past_orders": shape(past), "lang": current_lang(&jar), @@ -272,6 +276,8 @@ async fn order_detail_page( "logged_in_admin": false, "logged_in_customer": true, "account_nav": true, + "customer_name": user.name, + "customer_account_type": user.account_type, "order": order_view::detail( &order, settings::get(&ctx, "bank_iban").unwrap_or(""), @@ -293,6 +299,7 @@ struct ChangePasswordForm { fn password_view( v: &TeraView, jar: &CookieJar, + user: &users::Model, changed: bool, error: Option<&str>, ) -> Result { @@ -303,6 +310,8 @@ fn password_view( "logged_in_admin": false, "logged_in_customer": true, "account_nav": true, + "customer_name": user.name, + "customer_account_type": user.account_type, "changed": changed, "error": error, "lang": current_lang(jar), @@ -322,7 +331,7 @@ async fn change_password_page( if guard::is_admin(&ctx, &user) { return format::redirect("/admin/dashboard"); } - password_view(&v, &jar, false, None) + password_view(&v, &jar, &user, false, None) } #[debug_handler] @@ -339,18 +348,19 @@ async fn change_password( return format::redirect("/admin/dashboard"); } if !user.verify_password(&form.current_password) { - return password_view(&v, &jar, false, Some("current")); + return password_view(&v, &jar, &user, false, Some("current")); } if form.password != form.password_confirm { - return password_view(&v, &jar, false, Some("mismatch")); + return password_view(&v, &jar, &user, false, Some("mismatch")); } if form.password.len() < 8 { - return password_view(&v, &jar, false, Some("weak")); + return password_view(&v, &jar, &user, false, Some("weak")); } - user.into_active_model() + let user = user + .into_active_model() .reset_password(&ctx.db, &form.password) .await?; - password_view(&v, &jar, true, None) + password_view(&v, &jar, &user, true, None) } pub fn routes() -> Routes { diff --git a/src/controllers/cart.rs b/src/controllers/cart.rs index e181a95..5bb4542 100644 --- a/src/controllers/cart.rs +++ b/src/controllers/cart.rs @@ -234,7 +234,7 @@ async fn show( // Drop any now-invalid lines from the cookie so the badge stays accurate. let rebuilt = serialize_cart(&valid); - let (logged_in_admin, logged_in_customer) = guard::chrome(&ctx, &jar).await; + let c = guard::chrome(&ctx, &jar).await; let response = format::view( &v, "shop/cart.html", @@ -242,8 +242,10 @@ async fn show( "items": lines, "total": format_price(total), "currency": currency, - "logged_in_admin": logged_in_admin, - "logged_in_customer": logged_in_customer, + "logged_in_admin": c.logged_in_admin, + "logged_in_customer": c.logged_in_customer, + "customer_name": c.customer_name, + "customer_account_type": c.customer_account_type, "lang": current_lang(&jar), }), )?; diff --git a/src/controllers/checkout.rs b/src/controllers/checkout.rs index 964b456..1b85685 100644 --- a/src/controllers/checkout.rs +++ b/src/controllers/checkout.rs @@ -357,7 +357,7 @@ async fn order_confirmation( .filter(order_items::Column::OrderId.eq(order.id)) .all(&ctx.db) .await?; - let (logged_in_admin, logged_in_customer) = guard::chrome(&ctx, &jar).await; + let c = guard::chrome(&ctx, &jar).await; let account_created = params.contains_key("account_created"); format::view( @@ -370,8 +370,10 @@ async fn order_confirmation( settings::get(&ctx, "bank_account_name").unwrap_or(""), ), "items": view::items(&items), - "logged_in_admin": logged_in_admin, - "logged_in_customer": logged_in_customer, + "logged_in_admin": c.logged_in_admin, + "logged_in_customer": c.logged_in_customer, + "customer_name": c.customer_name, + "customer_account_type": c.customer_account_type, "account_created": account_created, "lang": current_lang(&jar), }), diff --git a/src/controllers/home.rs b/src/controllers/home.rs index a526b68..c46d3b1 100644 --- a/src/controllers/home.rs +++ b/src/controllers/home.rs @@ -13,15 +13,17 @@ async fn index( State(ctx): State, ) -> Result { let products = shop::featured_products(&ctx, 8).await?; - let (logged_in_admin, logged_in_customer) = guard::chrome(&ctx, &jar).await; + let c = guard::chrome(&ctx, &jar).await; format::view( &v, "home/index.html", json!({ "products": products, - "logged_in_admin": logged_in_admin, - "logged_in_customer": logged_in_customer, + "logged_in_admin": c.logged_in_admin, + "logged_in_customer": c.logged_in_customer, + "customer_name": c.customer_name, + "customer_account_type": c.customer_account_type, "lang": current_lang(&jar), }), ) diff --git a/src/controllers/shop.rs b/src/controllers/shop.rs index 4bdc713..d124d11 100644 --- a/src/controllers/shop.rs +++ b/src/controllers/shop.rs @@ -69,14 +69,16 @@ async fn index( .all(&ctx.db) .await?; - let (logged_in_admin, logged_in_customer) = guard::chrome(&ctx, &jar).await; + let c = guard::chrome(&ctx, &jar).await; format::view( &v, "shop/index.html", json!({ "products": product_rows(&ctx, list).await?, - "logged_in_admin": logged_in_admin, - "logged_in_customer": logged_in_customer, + "logged_in_admin": c.logged_in_admin, + "logged_in_customer": c.logged_in_customer, + "customer_name": c.customer_name, + "customer_account_type": c.customer_account_type, "lang": current_lang(&jar), }), ) @@ -110,7 +112,7 @@ async fn show( None => None, }; - let (logged_in_admin, logged_in_customer) = guard::chrome(&ctx, &jar).await; + let c = guard::chrome(&ctx, &jar).await; format::view( &v, "shop/show.html", @@ -118,8 +120,10 @@ async fn show( "product": view::product_card(&product, None, category.as_ref().map(|c| c.name.clone())), "images": images.iter().map(|i| i.image_id.clone()).collect::>(), "category": category, - "logged_in_admin": logged_in_admin, - "logged_in_customer": logged_in_customer, + "logged_in_admin": c.logged_in_admin, + "logged_in_customer": c.logged_in_customer, + "customer_name": c.customer_name, + "customer_account_type": c.customer_account_type, "lang": current_lang(&jar), }), ) @@ -155,7 +159,7 @@ async fn category( .all(&ctx.db) .await?; - let (logged_in_admin, logged_in_customer) = guard::chrome(&ctx, &jar).await; + let c = guard::chrome(&ctx, &jar).await; format::view( &v, "shop/category.html", @@ -164,8 +168,10 @@ async fn category( "breadcrumbs": breadcrumbs, "children": children, "products": product_rows(&ctx, list).await?, - "logged_in_admin": logged_in_admin, - "logged_in_customer": logged_in_customer, + "logged_in_admin": c.logged_in_admin, + "logged_in_customer": c.logged_in_customer, + "customer_name": c.customer_name, + "customer_account_type": c.customer_account_type, "lang": current_lang(&jar), }), ) diff --git a/src/shared/guard.rs b/src/shared/guard.rs index 6a3d8a1..e24fe1e 100644 --- a/src/shared/guard.rs +++ b/src/shared/guard.rs @@ -46,13 +46,30 @@ pub async fn logged_in(ctx: &AppContext, jar: &CookieJar) -> bool { } } -/// Nav chrome flags for storefront pages, in one DB lookup: returns -/// `(logged_in_admin, logged_in_customer)`. A customer is any authenticated -/// non-admin user. Both are `false` for anonymous visitors. -pub async fn chrome(ctx: &AppContext, jar: &CookieJar) -> (bool, bool) { +/// Nav chrome for storefront pages, resolved in one DB lookup. A customer is any +/// authenticated non-admin user; `customer_name`/`customer_account_type` are set +/// only for such a customer (used by the navbar profile menu). Everything is the +/// zero value for anonymous visitors and the name/type stay `None` for admins. +#[derive(Debug, Default)] +pub struct Chrome { + pub logged_in_admin: bool, + pub logged_in_customer: bool, + pub customer_name: Option, + pub customer_account_type: Option, +} + +pub async fn chrome(ctx: &AppContext, jar: &CookieJar) -> Chrome { match current_user(ctx, jar).await { - Some(user) if is_admin(ctx, &user) => (true, false), - Some(_) => (false, true), - None => (false, false), + Some(user) if is_admin(ctx, &user) => Chrome { + logged_in_admin: true, + ..Default::default() + }, + Some(user) => Chrome { + logged_in_customer: true, + customer_name: Some(user.name), + customer_account_type: Some(user.account_type), + ..Default::default() + }, + None => Chrome::default(), } }