From 7b8f2ade51d6f5c3afa9e4bfccd51c31c79ef352 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adrian=20Wo=C5=BAniak?= Date: Fri, 1 May 2020 19:11:10 +0200 Subject: [PATCH] Add profile page --- jirs-client/js/css/aside.css | 6 +++ jirs-client/src/lib.rs | 16 +++++++ jirs-client/src/model.rs | 24 ++++++++++ jirs-client/src/profile.rs | 68 +++++++++++++++++++++++++++ jirs-client/src/project_settings.rs | 14 +++--- jirs-client/src/shared/aside.rs | 15 +++--- jirs-client/src/shared/mod.rs | 2 +- jirs-client/src/shared/navbar_left.rs | 26 ++++++++-- jirs-server/src/db/projects.rs | 4 +- 9 files changed, 154 insertions(+), 21 deletions(-) create mode 100644 jirs-client/src/profile.rs diff --git a/jirs-client/js/css/aside.css b/jirs-client/js/css/aside.css index 31f33a36..d4c57096 100644 --- a/jirs-client/js/css/aside.css +++ b/jirs-client/js/css/aside.css @@ -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; diff --git a/jirs-client/src/lib.rs b/jirs-client/src/lib.rs index d4751b78..289d7b93 100644 --- a/jirs-client/src/lib.rs +++ b/jirs-client/src/lib.rs @@ -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) { 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 { 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 { 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)), diff --git a/jirs-client/src/model.rs b/jirs-client/src/model.rs index b8cd54e9..a2258948 100644 --- a/jirs-client/src/model.rs +++ b/jirs-client/src/model.rs @@ -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), @@ -366,6 +389,7 @@ pub enum PageContent { ProjectSettings(Box), Invite(Box), Users(Box), + Profile(Box), } #[derive(Debug)] diff --git a/jirs-client/src/profile.rs b/jirs-client/src/profile.rs new file mode 100644 index 00000000..7320243b --- /dev/null +++ b/jirs-client/src/profile.rs @@ -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) { + 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 { + 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![]) +} diff --git a/jirs-client/src/project_settings.rs b/jirs-client/src/project_settings.rs index f06aafa9..d9ee2987 100644 --- a/jirs-client/src/project_settings.rs +++ b/jirs-client/src/project_settings.rs @@ -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 they’re 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) { - 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) 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) _ => (), } + 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); diff --git a/jirs-client/src/shared/aside.rs b/jirs-client/src/shared/aside.rs index a3730f03..bd671ff8 100644 --- a/jirs-client/src/shared/aside.rs +++ b/jirs-client/src/shared/aside.rs @@ -14,20 +14,17 @@ pub fn render(model: &Model) -> Node { 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"], ""] ], ], }; diff --git a/jirs-client/src/shared/mod.rs b/jirs-client/src/shared/mod.rs index 603ce009..a2f5dcf9 100644 --- a/jirs-client/src/shared/mod.rs +++ b/jirs-client/src/shared/mod.rs @@ -40,7 +40,7 @@ pub trait ToNode { } pub fn divider() -> Node { - div![attrs![At::Class => "divider"], ""] + div![class!["divider"], ""] } pub fn inner_layout( diff --git a/jirs-client/src/shared/navbar_left.rs b/jirs-client/src/shared/navbar_left.rs index ab225b9e..ed1a1818 100644 --- a/jirs-client/src/shared/navbar_left.rs +++ b/jirs-client/src/shared/navbar_left.rs @@ -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> { 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> { 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> { fn navbar_left_item(_model: &Model, text: &str, logo: Icon) -> Node { div![ - attrs![At::Class => "item"], + class!["item"], i![attrs![At::Class => format!("styledIcon {}", logo)]], - span![attrs![At::Class => "itemText"], text] + span![class!["itemText"], text] ] } diff --git a/jirs-server/src/db/projects.rs b/jirs-server/src/db/projects.rs index 78bea0ef..164da9ea 100644 --- a/jirs-server/src/db/projects.rs +++ b/jirs-server/src/db/projects.rs @@ -20,13 +20,13 @@ impl Handler for DbExecutor { type Result = Result; 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!( "{}",