diff --git a/src/widgets/login_screen.rs b/src/widgets/login_screen.rs index a624c57..8f11e13 100644 --- a/src/widgets/login_screen.rs +++ b/src/widgets/login_screen.rs @@ -28,6 +28,10 @@ pub enum LoginStatus { Error(String), } +/// A struct that represents a login attempt, containing a username, a default nick, and a password. +/// None are validated in any way. +/// +/// Warning: this struct contains the user's password; keep it in memory only as short as possible. #[derive(Clone)] pub struct LoginAttempt { pub username: String, @@ -45,6 +49,11 @@ impl Debug for LoginAttempt { } } +/// The props for the login screen widget, including: +/// * The xmpp address to pre-fill in the username field. +/// * The default nick to pre-fill in the default nick field. +/// * The current login state, which will change as the user attempts to log in. +/// * The event handler for when the user clicks the login button. #[derive(Props)] pub struct LoginScreenProps<'a> { cached_username: String, @@ -53,10 +62,32 @@ pub struct LoginScreenProps<'a> { on_login_attempt: EventHandler<'a, LoginAttempt>, } +/// A login screen widget. +/// +/// This widget is used to query the user for their login credentials, namely: +/// * Username (XMPP address/Jid) +/// * Default nick +/// * Password +/// +/// The default nick and username may be pre-filled from the cache. +/// +/// The widget itself only contains the UI, and does not know how to validate the credentials +/// or log the user in; the parent context is responsible for that. +/// +/// As a small UX improvement, the widget will automatically fill in the default nick +/// based on the username, if the user has not changed the default nick. pub fn LoginScreen<'a>(cx: Scope<'a, LoginScreenProps>) -> Element<'a> { + + // The username. let username = use_state(cx, || cx.props.cached_username.clone()); + + // The default nick. let default_nick = use_state(cx, || cx.props.cached_nick.clone()); + + // The password. let password = use_state(cx, || "".to_string()); + + // Track whether the default nick was directly changed by the user. let default_nick_was_changed = use_state(cx, || false); render! { @@ -64,13 +95,17 @@ pub fn LoginScreen<'a>(cx: Scope<'a, LoginScreenProps>) -> Element<'a> { div { class: "login-form", + // The username input. input { "type": "text", - placeholder: "Username", + placeholder: "XMPP Address", value: "{username}", oninput: move |x| { + + // Set the username state variable. username.set(x.value.clone()); + // If the default nick was not changed by the user, set it to the part before the @. if !default_nick_was_changed.get() { if let Some(before_at) = x.value.split('@').next() { default_nick.set(before_at.to_string()); @@ -79,16 +114,21 @@ pub fn LoginScreen<'a>(cx: Scope<'a, LoginScreenProps>) -> Element<'a> { }, } + // The default nick input. input { placeholder: "Default nick", "type": "text", value: "{default_nick}", oninput: move |x| { + // Store the nick in the state variable. default_nick.set(x.value.clone()); + + // Mark the nick as changed, so that it won't be overwritten by the username. default_nick_was_changed.set(true); }, } + // The password input. input { placeholder: "Password", "type": "password", @@ -109,11 +149,11 @@ pub fn LoginScreen<'a>(cx: Scope<'a, LoginScreenProps>) -> Element<'a> { rsx! { button { - class: match cx.props.login_state { - LoginStatus::LoggedOut => "login-button logged-out", - LoginStatus::LoggingIn => "login-button logging-in", - LoginStatus::LoggedIn => "login-button logged-in", - LoginStatus::Error(_) => "error", + disabled: match cx.props.login_state { + LoginStatus::LoggedIn => true, + LoginStatus::LoggedOut => false, + LoginStatus::LoggingIn => true, + LoginStatus::Error(_) => false, }, onclick: move |_| { @@ -124,7 +164,12 @@ pub fn LoginScreen<'a>(cx: Scope<'a, LoginScreenProps>) -> Element<'a> { }); }, - "Login" + match cx.props.login_state { + LoginStatus::LoggedOut => "Log in", + LoginStatus::LoggingIn => "Logging in...", + LoginStatus::LoggedIn => "Logged in", + LoginStatus::Error(_) => "Log in", + } } }