Add and style register view
This commit is contained in:
parent
b22210d55a
commit
0e2cc62c30
@ -52,3 +52,8 @@
|
|||||||
display: block;
|
display: block;
|
||||||
line-height: 32px;
|
line-height: 32px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#login .twoRow {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
37
jirs-client/js/css/register.css
Normal file
37
jirs-client/js/css/register.css
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
#register > .styledForm {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
margin: 0 auto 24px;
|
||||||
|
width: 400px;
|
||||||
|
background: rgb(255, 255, 255) none repeat scroll 0 0;
|
||||||
|
border-radius: 3px;
|
||||||
|
box-shadow: rgba(0, 0, 0, 0.1) 0 0 10px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
color: var(--textMedium);
|
||||||
|
}
|
||||||
|
|
||||||
|
#register > .styledForm:first-of-type {
|
||||||
|
margin-top: 124.5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#register .twoRow {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
#register > .styledForm > .formElement > .noPasswordSection {
|
||||||
|
line-height: 32px;
|
||||||
|
margin-top: 15px;
|
||||||
|
display: flex;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
#register > .styledForm > .formElement > .noPasswordSection > .styledIcon {
|
||||||
|
margin-right: 5px;
|
||||||
|
line-height: 32px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#register > .styledForm > .formElement > .noPasswordSection > span {
|
||||||
|
display: block;
|
||||||
|
line-height: 32px;
|
||||||
|
}
|
11
jirs-client/js/css/styledLink.css
Normal file
11
jirs-client/js/css/styledLink.css
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
.styledLink {
|
||||||
|
color: var(--textDark);
|
||||||
|
font-family: var(--font-medium);
|
||||||
|
height: 32px;
|
||||||
|
vertical-align: middle;
|
||||||
|
line-height: 2;
|
||||||
|
appearance: none;
|
||||||
|
cursor: pointer;
|
||||||
|
user-select: none;
|
||||||
|
font-size: 14.5px;
|
||||||
|
}
|
@ -18,9 +18,11 @@
|
|||||||
@import "./css/styledEditor.css";
|
@import "./css/styledEditor.css";
|
||||||
@import "./css/styledComment.css";
|
@import "./css/styledComment.css";
|
||||||
@import "./css/styledPage.css";
|
@import "./css/styledPage.css";
|
||||||
|
@import "./css/styledLink.css";
|
||||||
@import "./css/app.css";
|
@import "./css/app.css";
|
||||||
@import "./css/issue.css";
|
@import "./css/issue.css";
|
||||||
@import "./css/project.css";
|
@import "./css/project.css";
|
||||||
@import "./css/projectSettings.css";
|
@import "./css/projectSettings.css";
|
||||||
@import "./css/timeTracking.css";
|
@import "./css/timeTracking.css";
|
||||||
@import "./css/login.css";
|
@import "./css/login.css";
|
||||||
|
@import "./css/register.css";
|
||||||
|
@ -10,13 +10,14 @@ use crate::shared::styled_editor::Mode as TabMode;
|
|||||||
use crate::shared::styled_select::StyledSelectChange;
|
use crate::shared::styled_select::StyledSelectChange;
|
||||||
|
|
||||||
mod api;
|
mod api;
|
||||||
mod login;
|
|
||||||
mod modal;
|
mod modal;
|
||||||
mod model;
|
mod model;
|
||||||
mod project;
|
mod project;
|
||||||
mod project_settings;
|
mod project_settings;
|
||||||
mod register;
|
|
||||||
mod shared;
|
mod shared;
|
||||||
|
mod sign_in;
|
||||||
|
mod sign_up;
|
||||||
|
mod validations;
|
||||||
mod ws;
|
mod ws;
|
||||||
|
|
||||||
pub type AvatarFilterActive = bool;
|
pub type AvatarFilterActive = bool;
|
||||||
@ -30,7 +31,8 @@ pub enum EditIssueModalSection {
|
|||||||
|
|
||||||
#[derive(Clone, Debug, PartialOrd, PartialEq, Hash)]
|
#[derive(Clone, Debug, PartialOrd, PartialEq, Hash)]
|
||||||
pub enum FieldId {
|
pub enum FieldId {
|
||||||
Login(LoginFieldId),
|
SignIn(SignInFieldId),
|
||||||
|
SignUp(SignUpFieldId),
|
||||||
// issue
|
// issue
|
||||||
AddIssueModal(IssueFieldId),
|
AddIssueModal(IssueFieldId),
|
||||||
EditIssueModal(EditIssueModalSection),
|
EditIssueModal(EditIssueModalSection),
|
||||||
@ -103,10 +105,14 @@ impl std::fmt::Display for FieldId {
|
|||||||
ProjectFieldId::Description => f.write_str("projectSettings-description"),
|
ProjectFieldId::Description => f.write_str("projectSettings-description"),
|
||||||
ProjectFieldId::Category => f.write_str("projectSettings-category"),
|
ProjectFieldId::Category => f.write_str("projectSettings-category"),
|
||||||
},
|
},
|
||||||
FieldId::Login(sub) => match sub {
|
FieldId::SignIn(sub) => match sub {
|
||||||
LoginFieldId::Email => f.write_str("login-email"),
|
SignInFieldId::Email => f.write_str("login-email"),
|
||||||
LoginFieldId::Username => f.write_str("login-username"),
|
SignInFieldId::Username => f.write_str("login-username"),
|
||||||
LoginFieldId::Token => f.write_str("login-token"),
|
SignInFieldId::Token => f.write_str("login-token"),
|
||||||
|
},
|
||||||
|
FieldId::SignUp(sub) => match sub {
|
||||||
|
SignUpFieldId::Username => f.write_str("signUp-email"),
|
||||||
|
SignUpFieldId::Email => f.write_str("signUp-username"),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -193,7 +199,7 @@ fn update(msg: Msg, model: &mut model::Model, orders: &mut impl Orders<Msg>) {
|
|||||||
}
|
}
|
||||||
Msg::AuthTokenErased => {
|
Msg::AuthTokenErased => {
|
||||||
seed::push_route(vec!["login"]);
|
seed::push_route(vec!["login"]);
|
||||||
orders.skip().send_msg(Msg::ChangePage(Page::Login));
|
orders.skip().send_msg(Msg::ChangePage(Page::SignIn));
|
||||||
authorize_or_redirect();
|
authorize_or_redirect();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -210,8 +216,8 @@ fn update(msg: Msg, model: &mut model::Model, orders: &mut impl Orders<Msg>) {
|
|||||||
match model.page {
|
match model.page {
|
||||||
Page::Project | Page::AddIssue | Page::EditIssue(..) => project::update(msg, model, orders),
|
Page::Project | Page::AddIssue | Page::EditIssue(..) => project::update(msg, model, orders),
|
||||||
Page::ProjectSettings => project_settings::update(msg, model, orders),
|
Page::ProjectSettings => project_settings::update(msg, model, orders),
|
||||||
Page::Login => login::update(msg, model, orders),
|
Page::SignIn => sign_in::update(msg, model, orders),
|
||||||
Page::Register => register::update(msg, model, orders),
|
Page::SignUp => sign_up::update(msg, model, orders),
|
||||||
}
|
}
|
||||||
if cfg!(debug_assertions) {
|
if cfg!(debug_assertions) {
|
||||||
// debug!(model);
|
// debug!(model);
|
||||||
@ -223,8 +229,8 @@ fn view(model: &model::Model) -> Node<Msg> {
|
|||||||
Page::Project | Page::AddIssue => project::view(model),
|
Page::Project | Page::AddIssue => project::view(model),
|
||||||
Page::EditIssue(_id) => project::view(model),
|
Page::EditIssue(_id) => project::view(model),
|
||||||
Page::ProjectSettings => project_settings::view(model),
|
Page::ProjectSettings => project_settings::view(model),
|
||||||
Page::Login => login::view(model),
|
Page::SignIn => sign_in::view(model),
|
||||||
Page::Register => register::view(model),
|
Page::SignUp => sign_up::view(model),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -241,8 +247,8 @@ fn routes(url: Url) -> Option<Msg> {
|
|||||||
},
|
},
|
||||||
"add-issue" => Some(Msg::ChangePage(Page::AddIssue)),
|
"add-issue" => Some(Msg::ChangePage(Page::AddIssue)),
|
||||||
"project-settings" => Some(Msg::ChangePage(model::Page::ProjectSettings)),
|
"project-settings" => Some(Msg::ChangePage(model::Page::ProjectSettings)),
|
||||||
"login" => Some(Msg::ChangePage(model::Page::Login)),
|
"login" => Some(Msg::ChangePage(model::Page::SignIn)),
|
||||||
"register" => Some(Msg::ChangePage(model::Page::Register)),
|
"register" => Some(Msg::ChangePage(model::Page::SignUp)),
|
||||||
_ => Some(Msg::ChangePage(model::Page::Project)),
|
_ => Some(Msg::ChangePage(model::Page::Project)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -140,8 +140,8 @@ pub enum Page {
|
|||||||
EditIssue(IssueId),
|
EditIssue(IssueId),
|
||||||
AddIssue,
|
AddIssue,
|
||||||
ProjectSettings,
|
ProjectSettings,
|
||||||
Login,
|
SignIn,
|
||||||
Register,
|
SignUp,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Page {
|
impl Page {
|
||||||
@ -151,8 +151,8 @@ impl Page {
|
|||||||
Page::EditIssue(id) => format!("/issues/{id}", id = id),
|
Page::EditIssue(id) => format!("/issues/{id}", id = id),
|
||||||
Page::AddIssue => "/add-issues".to_string(),
|
Page::AddIssue => "/add-issues".to_string(),
|
||||||
Page::ProjectSettings => "/project-settings".to_string(),
|
Page::ProjectSettings => "/project-settings".to_string(),
|
||||||
Page::Login => "/login".to_string(),
|
Page::SignIn => "/login".to_string(),
|
||||||
Page::Register => "/register".to_string(),
|
Page::SignUp => "/register".to_string(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -219,7 +219,7 @@ impl ProjectSettingsPage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Default)]
|
#[derive(Debug, Default)]
|
||||||
pub struct LoginPage {
|
pub struct SignInPage {
|
||||||
pub username: String,
|
pub username: String,
|
||||||
pub email: String,
|
pub email: String,
|
||||||
pub token: String,
|
pub token: String,
|
||||||
@ -231,9 +231,20 @@ pub struct LoginPage {
|
|||||||
pub token_touched: bool,
|
pub token_touched: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Default)]
|
||||||
|
pub struct SignUpPage {
|
||||||
|
pub username: String,
|
||||||
|
pub email: String,
|
||||||
|
pub sign_up_success: bool,
|
||||||
|
// touched
|
||||||
|
pub username_touched: bool,
|
||||||
|
pub email_touched: bool,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum PageContent {
|
pub enum PageContent {
|
||||||
Login(LoginPage),
|
SignIn(SignInPage),
|
||||||
|
SignUp(SignUpPage),
|
||||||
Project(ProjectPage),
|
Project(ProjectPage),
|
||||||
ProjectSettings(ProjectSettingsPage),
|
ProjectSettings(ProjectSettingsPage),
|
||||||
}
|
}
|
||||||
|
@ -1,8 +0,0 @@
|
|||||||
use crate::{model, Msg};
|
|
||||||
use seed::{prelude::*, *};
|
|
||||||
|
|
||||||
pub fn update(_msg: Msg, _model: &mut model::Model, _orders: &mut impl Orders<Msg>) {}
|
|
||||||
|
|
||||||
pub fn view(_model: &model::Model) -> Node<Msg> {
|
|
||||||
div![]
|
|
||||||
}
|
|
@ -16,6 +16,7 @@ pub mod styled_field;
|
|||||||
pub mod styled_form;
|
pub mod styled_form;
|
||||||
pub mod styled_icon;
|
pub mod styled_icon;
|
||||||
pub mod styled_input;
|
pub mod styled_input;
|
||||||
|
pub mod styled_link;
|
||||||
pub mod styled_modal;
|
pub mod styled_modal;
|
||||||
pub mod styled_select;
|
pub mod styled_select;
|
||||||
pub mod styled_select_child;
|
pub mod styled_select_child;
|
||||||
|
85
jirs-client/src/shared/styled_link.rs
Normal file
85
jirs-client/src/shared/styled_link.rs
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
use seed::{prelude::*, *};
|
||||||
|
|
||||||
|
use crate::shared::ToNode;
|
||||||
|
use crate::Msg;
|
||||||
|
|
||||||
|
pub struct StyledLink {
|
||||||
|
children: Vec<Node<Msg>>,
|
||||||
|
class_list: Vec<String>,
|
||||||
|
href: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl StyledLink {
|
||||||
|
pub fn build() -> StyledLinkBuilder {
|
||||||
|
StyledLinkBuilder::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct StyledLinkBuilder {
|
||||||
|
children: Vec<Node<Msg>>,
|
||||||
|
class_list: Vec<String>,
|
||||||
|
href: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl StyledLinkBuilder {
|
||||||
|
pub fn add_child(mut self, child: Node<Msg>) -> Self {
|
||||||
|
self.children.push(child);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
pub fn add_class<S>(mut self, name: S) -> Self
|
||||||
|
where
|
||||||
|
S: Into<String>,
|
||||||
|
{
|
||||||
|
self.class_list.push(name.into());
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn href<S>(mut self, href: S) -> Self
|
||||||
|
where
|
||||||
|
S: Into<String>,
|
||||||
|
{
|
||||||
|
self.href = href.into();
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn text<S>(mut self, s: S) -> Self
|
||||||
|
where
|
||||||
|
S: Into<String>,
|
||||||
|
{
|
||||||
|
let text: String = s.into();
|
||||||
|
self.children.push(span![text]);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn build(self) -> StyledLink {
|
||||||
|
StyledLink {
|
||||||
|
children: self.children,
|
||||||
|
class_list: self.class_list,
|
||||||
|
href: self.href,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ToNode for StyledLink {
|
||||||
|
fn into_node(self) -> Node<Msg> {
|
||||||
|
render(self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn render(values: StyledLink) -> Node<Msg> {
|
||||||
|
let StyledLink {
|
||||||
|
children,
|
||||||
|
mut class_list,
|
||||||
|
href,
|
||||||
|
} = values;
|
||||||
|
class_list.push("styledLink".to_string());
|
||||||
|
|
||||||
|
a![
|
||||||
|
attrs![
|
||||||
|
At::Class => class_list.join(" "),
|
||||||
|
At::Href => href,
|
||||||
|
],
|
||||||
|
children,
|
||||||
|
]
|
||||||
|
}
|
@ -6,40 +6,42 @@ use uuid::Uuid;
|
|||||||
use jirs_data::WsMsg;
|
use jirs_data::WsMsg;
|
||||||
|
|
||||||
use crate::api::send_ws_msg;
|
use crate::api::send_ws_msg;
|
||||||
use crate::model::{LoginPage, Page, PageContent};
|
use crate::model::{Page, PageContent, SignInPage};
|
||||||
use crate::shared::styled_button::StyledButton;
|
use crate::shared::styled_button::StyledButton;
|
||||||
use crate::shared::styled_field::StyledField;
|
use crate::shared::styled_field::StyledField;
|
||||||
use crate::shared::styled_form::StyledForm;
|
use crate::shared::styled_form::StyledForm;
|
||||||
use crate::shared::styled_icon::{Icon, StyledIcon};
|
use crate::shared::styled_icon::{Icon, StyledIcon};
|
||||||
use crate::shared::styled_input::StyledInput;
|
use crate::shared::styled_input::StyledInput;
|
||||||
|
use crate::shared::styled_link::StyledLink;
|
||||||
use crate::shared::{outer_layout, write_auth_token, ToNode};
|
use crate::shared::{outer_layout, write_auth_token, ToNode};
|
||||||
use crate::{model, FieldId, LoginFieldId, Msg};
|
use crate::validations::{is_email, is_token};
|
||||||
|
use crate::{model, FieldId, Msg, SignInFieldId};
|
||||||
|
|
||||||
pub fn update(msg: Msg, model: &mut model::Model, orders: &mut impl Orders<Msg>) {
|
pub fn update(msg: Msg, model: &mut model::Model, orders: &mut impl Orders<Msg>) {
|
||||||
if model.page != Page::Login {
|
if model.page != Page::SignIn {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if msg == Msg::ChangePage(Page::Login) {
|
if msg == Msg::ChangePage(Page::SignIn) {
|
||||||
model.page_content = PageContent::Login(LoginPage::default());
|
model.page_content = PageContent::SignIn(SignInPage::default());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let page = match &mut model.page_content {
|
let page = match &mut model.page_content {
|
||||||
PageContent::Login(page) => page,
|
PageContent::SignIn(page) => page,
|
||||||
_ => return,
|
_ => return,
|
||||||
};
|
};
|
||||||
|
|
||||||
match msg {
|
match msg {
|
||||||
Msg::InputChanged(FieldId::Login(LoginFieldId::Username), value) => {
|
Msg::InputChanged(FieldId::SignIn(SignInFieldId::Username), value) => {
|
||||||
page.username = value;
|
page.username = value;
|
||||||
page.username_touched = true;
|
page.username_touched = true;
|
||||||
}
|
}
|
||||||
Msg::InputChanged(FieldId::Login(LoginFieldId::Email), value) => {
|
Msg::InputChanged(FieldId::SignIn(SignInFieldId::Email), value) => {
|
||||||
page.email = value;
|
page.email = value;
|
||||||
page.email_touched = true;
|
page.email_touched = true;
|
||||||
}
|
}
|
||||||
Msg::InputChanged(FieldId::Login(LoginFieldId::Token), value) => {
|
Msg::InputChanged(FieldId::SignIn(SignInFieldId::Token), value) => {
|
||||||
page.token = value;
|
page.token = value;
|
||||||
page.token_touched = true;
|
page.token_touched = true;
|
||||||
}
|
}
|
||||||
@ -78,11 +80,11 @@ pub fn update(msg: Msg, model: &mut model::Model, orders: &mut impl Orders<Msg>)
|
|||||||
|
|
||||||
pub fn view(model: &model::Model) -> Node<Msg> {
|
pub fn view(model: &model::Model) -> Node<Msg> {
|
||||||
let page = match &model.page_content {
|
let page = match &model.page_content {
|
||||||
PageContent::Login(page) => page,
|
PageContent::SignIn(page) => page,
|
||||||
_ => return empty![],
|
_ => return empty![],
|
||||||
};
|
};
|
||||||
|
|
||||||
let username = StyledInput::build(FieldId::Login(LoginFieldId::Username))
|
let username = StyledInput::build(FieldId::SignIn(SignInFieldId::Username))
|
||||||
.value(page.username.as_str())
|
.value(page.username.as_str())
|
||||||
.valid(!page.username_touched || page.username.len() > 1)
|
.valid(!page.username_touched || page.username.len() > 1)
|
||||||
.build()
|
.build()
|
||||||
@ -93,7 +95,7 @@ pub fn view(model: &model::Model) -> Node<Msg> {
|
|||||||
.build()
|
.build()
|
||||||
.into_node();
|
.into_node();
|
||||||
|
|
||||||
let email = StyledInput::build(FieldId::Login(LoginFieldId::Email))
|
let email = StyledInput::build(FieldId::SignIn(SignInFieldId::Email))
|
||||||
.value(page.email.as_str())
|
.value(page.email.as_str())
|
||||||
.valid(!page.email_touched || is_email(page.email.as_str()))
|
.valid(!page.email_touched || is_email(page.email.as_str()))
|
||||||
.build()
|
.build()
|
||||||
@ -116,7 +118,15 @@ pub fn view(model: &model::Model) -> Node<Msg> {
|
|||||||
}
|
}
|
||||||
.build()
|
.build()
|
||||||
.into_node();
|
.into_node();
|
||||||
let submit_field = StyledField::build().input(submit).build().into_node();
|
let register_link = StyledLink::build()
|
||||||
|
.text("Register")
|
||||||
|
.href("/register")
|
||||||
|
.build()
|
||||||
|
.into_node();
|
||||||
|
let submit_field = StyledField::build()
|
||||||
|
.input(div![class!["twoRow"], submit, register_link,])
|
||||||
|
.build()
|
||||||
|
.into_node();
|
||||||
|
|
||||||
let help_icon = StyledIcon::build(Icon::Help)
|
let help_icon = StyledIcon::build(Icon::Help)
|
||||||
.add_class("noPasswordHelp")
|
.add_class("noPasswordHelp")
|
||||||
@ -145,7 +155,7 @@ pub fn view(model: &model::Model) -> Node<Msg> {
|
|||||||
.build()
|
.build()
|
||||||
.into_node();
|
.into_node();
|
||||||
|
|
||||||
let token = StyledInput::build(FieldId::Login(LoginFieldId::Token))
|
let token = StyledInput::build(FieldId::SignIn(SignInFieldId::Token))
|
||||||
.value(page.token.as_str())
|
.value(page.token.as_str())
|
||||||
.valid(!page.token_touched || is_token(page.token.as_str()))
|
.valid(!page.token_touched || is_token(page.token.as_str()))
|
||||||
.build()
|
.build()
|
||||||
@ -177,28 +187,3 @@ pub fn view(model: &model::Model) -> Node<Msg> {
|
|||||||
let children = vec![sign_in_form, bind_token_form];
|
let children = vec![sign_in_form, bind_token_form];
|
||||||
outer_layout(model, "login", children)
|
outer_layout(model, "login", children)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_token(s: &str) -> bool {
|
|
||||||
uuid::Uuid::from_str(s).is_ok()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn is_email(s: &str) -> bool {
|
|
||||||
let mut has_at = false;
|
|
||||||
let mut has_dot = false;
|
|
||||||
|
|
||||||
for c in s.chars() {
|
|
||||||
match c {
|
|
||||||
'\n' | ' ' | '\t' | '\r' => return false,
|
|
||||||
'@' if !has_at => {
|
|
||||||
has_at = true;
|
|
||||||
}
|
|
||||||
'@' if has_at => return false,
|
|
||||||
'.' if has_at => {
|
|
||||||
has_dot = true;
|
|
||||||
}
|
|
||||||
_ if has_dot => return true,
|
|
||||||
_ => (),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
127
jirs-client/src/sign_up.rs
Normal file
127
jirs-client/src/sign_up.rs
Normal file
@ -0,0 +1,127 @@
|
|||||||
|
use seed::{prelude::*, *};
|
||||||
|
|
||||||
|
use jirs_data::{SignUpFieldId, WsMsg};
|
||||||
|
|
||||||
|
use crate::model::{Page, PageContent, SignUpPage};
|
||||||
|
use crate::shared::styled_button::StyledButton;
|
||||||
|
use crate::shared::styled_field::StyledField;
|
||||||
|
use crate::shared::styled_form::StyledForm;
|
||||||
|
use crate::shared::styled_icon::{Icon, StyledIcon};
|
||||||
|
use crate::shared::styled_input::StyledInput;
|
||||||
|
use crate::shared::styled_link::StyledLink;
|
||||||
|
use crate::shared::{outer_layout, ToNode};
|
||||||
|
use crate::validations::is_email;
|
||||||
|
use crate::{model, FieldId, Msg};
|
||||||
|
|
||||||
|
pub fn update(msg: Msg, model: &mut model::Model, _orders: &mut impl Orders<Msg>) {
|
||||||
|
if model.page != Page::SignUp {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if msg == Msg::ChangePage(Page::SignUp) {
|
||||||
|
model.page_content = PageContent::SignUp(SignUpPage::default());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let page = match &mut model.page_content {
|
||||||
|
PageContent::SignUp(page) => page,
|
||||||
|
_ => return,
|
||||||
|
};
|
||||||
|
|
||||||
|
match msg {
|
||||||
|
Msg::InputChanged(FieldId::SignUp(SignUpFieldId::Username), value) => {
|
||||||
|
page.username = value;
|
||||||
|
page.username_touched = true;
|
||||||
|
}
|
||||||
|
Msg::InputChanged(FieldId::SignUp(SignUpFieldId::Email), value) => {
|
||||||
|
page.email = value;
|
||||||
|
page.email_touched = true;
|
||||||
|
}
|
||||||
|
Msg::WsMsg(WsMsg::SignUpSuccess) => {
|
||||||
|
page.sign_up_success = true;
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn view(model: &model::Model) -> Node<Msg> {
|
||||||
|
let page = match &model.page_content {
|
||||||
|
PageContent::SignUp(page) => page,
|
||||||
|
_ => return empty![],
|
||||||
|
};
|
||||||
|
|
||||||
|
let username = StyledInput::build(FieldId::SignUp(SignUpFieldId::Username))
|
||||||
|
.value(page.username.as_str())
|
||||||
|
.valid(!page.username_touched || page.username.len() > 1)
|
||||||
|
.build()
|
||||||
|
.into_node();
|
||||||
|
let username_field = StyledField::build()
|
||||||
|
.label("Username")
|
||||||
|
.input(username)
|
||||||
|
.build()
|
||||||
|
.into_node();
|
||||||
|
|
||||||
|
let email = StyledInput::build(FieldId::SignUp(SignUpFieldId::Email))
|
||||||
|
.value(page.email.as_str())
|
||||||
|
.valid(!page.email_touched || is_email(page.email.as_str()))
|
||||||
|
.build()
|
||||||
|
.into_node();
|
||||||
|
let email_field = StyledField::build()
|
||||||
|
.label("E-Mail")
|
||||||
|
.input(email)
|
||||||
|
.build()
|
||||||
|
.into_node();
|
||||||
|
|
||||||
|
let submit = if page.sign_up_success {
|
||||||
|
StyledButton::build()
|
||||||
|
.success()
|
||||||
|
.text("✓ Please check your mail")
|
||||||
|
} else {
|
||||||
|
StyledButton::build()
|
||||||
|
.primary()
|
||||||
|
.text("Register")
|
||||||
|
.on_click(mouse_ev(Ev::Click, |_| Msg::SignInRequest))
|
||||||
|
}
|
||||||
|
.build()
|
||||||
|
.into_node();
|
||||||
|
|
||||||
|
let sign_in_link = StyledLink::build()
|
||||||
|
.text("Sign In")
|
||||||
|
.href("/login")
|
||||||
|
.build()
|
||||||
|
.into_node();
|
||||||
|
|
||||||
|
let submit_field = StyledField::build()
|
||||||
|
.input(div![class!["twoRow"], submit, sign_in_link,])
|
||||||
|
.build()
|
||||||
|
.into_node();
|
||||||
|
|
||||||
|
let help_icon = StyledIcon::build(Icon::Help)
|
||||||
|
.add_class("noPasswordHelp")
|
||||||
|
.size(22)
|
||||||
|
.build()
|
||||||
|
.into_node();
|
||||||
|
|
||||||
|
let no_pass_section = div![
|
||||||
|
class!["noPasswordSection"],
|
||||||
|
attrs![At::Title => "We don't believe password is helping anyone. Instead after user provide correct login and e-mail he'll receive mail with 1-use token."],
|
||||||
|
help_icon,
|
||||||
|
span!["Why I don't see password?"]
|
||||||
|
];
|
||||||
|
|
||||||
|
let sign_up_form = StyledForm::build()
|
||||||
|
.heading("Sign In to your account")
|
||||||
|
.on_submit(ev(Ev::Submit, |ev| {
|
||||||
|
ev.stop_propagation();
|
||||||
|
ev.prevent_default();
|
||||||
|
Msg::SignInRequest
|
||||||
|
}))
|
||||||
|
.add_field(username_field)
|
||||||
|
.add_field(email_field)
|
||||||
|
.add_field(submit_field)
|
||||||
|
.add_field(no_pass_section)
|
||||||
|
.build()
|
||||||
|
.into_node();
|
||||||
|
let children = vec![sign_up_form];
|
||||||
|
outer_layout(model, "register", children)
|
||||||
|
}
|
26
jirs-client/src/validations.rs
Normal file
26
jirs-client/src/validations.rs
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
pub fn is_email(s: &str) -> bool {
|
||||||
|
let mut has_at = false;
|
||||||
|
let mut has_dot = false;
|
||||||
|
|
||||||
|
for c in s.chars() {
|
||||||
|
match c {
|
||||||
|
'\n' | ' ' | '\t' | '\r' => return false,
|
||||||
|
'@' if !has_at => {
|
||||||
|
has_at = true;
|
||||||
|
}
|
||||||
|
'@' if has_at => return false,
|
||||||
|
'.' if has_at => {
|
||||||
|
has_dot = true;
|
||||||
|
}
|
||||||
|
_ if has_dot => return true,
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_token(s: &str) -> bool {
|
||||||
|
uuid::Uuid::from_str(s).is_ok()
|
||||||
|
}
|
@ -484,12 +484,18 @@ pub enum ProjectFieldId {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialOrd, PartialEq, Hash)]
|
#[derive(Serialize, Deserialize, Clone, Debug, PartialOrd, PartialEq, Hash)]
|
||||||
pub enum LoginFieldId {
|
pub enum SignInFieldId {
|
||||||
Username,
|
Username,
|
||||||
Email,
|
Email,
|
||||||
Token,
|
Token,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Clone, Debug, PartialOrd, PartialEq, Hash)]
|
||||||
|
pub enum SignUpFieldId {
|
||||||
|
Username,
|
||||||
|
Email,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialOrd, PartialEq, Hash)]
|
#[derive(Serialize, Deserialize, Clone, Debug, PartialOrd, PartialEq, Hash)]
|
||||||
pub enum CommentFieldId {
|
pub enum CommentFieldId {
|
||||||
Body,
|
Body,
|
||||||
@ -524,6 +530,7 @@ pub enum WsMsg {
|
|||||||
BindTokenCheck(Uuid),
|
BindTokenCheck(Uuid),
|
||||||
BindTokenBad,
|
BindTokenBad,
|
||||||
BindTokenOk(Uuid),
|
BindTokenOk(Uuid),
|
||||||
|
SignUpSuccess,
|
||||||
|
|
||||||
// project page
|
// project page
|
||||||
ProjectRequest,
|
ProjectRequest,
|
||||||
|
Loading…
Reference in New Issue
Block a user