initial create user
This commit is contained in:
+1
-4
@@ -19,12 +19,9 @@ crate-type = ["cdylib", "rlib"]
|
||||
fluent = "0.15"
|
||||
seed = "0.8"
|
||||
serde = "1.0"
|
||||
strum_macros = "0.20"
|
||||
unic-langid = "0.9"
|
||||
strum_macros = "0.20"
|
||||
strum = "0.20"
|
||||
enum-map = "1"
|
||||
|
||||
shared = { path = "../shared" }
|
||||
|
||||
[profile.release]
|
||||
opt-level = 'z'
|
||||
@@ -28,9 +28,11 @@ edit-user-headline = Benutzereinstellungen von: {$username}
|
||||
username = Benutzername
|
||||
email = Email
|
||||
password = Passwort
|
||||
password-placeholder = Leer lassen um das Passwort nicht zu ändern
|
||||
leave-password-empty-hint = Leer lassen um das Passwort nicht zu ändern
|
||||
save-user = Benutzer speichern
|
||||
edit-user = Benutzer editieren
|
||||
create-user = Benutzer Erstellen
|
||||
new-user = Neuer Benutzer
|
||||
make-user-admin = Zum Administrator befördern
|
||||
make-user-regular = Zurückstufen zum normalen Nutzer
|
||||
|
||||
|
||||
@@ -28,9 +28,11 @@ edit-user-headline = Change Settings of: {$username}
|
||||
username = Username
|
||||
email = Email
|
||||
password = Password
|
||||
password-placeholder = Leave this empty to keep the current password
|
||||
leave-password-empty-hint = Leave this empty to keep the current password
|
||||
save-user = Save this user
|
||||
edit-user = Edit this user
|
||||
new-user = Neuer Benutzer
|
||||
create-user = Create user
|
||||
make-user-admin = Promote to admin
|
||||
make-user-regular = Demote to regular
|
||||
|
||||
|
||||
+7
-3
@@ -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")
|
||||
}
|
||||
}
|
||||
|
||||
// ------ ------
|
||||
|
||||
@@ -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
@@ -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))
|
||||
]
|
||||
]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user