initial create user

This commit is contained in:
2021-08-12 15:48:02 +02:00
committed by dietrich
parent b9866a8c19
commit 6837495eba
16 changed files with 650 additions and 288 deletions
+7 -3
View File
@@ -49,13 +49,13 @@ enum Page {
impl Page {
fn init(mut url: Url, orders: &mut impl Orders<Msg>, i18n: I18n) -> Self {
url.next_path_part();
let result = match url.remaining_path_parts().as_slice() {
[] | ["list_links"] => Self::Home(pages::list_links::init(
let result = match url.next_path_part() {
None | Some("list_links") => Self::Home(pages::list_links::init(
url,
&mut orders.proxy(Msg::ListLinksMsg),
i18n,
)),
["list_users"] => Self::ListUsers(pages::list_users::init(
Some("list_users") => Self::ListUsers(pages::list_users::init(
url,
&mut orders.proxy(Msg::ListUsersMsg),
i18n,
@@ -144,6 +144,10 @@ impl<'a> Urls<'a> {
pub fn list_users(self) -> Url {
self.base_url().add_path_part("list_users")
}
#[must_use]
pub fn create_user(self) -> Url {
self.list_users().add_path_part("create_user")
}
}
// ------ ------
+6 -2
View File
@@ -25,8 +25,12 @@ pub fn navigation(i18n: &I18n, base_url: &Url) -> Node<Msg> {
],],
li![a![ev(Ev::Click, |_| Msg::NoMessage), t!("add-link"),],],
li![a![
attrs! {At::Href => "#"},
ev(Ev::Click, |_| Msg::NoMessage),
attrs! {At::Href => crate::Urls::new(base_url).create_user()},
ev(Ev::Click, |_| Msg::ListUsersMsg(
super::pages::list_users::Msg::Edit(
super::pages::list_users::UserEditMsg::CreateNewUser
)
)),
t!("invite-user"),
],],
li![a![
+259 -38
View File
@@ -1,21 +1,33 @@
use std::cell::RefCell;
use enum_map::EnumMap;
use seed::{attrs, button, h1, input, log, prelude::*, section, table, td, th, tr, Url, C};
use seed::{
a, attrs, button, div, h1, input, log, p, prelude::*, section, table, td, th, tr, Url, C, IF,
};
use shared::{
apirequests::general::{Operation, Ordering},
apirequests::users::{UserOverviewColumns, UserRequestForm},
apirequests::{
general::{EditMode, SuccessMessage},
users::{UserDelta, UserOverviewColumns, UserRequestForm},
},
datatypes::User,
};
use crate::i18n::I18n;
#[must_use]
pub fn init(_: Url, orders: &mut impl Orders<Msg>, i18n: I18n) -> Model {
orders.send_msg(Msg::Fetch);
pub fn init(mut url: Url, orders: &mut impl Orders<Msg>, i18n: I18n) -> Model {
orders.send_msg(Msg::Query(UserQueryMsg::Fetch));
let user_edit = match url.next_path_part() {
Some("create_user") => Some(RefCell::new(UserDelta::default())),
None | Some(_) => None,
};
Model {
users: Vec::new(),
i18n,
formconfig: UserRequestForm::default(),
inputs: EnumMap::default(),
user_edit: None,
user_edit,
last_message: None,
}
}
#[derive(Debug)]
@@ -24,13 +36,8 @@ pub struct Model {
i18n: I18n,
formconfig: UserRequestForm,
inputs: EnumMap<UserOverviewColumns, FilterInput>,
user_edit: Option<UserEditMode>,
}
#[derive(Debug)]
enum UserEditMode {
Create { user: User },
Edit { user: User },
user_edit: Option<RefCell<UserDelta>>,
last_message: Option<SuccessMessage>,
}
#[derive(Default, Debug, Clone)]
@@ -40,32 +47,61 @@ struct FilterInput {
#[derive(Clone)]
pub enum Msg {
Query(UserQueryMsg),
Edit(UserEditMsg),
}
/// All the messages on user Querying
#[derive(Clone)]
pub enum UserQueryMsg {
Fetch,
FailedToFetchUsers,
OrderBy(UserOverviewColumns),
Received(Vec<User>),
IdFilterChanged(String),
EmailFilterChanged(String),
UsernameFilterChanged(String),
EditUserSelected(User),
}
/// All the messages on user editing
#[derive(Clone)]
pub enum UserEditMsg {
EditUserSelected(UserDelta),
CreateNewUser,
UserCreated(SuccessMessage),
EditUsernameChanged(String),
EditEmailChanged(String),
EditPasswordChanged(String),
SaveUser,
FailedToCreateUser,
}
/// # Panics
/// Sould only panic on bugs.
pub fn update(msg: Msg, model: &mut Model, orders: &mut impl Orders<Msg>) {
match msg {
Msg::Fetch => {
Msg::Query(msg) => process_query_messages(msg, model, orders),
Msg::Edit(msg) => process_user_edit_messages(msg, model, orders),
}
}
pub fn process_query_messages(msg: UserQueryMsg, model: &mut Model, orders: &mut impl Orders<Msg>) {
match msg {
UserQueryMsg::Fetch => {
orders.skip(); // No need to rerender
let data = model.formconfig.clone(); // complicated way to move into the closure
orders.perform_cmd(async {
let data = data;
let response = fetch(
let response = match fetch(
Request::new("/admin/json/list_users/")
.method(Method::Post)
.json(&data)
.expect("serialization failed"),
)
.await
.expect("HTTP request failed");
{
Ok(response) => response,
Err(_) => return Msg::Query(UserQueryMsg::FailedToFetchUsers),
};
let users: Vec<User> = response
.check_status() // ensure we've got 2xx status
@@ -74,10 +110,10 @@ pub fn update(msg: Msg, model: &mut Model, orders: &mut impl Orders<Msg>) {
.await
.expect("deserialization failed");
Msg::Received(users)
Msg::Query(UserQueryMsg::Received(users))
});
}
Msg::OrderBy(column) => {
UserQueryMsg::OrderBy(column) => {
model.formconfig.order = model.formconfig.order.as_ref().map_or_else(
|| {
Some(Operation {
@@ -96,7 +132,7 @@ pub fn update(msg: Msg, model: &mut Model, orders: &mut impl Orders<Msg>) {
})
},
);
orders.send_msg(Msg::Fetch);
orders.send_msg(Msg::Query(UserQueryMsg::Fetch));
model.users.sort_by(match column {
UserOverviewColumns::Id => |o: &User, t: &User| o.id.cmp(&t.id),
@@ -104,34 +140,115 @@ pub fn update(msg: Msg, model: &mut Model, orders: &mut impl Orders<Msg>) {
UserOverviewColumns::Email => |o: &User, t: &User| o.email.cmp(&t.email),
})
}
Msg::Received(response) => {
UserQueryMsg::Received(response) => {
model.users = response;
}
Msg::IdFilterChanged(s) => {
UserQueryMsg::IdFilterChanged(s) => {
log!("Filter is: ", &s);
let sanit = s.chars().filter(|x| x.is_numeric()).collect();
model.formconfig.filter[UserOverviewColumns::Id].sieve = sanit;
orders.send_msg(Msg::Fetch);
orders.send_msg(Msg::Query(UserQueryMsg::Fetch));
}
Msg::UsernameFilterChanged(s) => {
UserQueryMsg::UsernameFilterChanged(s) => {
log!("Filter is: ", &s);
let sanit = s.chars().filter(|x| x.is_alphanumeric()).collect();
model.formconfig.filter[UserOverviewColumns::Username].sieve = sanit;
orders.send_msg(Msg::Fetch);
orders.send_msg(Msg::Query(UserQueryMsg::Fetch));
}
Msg::EmailFilterChanged(s) => {
UserQueryMsg::EmailFilterChanged(s) => {
log!("Filter is: ", &s);
// FIXME: Sanitazion does not work for @
let sanit = s.chars().filter(|x| x.is_alphanumeric()).collect();
model.formconfig.filter[UserOverviewColumns::Email].sieve = sanit;
orders.send_msg(Msg::Fetch);
orders.send_msg(Msg::Query(UserQueryMsg::Fetch));
}
Msg::EditUserSelected(user) => {
log!("Editing user: ", user);
model.user_edit = Some(UserEditMode::Edit { user })
UserQueryMsg::FailedToFetchUsers => {
log!("Failed to fetch users");
}
}
}
pub fn process_user_edit_messages(
msg: UserEditMsg,
model: &mut Model,
orders: &mut impl Orders<Msg>,
) {
match msg {
UserEditMsg::EditUserSelected(user) => {
log!("Editing user: ", user);
model.user_edit = Some(RefCell::new(user))
}
UserEditMsg::CreateNewUser => {
log!("Creating new user");
model.user_edit = Some(RefCell::new(UserDelta::default()))
}
UserEditMsg::EditUsernameChanged(s) => {
log!("New Username is: ", &s);
if let Some(ref ue) = model.user_edit {
ue.try_borrow_mut()
.expect("Failed to borrow mutably")
.username = s;
};
}
UserEditMsg::EditEmailChanged(s) => {
log!("New Email is: ", &s);
if let Some(ref ue) = model.user_edit {
ue.try_borrow_mut().expect("Failed to borrow mutably").email = s;
};
}
UserEditMsg::EditPasswordChanged(s) => {
log!("New Password is: ", &s);
if let Some(ref ue) = model.user_edit {
ue.try_borrow_mut()
.expect("Failed to borrow mutably")
.password = Some(s);
};
}
UserEditMsg::SaveUser => {
let data = model
.user_edit
.as_ref()
.expect("Somehow a user should exist!")
.borrow()
.clone(); // complicated way to move into the closure
log!("Saving User: ", &data.username);
orders.perform_cmd(async {
let data = data;
let response = match fetch(
Request::new("/admin/json/create_user/")
.method(Method::Post)
.json(&data)
.expect("serialization failed"),
)
.await
{
Ok(response) => response,
Err(_) => return Msg::Edit(UserEditMsg::FailedToCreateUser),
};
let message: SuccessMessage = response
.check_status() // ensure we've got 2xx status
.expect("status check failed")
.json()
.await
.expect("deserialization failed");
Msg::Edit(UserEditMsg::UserCreated(message))
});
}
UserEditMsg::FailedToCreateUser => {
log!("Failed to create user");
}
UserEditMsg::UserCreated(u) => {
log!(u, "created user");
model.last_message = Some(u);
model.user_edit = None;
orders.send_msg(Msg::Query(UserQueryMsg::Fetch));
}
}
}
#[must_use]
/// # Panics
/// Sould only panic on bugs.
@@ -140,6 +257,11 @@ pub fn view(model: &Model) -> Node<Msg> {
let t = move |key: &str| lang.translate(key, None);
section![
h1!("List Users Page from list_users"),
if let Some(message) = &model.last_message {
div![C!("Message"), &message.message]
} else {
section!()
},
table![
// Column Headlines
view_user_table_head(&t),
@@ -148,22 +270,36 @@ pub fn view(model: &Model) -> Node<Msg> {
// Add all the users one line for each
model.users.iter().map(view_user)
],
button![ev(Ev::Click, |_| Msg::Fetch), "Refresh"]
button![
ev(Ev::Click, |_| Msg::Query(UserQueryMsg::Fetch)),
"Refresh"
],
if let Some(l) = &model.user_edit {
edit_or_create_user(l, t)
} else {
section!()
},
]
}
fn view_user_table_head<F: Fn(&str) -> String>(t: F) -> Node<Msg> {
tr![
th![
ev(Ev::Click, |_| Msg::OrderBy(UserOverviewColumns::Id)),
ev(Ev::Click, |_| Msg::Query(UserQueryMsg::OrderBy(
UserOverviewColumns::Id
))),
t("userid")
],
th![
ev(Ev::Click, |_| Msg::OrderBy(UserOverviewColumns::Email)),
ev(Ev::Click, |_| Msg::Query(UserQueryMsg::OrderBy(
UserOverviewColumns::Email
))),
t("email")
],
th![
ev(Ev::Click, |_| Msg::OrderBy(UserOverviewColumns::Username)),
ev(Ev::Click, |_| Msg::Query(UserQueryMsg::OrderBy(
UserOverviewColumns::Username
))),
t("username")
],
]
@@ -178,7 +314,9 @@ fn view_user_table_filter_input<F: Fn(&str) -> String>(model: &Model, t: F) -> N
At::Type => "search",
At::Placeholder => t("search-placeholder")
},
input_ev(Ev::Input, Msg::IdFilterChanged),
input_ev(Ev::Input, |s| {
Msg::Query(UserQueryMsg::IdFilterChanged(s))
}),
el_ref(&model.inputs[UserOverviewColumns::Id].filter_input),
]],
td![input![
@@ -188,7 +326,9 @@ fn view_user_table_filter_input<F: Fn(&str) -> String>(model: &Model, t: F) -> N
At::Type => "search",
At::Placeholder => t("search-placeholder")
},
input_ev(Ev::Input, Msg::EmailFilterChanged),
input_ev(Ev::Input, |s| {
Msg::Query(UserQueryMsg::EmailFilterChanged(s))
}),
el_ref(&model.inputs[UserOverviewColumns::Email].filter_input),
]],
td![input![
@@ -198,19 +338,100 @@ fn view_user_table_filter_input<F: Fn(&str) -> String>(model: &Model, t: F) -> N
At::Type => "search",
At::Placeholder => t("search-placeholder")
},
input_ev(Ev::Input, Msg::UsernameFilterChanged),
input_ev(Ev::Input, |s| {
Msg::Query(UserQueryMsg::UsernameFilterChanged(s))
}),
el_ref(&model.inputs[UserOverviewColumns::Username].filter_input),
]],
]
}
fn view_user(l: &User) -> Node<Msg> {
let user = l.clone();
let user = UserDelta::from(l.clone());
tr![
ev(Ev::Click, |_| Msg::EditUserSelected(user)),
ev(Ev::Click, |_| Msg::Edit(UserEditMsg::EditUserSelected(
user
))),
td![&l.id],
td![&l.email],
//td![a![attrs![At::Href => &l.link.target], &l.link.target]],
td![&l.username],
]
}
fn edit_or_create_user<F: Fn(&str) -> String>(l: &RefCell<UserDelta>, t: F) -> Node<Msg> {
let user = l.borrow();
div![
C!["editdialog", "center"],
h1![match &user.edit {
EditMode::Edit => t("edit-user"),
EditMode::Create => t("new-user"),
}],
table![
tr![
th![
ev(Ev::Click, |_| Msg::Query(UserQueryMsg::OrderBy(
UserOverviewColumns::Username
))),
t("username")
],
td![input![
attrs! {
At::Value => &user.username,
At::Type => "text",
At::Placeholder => t("username")
},
input_ev(Ev::Input, |s| {
Msg::Edit(UserEditMsg::EditUsernameChanged(s))
}),
]]
],
tr![
th![
ev(Ev::Click, |_| Msg::Query(UserQueryMsg::OrderBy(
UserOverviewColumns::Email
))),
t("email")
],
td![input![
attrs! {
At::Value => &user.email,
At::Type => "email",
At::Placeholder => t("email")
},
input_ev(Ev::Input, |s| {
Msg::Edit(UserEditMsg::EditEmailChanged(s))
}),
]]
],
tr![
th![
ev(Ev::Click, |_| Msg::Query(UserQueryMsg::OrderBy(
UserOverviewColumns::Email
))),
t("password")
],
td![
input![
attrs! {
At::Type => "password",
At::Placeholder => t("password")
},
input_ev(Ev::Input, |s| {
Msg::Edit(UserEditMsg::EditPasswordChanged(s))
}),
],
IF!(user.edit == EditMode::Edit => p![t("leave-password-empty-hint")])
]
]
],
a![
match &user.edit {
EditMode::Edit => t("edit-user"),
EditMode::Create => t("create-user"),
},
C!["button"],
ev(Ev::Click, |_| Msg::Edit(UserEditMsg::SaveUser))
]
]
}