1use std::rc::Rc;
2use vertigo::{Css, DomNode, KeyDownEvent, Resource, Value, bind, bind_rc, css, dom, transaction};
3
4pub type OnSubmit = Rc<dyn Fn(&str, &str)>;
5
6pub struct Login<T> {
7 pub on_submit: OnSubmit,
8 pub token_result: Value<Option<Resource<T>>>,
9 pub params: LoginParams,
10}
11
12pub struct LoginParams {
13 pub css: Css,
14 pub add_css: Css,
15 pub line_css: Css,
16 pub line_add_css: Css,
17 pub input_css: Css,
18 pub submit_css: Css,
19 pub submit_add_css: Css,
20 pub error_message: Rc<dyn Fn(String) -> String>,
21 pub username_label: String,
22 pub password_label: String,
23 pub button_label: String,
24 pub waiting_label: String,
25}
26
27impl Default for LoginParams {
28 fn default() -> Self {
29 Self {
30 css: css! {"
31 width: 250px;
32 margin: auto;
33 padding: 10px;
34 margin-bottom: 10px;
35 "},
36 add_css: Css::default(),
37 line_css: css! {"
38 min-height: 1em;
39 margin-bottom: 5px;
40 "},
41 line_add_css: Css::default(),
42 input_css: Css::default(),
43 submit_css: css! {"
44 margin-top: 15px;
45 "},
46 submit_add_css: Css::default(),
47 error_message: Rc::new(|err| err),
48 username_label: "Username:".to_string(),
49 password_label: "Password:".to_string(),
50 button_label: "Login".to_string(),
51 waiting_label: "Logging in...".to_string(),
52 }
53 }
54}
55
56impl<T: Clone + PartialEq + 'static> Login<T> {
57 pub fn into_component(self) -> Self {
58 self
59 }
60
61 pub fn mount(&self) -> DomNode {
62 let Self {
63 on_submit,
64 token_result,
65 params,
66 } = self;
67
68 let username = Value::<String>::default();
69 let password = Value::<String>::default();
70
71 let submit = bind_rc!(on_submit, username, password, || transaction(|ctx| {
72 on_submit(&username.get(ctx), &password.get(ctx));
73 }));
74
75 let on_key_down = bind!(submit, |key: KeyDownEvent| {
76 if key.code == "Enter" || key.code == "NumpadEnter" {
77 submit();
78 return true;
79 }
80 false
81 });
82
83 let css = ¶ms.css + ¶ms.add_css;
84 let line_css = ¶ms.line_css + ¶ms.line_add_css;
85 let submit_css = ¶ms.submit_css + ¶ms.submit_add_css;
86 let error_message = params.error_message.clone();
87 let waiting_label = params.waiting_label.clone();
88
89 let message_div = bind!(
90 line_css,
91 token_result.render_value(move |token_result| {
92 let css_error = line_css.clone().push_str("color: red;");
93
94 match token_result {
95 Some(Resource::Loading) => dom! {
96 <div css={line_css.clone()}>{&waiting_label}</div>
97 },
98 Some(Resource::Error(err)) => dom! {
99 <div css={css_error}>{error_message(err)}</div>
100 },
101 _ => dom! {
102 <div css={line_css.clone()} />
103 },
104 }
105 })
106 );
107
108 let on_username_change = bind!(username, |new_value: String| username.set(new_value));
109
110 let username_div = dom! {
111 <div css={line_css.clone()}>
112 <div>{¶ms.username_label}</div>
113 <input
114 css={¶ms.input_css}
115 value={username.to_computed()}
116 on_input={on_username_change}
117 />
118 </div>
119 };
120
121 let on_password_change = bind!(password, |new_value| password.set(new_value));
122
123 let password_div = dom! {
124 <div css={line_css}>
125 <div>{¶ms.password_label}</div>
126 <input
127 css={¶ms.input_css}
128 value={password.to_computed()}
129 on_input={on_password_change}
130 type="password"
131 />
132 </div>
133 };
134
135 dom! {
136 <div css={css} {on_key_down}>
137 { message_div }
138 { username_div }
139 { password_div }
140 <div css={submit_css}>
141 <input type="submit" value={¶ms.button_label} on_click={move |_| submit()} />
142 </div>
143 </div>
144 }
145 }
146}