Add basic login page

This commit is contained in:
pjht 2024-10-16 13:51:53 -05:00
parent 014525b683
commit 8a8e773795
Signed by: pjht
GPG Key ID: CA239FC6934E6F3A
4 changed files with 98 additions and 44 deletions

View File

@ -1,7 +1,7 @@
pub mod components;
mod pages;
use components::Nav;
use pages::Home;
use pages::{Home, Login};
use yew::prelude::*;
use yew_router::prelude::*;
@ -10,6 +10,8 @@ use yew_router::prelude::*;
enum Route {
#[at("/")]
Home,
#[at("/login")]
Login,
#[not_found]
#[at("/404")]
NotFound,
@ -18,6 +20,7 @@ enum Route {
fn switch(routes: Route) -> Html {
match routes {
Route::Home => html! { <Home /> },
Route::Login => html! { <Login /> },
Route::NotFound => html! {
<>
<Nav />

View File

@ -1,2 +1,4 @@
mod home;
pub use home::Home;
mod login;
pub use login::Login;

View File

@ -2,15 +2,16 @@ use std::time::Duration;
use chrono::Local;
use common::ChatMessage;
use gloo::{console::log, events::EventListener, net::http::Request};
use gloo::{console::log, events::EventListener};
use wasm_bindgen::{JsCast, JsValue};
use web_sys::{js_sys::Uint8Array, CloseEvent, Event, MessageEvent, WebSocket};
use yew::{
platform::{spawn_local, time::sleep},
prelude::*,
};
use yew::{platform::time::sleep, prelude::*};
use yew_router::prelude::RouterScopeExt;
use crate::components::{Alert, Message, Nav};
use crate::{
components::{Alert, Message, Nav},
Route,
};
pub enum HomeMessage {
SubmittedMessage(String),
@ -18,7 +19,6 @@ pub enum HomeMessage {
WsClose(CloseEvent),
WsOpen,
WsReconnect,
Authenticated,
}
#[derive(PartialEq, Eq)]
@ -28,7 +28,6 @@ enum WsState {
}
pub struct Home {
authenticated: bool,
messages: Vec<ChatMessage>,
chat_ws: Option<WebSocketConn>,
ws_state: WsState,
@ -88,26 +87,6 @@ impl Home {
listeners: [open_ev, close_ev, msg_ev],
});
}
fn authenticate(ctx: &Context<Self>) {
log!("Authenticating to backend");
let auth_cb = ctx.link().callback(|_: ()| HomeMessage::Authenticated);
spawn_local(async move {
let location = web_sys::window().unwrap().location();
let login_url = format!(
"{}//{}/api/login",
location.protocol().unwrap(),
location.host().unwrap()
);
Request::post(&login_url)
.body("")
.unwrap()
.send()
.await
.unwrap();
auth_cb.emit(());
});
}
}
impl Component for Home {
@ -116,9 +95,7 @@ impl Component for Home {
type Properties = ();
fn create(ctx: &Context<Self>) -> Self {
Self::authenticate(ctx);
let mut slf = Self {
authenticated: false,
messages: Vec::new(),
chat_ws: None,
ws_state: WsState::Closed,
@ -147,7 +124,7 @@ impl Component for Home {
}
})
};
let disable_input = self.ws_state != WsState::Open || !self.authenticated;
let disable_input = self.ws_state != WsState::Open;
html! {
<div class="myvh-100 d-flex flex-column">
<Nav />
@ -155,9 +132,6 @@ impl Component for Home {
if self.ws_state != WsState::Open {
<Alert message="Connection to backend lost, trying to reconnect" />
}
if !self.authenticated {
<Alert message="Authenticating to backend" />
}
<div
ref={self.message_container_ref.clone()}
class="d-flex border rounded flex-grow-1 flex-column-reverse overflow-auto mb-3"
@ -203,10 +177,8 @@ impl Component for Home {
let code = close_event.code();
log!("WS connection closed with code", code);
if code == 4000 {
log!("Unauthorized, reauthenticating");
self.ws_state = WsState::Closed;
self.authenticated = false;
Self::authenticate(ctx);
log!("Unauthorized, redirecting to login page");
ctx.link().navigator().unwrap().push(&Route::Login);
return true;
}
if self.ws_state == WsState::Closed {
@ -245,11 +217,6 @@ impl Component for Home {
}
false
}
HomeMessage::Authenticated => {
self.connect_ws(ctx);
self.authenticated = true;
true
}
}
}

View File

@ -0,0 +1,82 @@
use crate::{components::Nav, Route};
use gloo::net::http::Request;
use yew::{platform::spawn_local, prelude::*};
use yew_router::hooks::use_navigator;
#[function_component]
#[allow(non_snake_case)] // Component names should be in PascalCase
pub fn Login() -> Html {
let disable_input = use_state(|| false);
let navigator = use_navigator();
let login_submitted = {
let disable_input = disable_input.clone();
let navigator = navigator.clone();
move || {
disable_input.set(true);
let navigator = navigator.clone();
spawn_local(async move {
let location = web_sys::window().unwrap().location();
let login_url = format!(
"{}//{}/api/login",
location.protocol().unwrap(),
location.host().unwrap()
);
Request::post(&login_url)
.body("")
.unwrap()
.send()
.await
.unwrap();
navigator.as_ref().unwrap().push(&Route::Home);
});
}
};
let password_keypress = {
let login_submitted = login_submitted.clone();
Callback::from(move |press: KeyboardEvent| {
if press.key() == "Enter" {
login_submitted();
}
})
};
html! {
<>
<Nav />
<div class="container-fluid d-flex flex-column align-items-center" style="max-width: 570px">
<h1>{ "Login" }</h1>
<input
type="text"
id="username"
class="form-control"
placeholder="Username"
disabled={*disable_input}
/>
<input
type="password"
id="password"
class="form-control"
placeholder="Password"
disabled={*disable_input}
onkeydown={password_keypress}
/>
if !*disable_input {
<input
type="button"
value="Login"
class="btn btn-success mt-3"
onclick={Callback::from(move |_| login_submitted())}
/>
} else {
<button class="btn btn-success mt-3" disabled=true>
<span
class="spinner-border spinner-border-sm"
role="status"
aria-hidden="true"
/>
{ "Login" }
</button>
}
</div>
</>
}
}