diff --git a/extensions/pagetop-user/src/component/admin/user_form.rs b/extensions/pagetop-user/src/component/admin/user_form.rs index 0f50aee5..9795bb08 100644 --- a/extensions/pagetop-user/src/component/admin/user_form.rs +++ b/extensions/pagetop-user/src/component/admin/user_form.rs @@ -3,6 +3,7 @@ use pagetop::prelude::*; use crate::LOCALES_USER; +use crate::config::{user_language_applies, user_timezone_applies}; use crate::user_path; use crate::{ADMIN_USERS_PATH, PROFILE_EDIT_PATH}; @@ -86,25 +87,33 @@ impl Component for UserForm { .with_name("display_name") .with_value(self.display_name()) .with_label(Lc::t("field-display-name", &LOCALES_USER)), - ) - .with_child( + ); + + // Sólo se ofrecen si se aplican (ver `config::user_language_applies()` y + // `config::user_timezone_applies()`). + if user_language_applies() { + form = form.with_child( form::SelectLanguage::new() .with_name("language") .with_label(Lc::t("field-language", &LOCALES_USER)) .with_selected(self.language()), - ) - .with_child( + ); + } + if user_timezone_applies() { + form = form.with_child( form::SelectTimezone::new() .with_name("timezone") .with_label(Lc::t("field-timezone", &LOCALES_USER)) + .with_utc_offset(true) .with_selected(self.timezone()), - ) - .with_child( - form::SelectTheme::new() - .with_name("theme") - .with_label(Lc::t("field-theme", &LOCALES_USER)) - .with_selected(self.theme()), ); + } + form = form.with_child( + form::SelectTheme::new() + .with_name("theme") + .with_label(Lc::t("field-theme", &LOCALES_USER)) + .with_selected(self.theme()), + ); if *self.mode() == UserFormMode::New { form = form.with_child(PasswordConfirm::new()); diff --git a/extensions/pagetop-user/src/config.rs b/extensions/pagetop-user/src/config.rs index f1922d86..5975c638 100644 --- a/extensions/pagetop-user/src/config.rs +++ b/extensions/pagetop-user/src/config.rs @@ -134,3 +134,17 @@ impl Default for AdminConfig { AdminConfig { list_page_size: 20 } } } + +// **< Preferencias del usuario >******************************************************************* + +// Si se aplica el idioma propio del usuario: no con `lang_negotiation = "ConfigOnly"`, que usa +// siempre el de la configuración. Si no se aplica, no se ofrece elegirlo ni se borra el guardado. +pub(crate) fn user_language_applies() -> bool { + global::SETTINGS.app.lang_negotiation != global::LangNegotiation::ConfigOnly +} + +// Si se aplica la zona horaria propia del usuario (`app.timezone_per_user`). Si no se aplica, no se +// ofrece elegirla ni se borra la guardada. +pub(crate) fn user_timezone_applies() -> bool { + global::SETTINGS.app.timezone_per_user +} diff --git a/extensions/pagetop-user/src/handlers/account.rs b/extensions/pagetop-user/src/handlers/account.rs index d92876d9..241db472 100644 --- a/extensions/pagetop-user/src/handlers/account.rs +++ b/extensions/pagetop-user/src/handlers/account.rs @@ -9,6 +9,7 @@ use crate::account::{Account, UserStatus}; use crate::auth; use crate::component::admin::{UserForm, UserFormMode, status_key}; use crate::component::{ChangePasswordForm, language_name, multiline_text, theme_name}; +use crate::config::{user_language_applies, user_timezone_applies}; use crate::entity::{role, user}; use crate::error::AuthError; use crate::handlers::admin::map_auth_error; @@ -119,17 +120,23 @@ async fn profile_details(user: &user::Model, status: UserStatus, cx: &mut Contex .with_cell(multiline_text( user.about.clone().unwrap_or_else(|| "-".into()), )), - ) - .with_row( + ); + // Sólo se muestran si se aplican, igual que en el formulario de edición. + if user_language_applies() { + table = table.with_row( table::Row::new() .with_cell(Lc::t("field-language", &LOCALES_USER)) .with_cell(language_name(user.language.as_deref())), - ) - .with_row( + ); + } + if user_timezone_applies() { + table = table.with_row( table::Row::new() .with_cell(Lc::t("field-timezone", &LOCALES_USER)) .with_cell(user.timezone.as_deref().unwrap_or("-")), - ) + ); + } + table = table .with_row( table::Row::new() .with_cell(Lc::t("field-theme", &LOCALES_USER)) diff --git a/extensions/pagetop-user/src/handlers/admin/users.rs b/extensions/pagetop-user/src/handlers/admin/users.rs index dfee0f98..ce8fbeda 100644 --- a/extensions/pagetop-user/src/handlers/admin/users.rs +++ b/extensions/pagetop-user/src/handlers/admin/users.rs @@ -14,7 +14,7 @@ use crate::component::admin::{ AdminPasswordForm, USER_ADMIN_FORM_ID, UserForm, UserFormMode, UserTable, status_key, }; use crate::component::{language_name, multiline_text, theme_name}; -use crate::config::SETTINGS; +use crate::config::{SETTINGS, user_language_applies, user_timezone_applies}; use crate::entity::{role, user}; use crate::error::AuthError; use crate::handlers::admin::{back_link, frame, map_auth_error}; @@ -589,17 +589,23 @@ async fn user_view_details(user: &user::Model, status: UserStatus, cx: &mut Cont .with_cell(multiline_text( user.about.clone().unwrap_or_else(|| "-".into()), )), - ) - .with_row( + ); + // Sólo se muestran si se aplican, igual que en el formulario de edición. + if user_language_applies() { + table = table.with_row( table::Row::new() .with_cell(Lc::t("field-language", &LOCALES_USER)) .with_cell(language_name(user.language.as_deref())), - ) - .with_row( + ); + } + if user_timezone_applies() { + table = table.with_row( table::Row::new() .with_cell(Lc::t("field-timezone", &LOCALES_USER)) .with_cell(user.timezone.as_deref().unwrap_or("-")), - ) + ); + } + table = table .with_row( table::Row::new() .with_cell(Lc::t("field-theme", &LOCALES_USER)) diff --git a/extensions/pagetop-user/src/service/user_admin.rs b/extensions/pagetop-user/src/service/user_admin.rs index d2520f4f..1dde30a9 100644 --- a/extensions/pagetop-user/src/service/user_admin.rs +++ b/extensions/pagetop-user/src/service/user_admin.rs @@ -10,6 +10,7 @@ use pagetop_seaorm::db::{ }; use crate::account::UserStatus; +use crate::config::{user_language_applies, user_timezone_applies}; use crate::entity::{role, user, user_role}; use crate::error::AuthError; use crate::password; @@ -174,8 +175,18 @@ pub(crate) struct NewUserData<'a> { pub(crate) async fn create_user(data: NewUserData<'_>) -> Result { password::validate_strength(data.password)?; password::passwords_match(data.password, data.confirm_password)?; - let language = validate_language(data.language)?; - let timezone = validate_timezone(data.timezone)?; + // El idioma y la zona horaria que no se aplican no se ofrecen en el formulario: se ignora lo + // que pudiera llegar y el usuario se crea sin ellos. + let language = if user_language_applies() { + validate_language(data.language)? + } else { + None + }; + let timezone = if user_timezone_applies() { + validate_timezone(data.timezone)? + } else { + None + }; let theme = validate_theme(data.theme)?; ensure_username_available(data.username, None).await?; ensure_email_available(data.email, None).await?; @@ -230,8 +241,24 @@ pub(crate) struct UserUpdateData<'a> { } pub(crate) async fn update_user(user_id: i32, data: UserUpdateData<'_>) -> Result<(), AuthError> { - let language = validate_language(data.language)?; - let timezone = validate_timezone(data.timezone)?; + // El idioma y la zona horaria que no se aplican tampoco se ofrecen en el formulario, así que no + // llegan: se conserva lo guardado por si se vuelven a aplicar. + let language = if user_language_applies() { + Set(validate_language(data.language)?.map(str::to_owned)) + } else { + ActiveValue::NotSet + }; + let timezone = if user_timezone_applies() { + let timezone = match validate_timezone(data.timezone) { + Err(AuthError::InvalidTimezone) => { + keep_current_timezone(user_id, data.timezone).await? + } + result => result?, + }; + Set(timezone.map(str::to_owned)) + } else { + ActiveValue::NotSet + }; let theme = validate_theme(data.theme)?; // El navegador envía los saltos de línea de un `