User management completed. Leptos upgraded to 0.5.0.
parent
a7188e8153
commit
e7af2d402d
@ -1,44 +1,18 @@
|
||||
use leptos::*;
|
||||
use crate::locales::trl;
|
||||
use crate::pages::company_info::CompanyInfo;
|
||||
use crate::pages::users::Users;
|
||||
|
||||
#[component]
|
||||
pub fn Settings() -> impl IntoView {
|
||||
view! {
|
||||
<h1>{trl("Settings")}</h1>
|
||||
<div class="row mb-5">
|
||||
<div class="col-md-6 col-lg-4 mb-3">
|
||||
<div class="col-md">
|
||||
<CompanyInfo/>
|
||||
</div>
|
||||
<div class="col-md-6 col-lg-4 mb-3">
|
||||
<div class="card h-100">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">"Card title"</h5>
|
||||
<h6 class="card-subtitle text-muted">"Support card subtitle"</h6>
|
||||
</div>
|
||||
<img class="img-fluid" src="../assets/img/elements/13.jpg" alt="Card image cap" />
|
||||
<div class="card-body">
|
||||
<p class="card-text">"Bear claw sesame snaps gummies chocolate."</p>
|
||||
<a href="javascript:void(0);" class="card-link">"Card link"</a>
|
||||
<a href="javascript:void(0);" class="card-link">"Another link"</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6 col-lg-4 mb-3">
|
||||
<div class="card h-100">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">"Card title"</h5>
|
||||
<h6 class="card-subtitle text-muted">"Support card subtitle"</h6>
|
||||
<img
|
||||
class="img-fluid d-flex mx-auto my-4"
|
||||
src="../assets/img/elements/4.jpg"
|
||||
alt="Card image cap"
|
||||
/>
|
||||
<p class="card-text">"Bear claw sesame snaps gummies chocolate."</p>
|
||||
<a href="javascript:void(0);" class="card-link">Card link</a>
|
||||
<a href="javascript:void(0);" class="card-link">Another link</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md">
|
||||
<Users/>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
@ -0,0 +1,17 @@
|
||||
use leptos::*;
|
||||
use crate::backend::data::User;
|
||||
use crate::backend::user::DeleteUser;
|
||||
use crate::components::data_form::QuestionDialog;
|
||||
use crate::components::modal_box::DialogOpener;
|
||||
|
||||
#[component]
|
||||
pub fn user_delete(user: ReadSignal<User>, opener: DialogOpener) -> impl IntoView {
|
||||
let del_user = create_server_action::<DeleteUser>();
|
||||
|
||||
view! {
|
||||
<QuestionDialog opener=opener action=del_user title="Delete user">
|
||||
<input type="hidden" prop:value={move || user.get().id()} name="id"/>
|
||||
<div>"Are you sure you want to delete user "{move || user.get().full_name}"?"</div>
|
||||
</QuestionDialog>
|
||||
}
|
||||
}
|
@ -0,0 +1,101 @@
|
||||
use leptos::*;
|
||||
use crate::backend::user::CreateUser;
|
||||
use crate::components::data_form::DataForm;
|
||||
use crate::components::modal_box::DialogOpener;
|
||||
|
||||
#[component]
|
||||
pub fn user_edit(opener: DialogOpener) -> impl IntoView {
|
||||
let create_usr = create_server_action::<CreateUser>();
|
||||
view! {
|
||||
<DataForm opener=opener action=create_usr title="Create user">
|
||||
//<input type="hidden" value={move || company.get().id()} name="company[id]"/>
|
||||
<div class="row">
|
||||
<div class="col mb-3">
|
||||
<label for="username" class="form-label">"Username"</label>
|
||||
<input
|
||||
type="text"
|
||||
id="username"
|
||||
class="form-control"
|
||||
placeholder="Enter username"
|
||||
prop:value={move || opener.empty()}
|
||||
name="user[login]"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col mb-3">
|
||||
<label for="password" class="form-label">"Password"</label>
|
||||
<input
|
||||
type="password"
|
||||
id="password"
|
||||
class="form-control"
|
||||
prop:value={move || opener.empty()}
|
||||
name="user[password]"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col mb-3">
|
||||
<label for="passwordVer" class="form-label">"Verify password"</label>
|
||||
<input
|
||||
type="password"
|
||||
id="passwordVer"
|
||||
class="form-control"
|
||||
prop:value={move || opener.empty()}
|
||||
name="user[password_ver]"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col mb-3">
|
||||
<label for="fullName" class="form-label">"Full name"</label>
|
||||
<input
|
||||
type="text"
|
||||
id="fullName"
|
||||
class="form-control"
|
||||
placeholder="Enter full name"
|
||||
prop:value={move || opener.empty()}
|
||||
name="user[full_name]"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col mb-3">
|
||||
<label for="email" class="form-label">"Email"</label>
|
||||
<input
|
||||
type="text"
|
||||
id="email"
|
||||
class="form-control"
|
||||
placeholder="Enter email"
|
||||
prop:value={move || opener.empty()}
|
||||
name="user[email]"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col mb-3">
|
||||
<input
|
||||
type="checkbox"
|
||||
id="admin"
|
||||
class="form-check-input"
|
||||
prop:checked={move || opener.not_checked()}
|
||||
name="user[admin]"
|
||||
/>
|
||||
<label for="admin" class="form-label">"Admin"</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col mb-3">
|
||||
<input
|
||||
type="checkbox"
|
||||
id="getEmails"
|
||||
class="form-check-input"
|
||||
prop:checked={move || opener.not_checked()}
|
||||
name="user[get_emails]"
|
||||
/>
|
||||
<label for="geEmails" class="form-label">"Get emails"</label>
|
||||
</div>
|
||||
</div>
|
||||
</DataForm>
|
||||
}
|
||||
}
|
@ -0,0 +1,116 @@
|
||||
use leptos::*;
|
||||
use crate::backend::data::{ApiResponse, User};
|
||||
use crate::backend::user::get_users;
|
||||
use crate::components::modal_box::DialogOpener;
|
||||
use crate::components::user_menu::MenuOpener;
|
||||
use crate::locales::trl;
|
||||
use crate::pages::change_pwd::ChangePassword;
|
||||
use crate::pages::profile_edit::ProfileEdit;
|
||||
use crate::pages::user_delete::UserDelete;
|
||||
use crate::pages::user_edit::UserEdit;
|
||||
|
||||
#[component]
|
||||
pub fn users() -> impl IntoView {
|
||||
let editor = DialogOpener::new();
|
||||
let profile_editor = DialogOpener::new();
|
||||
let pwd_dialog = DialogOpener::new();
|
||||
let delete_dialog = DialogOpener::new();
|
||||
let users = create_blocking_resource(
|
||||
move || editor.visible() || profile_editor.visible() || delete_dialog.visible(), move |_| {get_users()});
|
||||
let (usr, set_usr) = create_signal::<Vec<User>>(vec![]);
|
||||
let (profile, set_profile) = create_signal(User::default());
|
||||
|
||||
view! {
|
||||
<UserEdit opener=editor/>
|
||||
<ProfileEdit user=profile opener=profile_editor/>
|
||||
<ChangePassword opener=pwd_dialog user=profile/>
|
||||
<UserDelete opener=delete_dialog user=profile/>
|
||||
<div class="card mb-3">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title"><i class="bx bx-user"></i>" "{trl("Users")}</h5>
|
||||
<Transition fallback=move || view! {<p>{trl("Loading...")}</p> }>
|
||||
<table class="table card-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{trl("Username")}</th>
|
||||
<th>{trl("Full name")}</th>
|
||||
<th>{trl("Admin")}</th>
|
||||
<th>{trl("Actions")}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
{move || {
|
||||
users.get().map(|u| match u {
|
||||
Err(e) => {
|
||||
let err = if e.to_string().contains("403") {
|
||||
"Only admin can edit users".to_string()
|
||||
} else {
|
||||
e.to_string()
|
||||
};
|
||||
view! {<tbody class="table-border-bottom-0">
|
||||
<tr><td colspan=4>{trl(&err)}</td></tr></tbody>}}
|
||||
Ok(u) => {
|
||||
match u {
|
||||
ApiResponse::Data(u) => {
|
||||
set_usr.update(|users| *users = u.clone());
|
||||
view! {<tbody class="table-border-bottom-0">
|
||||
<For each=move || usr.get()
|
||||
key=|user| user.id()
|
||||
children=move |user: User| {
|
||||
let menu = MenuOpener::new();
|
||||
let user_profile = user.clone();
|
||||
let user_passwd = user.clone();
|
||||
let user_delete = user.clone();
|
||||
view! {
|
||||
<tr>
|
||||
<td>{&user.login}</td>
|
||||
<td>{&user.full_name.unwrap_or("".to_string())}</td>
|
||||
<td>{if user.admin {view! {<i class="bx bx-check"></i>}}
|
||||
else {view! {<i></i>}}}</td>
|
||||
<td>
|
||||
<div class="dropdown">
|
||||
<button type="button" class="btn p-0 dropdown-toggle hide-arrow"
|
||||
on:click=move |_| menu.toggle()>
|
||||
<i class="bx bx-dots-vertical-rounded"></i>
|
||||
</button>
|
||||
<div class={move || if menu.visible() {"dropdown-menu show"} else {"dropdown-menu"} }
|
||||
style="position: absolute; insert: 0px 0px auto; margin: 0px; transform: translate3d(-160px, 0px, 0px);"
|
||||
on:mouseleave=move |_| menu.toggle()>
|
||||
<a class="dropdown-item" href="javascript:void(0);" on:click=move |_| {
|
||||
set_profile.set(user_profile.clone());
|
||||
profile_editor.show();
|
||||
}>
|
||||
<i class="bx bx-edit-alt me-1"></i> {trl("Edit")}</a>
|
||||
<a class="dropdown-item" href="javascript:void(0);" on:click=move |_| {
|
||||
set_profile.set(user_passwd.clone());
|
||||
pwd_dialog.show();
|
||||
}>
|
||||
<i class="bx bx-lock me-1"></i> {trl("Change password")}</a>
|
||||
<a class="dropdown-item" href="javascript:void(0);" on:click=move |_| {
|
||||
set_profile.set(user_delete.clone());
|
||||
delete_dialog.show();
|
||||
}>
|
||||
<i class="bx bx-trash me-1"></i> {trl("Delete")}</a>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
}/></tbody>
|
||||
}
|
||||
}
|
||||
ApiResponse::Error(e) => {view! {<tbody class="table-border-bottom-0">
|
||||
<tr><td colspan=4>{trl(&e)}</td></tr></tbody>}}
|
||||
}
|
||||
}
|
||||
})
|
||||
}}
|
||||
</table>
|
||||
</Transition>
|
||||
<a href="#" class="card-link" on:click=move |_| editor.show()>
|
||||
<i class="bx bx-plus-circle fs-4 lh-0"></i>
|
||||
</a>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue