profile of a new registered users

This commit is contained in:
Priec
2026-06-18 21:11:48 +02:00
parent b9c1277876
commit c6624e1b3d
20 changed files with 501 additions and 17 deletions

View File

@@ -10,9 +10,9 @@ use time::Duration as TimeDuration;
use crate::{
controllers::cart::{resolve_cart, CART_COOKIE},
models::{order_items, orders, shipping_methods},
models::{customer_profiles::{self, ProfileFields}, order_items, orders, shipping_methods},
controllers::i18n::current_lang,
shared::{money::format_price, settings},
shared::{guard, money::format_price, settings},
views::checkout as view,
};
@@ -33,6 +33,8 @@ struct CheckoutForm {
carrier_code: String,
pickup_point_id: Option<String>,
pickup_point_name: Option<String>,
// Present (as "on") only when a logged-in customer ticks "save my address".
save_profile: Option<String>,
}
fn trimmed(value: &str) -> Option<String> {
@@ -86,6 +88,19 @@ async fn checkout_page(
})
.collect();
// Prefill the form for a logged-in customer: contact name/email come from
// the user account, the address/phone from their saved profile (if any).
let user = guard::current_user(&ctx, &jar).await;
let is_admin = user.as_ref().is_some_and(|u| guard::is_admin(&ctx, u));
let is_customer = user.is_some() && !is_admin;
let profile = match (&user, is_customer) {
(Some(u), true) => customer_profiles::Model::find_for_user(&ctx.db, u.id).await?,
_ => None,
};
let p = |get: fn(&customer_profiles::Model) -> Option<String>| {
profile.as_ref().and_then(get)
};
format::view(
&v,
"shop/checkout.html",
@@ -96,6 +111,16 @@ async fn checkout_page(
"currency": currency,
"shipping_methods": methods,
"packeta_api_key": settings::get(&ctx, "packeta_api_key").unwrap_or(""),
"logged_in_admin": is_admin,
"logged_in_customer": is_customer,
"prefill_email": user.as_ref().filter(|_| is_customer).map(|u| u.email.clone()),
"prefill_name": user.as_ref().filter(|_| is_customer).map(|u| u.name.clone()),
"prefill_phone_prefix": p(|x| x.phone_prefix.clone()),
"prefill_phone": p(|x| x.phone.clone()),
"prefill_address": p(|x| x.address.clone()),
"prefill_city": p(|x| x.city.clone()),
"prefill_zip": p(|x| x.zip.clone()),
"prefill_country": p(|x| x.country.clone()),
"lang": current_lang(&jar),
}),
)
@@ -119,7 +144,7 @@ async fn place_order(
trimmed(&form.phone).ok_or_else(|| Error::BadRequest("phone is required".to_string()))?;
let phone = match trimmed(&form.phone_prefix) {
Some(prefix) => format!("{prefix} {number}"),
None => number,
None => number.clone(),
};
// Contact and shipping-address fields are mandatory (also enforced in the
@@ -157,6 +182,28 @@ async fn place_order(
(None, None)
};
// If a logged-in customer opted in, persist this address to their profile
// so the next checkout is prefilled. Phone is stored split (prefix + number)
// to match the profile/checkout fields. Best-effort: a failure here is logged
// but must not block the order.
if form.save_profile.is_some() {
if let Some(user) = guard::current_user(&ctx, &jar).await {
if !guard::is_admin(&ctx, &user) {
let fields = ProfileFields {
phone_prefix: trimmed(&form.phone_prefix),
phone: Some(number.clone()),
address: Some(address.clone()),
city: Some(city.clone()),
zip: Some(zip.clone()),
country: Some(country.clone()),
};
if let Err(err) = customer_profiles::Model::upsert(&ctx.db, user.id, fields).await {
tracing::error!(error = %err, user_id = user.id, "failed to save checkout profile");
}
}
}
}
let order = orders::place(
&ctx,
&valid,
@@ -198,6 +245,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;
format::view(
&v,
@@ -209,6 +257,8 @@ 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,
"lang": current_lang(&jar),
}),
)