From d40c3792167e9d1af37202f21878e42095cfb06a Mon Sep 17 00:00:00 2001 From: Amatsugu Date: Sat, 17 May 2025 16:55:51 -0400 Subject: [PATCH] Login Implementation --- AobaClient/Cargo.lock | 2 + AobaClient/Cargo.toml | 2 + AobaClient/assets/style/inputs.scss | 5 +++ AobaClient/assets/style/main.scss | 39 +++++++++++++++++- AobaClient/src/components/basic/input.rs | 1 + AobaClient/src/components/icons.rs | 52 ++++++++++++++++++++++++ AobaClient/src/components/mod.rs | 3 ++ AobaClient/src/components/notif.rs | 44 ++++++++++++++++++++ AobaClient/src/contexts/auth_context.rs | 31 +++++++++++++- AobaClient/src/layouts/basic_layout.rs | 10 ----- AobaClient/src/layouts/main_layout.rs | 8 ++-- AobaClient/src/layouts/mod.rs | 2 - AobaClient/src/main.rs | 2 +- AobaClient/src/views/login.rs | 23 ++++++++--- AobaCore/AccountsService.cs | 2 + 15 files changed, 201 insertions(+), 25 deletions(-) create mode 100644 AobaClient/src/components/icons.rs create mode 100644 AobaClient/src/components/notif.rs delete mode 100644 AobaClient/src/layouts/basic_layout.rs diff --git a/AobaClient/Cargo.lock b/AobaClient/Cargo.lock index 5520202..e106215 100644 --- a/AobaClient/Cargo.lock +++ b/AobaClient/Cargo.lock @@ -37,12 +37,14 @@ name = "aoba-client" version = "0.1.0" dependencies = [ "dioxus", + "js-sys", "prost", "serde", "serde_repr", "tonic", "tonic-build", "tonic-web-wasm-client", + "web-sys", ] [[package]] diff --git a/AobaClient/Cargo.toml b/AobaClient/Cargo.toml index f36f8eb..a7dd6fa 100644 --- a/AobaClient/Cargo.toml +++ b/AobaClient/Cargo.toml @@ -16,6 +16,8 @@ tonic = { version = "*", default-features = false, features = [ ] } prost = "0.13" tonic-web-wasm-client = "0.7" +js-sys = { version = "0.3.77" } +web-sys = { version = "0.3.77", features = ["Storage", "Window"] } [build-dependencies] tonic-build = { version = "*", default-features = false, features = ["prost"] } diff --git a/AobaClient/assets/style/inputs.scss b/AobaClient/assets/style/inputs.scss index f139d76..a25384c 100644 --- a/AobaClient/assets/style/inputs.scss +++ b/AobaClient/assets/style/inputs.scss @@ -2,3 +2,8 @@ display: grid; width: 100%; } + +label { + display: flex; + flex-direction: column; +} diff --git a/AobaClient/assets/style/main.scss b/AobaClient/assets/style/main.scss index 1376250..52c5388 100644 --- a/AobaClient/assets/style/main.scss +++ b/AobaClient/assets/style/main.scss @@ -18,7 +18,7 @@ body { margin: 0; } -#main { +#main:has(#content) { display: grid; grid-template-columns: $navBarSize 1fr; grid-template-areas: "Nav Content"; @@ -65,3 +65,40 @@ body { } } } + +#main:has(#centralModal) { + display: grid; + place-items: center; + height: 100dvh; + width: 100dvw; +} + +#centralModal { + display: flex; + flex-direction: column; +} + +form { + display: flex; + flex-direction: column; + gap: 5px; +} + +.notif { + background-color: red; + display: grid; + grid-template-columns: 50px 1fr; + height: 50px; + border-radius: 20px; + border-bottom-left-radius: 0; + border-top-right-radius: 0; + + .icon { + padding: 10px; + } + + .message { + padding: 10px; + align-self: center; + } +} diff --git a/AobaClient/src/components/basic/input.rs b/AobaClient/src/components/basic/input.rs index f00202d..1275e38 100644 --- a/AobaClient/src/components/basic/input.rs +++ b/AobaClient/src/components/basic/input.rs @@ -21,6 +21,7 @@ pub fn Input(props: InputProps) -> Element { input { type : props.r#type.unwrap_or("text".into()), value: props.value, + oninput: move |e| if let Some(mut s) = props.value { s.set(e.value()); }, name: props.name, placeholder:ph, required: props.required diff --git a/AobaClient/src/components/icons.rs b/AobaClient/src/components/icons.rs new file mode 100644 index 0000000..df35daf --- /dev/null +++ b/AobaClient/src/components/icons.rs @@ -0,0 +1,52 @@ +use dioxus::prelude::*; + +#[component] +pub fn Info() -> Element { + rsx! { + svg { + class: "size-6", + fill: "currentColor", + view_box: "0 0 24 24", + xmlns: "http://www.w3.org/2000/svg", + path { + clip_rule: "evenodd", + d: "M2.25 12c0-5.385 4.365-9.75 9.75-9.75s9.75 4.365 9.75 9.75-4.365 9.75-9.75 9.75S2.25 17.385 2.25 12Zm8.706-1.442c1.146-.573 2.437.463 2.126 1.706l-.709 2.836.042-.02a.75.75 0 0 1 .67 1.34l-.04.022c-1.147.573-2.438-.463-2.127-1.706l.71-2.836-.042.02a.75.75 0 1 1-.671-1.34l.041-.022ZM12 9a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5Z", + fill_rule: "evenodd", + } + } + } +} + +#[component] +pub fn Warn() -> Element { + rsx! { + svg { + class: "size-6", + fill: "currentColor", + view_box: "0 0 24 24", + xmlns: "http://www.w3.org/2000/svg", + path { + clip_rule: "evenodd", + d: "M9.401 3.003c1.155-2 4.043-2 5.197 0l7.355 12.748c1.154 2-.29 4.5-2.599 4.5H4.645c-2.309 0-3.752-2.5-2.598-4.5L9.4 3.003ZM12 8.25a.75.75 0 0 1 .75.75v3.75a.75.75 0 0 1-1.5 0V9a.75.75 0 0 1 .75-.75Zm0 8.25a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5Z", + fill_rule: "evenodd", + } + } + } +} + +#[component] +pub fn Error() -> Element { + rsx! { + svg { + class: "size-6", + fill: "currentColor", + view_box: "0 0 24 24", + xmlns: "http://www.w3.org/2000/svg", + path { + clip_rule: "evenodd", + d: "M9.401 3.003c1.155-2 4.043-2 5.197 0l7.355 12.748c1.154 2-.29 4.5-2.599 4.5H4.645c-2.309 0-3.752-2.5-2.598-4.5L9.4 3.003ZM12 8.25a.75.75 0 0 1 .75.75v3.75a.75.75 0 0 1-1.5 0V9a.75.75 0 0 1 .75-.75Zm0 8.25a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5Z", + fill_rule: "evenodd", + } + } + } +} diff --git a/AobaClient/src/components/mod.rs b/AobaClient/src/components/mod.rs index f45d407..7a144a5 100644 --- a/AobaClient/src/components/mod.rs +++ b/AobaClient/src/components/mod.rs @@ -2,8 +2,11 @@ pub mod basic; mod media_grid; mod media_item; mod navbar; +mod notif; mod search; pub use media_grid::*; pub use media_item::*; pub use navbar::*; +pub use notif::*; pub use search::*; +mod icons; diff --git a/AobaClient/src/components/notif.rs b/AobaClient/src/components/notif.rs new file mode 100644 index 0000000..ba089ed --- /dev/null +++ b/AobaClient/src/components/notif.rs @@ -0,0 +1,44 @@ +use dioxus::{html::u::icon, prelude::*}; + +use crate::components::icons; + +#[derive(PartialEq, Clone, Props)] +pub struct NotifProps { + r#type: Option, + message: String, +} + +#[derive(PartialEq, Clone)] +pub enum NotifType { + Notice, + Error, + Warning, +} + +#[component] +pub fn Notif(props: NotifProps) -> Element { + let t = props.r#type.unwrap_or(NotifType::Notice); + let type_class = match t { + NotifType::Notice => "notice", + NotifType::Error => "error", + NotifType::Warning => "warning", + }; + let m = props.message; + rsx! { + div{ + class: "notif {type_class}", + div{ + class: "icon", + match t { + NotifType::Notice => icons::Error(), + NotifType::Error => icons::Error(), + NotifType::Warning => icons::Warn(), + } + } + div{ + class: "message", + "{m}" + } + } + } +} diff --git a/AobaClient/src/contexts/auth_context.rs b/AobaClient/src/contexts/auth_context.rs index 687d5df..154f661 100644 --- a/AobaClient/src/contexts/auth_context.rs +++ b/AobaClient/src/contexts/auth_context.rs @@ -1,6 +1,35 @@ -use dioxus::signals::Signal; +use dioxus::signals::{Signal, Writable}; +use web_sys::window; #[derive(Clone, Copy, Default)] pub struct AuthContext { pub jwt: Signal>, } + +impl AuthContext { + pub fn set_token(&mut self, token: String) { + self.jwt.set(Some(token.clone())); + let local_storage = window().unwrap().local_storage().unwrap().unwrap(); + _ = local_storage.set_item("token", token.as_str()); + } + + pub fn new() -> Self { + println!("new"); + let local_storage = window().unwrap().local_storage().unwrap().unwrap(); + match local_storage.get_item("token") { + Ok(value) => { + if let Some(jwt) = value { + println!("jwt"); + return AuthContext { + jwt: Signal::new(Some(jwt)), + }; + } + return AuthContext::default(); + } + Err(_) => { + println!("err"); + AuthContext::default() + } + } + } +} diff --git a/AobaClient/src/layouts/basic_layout.rs b/AobaClient/src/layouts/basic_layout.rs deleted file mode 100644 index 85d71ca..0000000 --- a/AobaClient/src/layouts/basic_layout.rs +++ /dev/null @@ -1,10 +0,0 @@ -use dioxus::prelude::*; - -use crate::route::Route; - -#[component] -pub fn BasicLayout() -> Element { - rsx! { - Outlet:: {} - } -} diff --git a/AobaClient/src/layouts/main_layout.rs b/AobaClient/src/layouts/main_layout.rs index e3a1b26..a910250 100644 --- a/AobaClient/src/layouts/main_layout.rs +++ b/AobaClient/src/layouts/main_layout.rs @@ -1,14 +1,14 @@ use dioxus::prelude::*; -use crate::{components::Navbar, contexts::AuthContext, layouts::BasicLayout, views::Login, Route}; +use crate::{components::Navbar, contexts::AuthContext, views::Login, Route}; #[component] pub fn MainLayout() -> Element { let auth_context = use_context::(); - // if auth_context.jwt.cloned().is_none() { - // return rsx! { Login { } }; - // } + if auth_context.jwt.cloned().is_none() { + return rsx! { Login { } }; + } return rsx! { Navbar {} diff --git a/AobaClient/src/layouts/mod.rs b/AobaClient/src/layouts/mod.rs index 1eed39d..c9c4196 100644 --- a/AobaClient/src/layouts/mod.rs +++ b/AobaClient/src/layouts/mod.rs @@ -1,4 +1,2 @@ -mod basic_layout; mod main_layout; -pub use basic_layout::*; pub use main_layout::*; diff --git a/AobaClient/src/main.rs b/AobaClient/src/main.rs index cba8dbf..50ad648 100644 --- a/AobaClient/src/main.rs +++ b/AobaClient/src/main.rs @@ -24,7 +24,7 @@ fn main() { #[component] fn App() -> Element { - let _auth_state = use_context_provider(|| AuthContext::default()); + let _auth_state = use_context_provider(|| AuthContext::new()); rsx! { document::Link { rel: "icon", href: FAVICON } document::Link { rel: "preconnect", href: "https://fonts.googleapis.com" } diff --git a/AobaClient/src/views/login.rs b/AobaClient/src/views/login.rs index d96e21f..79718aa 100644 --- a/AobaClient/src/views/login.rs +++ b/AobaClient/src/views/login.rs @@ -2,7 +2,7 @@ use dioxus::prelude::*; use tonic::IntoRequest; use crate::{ - components::basic::{Button, Input}, + components::{basic::Input, Notif, NotifType}, contexts::AuthContext, rpc::{aoba::Credentials, get_auth_rpc_client}, }; @@ -11,9 +11,16 @@ use crate::{ pub fn Login() -> Element { let username = use_signal(|| "".to_string()); let password = use_signal(|| "".to_string()); + let mut error: Signal> = use_signal(|| None); let mut auth_context = use_context::(); - let onclick = move |_| { + let login = move |e: Event| { + e.prevent_default(); + if username.cloned().is_empty() || password.cloned().is_empty() { + error.set(Some("Username and Password are required".into())); + return; + } + spawn(async move { let mut auth = get_auth_rpc_client(); let result = auth @@ -31,8 +38,9 @@ pub fn Login() -> Element { crate::rpc::aoba::login_response::Result::Jwt(jwt) => { auth_context.jwt.set(Some(jwt.token)); } - crate::rpc::aoba::login_response::Result::Error(_login_error) => { + crate::rpc::aoba::login_response::Result::Error(login_error) => { auth_context.jwt.set(None); + error.set(Some(login_error.message)); } }; } @@ -46,12 +54,15 @@ pub fn Login() -> Element { rsx! { div{ id: "centralModal", + if let Some(err) = error.cloned() { + Notif{ type: NotifType::Error, message: err} + } form{ Input { type : "text", name: "username", label: "Username", value: username, required: true }, Input { type : "password", name: "password", label: "Password", value: password, required: true }, - Button { - text: "Login!", - onclick: onclick + button { + onclick: login, + "Login!", } } } diff --git a/AobaCore/AccountsService.cs b/AobaCore/AccountsService.cs index 72704e4..2386321 100644 --- a/AobaCore/AccountsService.cs +++ b/AobaCore/AccountsService.cs @@ -25,6 +25,8 @@ public class AccountsService(IMongoDatabase db) public async Task VerifyLoginAsync(string username, string password, CancellationToken cancellationToken = default) { var user = await _users.Find(u => u.Username == username).FirstOrDefaultAsync(cancellationToken); + if(user == null) + return null; if(user.IsArgon && Argon2.Verify(user.PasswordHash, password)) return user;