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
9pub type RenderInput = Rc<dyn Fn(Value<String>) -> DomNode>;
12
13pub type RenderSubmit = Rc<dyn Fn(Rc<dyn Fn()>) -> DomNode>;
15
16#[component]
17pub fn Login<T: Clone + PartialEq + 'static>(
18 on_submit: OnSubmit,
19 token_result: Value<Option<Resource<T>>>,
20 params: LoginParams,
21 i: AttrGroup,
23 s: AttrGroup,
25) {
26 mount_login(on_submit, token_result, params, i, s)
27}
28
29pub struct LoginParams {
30 pub css: Css,
31 pub add_css: Css,
32 pub line_css: Css,
33 pub line_add_css: Css,
34 pub input_css: Css,
35 pub submit_css: Css,
36 pub submit_add_css: Css,
37 pub error_message: Rc<dyn Fn(String) -> String>,
38 pub username_label: String,
39 pub password_label: String,
40 pub button_label: String,
41 pub waiting_label: String,
42 pub header: Option<DomNode>,
44 pub footer: Option<DomNode>,
46 pub render_username: Option<RenderInput>,
49 pub render_password: Option<RenderInput>,
52 pub render_submit: Option<RenderSubmit>,
55}
56
57impl Default for LoginParams {
58 fn default() -> Self {
59 Self {
60 css: css! {"
61 width: 250px;
62 margin: auto;
63 padding: 10px;
64 margin-bottom: 10px;
65 "},
66 add_css: Css::default(),
67 line_css: css! {"
68 min-height: 1em;
69 margin-bottom: 5px;
70 "},
71 line_add_css: Css::default(),
72 input_css: Css::default(),
73 submit_css: css! {"
74 margin-top: 15px;
75 "},
76 submit_add_css: Css::default(),
77 error_message: Rc::new(|err| err),
78 username_label: "Username:".to_string(),
79 password_label: "Password:".to_string(),
80 button_label: "Login".to_string(),
81 waiting_label: "Logging in...".to_string(),
82 header: None,
83 footer: None,
84 render_username: None,
85 render_password: None,
86 render_submit: None,
87 }
88 }
89}
90
91pub fn mount_login<T: Clone + PartialEq + 'static>(
92 on_submit: OnSubmit,
93 token_result: Value<Option<Resource<T>>>,
94 params: LoginParams,
95 input_attrs: AttrGroup,
96 submit_attrs: AttrGroup,
97) -> DomNode {
98 let username = Value::<String>::default();
99 let password = Value::<String>::default();
100
101 let submit = bind_rc!(on_submit, username, password, || transaction(|ctx| {
102 on_submit(&username.get(ctx), &password.get(ctx));
103 }));
104
105 let on_key_down = bind!(submit, |key: KeyDownEvent| {
106 if key.code == "Enter" || key.code == "NumpadEnter" {
107 submit();
108 return true;
109 }
110 false
111 });
112
113 let css = ¶ms.css + ¶ms.add_css;
114 let line_css = ¶ms.line_css + ¶ms.line_add_css;
115 let submit_css = ¶ms.submit_css + ¶ms.submit_add_css;
116 let error_message = params.error_message.clone();
117 let waiting_label = params.waiting_label.clone();
118
119 let message_div = bind!(
120 line_css,
121 token_result.render_value(move |token_result| {
122 let css_error = line_css.clone().push_str("color: red;");
123
124 match token_result {
125 Some(Resource::Loading) => dom! {
126 <div css={line_css.clone()}>{&waiting_label}</div>
127 },
128 Some(Resource::Error(err)) => dom! {
129 <div css={css_error}>{error_message(err)}</div>
130 },
131 _ => dom! {
132 <div css={line_css.clone()} />
133 },
134 }
135 })
136 );
137
138 let username_div = match ¶ms.render_username {
139 Some(render) => render(username.clone()),
140 None => {
141 let on_username_change = bind!(username, |new_value: String| username.set(new_value));
142 dom! {
143 <div css={line_css.clone()}>
144 <div>{¶ms.username_label}</div>
145 <input
146 css={¶ms.input_css}
147 value={username.to_computed()}
148 on_input={on_username_change}
149 {..input_attrs.clone()}
150 />
151 </div>
152 }
153 }
154 };
155
156 let password_div = match ¶ms.render_password {
157 Some(render) => render(password.clone()),
158 None => {
159 let on_password_change = bind!(password, |new_value| password.set(new_value));
160 dom! {
161 <div css={line_css}>
162 <div>{¶ms.password_label}</div>
163 <input
164 css={¶ms.input_css}
165 value={password.to_computed()}
166 on_input={on_password_change}
167 type="password"
168 {..input_attrs}
169 />
170 </div>
171 }
172 }
173 };
174
175 let submit_div = match ¶ms.render_submit {
176 Some(render) => render(submit.clone()),
177 None => dom! {
178 <div css={submit_css}>
179 <input
180 type="submit"
181 value={¶ms.button_label}
182 on_click={move |_| submit()}
183 {..submit_attrs}
184 />
185 </div>
186 },
187 };
188
189 let header = params.header;
190 let footer = params.footer;
191
192 dom! {
193 <div css={css} {on_key_down}>
194 { ..header }
195 { message_div }
196 { username_div }
197 { password_div }
198 { submit_div }
199 { ..footer }
200 </div>
201 }
202}