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