238 lines
9.1 KiB
Rust
238 lines
9.1 KiB
Rust
use bounce::helmet::Helmet;
|
|
use cynic::QueryBuilder;
|
|
use std::rc::Rc;
|
|
use web_sys::HtmlInputElement;
|
|
use yew::prelude::*;
|
|
use yew_router::prelude::*;
|
|
use yewdux::prelude::*;
|
|
|
|
use crate::{
|
|
components,
|
|
graphql::{self, ReqwestExt},
|
|
routes, stores,
|
|
};
|
|
|
|
pub enum Msg {
|
|
UpdateUser(Rc<stores::User>),
|
|
UpdateNotifications(Rc<stores::Notifications>),
|
|
Login,
|
|
LoginDone {
|
|
user_data: stores::UserData,
|
|
result: graphql::GraphQLResult<graphql::queries::VerifyLogin>,
|
|
},
|
|
}
|
|
|
|
pub struct Login {
|
|
user: Rc<stores::User>,
|
|
user_dispatch: Dispatch<stores::User>,
|
|
notifications: Rc<stores::Notifications>,
|
|
notifications_dispatch: Dispatch<stores::Notifications>,
|
|
username: NodeRef,
|
|
password: NodeRef,
|
|
loading: bool,
|
|
error: Option<&'static str>,
|
|
}
|
|
|
|
impl Component for Login {
|
|
type Message = Msg;
|
|
type Properties = ();
|
|
|
|
fn create(ctx: &Context<Self>) -> Self {
|
|
let user_dispatch = Dispatch::subscribe(ctx.link().callback(Msg::UpdateUser));
|
|
let notifications_dispatch =
|
|
Dispatch::subscribe(ctx.link().callback(Msg::UpdateNotifications));
|
|
|
|
Self {
|
|
user: user_dispatch.get(),
|
|
user_dispatch,
|
|
notifications: notifications_dispatch.get(),
|
|
notifications_dispatch,
|
|
username: NodeRef::default(),
|
|
password: NodeRef::default(),
|
|
loading: false,
|
|
error: None,
|
|
}
|
|
}
|
|
|
|
fn update(&mut self, ctx: &Context<Self>, msg: Self::Message) -> bool {
|
|
match msg {
|
|
Msg::UpdateUser(x) => {
|
|
self.error = None;
|
|
|
|
let prev = self.user.logged_in();
|
|
self.user = x;
|
|
|
|
prev != self.user.logged_in()
|
|
}
|
|
Msg::UpdateNotifications(x) => {
|
|
self.notifications = x;
|
|
|
|
false
|
|
}
|
|
Msg::Login => {
|
|
self.error = None;
|
|
|
|
let username = match self.username.cast::<HtmlInputElement>().map(|x| x.value()) {
|
|
Some(x) => {
|
|
if x.is_empty() {
|
|
return false;
|
|
} else {
|
|
x
|
|
}
|
|
}
|
|
None => {
|
|
return false;
|
|
}
|
|
};
|
|
let password = match self.password.cast::<HtmlInputElement>().map(|x| x.value()) {
|
|
Some(x) => {
|
|
if x.is_empty() {
|
|
return false;
|
|
} else {
|
|
x
|
|
}
|
|
}
|
|
None => {
|
|
return false;
|
|
}
|
|
};
|
|
|
|
self.loading = true;
|
|
let operation =
|
|
graphql::queries::VerifyLogin::build(graphql::queries::VerifyLoginVariables {
|
|
username: username.to_owned(),
|
|
password: password.to_owned(),
|
|
});
|
|
ctx.link().send_future(async move {
|
|
Msg::LoginDone {
|
|
user_data: stores::UserData { username, password },
|
|
result: graphql::client(None).run_graphql(operation).await,
|
|
}
|
|
});
|
|
|
|
true
|
|
}
|
|
Msg::LoginDone { user_data, result } => {
|
|
self.loading = false;
|
|
|
|
match result {
|
|
Ok(resp) => {
|
|
if let Some(errors) = resp.errors {
|
|
self.notifications_dispatch.reduce_mut(|notifs| {
|
|
for e in errors {
|
|
notifs.push(stores::Notification::danger(Some(
|
|
components::notification::NotificationMessage::GraphQLError(
|
|
e,
|
|
),
|
|
)));
|
|
}
|
|
});
|
|
|
|
true
|
|
} else if resp.data.unwrap().verify_login {
|
|
self.error = None;
|
|
self.user_dispatch.set(stores::User(Some(user_data)));
|
|
|
|
false
|
|
} else {
|
|
const MESSAGE: &str = "Username or password not correct";
|
|
|
|
self.error = Some(MESSAGE);
|
|
self.notifications_dispatch.reduce_mut(|notifs| {
|
|
notifs.push(stores::Notification {
|
|
notification_type:
|
|
components::notification::NotificationType::Danger,
|
|
message: Some(
|
|
components::notification::NotificationMessage::Text(vec![
|
|
MESSAGE.to_string(),
|
|
]),
|
|
),
|
|
});
|
|
});
|
|
|
|
true
|
|
}
|
|
}
|
|
Err(e) => {
|
|
self.notifications_dispatch.reduce_mut(|notifs| {
|
|
notifs.push(stores::Notification {
|
|
notification_type:
|
|
components::notification::NotificationType::Danger,
|
|
message: Some(components::notification::NotificationMessage::Text(
|
|
vec![e.to_string()],
|
|
)),
|
|
});
|
|
});
|
|
|
|
false
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
fn view(&self, ctx: &Context<Self>) -> Html {
|
|
if self.user.logged_in() {
|
|
ctx.link().navigator().unwrap().push(&routes::Route::Index);
|
|
}
|
|
|
|
html! {
|
|
<>
|
|
<Helmet>
|
|
<title>{ "Login" }</title>
|
|
</Helmet>
|
|
|
|
<div class={classes!("columns", "is-centered", "my-auto")}>
|
|
<div class={classes!("column", "is-half", "box")}>
|
|
<div class={classes!("p-4")}>
|
|
<form onsubmit={ctx.link().callback(|e: SubmitEvent| { e.prevent_default(); Msg::Login })}>
|
|
<div class={classes!("field")}>
|
|
<p class={classes!("control", "has-icons-left")}>
|
|
<input
|
|
class={classes!("input")}
|
|
type="text"
|
|
placeholder="Username"
|
|
ref={self.username.clone()}
|
|
/>
|
|
<span class={classes!("icon", "is-small", "is-left")}>
|
|
<i class={classes!("fa-solid", "fa-envelope")} />
|
|
</span>
|
|
</p>
|
|
</div>
|
|
<div class={classes!("field")}>
|
|
<p class={classes!("control", "has-icons-left")}>
|
|
<input
|
|
class={classes!("input")}
|
|
type="password"
|
|
placeholder="Password"
|
|
ref={self.password.clone()}
|
|
/>
|
|
<span class={classes!("icon", "is-small", "is-left")}>
|
|
<i class={classes!("fa-solid", "fa-lock")} />
|
|
</span>
|
|
</p>
|
|
</div>
|
|
<div class={classes!("field")}>
|
|
<p class={classes!("control")}>
|
|
<button
|
|
type="submit"
|
|
class={classes!("button", "is-success", "is-fullwidth", if self.loading { Some("is-loading") } else { None })}
|
|
>
|
|
{ "Login" }
|
|
</button>
|
|
</p>
|
|
</div>
|
|
if let Some(message) = self.error {
|
|
<p class={classes!("help", "is-danger")}>
|
|
{ message }
|
|
</p>
|
|
}
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</>
|
|
}
|
|
}
|
|
}
|