gitea_pages/frontend/src/routes/login.rs

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>
</>
}
}
}