app: front end improvements

Added colour to required asterisks
    Added padding to the footer
    Added bootstrap class to select elements
    Added various icons to buttons
    Fixed various button layouts
    Reworded some messages
    Moved around some form elements

 Fixes #12
This commit is contained in:
Lewis Larsen 2022-11-03 14:40:02 +00:00 committed by GitHub
parent e0c0efcb2f
commit ba0dc33583
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 168 additions and 98 deletions

View File

@ -169,12 +169,13 @@ impl Component for AddGroupMemberComponent {
}
</Select>
</div>
<div class="col-sm-1">
<div class="col-3">
<button
class="btn btn-success"
class="btn btn-secondary"
disabled=self.selected_user.is_none() || self.common.is_task_running()
onclick=self.common.callback(|_| Msg::SubmitAddMember)>
{"Add"}
<i class="bi-person-plus me-2"></i>
{"Add to group"}
</button>
</div>
</div>

View File

@ -182,12 +182,13 @@ impl Component for AddUserToGroupComponent {
}
</Select>
</div>
<div class="col-sm-1">
<div class="col-sm-3">
<button
class="btn btn-success"
class="btn btn-secondary"
disabled=self.selected_group.is_none() || self.common.is_task_running()
onclick=self.common.callback(|_| Msg::SubmitAddGroup)>
{"Add"}
<i class="bi-person-plus me-2"></i>
{"Add to group"}
</button>
</div>
</div>

View File

@ -98,10 +98,11 @@ impl Component for App {
let link = self.link.clone();
let is_admin = self.is_admin();
html! {
<div class="container shadow-sm py-3">
{self.view_banner()}
<div>
{self.view_banner()}
<div class="container py-3 bg-kug">
<div class="row justify-content-center" style="padding-bottom: 80px;">
<div class="shadow-sm py-3" style="max-width: 1000px">
<div class="py-3" style="max-width: 1000px">
<Router<AppRoute>
render = Router::render(move |s| Self::dispatch_route(s, &link, is_admin))
/>
@ -109,6 +110,7 @@ impl Component for App {
</div>
{self.view_footer()}
</div>
</div>
}
}
}
@ -171,7 +173,10 @@ impl App {
AppRoute::Index | AppRoute::ListUsers => html! {
<div>
<UserTable />
<NavButton classes="btn btn-primary" route=AppRoute::CreateUser>{"Create a user"}</NavButton>
<NavButton classes="btn btn-primary" route=AppRoute::CreateUser>
<i class="bi-person-plus me-2"></i>
{"Create a user"}
</NavButton>
</div>
},
AppRoute::CreateGroup => html! {
@ -180,7 +185,10 @@ impl App {
AppRoute::ListGroups => html! {
<div>
<GroupTable />
<NavButton classes="btn btn-primary" route=AppRoute::CreateGroup>{"Create a group"}</NavButton>
<NavButton classes="btn btn-primary" route=AppRoute::CreateGroup>
<i class="bi-plus-circle me-2"></i>
{"Create a group"}
</NavButton>
</div>
},
AppRoute::GroupDetails(group_id) => html! {
@ -203,11 +211,11 @@ impl App {
fn view_banner(&self) -> Html {
html! {
<header class="p-3 mb-4 border-bottom shadow-sm">
<header class="p-2 mb-3 border-bottom">
<div class="container">
<div class="d-flex flex-wrap align-items-center justify-content-center justify-content-lg-start">
<a href="/" class="d-flex align-items-center mb-2 mb-lg-0 me-md-5 text-dark text-decoration-none">
<h1>{"LLDAP"}</h1>
<a href="/" class="d-flex align-items-center mt-2 mb-lg-0 me-md-5 text-dark text-decoration-none">
<h2>{"LLDAP"}</h2>
</a>
<ul class="nav col-12 col-lg-auto me-lg-auto mb-2 justify-content-center mb-md-0">
@ -215,15 +223,17 @@ impl App {
<>
<li>
<Link
classes="nav-link px-2 link-dark h4"
classes="nav-link px-2 link-dark h6"
route=AppRoute::ListUsers>
<i class="bi-people me-2"></i>
{"Users"}
</Link>
</li>
<li>
<Link
classes="nav-link px-2 link-dark h4"
classes="nav-link px-2 link-dark h6"
route=AppRoute::ListGroups>
<i class="bi-collection me-2"></i>
{"Groups"}
</Link>
</li>
@ -246,6 +256,15 @@ impl App {
<path d="M11 6a3 3 0 1 1-6 0 3 3 0 0 1 6 0z"/>
<path fill-rule="evenodd" d="M0 8a8 8 0 1 1 16 0A8 8 0 0 1 0 8zm8-7a7 7 0 0 0-5.468 11.37C3.242 11.226 4.805 10 8 10s4.757 1.225 5.468 2.37A7 7 0 0 0 8 1z"/>
</svg>
{
if let Some((user_id, _)) = &self.user_info {
html! {
<span class="ms-2">
{user_id}
</span>
}
} else { html!{} }
}
</a>
{if let Some((user_id, _)) = &self.user_info { html! {
<ul
@ -256,7 +275,7 @@ impl App {
<Link
classes="dropdown-item"
route=AppRoute::UserDetails(user_id.clone())>
{"Profile"}
{"View details"}
</Link>
</li>
<li><hr class="dropdown-divider" /></li>
@ -274,7 +293,7 @@ impl App {
fn view_footer(&self) -> Html {
html! {
<footer class="text-center text-muted fixed-bottom bg-light">
<footer class="text-center text-muted fixed-bottom bg-light py-2">
<div>
<span>{format!("LLDAP version {}", env!("CARGO_PKG_VERSION"))}</span>
</div>

View File

@ -220,6 +220,20 @@ impl Component for ChangePasswordForm {
type Field = yew_form::Field<FormModel>;
html! {
<>
<div class="mb-2 mt-2">
<h5 class="fw-bold">
{"Change password"}
</h5>
</div>
{
if let Some(e) = &self.common.error {
html! {
<div class="alert alert-danger mt-3 mb-3">
{e.to_string() }
</div>
}
} else { html! {} }
}
<form
class="form">
{if !is_admin { html! {
@ -243,10 +257,12 @@ impl Component for ChangePasswordForm {
</div>
</div>
}} else { html! {} }}
<div class="form-group row">
<div class="form-group row mb-3">
<label for="new_password"
class="form-label col-sm-2 col-form-label">
{"New password*:"}
{"New Password"}
<span class="text-danger">{"*"}</span>
{":"}
</label>
<div class="col-sm-10">
<Field
@ -263,10 +279,12 @@ impl Component for ChangePasswordForm {
</div>
</div>
</div>
<div class="form-group row">
<div class="form-group row mb-3">
<label for="confirm_password"
class="form-label col-sm-2 col-form-label">
{"Confirm password*:"}
{"Confirm Password"}
<span class="text-danger">{"*"}</span>
{":"}
</label>
<div class="col-sm-10">
<Field
@ -283,31 +301,23 @@ impl Component for ChangePasswordForm {
</div>
</div>
</div>
<div class="form-group row">
<div class="form-group row justify-content-center">
<button
class="btn btn-primary col-sm-1 col-form-label"
class="btn btn-primary col-auto col-form-label"
type="submit"
disabled=self.common.is_task_running()
onclick=self.common.callback(|e: MouseEvent| {e.prevent_default(); Msg::Submit})>
{"Submit"}
<i class="bi-save me-2"></i>
{"Save changes"}
</button>
<NavButton
classes="btn btn-secondary ms-2 col-auto col-form-label"
route=AppRoute::UserDetails(self.common.username.clone())>
<i class="bi-arrow-return-left me-2"></i>
{"Back"}
</NavButton>
</div>
</form>
{ if let Some(e) = &self.common.error {
html! {
<div class="alert alert-danger">
{e.to_string() }
</div>
}
} else { html! {} }
}
<div>
<NavButton
classes="btn btn-primary"
route=AppRoute::UserDetails(self.common.username.clone())>
{"Back"}
</NavButton>
</div>
</>
}
}

View File

@ -100,14 +100,16 @@ impl Component for CreateGroupForm {
type Field = yew_form::Field<CreateGroupModel>;
html! {
<div class="row justify-content-center">
<form class="form shadow-sm py-3" style="max-width: 636px">
<form class="form py-3" style="max-width: 636px">
<div class="row mb-3">
<h5 class="fw-bold">{"Create a group"}</h5>
</div>
<div class="form-group row mb-3">
<label for="groupname"
class="form-label col-4 col-form-label">
{"Group name*:"}
{"Group name"}
<span class="text-danger">{"*"}</span>
{":"}
</label>
<div class="col-8">
<Field
@ -129,6 +131,7 @@ impl Component for CreateGroupForm {
type="submit"
disabled=self.common.is_task_running()
onclick=self.common.callback(|e: MouseEvent| {e.prevent_default(); Msg::SubmitForm})>
<i class="bi-save me-2"></i>
{"Submit"}
</button>
</div>

View File

@ -194,14 +194,16 @@ impl Component for CreateUserForm {
type Field = yew_form::Field<CreateUserModel>;
html! {
<div class="row justify-content-center">
<form class="form shadow-sm py-3" style="max-width: 636px">
<form class="form py-3" style="max-width: 636px">
<div class="row mb-3">
<h5 class="fw-bold">{"Create a user"}</h5>
</div>
<div class="form-group row mb-3">
<label for="username"
class="form-label col-4 col-form-label">
{"User name*:"}
{"User name"}
<span class="text-danger">{"*"}</span>
{":"}
</label>
<div class="col-8">
<Field
@ -220,7 +222,9 @@ impl Component for CreateUserForm {
<div class="form-group row mb-3">
<label for="email"
class="form-label col-4 col-form-label">
{"Email*:"}
{"Email"}
<span class="text-danger">{"*"}</span>
{":"}
</label>
<div class="col-8">
<Field
@ -240,7 +244,9 @@ impl Component for CreateUserForm {
<div class="form-group row mb-3">
<label for="display-name"
class="form-label col-4 col-form-label">
{"Display name*:"}
{"Display name"}
<span class="text-danger">{"*"}</span>
{":"}
</label>
<div class="col-8">
<Field
@ -340,11 +346,13 @@ impl Component for CreateUserForm {
disabled=self.common.is_task_running()
type="submit"
onclick=self.common.callback(|e: MouseEvent| {e.prevent_default(); Msg::SubmitForm})>
<i class="bi-save me-2"></i>
{"Submit"}
</button>
</div>
</form>
{ if let Some(e) = &self.common.error {
{
if let Some(e) = &self.common.error {
html! {
<div class="alert alert-danger">
{e.to_string() }

View File

@ -154,12 +154,16 @@ impl DeleteGroup {
type="button"
class="btn btn-secondary"
onclick=self.common.callback(|_| Msg::DismissModal)>
<i class="bi-x-circle me-2"></i>
{"Cancel"}
</button>
<button
type="button"
onclick=self.common.callback(|_| Msg::ConfirmDeleteGroup)
class="btn btn-danger">{"Yes, I'm sure"}</button>
class="btn btn-danger">
<i class="bi-check-circle me-2"></i>
{"Yes, I'm sure"}
</button>
</div>
</div>
</div>

View File

@ -152,12 +152,16 @@ impl DeleteUser {
type="button"
class="btn btn-secondary"
onclick=self.common.callback(|_| Msg::DismissModal)>
{"Cancel"}
<i class="bi-x-circle me-2"></i>
{"Cancel"}
</button>
<button
type="button"
onclick=self.common.callback(|_| Msg::ConfirmDeleteUser)
class="btn btn-danger">{"Yes, I'm sure"}</button>
class="btn btn-danger">
<i class="bi-check-circle me-2"></i>
{"Yes, I'm sure"}
</button>
</div>
</div>
</div>

View File

@ -133,7 +133,7 @@ impl GroupDetails {
<>
<h5 class="fw-bold">{"Members"}</h5>
<div class="table-responsive">
<table class="table table-striped">
<table class="table table-hover">
<thead>
<tr key="headerRow">
<th>{"User Id"}</th>
@ -145,7 +145,7 @@ impl GroupDetails {
{if g.users.is_empty() {
html! {
<tr key="EmptyRow">
<td>{"No members"}</td>
<td>{"There are no users in this group."}</td>
<td/>
</tr>
}

View File

@ -94,7 +94,7 @@ impl GroupTable {
let make_table = |groups: &Vec<Group>| {
html! {
<div class="table-responsive">
<table class="table table-striped">
<table class="table table-hover">
<thead>
<tr>
<th>{"Group name"}</th>

View File

@ -195,6 +195,7 @@ impl Component for LoginForm {
class="btn btn-primary"
disabled=self.common.is_task_running()
onclick=self.common.callback(|e: MouseEvent| {e.prevent_default(); Msg::Submit})>
<i class="bi-box-arrow-in-right me-2"/>
{"Login"}
</button>
<NavButton

View File

@ -113,6 +113,7 @@ impl Component for ResetPasswordStep1Form {
class="btn btn-primary"
disabled=self.common.is_task_running()
onclick=self.common.callback(|e: MouseEvent| {e.prevent_default(); Msg::Submit})>
<i class="bi-check-circle me-2"/>
{"Reset password"}
</button>
<NavButton
@ -127,7 +128,7 @@ impl Component for ResetPasswordStep1Form {
<div class="form-group">
{ if let Some(e) = &self.common.error {
html! {
<div class="alert alert-danger">
<div class="alert alert-danger mb-2">
{e.to_string() }
</div>
}

View File

@ -67,7 +67,7 @@ impl Component for Select {
fn view(&self) -> Html {
html! {
<select
<select class="form-select"
ref=self.node_ref.clone()
disabled=self.props.children.is_empty()
onchange=self.link.callback(SelectMsg::OnSelectChange)>

View File

@ -129,7 +129,7 @@ impl UserDetails {
<>
<h5 class="row m-3 fw-bold">{"Group memberships"}</h5>
<div class="table-responsive">
<table class="table table-striped">
<table class="table table-hover">
<thead>
<tr key="headerRow">
<th>{"Group"}</th>
@ -140,7 +140,7 @@ impl UserDetails {
{if u.groups.is_empty() {
html! {
<tr key="EmptyRow">
<td>{"Not member of any group"}</td>
<td>{"This user is not a member of any groups."}</td>
</tr>
}
} else {
@ -197,15 +197,19 @@ impl Component for UserDetails {
html! {
<>
<h3>{u.id.to_string()}</h3>
<UserDetailsForm
user=u.clone() />
<div class="row justify-content-center">
<div class="d-flex flex-row-reverse">
<NavButton
route=AppRoute::ChangePassword(u.id.clone())
classes="btn btn-primary col-auto">
{"Change password"}
classes="btn btn-secondary">
<i class="bi-key me-2"></i>
{"Modify password"}
</NavButton>
</div>
<div>
<h5 class="row m-3 fw-bold">{"User details"}</h5>
</div>
<UserDetailsForm
user=u.clone() />
{self.view_group_memberships(u)}
{self.view_add_group_button(u)}
{self.view_messages(error)}

View File

@ -186,30 +186,33 @@ impl Component for UserDetailsForm {
{"User ID: "}
</label>
<div class="col-8">
<span id="userId" class="form-constrol-static"><b>{&self.common.user.id}</b></span>
<span id="userId" class="form-control-static"><i>{&self.common.user.id}</i></span>
</div>
</div>
<div class="form-group row mb-3">
<div class="col-4 col-form-label">
<img
id="avatarDisplay"
src={format!("data:image/jpeg;base64, {}", avatar_string)}
style="max-height:128px;max-width:128px;height:auto;width:auto;"
alt="Avatar" />
</div>
<label for="creationDate"
class="form-label col-4 col-form-label">
{"Creation date: "}
</label>
<div class="col-8">
<input
class="form-control"
id="avatarInput"
type="file"
accept="image/jpeg"
oninput=self.common.callback(|_| Msg::Update) />
<span id="creationDate" class="form-control-static">{&self.common.user.creation_date.date().naive_local()}</span>
</div>
</div>
<div class="form-group row mb-3">
<label for="uuid"
class="form-label col-4 col-form-label">
{"UUID: "}
</label>
<div class="col-8">
<span id="creationDate" class="form-control-static">{&self.common.user.uuid}</span>
</div>
</div>
<div class="form-group row mb-3">
<label for="email"
class="form-label col-4 col-form-label">
{"Email*: "}
{"Email"}
<span class="text-danger">{"*"}</span>
{":"}
</label>
<div class="col-8">
<Field
@ -228,7 +231,9 @@ impl Component for UserDetailsForm {
<div class="form-group row mb-3">
<label for="display_name"
class="form-label col-4 col-form-label">
{"Display Name*: "}
{"Display Name"}
<span class="text-danger">{"*"}</span>
{":"}
</label>
<div class="col-8">
<Field
@ -278,35 +283,44 @@ impl Component for UserDetailsForm {
</div>
</div>
</div>
<div class="form-group row mb-3">
<label for="creationDate"
class="form-label col-4 col-form-label">
{"Creation date: "}
<div class="form-group row align-items-center mb-3">
<label for="avatar"
class="form-label col-4 col-form-label">
{"Avatar: "}
</label>
<div class="col-8">
<span id="creationDate" class="form-constrol-static">{&self.common.user.creation_date.date().naive_local()}</span>
<div class="row align-items-center">
<div class="col-8">
<input
class="form-control"
id="avatarInput"
type="file"
accept="image/jpeg"
oninput=self.common.callback(|_| Msg::Update) />
</div>
<div class="col-4">
<img
id="avatarDisplay"
src={format!("data:image/jpeg;base64, {}", avatar_string)}
style="max-height:128px;max-width:128px;height:auto;width:auto;"
alt="Avatar" />
</div>
</div>
</div>
</div>
<div class="form-group row mb-3">
<label for="uuid"
class="form-label col-4 col-form-label">
{"UUID: "}
</label>
<div class="col-8">
<span id="creationDate" class="form-constrol-static">{&self.common.user.uuid}</span>
</div>
</div>
<div class="form-group row justify-content-center">
<div class="form-group row justify-content-center mt-3">
<button
type="submit"
class="btn btn-primary col-auto col-form-label"
disabled=self.common.is_task_running()
onclick=self.common.callback(|e: MouseEvent| {e.prevent_default(); Msg::SubmitClicked})>
{"Update"}
<i class="bi-save me-2"></i>
{"Save changes"}
</button>
</div>
</form>
{ if let Some(e) = &self.common.error {
{
if let Some(e) = &self.common.error {
html! {
<div class="alert alert-danger">
{e.to_string() }
@ -315,7 +329,7 @@ impl Component for UserDetailsForm {
} else { html! {} }
}
<div hidden=!self.just_updated>
<span>{"User successfully updated!"}</span>
<div class="alert alert-success mt-4">{"User successfully updated!"}</div>
</div>
</div>
}

View File

@ -100,7 +100,7 @@ impl UserTable {
let make_table = |users: &Vec<User>| {
html! {
<div class="table-responsive">
<table class="table table-striped">
<table class="table table-hover">
<thead>
<tr>
<th>{"User ID"}</th>

View File

@ -1,4 +1,4 @@
header h1 {
header h2 {
font-family: 'Bebas Neue', cursive;
}