diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index 86e7de6..0000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "rust-analyzer.linkedProjects": [ - "./Cargo.toml" - ], - "rust-analyzer.showUnlinkedFileNotification": false -} \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index ebd267f..06a41f1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -369,11 +369,13 @@ dependencies = [ "futures", "futures-util", "gloo", + "http", "infer", "js-sys", "log", "matrix-sdk", "mime", + "reqwest", "ruma", "serde", "serde_json", diff --git a/Cargo.toml b/Cargo.toml index 61dab6c..72823bb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,10 +17,10 @@ log = "0.4.17" matrix-sdk = { version = "0.6.2", default-features = false, features = ["js", "native-tls", "e2e-encryption", "indexeddb", "experimental-timeline"] } tokio = "1.27.0" url = "2.3.1" -web-sys = {version = "0.3.61", features = ["Document", "Element", "HtmlElement", "HtmlBodyElement", "Node", "NodeList", "Window", "console", "CssStyleDeclaration"]} +web-sys = {version = "0.3.61", features = ["Document", "Element", "HtmlElement", "HtmlBodyElement", "Node", "NodeList", "Window", "console", "CssStyleDeclaration", "Location"]} time = "0.3.22" anyhow = "1" -serde = { version = "1.0.96", features = ["derive"]} +serde = { version = "1.0.96", features = ["derive"] } mime = "0.3.17" js-sys = "0.3.64" wasm-bindgen = "0.2.55" @@ -31,3 +31,5 @@ infer = "0.15.0" ruma = { version = "0.7.4", features = ["unstable-sanitize", "unstable-msc2677", "unstable-msc3440", "client"] } uuid = "0.8" unic-langid = "0.9.1" +reqwest = "0.11" +http = "0.2" diff --git a/index.html b/index.html index f59d04f..e484920 100644 --- a/index.html +++ b/index.html @@ -224,26 +224,32 @@

- Ingresa un servidor + Completa tu información

- Únete a un servidor, por defecto puedes utilizar https://matrix.org + Crea un mundo, construye tu futuro

- + +
+
+ +
+
+
- +
diff --git a/public/login.css b/public/login.css index a9fcdf5..759abb0 100644 --- a/public/login.css +++ b/public/login.css @@ -29,8 +29,15 @@ padding: 0 8px; } -.button:active:not(:disabled) { - opacity: 0.7; +.button--tertiary { + border: none; + color: var(--text-loud); + border-radius: 0; + padding: 0 8px; +} + +.button--loading { + opacity: 0.8; } diff --git a/src/components/atoms/button.rs b/src/components/atoms/button.rs index cc5ad12..38f112c 100644 --- a/src/components/atoms/button.rs +++ b/src/components/atoms/button.rs @@ -13,6 +13,8 @@ pub struct ButtonProps<'a> { #[props(default = false)] disabled: bool, on_click: EventHandler<'a, MouseEvent>, + #[props(!optional)] + status: Option, } pub fn Button<'a>(cx: Scope<'a, ButtonProps<'a>>) -> Element<'a> { @@ -27,12 +29,31 @@ pub fn Button<'a>(cx: Scope<'a, ButtonProps<'a>>) -> Element<'a> { "" }; - cx.render(rsx!( - button { - class: "button {variant} {disabled}", - disabled: cx.props.disabled, - onclick: move |event| cx.props.on_click.call(event), - "{cx.props.text}" - } - )) + let loading = if cx.props.status.is_some() { + "button--loading" + } else { + "" + }; + + match &cx.props.status { + Some(s) => { + render!(rsx!( + button { + class: "button {variant} {loading}", + disabled: true, + "{s}" + } + )) + } + None => { + render!(rsx!( + button { + class: "button {variant} {disabled}", + disabled: cx.props.disabled, + onclick: move |event| cx.props.on_click.call(event), + "{cx.props.text}" + } + )) + } + } } diff --git a/src/components/molecules/attach_preview.rs b/src/components/molecules/attach_preview.rs index 60c8e68..6314dab 100644 --- a/src/components/molecules/attach_preview.rs +++ b/src/components/molecules/attach_preview.rs @@ -63,6 +63,7 @@ pub fn AttachPreview<'a>(cx: Scope<'a, AttachPreviewProps<'a>>) -> Element<'a> { Button { text: "{key_attach_preview_cta_cancel}", variant: &Variant::Secondary, + status: None, on_click: on_handle_card } } @@ -103,6 +104,7 @@ pub fn AttachPreview<'a>(cx: Scope<'a, AttachPreviewProps<'a>>) -> Element<'a> { Button { text: "{key_attach_preview_cta_cancel}", variant: &Variant::Secondary, + status: None, on_click: on_handle_card } } diff --git a/src/components/molecules/input_message.rs b/src/components/molecules/input_message.rs index 4928044..44f2c02 100644 --- a/src/components/molecules/input_message.rs +++ b/src/components/molecules/input_message.rs @@ -190,6 +190,7 @@ pub fn InputMessage<'a>(cx: Scope<'a, InputMessageProps<'a>>) -> Element<'a> { rsx!( Button { text: "{key_input_message_cta}", + status: None, on_click: move |event| { if let Some(l) = &cx.props.on_attach { let attachment = Attachment { diff --git a/src/components/organisms/login_form.rs b/src/components/organisms/login_form.rs index 3e0abe7..7a2c185 100644 --- a/src/components/organisms/login_form.rs +++ b/src/components/organisms/login_form.rs @@ -25,6 +25,8 @@ pub struct LoginFormProps<'a> { #[props(default = false)] clear_data: bool, on_handle: EventHandler<'a, FormLoginEvent>, + #[props(!optional)] + status: Option, } pub fn LoginForm<'a>(cx: Scope<'a, LoginFormProps<'a>>) -> Element<'a> { @@ -53,7 +55,7 @@ pub fn LoginForm<'a>(cx: Scope<'a, LoginFormProps<'a>>) -> Element<'a> { "{cx.props.description}" } - div { + div { class: "login-form__form__head", &cx.props.body @@ -71,12 +73,13 @@ pub fn LoginForm<'a>(cx: Scope<'a, LoginFormProps<'a>>) -> Element<'a> { } ) } - } + } div { class: "login-form__cta--filled", Button { text: "{cx.props.button_text}", + status: cx.props.status.clone(), on_click: move |_| { cx.props.on_handle.call(FormLoginEvent::FilledForm) } diff --git a/src/hooks/use_auth.rs b/src/hooks/use_auth.rs index 3b3365d..a1bb930 100644 --- a/src/hooks/use_auth.rs +++ b/src/hooks/use_auth.rs @@ -1,9 +1,12 @@ use dioxus::prelude::*; use gloo::storage::{errors::StorageError, LocalStorage}; use matrix_sdk::Client; +use ruma::api::IncomingResponse; use serde::{Deserialize, Serialize}; use url::Url; +use ruma::api::client::discovery::discover_homeserver::Response as WellKnownResponse; + use crate::pages::login::LoggedIn; #[derive(Debug, Clone, Serialize, Deserialize)] @@ -13,6 +16,12 @@ pub enum AuthError { ServerNotFound, } +impl From for AuthError { + fn from(_: serde_json::Error) -> Self { + AuthError::InvalidHomeserver + } +} + #[derive(Debug, Clone, Serialize, Deserialize)] pub struct CacheLogin { pub server: String, @@ -117,13 +126,31 @@ impl UseAuthState { let server = server_parsed.map_err(|_| AuthError::InvalidHomeserver)?; + let request_url = format!("{}.well-known/matrix/client", server.to_string()); + + let res = reqwest::Client::new() + .get(&request_url) + .send() + .await + .map_err(|_| AuthError::InvalidHomeserver)?; + + let body = res.text().await.map_err(|_| AuthError::InvalidHomeserver)?; + + let well_response = WellKnownResponse::try_from_http_response(http::Response::new(body)) + .map_err(|_| AuthError::InvalidHomeserver)?; + + let url_base = Url::parse(&well_response.homeserver.base_url) + .map_err(|_| AuthError::InvalidHomeserver)?; + let result = Client::builder() - .homeserver_url(&server.as_str()) + .homeserver_url(&url_base) .build() .await - .map(|_| server) + .map(|_| url_base) .map_err(|_| AuthError::ServerNotFound); + log::info!("client result: {:?}", result); + match result { Ok(server) => { self.data.with_mut(|l| l.server(server)); @@ -138,7 +165,7 @@ impl UseAuthState { } pub fn set_username(&self, username: &str, parse: bool) { - let mut username_parse = username.to_owned(); + let mut username_parse = username.trim().to_string(); if parse { if !username_parse.starts_with("@") { @@ -147,8 +174,9 @@ impl UseAuthState { if let Some(server) = &self.data.read().server { if let Some(domain) = server.domain() { - if !username_parse.ends_with(domain) { - username_parse = format!("{}:{}", username_parse, domain); + let domain_name = extract_domain_name(domain); + if !username_parse.ends_with(domain_name.as_str()) { + username_parse = format!("{}:{}", username_parse, domain_name); } } } @@ -161,7 +189,7 @@ impl UseAuthState { pub fn set_password(&self, password: &str) { self.data.with_mut(|l| { - l.password(password); + l.password(password.trim()); }); } @@ -215,3 +243,16 @@ impl UseAuthState { *self.logged_in.write() = LoggedIn(option); } } + +fn extract_domain_name(host: &str) -> String { + let segs: Vec<&str> = host + .split('.') + .filter(|&s| !s.is_empty()) + .rev() + .collect::>(); + + let suffix = segs[0]; + let domain = segs[1]; + + format!("{}.{}", domain, suffix) +} diff --git a/src/lib.rs b/src/lib.rs index 1fd09a7..f17ec65 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -32,6 +32,8 @@ pub mod services { pub mod utils { pub mod get_element; + pub mod get_homeserver; + pub mod get_param; pub mod i18n_get_key_value; pub mod matrix; pub mod nice_bytes; diff --git a/src/locales/en-US.json b/src/locales/en-US.json index 75ab054..2d772d7 100644 --- a/src/locales/en-US.json +++ b/src/locales/en-US.json @@ -8,6 +8,12 @@ "found": "Session found", "restoring": "Restoring session" } + }, + "errors": { + "homeserver": { + "invalid_url": "Url isn't valid" + }, + "restore": "Can't restore session" } }, "login": { @@ -52,7 +58,8 @@ "invalid_url": "Error: Relative URL without base", "unknown": "Error: Unknown error", "invalid_username_password": "Error: Invalid username or password", - "not_found": "User not found" + "not_found": "User not found", + "invalid_server": "Invalid server" }, "status": { "loading": "We are verifying your data", diff --git a/src/locales/es-ES.json b/src/locales/es-ES.json index 4127890..668632e 100644 --- a/src/locales/es-ES.json +++ b/src/locales/es-ES.json @@ -8,6 +8,12 @@ "fount": "Sesión encontrada", "restoring": "Restaurando sesión" } + }, + "errors": { + "homeserver": { + "invalid_url": "Url encontrada no es válida" + }, + "restore": "No se ha podido restaurar la sesión" } }, "login": { @@ -52,7 +58,8 @@ "invalid_url": "Error: URL relativa sin base", "unknown": "Error: Error desconocido", "invalid_username_password": "Error: Nombre de usuario o contraseña inválidos", - "not_found": "No se ha encontrado el usuario" + "not_found": "No se ha encontrado el usuario", + "invalid_server": "El servidor no es válido" }, "status": { "loading": "Estamos verificando tus datos", diff --git a/src/main.rs b/src/main.rs index 57067a3..0e34097 100644 --- a/src/main.rs +++ b/src/main.rs @@ -8,16 +8,18 @@ use chat::hooks::use_session::use_session; use chat::pages::login::{LoggedIn, Login}; use chat::pages::route::Route; use chat::pages::signup::Signup; -use chat::utils::get_element; +use chat::utils::get_homeserver::{Homeserver, HomeserverError}; +use chat::utils::{get_element, get_homeserver}; use chat::MatrixClientState; use dioxus::prelude::*; use dioxus_router::prelude::Router; use gloo::storage::errors::StorageError; use gloo::storage::LocalStorage; -use log::{info, LevelFilter}; +use log::LevelFilter; use chat::services::matrix::matrix::*; use dioxus_std::{i18n::*, translate}; +use futures_util::TryFutureExt; use matrix_sdk::config::SyncSettings; use matrix_sdk::ruma::exports::serde_json; use matrix_sdk::Client; @@ -25,9 +27,7 @@ use ruma::api::client::filter::{Filter, FilterDefinition, RoomEventFilter, RoomF use ruma::api::client::sync::sync_events; use ruma::events::EventType; use std::str::FromStr; -use std::time::Duration; use unic_langid::LanguageIdentifier; -use web_sys::console::info; use web_sys::window; fn main() { @@ -69,7 +69,10 @@ fn App(cx: Scope) -> Element { use_shared_state::(cx).expect("Unable to use before session"); let key_chat_common_error_sync = translate!(i18, "chat.common.error.sync"); - let key_chat_common_error_default_server = translate!(i18, "logout.chat.common.error.default_server"); + let key_chat_common_error_default_server = translate!(i18, "chat.common.error.default_server"); + let key_main_error_homeserver_invalid_url = + translate!(i18, "main.errors.homeserver.invalid_url"); + let key_main_error_restore = translate!(i18, "main.errors.restore"); let restoring_session = use_ref::(cx, || true); @@ -77,9 +80,19 @@ fn App(cx: Scope) -> Element { to_owned![client, auth, restoring_session, session, notification]; async move { - let Ok(c) = create_client("https://matrix.org").await else { - return notification.handle_error(&key_chat_common_error_default_server); - }; + let homeserver = Homeserver::new().map_err(|e| match e { + HomeserverError::InvalidUrl => key_main_error_homeserver_invalid_url, + })?; + + let c = create_client(&homeserver.get_base_url()) + .await + .map_err(|_| { + format!( + "{} {}", + key_chat_common_error_default_server, + homeserver.get_base_url() + ) + })?; client.set(MatrixClientState { client: Some(c.clone()), @@ -89,27 +102,32 @@ fn App(cx: Scope) -> Element { ::get("session_file"); let Ok(s) = serialized_session else { - return restoring_session.set(false); + restoring_session.set(false); + return Ok(()); }; let (c, sync_token) = restore_session(&s) .await - .expect("can't restore session: main"); + .map_err(|_| key_main_error_restore)?; client.set(MatrixClientState { client: Some(c.clone()), }); - let sync_response = session.sync(c.clone(), sync_token).await; - - let Ok(()) = sync_response else { - return notification.handle_error(&key_chat_common_error_sync); - }; + session + .sync(c.clone(), sync_token) + .await + .map_err(|_| key_chat_common_error_sync)?; auth.set_logged_in(true); restoring_session.set(false); + + Ok::<(), String>(()) } + .unwrap_or_else(move |e: String| { + notification.handle_error(&e); + }) }); render! { diff --git a/src/pages/chat/room/group.rs b/src/pages/chat/room/group.rs index a7e1de4..874fe67 100644 --- a/src/pages/chat/room/group.rs +++ b/src/pages/chat/room/group.rs @@ -346,6 +346,7 @@ pub fn RoomGroup(cx: Scope) -> Element { class: "group__cta__wrapper row", Button { text: "{i18n_get_key_value(&i18n_map, key_group_meta_cta_back)}", + status: None, variant: &Variant::Secondary, on_click: move |_| { handle_complete_group.set(false) @@ -353,6 +354,7 @@ pub fn RoomGroup(cx: Scope) -> Element { } Button { text: "{i18n_get_key_value(&i18n_map, key_group_meta_cta_create)}", + status: None, disabled: group_name.get().len() == 0, on_click: on_handle_create } @@ -421,6 +423,7 @@ pub fn RoomGroup(cx: Scope) -> Element { Button { text: "{i18n_get_key_value(&i18n_map, key_group_select_cta)}", disabled: if selected_users.read().profiles.len() == 0 { true } else { false }, + status: None, on_click: move |_| { handle_complete_group.set(true) } diff --git a/src/pages/login.rs b/src/pages/login.rs index adc9139..52df1e7 100644 --- a/src/pages/login.rs +++ b/src/pages/login.rs @@ -5,10 +5,16 @@ use dioxus_std::{translate, i18n::use_i18}; use gloo::storage::LocalStorage; use crate::{ components::{ - atoms::{MessageInput, input::InputType, LoadingStatus}, + atoms::{MessageInput, input::InputType}, organisms::{login_form::FormLoginEvent, LoginForm}, }, - utils::i18n_get_key_value::i18n_get_key_value, services::matrix::matrix::login, hooks::{use_client::use_client, use_init_app::BeforeSession, use_auth::{use_auth, CacheLogin}, use_session::use_session, use_notification::use_notification}, + utils::i18n_get_key_value::i18n_get_key_value, services::matrix::matrix::login, hooks::{ + use_client::use_client, + use_init_app::BeforeSession, + use_auth::{use_auth, CacheLogin}, + use_session::use_session, + use_notification::use_notification + }, }; #[derive(Debug, Clone)] @@ -23,19 +29,43 @@ pub enum LoggedInStatus { LoggedAs(String) } +impl LoggedInStatus { + fn has_status(&self) -> bool { + match self { + LoggedInStatus::Start | + LoggedInStatus::Loading | + LoggedInStatus::Done | + LoggedInStatus::Persisting | + LoggedInStatus::LoggedAs(_) => true, + _ => false + } + } + + fn get_text<'a>(&self, key_loading: &'a str, key_logged: &'a str, key_done: &'a str, key_persisting: &'a str) -> Option<&'a str> { + match self { + LoggedInStatus::Loading => Some(key_loading), + LoggedInStatus::LoggedAs(_) => Some(key_logged), + LoggedInStatus::Done => Some(key_done), + LoggedInStatus::Persisting => Some(key_persisting), + LoggedInStatus::Start => None + } + } +} + +enum LoginFrom { + SavedData, + FullForm +} + pub fn Login(cx: Scope) -> Element { let i18 = use_i18(cx); let key_chat_errors_not_found = translate!(i18, "login.chat_errors.not_found"); + let key_login_chat_errors_invalid_server = translate!(i18, "login.chat_errors.invalid_server"); let key_login_unlock_title = translate!(i18, "login.unlock.title"); let key_login_unlock_description = translate!(i18, "login.unlock.description"); let key_login_unlock_cta = translate!(i18, "login.unlock.cta"); - let key_login_chat_homeserver_message = "login-chat-homeserver-message"; - let key_login_chat_homeserver_description = "login-chat-homeserver-description"; - let key_login_chat_homeserver_placeholder = "login-chat-homeserver-placeholder"; - let key_login_chat_homeserver_cta = "login-chat-homeserver-cta"; - let key_login_chat_credentials_description = "login-chat-credentials-description"; let key_login_chat_credentials_title = "login-chat-credentials-title"; @@ -54,12 +84,12 @@ pub fn Login(cx: Scope) -> Element { let key_login_chat_errors_unknown = "login-chat-errors-unknown"; let key_login_chat_errors_invalid_username_password = "login-chat-errors-invalid-username-password"; - let i18n_map = HashMap::from([ - (key_login_chat_homeserver_message, translate!(i18, "login.chat_steps.homeserver.message")), - (key_login_chat_homeserver_description, translate!(i18, "login.chat_steps.homeserver.description")), - (key_login_chat_homeserver_placeholder, translate!(i18, "login.chat_steps.homeserver.placeholder")), - (key_login_chat_homeserver_cta, translate!(i18, "login.chat_steps.homeserver.cta")), + let key_login_status_loading = translate!(i18, "login.status.loading"); + let key_login_status_logged = translate!(i18, "login.status.logged"); + let key_login_status_done = translate!(i18, "login.status.done"); + let key_login_status_persisting = translate!(i18, "login.status.persisting"); + let i18n_map = HashMap::from([ (key_login_chat_credentials_title, translate!(i18, "login.chat_steps.credentials.title")), (key_login_chat_credentials_description, translate!(i18, "login.chat_steps.credentials.description")), @@ -88,6 +118,7 @@ pub fn Login(cx: Scope) -> Element { let username = use_state(cx, || String::from("")); let password = use_state(cx, || String::from("")); let error = use_state(cx, || None); + let login_from = use_state(cx, || if auth.is_storage_data() {LoginFrom::SavedData} else {LoginFrom::FullForm}); let before_session = use_shared_state::(cx).expect("Unable to use before session"); @@ -102,24 +133,14 @@ pub fn Login(cx: Scope) -> Element { &i18n_map, key_login_chat_errors_unknown, ); - let on_update_homeserver = move || { - cx.spawn({ - to_owned![homeserver, auth]; - - async move { - auth.set_server(homeserver.get()).await; - } - }) - }; - let on_handle_clear = Rc::new(move || { cx.spawn({ - to_owned![homeserver, username, password, auth]; + to_owned![username, password, auth, login_from]; async move { auth.reset(); + login_from.set(LoginFrom::FullForm); - homeserver.set(String::new()); username.set(String::new()); password.set(String::new()); } @@ -129,25 +150,33 @@ pub fn Login(cx: Scope) -> Element { let on_handle_clear_clone = on_handle_clear.clone(); let on_handle_login = Rc::new(move || { - auth.set_server(homeserver.get()); - auth.set_username(username.get(), true); - auth.set_password(password.get()); - cx.spawn({ - to_owned![auth, session, username, password, is_loading_loggedin, client, error, error_invalid_credentials, error_unknown, homeserver, notification]; - + to_owned![auth, session, username, password, is_loading_loggedin, client, error, error_invalid_credentials, error_unknown, homeserver, notification, key_login_chat_errors_invalid_server]; + async move { is_loading_loggedin.set(LoggedInStatus::Loading); - let login_config = auth.build(); + if username.get().contains(':') { + let parts = username.get().splitn(2, ':').collect::>(); + + if let Err(_) = auth.set_server(parts[1]).await { + notification.handle_error(&format!("{}: {}", key_login_chat_errors_invalid_server, parts[1])); + is_loading_loggedin.set(LoggedInStatus::Start); + return; + }; + } + auth.set_server(homeserver.get()).await; + auth.set_username(username.get(), true); + auth.set_password(password.get()); + + let login_config = auth.build(); + let Ok(info) = login_config else { - homeserver.set(String::new()); username.set(String::new()); password.set(String::new()); return auth.reset(); }; - let response = login( &info.server.to_string(), &info.username, @@ -161,12 +190,6 @@ pub fn Login(cx: Scope) -> Element { let display_name = c.account().get_display_name().await.ok().flatten(); - auth.persist_data(CacheLogin { - server: homeserver.get().to_string(), - username: username.get().to_string(), - display_name - }); - ::set( "session_file", serialized_session, @@ -184,7 +207,14 @@ pub fn Login(cx: Scope) -> Element { is_loading_loggedin.set(LoggedInStatus::LoggedAs(user_id.to_string())); - auth.set_logged_in(true) + auth.set_logged_in(true); + + auth.persist_data(CacheLogin { + server: homeserver.get().to_string(), + username: username.get().to_string(), + display_name + }); + } Err(err) => { is_loading_loggedin.set(LoggedInStatus::Start); @@ -197,7 +227,6 @@ pub fn Login(cx: Scope) -> Element { error.set(Some(error_unknown)) } - homeserver.set(String::new()); username.set(String::new()); password.set(String::new()); @@ -212,23 +241,27 @@ pub fn Login(cx: Scope) -> Element { let on_handle_login_clone = on_handle_login.clone(); use_coroutine(cx, |_: UnboundedReceiver::<()>| { - to_owned![auth, homeserver, username]; + to_owned![auth, homeserver, username, client]; async move { - let data = auth.get_storage_data(); + let Ok(data) = auth.get_storage_data() else { + let url = client.get().homeserver().await; + let Some(domain) = url.domain() else { + return; + }; + return homeserver.set(format!("{}://{}", url.scheme(), domain)); + }; - if let Ok(data) = data { - let deserialize_data = serde_json::from_str::(&data); + let deserialize_data = serde_json::from_str::(&data); - if let Ok(data) = deserialize_data { - auth.set_login_cache(data.clone()); + if let Ok(data) = deserialize_data { + auth.set_login_cache(data.clone()); - homeserver.set(data.server.clone()); - username.set(data.username.clone()); - - auth.set_server(&data.server).await; - auth.set_username(&data.username, true); - } + homeserver.set(data.server.clone()); + username.set(data.username.clone()); + + auth.set_server(&data.server).await; + auth.set_username(&data.username, true); } } }); @@ -243,9 +276,16 @@ pub fn Login(cx: Scope) -> Element { render!( div { class: "page--clamp", - if auth.is_storage_data() && *is_loading_loggedin.read() == LoggedInStatus::Start { + if (auth.is_storage_data() && matches!(*is_loading_loggedin.read(), LoggedInStatus::Start)) || (is_loading_loggedin.read().has_status() && matches!(*login_from.get(), LoginFrom::SavedData)) { let display_name = auth.get_login_cache().map(|data| data.display_name.unwrap_or(data.username)).unwrap_or(String::from("")); - + + let loggedin_status = is_loading_loggedin.read().get_text( + &key_login_status_loading, + &key_login_status_logged, + &key_login_status_done, + &key_login_status_persisting + ); + rsx!( LoginForm { title: "{key_login_unlock_title} {display_name}", @@ -254,6 +294,7 @@ pub fn Login(cx: Scope) -> Element { emoji: "👋", error: error.get().as_ref(), clear_data: true, + status: loggedin_status.map(|t|String::from(t)), on_handle: on_handle_form_event, body: render!(rsx!( div { @@ -278,44 +319,14 @@ pub fn Login(cx: Scope) -> Element { )) } ) - } else if auth.get().data.server.is_none() { - let on_handle_login = on_handle_login.clone(); - rsx!( - LoginForm { - title: "{i18n_get_key_value(&i18n_map, key_login_chat_homeserver_message)}", - description: "{i18n_get_key_value(&i18n_map, key_login_chat_homeserver_description)}", - button_text: "{i18n_get_key_value(&i18n_map, key_login_chat_homeserver_cta)}", - emoji: "🛰️", - error: error.get().as_ref(), - on_handle: move |event: FormLoginEvent| match event { - FormLoginEvent::FilledForm => on_update_homeserver(), - FormLoginEvent::Login => *before_session.write() = BeforeSession::Login, - FormLoginEvent::CreateAccount => *before_session.write() = BeforeSession::Signup, - FormLoginEvent::ClearData => on_handle_clear() - }, - body: render!(rsx!( - div { - MessageInput { - message: "{homeserver.get()}", - placeholder: "{i18n_get_key_value(&i18n_map, key_login_chat_homeserver_placeholder)}", - error: None, - on_input: move |event: FormEvent| { - homeserver.set(event.value.clone()) - }, - on_keypress: move |event: KeyboardEvent| { - if event.code() == keyboard_types::Code::Enter && !homeserver.get().is_empty() { - on_update_homeserver() - } - }, - on_click: move |_| { - on_update_homeserver() - } - } - } - )) - } - ) - } else if auth.get().data.username.is_none() || auth.get().data.password.is_none() { + } else if (auth.get().data.username.is_none() || auth.get().data.password.is_none()) || (is_loading_loggedin.read().has_status() && matches!(*login_from.get(), LoginFrom::FullForm)) { + let loggedin_status = is_loading_loggedin.read().get_text( + &key_login_status_loading, + &key_login_status_logged, + &key_login_status_done, + &key_login_status_persisting + ); + rsx!( LoginForm { title: "{i18n_get_key_value(&i18n_map, key_login_chat_credentials_title)}", @@ -324,6 +335,7 @@ pub fn Login(cx: Scope) -> Element { emoji: "👋", error: error.get().as_ref(), on_handle: on_handle_form_event, + status: loggedin_status.map(String::from), body: render!(rsx!( div { MessageInput { @@ -366,37 +378,6 @@ pub fn Login(cx: Scope) -> Element { )) } ) - } else { - let key_login_status_loading = translate!(i18, "login.status.loading"); - let key_login_status_logged = translate!(i18, "login.status.logged"); - let key_login_status_done = translate!(i18, "login.status.done"); - let key_login_status_persisting = translate!(i18, "login.status.persisting"); - - match &*is_loading_loggedin.read() { - LoggedInStatus::Loading => { - rsx!( - LoadingStatus {text: "{key_login_status_loading}"} - ) - } - LoggedInStatus::LoggedAs(user) => { - rsx!( - LoadingStatus {text: "{key_login_status_logged}"} - ) - }, - LoggedInStatus::Done => { - rsx!( - LoadingStatus {text: "{key_login_status_done}"} - ) - } - LoggedInStatus::Persisting => { - rsx!( - LoadingStatus {text: "{key_login_status_persisting}"} - ) - } - _ => { - rsx!(div{}) - } - } } } ) diff --git a/src/pages/profile/profile.rs b/src/pages/profile/profile.rs index 8d8437c..140200d 100644 --- a/src/pages/profile/profile.rs +++ b/src/pages/profile/profile.rs @@ -317,6 +317,7 @@ pub fn Profile(cx: Scope) -> Element { class: "profile__cta", Button { text: "{i18n_get_key_value(&i18n_map, key_username_cta_update)}", + status: None, on_click: move |_| { cx.spawn({ to_owned![client, original_profile, current_profile, attach]; @@ -391,6 +392,7 @@ pub fn Profile(cx: Scope) -> Element { class: "profile__cta", Button { text: "{key_management_info_cta}", + status: None, on_click: move |_| { navigator.push(Route::Verify { id: String::from("fidoid") }); } @@ -414,6 +416,7 @@ pub fn Profile(cx: Scope) -> Element { class: "profile__cta", Button { text: "{i18n_get_key_value(&i18n_map, key_management_deactivate_cta_deactivate)}", + status: None, on_click: move |_| { // cx.spawn({ // to_owned!(client); diff --git a/src/pages/profile/verify.rs b/src/pages/profile/verify.rs index 077ee3f..09fabb7 100644 --- a/src/pages/profile/verify.rs +++ b/src/pages/profile/verify.rs @@ -3,10 +3,7 @@ use dioxus_std::{i18n::use_i18, translate}; use log::info; use matrix_sdk::encryption::verification::{SasVerification, Verification}; -use crate::{ - components::atoms::Button, hooks::use_client::use_client, - utils::i18n_get_key_value::i18n_get_key_value, -}; +use crate::{components::atoms::Button, hooks::use_client::use_client}; use futures_util::StreamExt; @@ -276,12 +273,14 @@ pub fn Verify(cx: Scope, id: String) -> Element { class: "verify__spacer row", Button { text: "{key_verify_unverified_cta_disagree}", + status: None, on_click: move |_| { on_handle_cancel(sas.clone()); } } Button { text: "{key_verify_unverified_cta_match}", + status: None, on_click: move |_| { on_handle_confirm(sas.clone()); } diff --git a/src/pages/signup.rs b/src/pages/signup.rs index cc61b92..1572c37 100644 --- a/src/pages/signup.rs +++ b/src/pages/signup.rs @@ -279,6 +279,7 @@ pub fn Signup(cx: Scope) -> Element { description: "{i18n_get_key_value(&i18n_map, key_signup_chat_homeserver_description)}", button_text: "{i18n_get_key_value(&i18n_map, key_signup_chat_homeserver_cta)}", emoji: "🛰️", + status: None, error: error.get().as_ref(), on_handle: move |event: FormLoginEvent| match event { FormLoginEvent::FilledForm => on_update_homeserver(), @@ -314,6 +315,7 @@ pub fn Signup(cx: Scope) -> Element { description: "{i18n_get_key_value(&i18n_map, key_signup_chat_credentials_description)}", button_text: "{i18n_get_key_value(&i18n_map, key_signup_chat_credentials_cta)}", emoji: "✍️", + status: None, error: error.get().as_ref(), on_handle: move |event: FormLoginEvent| match event { FormLoginEvent::FilledForm => on_handle_login(), @@ -379,6 +381,7 @@ pub fn Signup(cx: Scope) -> Element { description: "{i18n_get_key_value(&i18n_map, key_signup_chat_captcha_description)}", button_text: "{i18n_get_key_value(&i18n_map, key_signup_chat_captcha_cta)}", emoji: "✍️", + status: None, error: error.get().as_ref(), on_handle: move |event: FormLoginEvent| match event { FormLoginEvent::FilledForm => on_handle_captcha(), diff --git a/src/utils/get_homeserver.rs b/src/utils/get_homeserver.rs new file mode 100644 index 0000000..1090f60 --- /dev/null +++ b/src/utils/get_homeserver.rs @@ -0,0 +1,40 @@ +use url::Url; +use web_sys::window; + +use super::get_param::get_param; + +#[derive(Clone, Debug)] +pub struct Homeserver { + base_url: String, +} + +pub enum HomeserverError { + InvalidUrl, +} + +impl Homeserver { + pub fn new() -> Result { + let base_url = from_homeserver_param() + .unwrap_or(from_current_host().ok_or(HomeserverError::InvalidUrl)?); + + Ok(Homeserver { base_url }) + } + + pub fn get_base_url(&self) -> &str { + self.base_url.as_str() + } +} + +fn from_current_host() -> Option { + let window = window()?; + let protocol = window.location().protocol().ok()?; + let host = window.location().host().ok()?; + + Some(format!("{}://{}", protocol, host)) +} + +fn from_homeserver_param() -> Option { + let param = get_param("homeserver")?; + let url = Url::parse(¶m).ok()?; + Some(url.to_string()) +} diff --git a/src/utils/get_param.rs b/src/utils/get_param.rs new file mode 100644 index 0000000..d7eec7a --- /dev/null +++ b/src/utils/get_param.rs @@ -0,0 +1,13 @@ +use web_sys::{window, UrlSearchParams}; + +pub fn get_param(param: &str) -> Option { + window()? + .location() + .search() + .ok() + .map(|search| { + let params = UrlSearchParams::new_with_str(&search).ok()?; + params.get(param) + }) + .flatten() +}