Add profile page

This commit is contained in:
Adrian Woźniak 2020-05-01 19:11:10 +02:00
parent f2cc69bcdd
commit 7b8f2ade51
9 changed files with 154 additions and 21 deletions

View File

@ -56,6 +56,12 @@ aside#navbar-left .item > .styledIcon {
line-height: 42px;
}
aside#navbar-left .item > .styledIcon > .styledAvatar {
margin-top: 7px;
width: 27px;
height: 27px;
}
aside#navbar-left > .item > .styledIcon.search {
font-size: 22px;
color: #DEEBFE;

View File

@ -13,6 +13,7 @@ mod api;
mod invite;
mod modal;
mod model;
mod profile;
mod project;
mod project_settings;
mod shared;
@ -37,6 +38,7 @@ pub enum FieldId {
SignUp(SignUpFieldId),
Invite(InviteFieldId),
Users(UsersFieldId),
Profile(UsersFieldId),
// issue
AddIssueModal(IssueFieldId),
EditIssueModal(EditIssueModalSection),
@ -127,6 +129,11 @@ impl std::fmt::Display for FieldId {
UsersFieldId::Email => f.write_str("users-email"),
UsersFieldId::UserRole => f.write_str("users-userRole"),
},
FieldId::Profile(sub) => match sub {
UsersFieldId::Username => f.write_str("profile-username"),
UsersFieldId::Email => f.write_str("profile-email"),
UsersFieldId::UserRole => f.write_str("profile-userRole"),
},
}
}
}
@ -150,10 +157,16 @@ pub enum ProjectPageChange {
SubmitForm,
}
#[derive(Clone, Debug, PartialEq)]
pub enum ProfilePageChange {
SubmitForm,
}
#[derive(Clone, Debug, PartialEq)]
pub enum PageChanged {
Users(UsersPageChange),
ProjectSettings(ProjectPageChange),
Profile(ProfilePageChange),
}
#[derive(Clone, Debug, PartialEq)]
@ -260,6 +273,7 @@ fn update(msg: Msg, model: &mut model::Model, orders: &mut impl Orders<Msg>) {
Page::SignUp => sign_up::update(msg, model, orders),
Page::Invite => invite::update(msg, model, orders),
Page::Users => users::update(msg, model, orders),
Page::Profile => profile::update(msg, model, orders),
}
if cfg!(debug_assertions) {
// debug!(model);
@ -275,6 +289,7 @@ fn view(model: &model::Model) -> Node<Msg> {
Page::SignUp => sign_up::view(model),
Page::Invite => invite::view(model),
Page::Users => users::view(model),
Page::Profile => profile::view(model),
}
}
@ -289,6 +304,7 @@ fn routes(url: Url) -> Option<Msg> {
Some(Ok(id)) => Some(Msg::ChangePage(model::Page::EditIssue(id))),
_ => None,
},
"profile" => Some(Msg::ChangePage(Page::Profile)),
"add-issue" => Some(Msg::ChangePage(Page::AddIssue)),
"project-settings" => Some(Msg::ChangePage(model::Page::ProjectSettings)),
"login" => Some(Msg::ChangePage(model::Page::SignIn)),

View File

@ -193,6 +193,7 @@ pub enum Page {
SignUp,
Invite,
Users,
Profile,
}
impl Page {
@ -206,6 +207,7 @@ impl Page {
Page::SignUp => "/register".to_string(),
Page::Invite => "/invite".to_string(),
Page::Users => "/users".to_string(),
Page::Profile => "/profile".to_string(),
}
}
}
@ -358,6 +360,27 @@ impl Default for UsersPage {
}
}
#[derive(Debug)]
pub struct ProfilePage {
pub name: StyledInputState,
pub email: StyledInputState,
}
impl ProfilePage {
pub fn new(user: &User) -> Self {
Self {
name: StyledInputState::new(
FieldId::Profile(UsersFieldId::Username),
user.name.as_str(),
),
email: StyledInputState::new(
FieldId::Profile(UsersFieldId::Email),
user.email.as_str(),
),
}
}
}
#[derive(Debug)]
pub enum PageContent {
SignIn(Box<SignInPage>),
@ -366,6 +389,7 @@ pub enum PageContent {
ProjectSettings(Box<ProjectSettingsPage>),
Invite(Box<InvitePage>),
Users(Box<UsersPage>),
Profile(Box<ProfilePage>),
}
#[derive(Debug)]

View File

@ -0,0 +1,68 @@
use seed::{prelude::*, *};
use jirs_data::*;
use crate::api::send_ws_msg;
use crate::model::{Model, Page, PageContent, ProfilePage};
use crate::shared::styled_field::StyledField;
use crate::shared::styled_form::StyledForm;
use crate::shared::styled_input::StyledInput;
use crate::shared::{inner_layout, ToNode};
use crate::{FieldId, Msg};
pub fn update(msg: Msg, model: &mut crate::model::Model, _orders: &mut impl Orders<Msg>) {
let user = match model.user {
Some(ref user) => user,
_ => return,
};
match msg {
Msg::WsMsg(WsMsg::AuthorizeLoaded(..)) | Msg::ChangePage(Page::Profile) => {
send_ws_msg(WsMsg::ProjectRequest);
model.page_content = PageContent::Profile(Box::new(ProfilePage::new(user)));
}
_ => (),
}
let _project_page = match &mut model.page_content {
PageContent::Profile(profile_page) => profile_page,
_ => return,
};
}
pub fn view(model: &Model) -> Node<Msg> {
let page = match &model.page_content {
PageContent::Profile(profile_page) => profile_page,
_ => return empty![],
};
let username = StyledInput::build(FieldId::Profile(UsersFieldId::Username))
.state(&page.name)
.valid(true)
.build()
.into_node();
let username_field = StyledField::build()
.label("Username")
.input(username)
.build()
.into_node();
let email = StyledInput::build(FieldId::Profile(UsersFieldId::Username))
.state(&page.email)
.valid(true)
.build()
.into_node();
let email_field = StyledField::build()
.label("E-Mail")
.input(email)
.build()
.into_node();
let content = StyledForm::build()
.heading("Profile")
.add_field(username_field)
.add_field(email_field)
.build()
.into_node();
inner_layout(model, "profile", vec![content], empty![])
}

View File

@ -19,10 +19,6 @@ static TIME_TRACKING_FIBONACCI: &'static str = "Tracking employees time carri
static TIME_TRACKING_HOURLY: &'static str = "Employees may feel intimidated by demands to track their time. Or they could feel that theyre constantly being watched and evaluated. And for overly ambitious managers, employee time tracking may open the doors to excessive micromanaging.";
pub fn update(msg: Msg, model: &mut model::Model, orders: &mut impl Orders<Msg>) {
if model.user.is_none() {
return;
}
if model.page != Page::ProjectSettings {
return;
}
@ -32,8 +28,10 @@ pub fn update(msg: Msg, model: &mut model::Model, orders: &mut impl Orders<Msg>)
send_ws_msg(WsMsg::ProjectRequest);
}
Msg::ChangePage(Page::ProjectSettings) => {
send_ws_msg(WsMsg::ProjectRequest);
build_page_content(model);
if model.user.is_some() {
send_ws_msg(WsMsg::ProjectRequest);
}
}
Msg::WsMsg(WsMsg::ProjectLoaded(..)) => {
build_page_content(model);
@ -41,9 +39,13 @@ pub fn update(msg: Msg, model: &mut model::Model, orders: &mut impl Orders<Msg>)
_ => (),
}
if model.user.is_none() || model.project.is_none() {
return;
}
let page = match &mut model.page_content {
PageContent::ProjectSettings(page) => page,
_ => return,
_ => return error!("bad content type"),
};
page.project_category_state.update(&msg, orders);
page.time_tracking.update(&msg);

View File

@ -14,20 +14,17 @@ pub fn render(model: &Model) -> Node<Msg> {
id!["projectInfo"],
project_icon,
div![
attrs![At::Class => "projectTexts";],
div![attrs![At::Class => "projectName";], project.name],
div![
attrs![At::Class => "projectCategory";],
project.category.to_string()
]
class!["projectTexts"],
div![class!["projectName"], project.name],
div![class!["projectCategory"], project.category.to_string()]
],
],
_ => li![
id!["projectInfo"],
div![
attrs![At::Class => "projectTexts";],
div![attrs![At::Class => "projectName";], ""],
div![attrs![At::Class => "projectCategory";], ""]
class!["projectTexts"],
div![class!["projectName"], ""],
div![class!["projectCategory"], ""]
],
],
};

View File

@ -40,7 +40,7 @@ pub trait ToNode {
}
pub fn divider() -> Node<Msg> {
div![attrs![At::Class => "divider"], ""]
div![class!["divider"], ""]
}
pub fn inner_layout(

View File

@ -1,6 +1,7 @@
use seed::{prelude::*, *};
use crate::model::Model;
use crate::shared::styled_avatar::StyledAvatar;
use crate::shared::styled_button::StyledButton;
use crate::shared::styled_icon::Icon;
use crate::shared::{styled_tooltip, ToNode};
@ -9,6 +10,19 @@ use crate::Msg;
pub fn render(model: &Model) -> Vec<Node<Msg>> {
let logo_svg = Node::from_html(include_str!("../../static/logo.svg"));
let user_icon = match model.user.as_ref() {
Some(user) => i![
class!["styledIcon"],
StyledAvatar::build()
.size(27)
.name(user.name.as_str())
.avatar_url(user.avatar_url.as_ref().cloned().unwrap_or_default())
.build()
.into_node()
],
_ => i![attrs![At::Class => format!("styledIcon {}", Icon::Plus)]],
};
vec![
about_tooltip_popup(model),
aside![
@ -24,7 +38,13 @@ pub fn render(model: &Model) -> Vec<Node<Msg>> {
span![attrs![At::Class => "itemText"], "Create Issue"]
],
div![
attrs![At::Class => "bottom"],
class!["bottom"],
div![
class!["item"],
attrs![At::Href=> "/profile"],
user_icon,
span![class!["itemText"], "Profile"]
],
about_tooltip(model, navbar_left_item(model, "About", Icon::Help)),
],
],
@ -33,9 +53,9 @@ pub fn render(model: &Model) -> Vec<Node<Msg>> {
fn navbar_left_item(_model: &Model, text: &str, logo: Icon) -> Node<Msg> {
div![
attrs![At::Class => "item"],
class!["item"],
i![attrs![At::Class => format!("styledIcon {}", logo)]],
span![attrs![At::Class => "itemText"], text]
span![class!["itemText"], text]
]
}

View File

@ -20,13 +20,13 @@ impl Handler<LoadCurrentProject> for DbExecutor {
type Result = Result<Project, ServiceErrors>;
fn handle(&mut self, msg: LoadCurrentProject, _ctx: &mut Self::Context) -> Self::Result {
use crate::schema::projects::dsl::{id, projects};
use crate::schema::projects::dsl::projects;
let conn = &self
.pool
.get()
.map_err(|_| ServiceErrors::DatabaseConnectionLost)?;
let query = projects.filter(id.eq(msg.project_id));
let query = projects.find(msg.project_id);
debug!(
"{}",