chiark / gitweb /
Prettify username and email in account registration.
authorSimon Tatham <anakin@pobox.com>
Sun, 21 Jan 2024 11:27:49 +0000 (11:27 +0000)
committerSimon Tatham <anakin@pobox.com>
Sun, 21 Jan 2024 11:37:29 +0000 (11:37 +0000)
The username is now shown with its domain suffix, but doesn't make you
type that suffix. And the email is validated for basic syntax.

Cargo.toml
src/login.rs

index 38a73fe62735725bd9126773d730b6e686f1b184..6a8ec4d5e953c7a0e0b9590e1d9b94a149e345c3 100644 (file)
@@ -8,6 +8,7 @@ edition = "2021"
 chrono = { version = "0.4.31", features = ["serde"] }
 clap = { version = "4.4.12", features = ["derive"] }
 crossterm = "0.27.0"
+email_address = "0.2.4"
 html2text = { version = "0.11.0", features = ["css"] }
 itertools = "0.12.0"
 lazy_static = "1.4.0"
index cb1295366c0f911138ca3698266edef3e5a5071e..2a73ae2b7823e30e408c0710023ba2360955b7b3 100644 (file)
@@ -1,6 +1,8 @@
+use email_address::EmailAddress;
 use reqwest::Url;
 use std::cell::RefCell;
 use std::rc::Rc;
+use std::str::FromStr;
 use sys_locale::get_locale;
 
 use super::activity_stack::UtilityActivity;
@@ -18,30 +20,75 @@ use super::tui::{
 use super::types::{Account, Application, Instance};
 use super::TopLevelError;
 
-#[derive(Default)]
-struct MandatoryString(String);
+struct Username {
+    name: String,
+    domain: Rc<RefCell<String>>,
+}
 
-impl MandatoryString {
+impl Username {
     fn ok(&self) -> bool {
-        !self.0.is_empty()
+        !self.name.is_empty()
     }
 }
 
-impl EditableMenuLineData for MandatoryString {
+impl EditableMenuLineData for Username {
     fn display(&self) -> ColouredString {
-        if self.0.is_empty() {
+        if self.name.is_empty() {
             ColouredString::uniform("none", 'r')
         } else {
-            ColouredString::plain(&self.0)
+            ColouredString::plain(&self.name)
+                + ColouredString::uniform(
+                    &format!("@{}", self.domain.borrow()),
+                    'f',
+                )
         }
     }
 
     fn to_text(&self) -> String {
-        self.0.clone()
+        self.name.clone()
     }
 
     fn update(&mut self, text: &str) {
-        *self = Self(text.to_owned());
+        let text = text
+            .split_once('@')
+            .map_or(text, |(prefix, _)| prefix)
+            .trim();
+        self.name = text.to_owned();
+    }
+}
+
+struct Email {
+    addr: String,
+}
+
+impl Email {
+    fn ok(&self) -> bool {
+        EmailAddress::is_valid(&self.addr)
+    }
+}
+
+impl EditableMenuLineData for Email {
+    fn display(&self) -> ColouredString {
+        if self.addr.is_empty() {
+            ColouredString::uniform("none", 'r')
+        } else if self.ok() {
+            ColouredString::plain(&self.addr)
+                + ColouredString::uniform(" valid", 'f')
+        } else {
+            ColouredString::plain(&self.addr)
+                + ColouredString::uniform(" invalid!", 'r')
+        }
+    }
+
+    fn to_text(&self) -> String {
+        self.addr.clone()
+    }
+
+    fn update(&mut self, text: &str) {
+        self.addr = match EmailAddress::from_str(text) {
+            Ok(addr) => addr.as_str().to_owned(),
+            Err(_) => text.to_owned(),
+        };
     }
 }
 
@@ -170,8 +217,8 @@ struct LoginMenu {
     para_login_url: Paragraph,
     el_logincode: EditableMenuLine<String>,
     para_login_outcome: Paragraph,
-    el_username: EditableMenuLine<MandatoryString>,
-    el_email: EditableMenuLine<MandatoryString>,
+    el_username: EditableMenuLine<Username>,
+    el_email: EditableMenuLine<Email>,
     el_password: EditableMenuLine<Password>,
     el_password_confirm: EditableMenuLine<Password>,
     ml_rules: MenuKeypressLine,
@@ -235,12 +282,17 @@ impl LoginMenu {
         let el_username = EditableMenuLine::new(
             Pr('N'),
             ColouredString::plain("Name of your new account: "),
-            MandatoryString::default(),
+            Username {
+                name: "".to_owned(),
+                domain: Rc::new(RefCell::new("".to_owned())),
+            },
         );
         let el_email = EditableMenuLine::new(
             Pr('E'),
             ColouredString::plain("Email address to associate with account: "),
-            MandatoryString::default(),
+            Email {
+                addr: "".to_owned(),
+            },
         );
         let password1 = Rc::new(RefCell::new("".to_owned()));
         let password2 = Rc::new(RefCell::new("".to_owned()));
@@ -421,6 +473,8 @@ impl LoginMenu {
                 );
                 self.para_server_id
                     .push_text(ColouredString::uniform(".", 'H'), false);
+                *self.el_username.get_data().domain.borrow_mut() =
+                    instance.domain.clone();
                 self.state = LoginState::ServerValid;
                 LogicalAction::Nothing
             }
@@ -593,8 +647,8 @@ impl LoginMenu {
 
         // Send the account registration request
         let token = client.register_account(
-            &self.el_username.get_data().0,
-            &self.el_email.get_data().0,
+            &self.el_username.get_data().name,
+            &self.el_email.get_data().addr,
             &self.el_password.get_data().this.borrow(),
             &language,
         )?;
@@ -633,7 +687,10 @@ impl LoginMenu {
                     false,
                 );
                 self.para_login_outcome.push_text(
-                    ColouredString::uniform(&self.el_email.get_data().0, 'K'),
+                    ColouredString::uniform(
+                        &self.el_email.get_data().addr,
+                        'K',
+                    ),
                     false,
                 );
                 self.para_login_outcome.push_text(