Login for the wasm interface
This commit is contained in:
+105
-16
@@ -4,7 +4,12 @@ pub mod pages;
|
||||
|
||||
use pages::list_links;
|
||||
use pages::list_users;
|
||||
use seed::attrs;
|
||||
use seed::button;
|
||||
use seed::input;
|
||||
use seed::label;
|
||||
use seed::{div, log, prelude::*, App, Url, C};
|
||||
use shared::apirequests::users::LoginUser;
|
||||
use shared::datatypes::User;
|
||||
|
||||
use crate::i18n::{I18n, Lang};
|
||||
@@ -27,6 +32,8 @@ fn init(url: Url, orders: &mut impl Orders<Msg>) -> Model {
|
||||
page: Page::init(url, orders, lang.clone()),
|
||||
i18n: lang,
|
||||
user: None,
|
||||
login_form: LoginForm::default(),
|
||||
login_data: LoginUser::default(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,6 +48,14 @@ struct Model {
|
||||
page: Page,
|
||||
i18n: i18n::I18n,
|
||||
user: Option<User>,
|
||||
login_form: LoginForm,
|
||||
login_data: LoginUser,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug)]
|
||||
struct LoginForm {
|
||||
username: ElRef<web_sys::HtmlInputElement>,
|
||||
password: ElRef<web_sys::HtmlInputElement>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
@@ -83,6 +98,10 @@ pub enum Msg {
|
||||
GetLoggedUser,
|
||||
UserReceived(User),
|
||||
NoMessage,
|
||||
NotAuthenticated,
|
||||
Login,
|
||||
UsernameChanged(String),
|
||||
PasswordChanged(String),
|
||||
}
|
||||
|
||||
fn update(msg: Msg, model: &mut Model, orders: &mut impl Orders<Msg>) {
|
||||
@@ -102,31 +121,62 @@ fn update(msg: Msg, model: &mut Model, orders: &mut impl Orders<Msg>) {
|
||||
}
|
||||
Msg::NoMessage => (),
|
||||
Msg::GetLoggedUser => {
|
||||
orders.skip(); // No need to rerender/ complicated way to move into the closure
|
||||
orders.skip(); // No need to rerender
|
||||
orders.perform_cmd(async {
|
||||
let response = fetch(
|
||||
// create request
|
||||
let request = unwrap_or_return!(
|
||||
Request::new("/admin/json/get_logged_user/")
|
||||
.method(Method::Post)
|
||||
.json(&())
|
||||
.expect("serialization failed"),
|
||||
)
|
||||
.await
|
||||
.expect("HTTP request failed");
|
||||
|
||||
let user: User = response
|
||||
.check_status() // ensure we've got 2xx status
|
||||
.expect("status check failed")
|
||||
.json()
|
||||
.await
|
||||
.expect("deserialization failed");
|
||||
.json(&()),
|
||||
Msg::NotAuthenticated
|
||||
);
|
||||
// perform and get response
|
||||
let response = unwrap_or_return!(fetch(request).await, Msg::NotAuthenticated);
|
||||
// validate response status
|
||||
let response = unwrap_or_return!(response.check_status(), Msg::NotAuthenticated);
|
||||
let user: User = unwrap_or_return!(response.json().await, Msg::NotAuthenticated);
|
||||
|
||||
Msg::UserReceived(user)
|
||||
});
|
||||
}
|
||||
Msg::UserReceived(user) => model.user = Some(user),
|
||||
Msg::NotAuthenticated => {if model.user.is_some() {model.user = None; logout(orders)}},
|
||||
Msg::Login => {login_user(model, orders)}
|
||||
Msg::UsernameChanged(s) => model.login_data.username = s,
|
||||
Msg::PasswordChanged(s) => model.login_data.password = s,
|
||||
}
|
||||
}
|
||||
|
||||
fn logout(orders: &mut impl Orders<Msg>) {
|
||||
orders.perform_cmd(async {let request = Request::new("/admin/logout/");
|
||||
unwrap_or_return!(fetch(request).await, Msg::GetLoggedUser);
|
||||
Msg::NotAuthenticated});
|
||||
|
||||
}
|
||||
|
||||
fn login_user(model: &mut Model, orders: &mut impl Orders<Msg>) {
|
||||
orders.skip(); // No need to rerender
|
||||
let data = model.login_data.clone();
|
||||
|
||||
orders.perform_cmd(async {
|
||||
let data = data;
|
||||
// create request
|
||||
let request = unwrap_or_return!(
|
||||
Request::new("/admin/json/login_user/")
|
||||
.method(Method::Post)
|
||||
.json(&data),
|
||||
Msg::NotAuthenticated
|
||||
);
|
||||
// perform and get response
|
||||
let response = unwrap_or_return!(fetch(request).await, Msg::NotAuthenticated);
|
||||
// validate response status
|
||||
let response = unwrap_or_return!(response.check_status(), Msg::NotAuthenticated);
|
||||
let user: User = unwrap_or_return!(response.json().await, Msg::NotAuthenticated);
|
||||
|
||||
Msg::UserReceived(user)
|
||||
});
|
||||
}
|
||||
|
||||
pub struct Urls<'a> {
|
||||
base_url: std::borrow::Cow<'a, Url>,
|
||||
}
|
||||
@@ -188,8 +238,14 @@ impl<'a> Urls<'a> {
|
||||
fn view(model: &Model) -> Node<Msg> {
|
||||
div![
|
||||
C!["page"],
|
||||
navigation::navigation(&model.i18n, &model.base_url, &model.user),
|
||||
view_content(&model.page, &model.base_url),
|
||||
if let Some(user) = &model.user {
|
||||
div![
|
||||
navigation::navigation(&model.i18n, &model.base_url, user),
|
||||
view_content(&model.page, &model.base_url)
|
||||
]
|
||||
} else {
|
||||
view_login(&model.i18n, &model)
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -205,6 +261,39 @@ fn view_content(page: &Page, url: &Url) -> Node<Msg> {
|
||||
]
|
||||
}
|
||||
|
||||
fn view_login(lang: &I18n, model: &Model) -> Node<Msg> {
|
||||
let t = move |key: &str| lang.translate(key, None);
|
||||
|
||||
div![
|
||||
C!["center", "login"],
|
||||
div![
|
||||
label![t("username")],
|
||||
input![
|
||||
input_ev(Ev::Input, |s| { Msg::UsernameChanged(s) }),
|
||||
attrs![
|
||||
At::Type => "text",
|
||||
At::Placeholder => t("username"),
|
||||
At::Name => "username",
|
||||
At::Value => model.login_data.username],
|
||||
el_ref(&model.login_form.username)
|
||||
]
|
||||
],
|
||||
div![
|
||||
label![t("password")],
|
||||
input![
|
||||
input_ev(Ev::Input, |s| { Msg::PasswordChanged(s) }),
|
||||
attrs![
|
||||
At::Type => "password",
|
||||
At::Placeholder => t("password"),
|
||||
At::Name => "password",
|
||||
At::Value => model.login_data.password],
|
||||
el_ref(&model.login_form.password)
|
||||
]
|
||||
],
|
||||
button![t("login"), ev(Ev::Click, |_| Msg::Login)]
|
||||
]
|
||||
}
|
||||
|
||||
// ------ ------
|
||||
// Start
|
||||
// ------ ------
|
||||
|
||||
+7
-14
@@ -5,21 +5,17 @@ use shared::datatypes::User;
|
||||
use crate::{i18n::I18n, Msg};
|
||||
|
||||
/// Generate the top navigation menu of all pages.
|
||||
///
|
||||
///
|
||||
/// The menu options are translated using the i18n module.
|
||||
#[must_use]
|
||||
pub fn navigation(i18n: &I18n, base_url: &Url, user: &Option<User>) -> Node<Msg> {
|
||||
pub fn navigation(i18n: &I18n, base_url: &Url, user: &User) -> Node<Msg> {
|
||||
// A shortcut for translating strings.
|
||||
let t = move |key: &str| i18n.translate(key, None);
|
||||
// Translate the wellcome message
|
||||
let welcome = if let Some(user) = user {
|
||||
i18n.translate(
|
||||
"welcome-user",
|
||||
Some(&fluent_args![ "username" => user.username.clone()]),
|
||||
)
|
||||
} else {
|
||||
t("welcome")
|
||||
};
|
||||
let welcome = i18n.translate(
|
||||
"welcome-user",
|
||||
Some(&fluent_args![ "username" => user.username.clone()]),
|
||||
);
|
||||
nav![
|
||||
ol![
|
||||
// A button for the homepage, the list of URLs
|
||||
@@ -57,10 +53,7 @@ pub fn navigation(i18n: &I18n, base_url: &Url, user: &Option<User>) -> Node<Msg>
|
||||
// The Welcome message
|
||||
li![div![welcome]],
|
||||
// The logout button
|
||||
li![a![
|
||||
attrs! {At::Href => "/admin/logout/"},
|
||||
t("logout"),
|
||||
]]
|
||||
li![a![ev(Ev::Click, |_| Msg::NotAuthenticated), t("logout"),]]
|
||||
]
|
||||
]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user