From 528ce0335c5b6b5aea3f3fb0ba2e4c46c2dd8b15 Mon Sep 17 00:00:00 2001 From: Matthew Kaminski Date: Mon, 19 Aug 2024 02:38:29 -0400 Subject: [PATCH] Add auth global state and login button --- src/components/layout.rs | 90 ++++++++++++++++++++++++++++------- src/templates/global_state.rs | 55 ++++++++++++++++----- 2 files changed, 115 insertions(+), 30 deletions(-) diff --git a/src/components/layout.rs b/src/components/layout.rs index 7c37254..6ffc68a 100644 --- a/src/components/layout.rs +++ b/src/components/layout.rs @@ -1,4 +1,7 @@ +use crate::templates::global_state::{AppStateRx, LoginState}; +use perseus::prelude::*; use sycamore::prelude::*; +use web_sys::Event; #[derive(Prop)] pub struct LayoutProps<'a, G: Html> { @@ -6,6 +9,8 @@ pub struct LayoutProps<'a, G: Html> { pub children: Children<'a, G>, } +// Using elements from here: https://flowbite.com/docs/components/buttons/ + #[component] pub fn Layout<'a, G: Html>( cx: Scope<'a>, @@ -15,33 +20,84 @@ pub fn Layout<'a, G: Html>( }: LayoutProps<'a, G>, ) -> View { let children = children.call(cx); + // Get global state to get authentication info + let global_state = Reactor::::from_cx(cx).get_global_state::(cx); + + // Check if the client is authenticated or not + #[cfg(client)] + global_state.auth.detect_state(); + + // TODO -> move into function + let handle_log_in = move |_event: Event| { + #[cfg(client)] + { + spawn_local_scoped(cx, async move { + let global_state = Reactor::::from_cx(cx).get_global_state::(cx); + global_state.auth.state.set(LoginState::Authenticated); + }); + } + }; view! { cx, + // Main page header header { - div (class = "flex items-center justify-between") { - div (class = "w-full text-gray-700 md:text-center text-2xl font-semibold") { + div (class = "flex items-center justify-between w-full md:text-center") { + div(class = "flex-1") {} + div(class = "text-gray-700 text-2xl font-semibold py-2") { "Pool Elo - Season 1" } - } - - div (class = "container mx-auto px-6 py-3") { - nav (class = "sm:flex sm:justify-center sm:items-center mt-4 hidden") { - div (class = "flex flex-col sm:flex-row"){ - a(href = "add-game-form", - class = "mt-3 text-gray-600 hover:underline sm:mx-3 sm:mt-0" - ) { "Add game result" } - a(href = "one-v-one-board", - class = "mt-3 text-gray-600 hover:underline sm:mx-3 sm:mt-0" - ) { "1v1 Leaderboard" } - a(href = "overall-board", - class = "mt-3 text-gray-600 hover:underline sm:mx-3 sm:mt-0" - ) { "Overall Leaderboard" } - } + div(class = "flex-1 py-2") {( + match *global_state.auth.state.get() { + LoginState::NotAuthenticated => { + view! { cx, + button(class = "text-gray-900 bg-white border border-gray-300 focus:outline-none hover:bg-gray-100 focus:ring-4 focus:ring-gray-100 font-medium rounded-lg text-sm px-5 py-2.5 me-2 mb-2 dark:bg-gray-800 dark:text-white dark:border-gray-600 dark:hover:bg-gray-700 dark:hover:border-gray-600 dark:focus:ring-gray-700") { + "Register" + } + button(on:click = handle_log_in,class = "text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:ring-blue-300 font-medium rounded-lg text-sm px-5 py-2.5 me-2 mb-2 dark:bg-blue-600 dark:hover:bg-blue-700 focus:outline-none dark:focus:ring-blue-800") { + "Login" + } + } + } + LoginState::Authenticated => { + view! { cx, + div { + "Hello {username}!" + } + } + } + // Will only appear for a few seconds + LoginState::Unknown => { + view! { cx, + div (class = "px-5 py-2.5 me-2 mb-2"){ + "Loading..." + } + } + }, + }) } } } main(style = "my-8") { + // Body header + div { + div (class = "container mx-auto px-6 py-3") { + nav (class = "sm:flex sm:justify-center sm:items-center mt-4 hidden") { + div (class = "flex flex-col sm:flex-row"){ + a(href = "add-game-form", + class = "mt-3 text-gray-600 hover:underline sm:mx-3 sm:mt-0" + ) { "Add game result" } + a(href = "one-v-one-board", + class = "mt-3 text-gray-600 hover:underline sm:mx-3 sm:mt-0" + ) { "1v1 Leaderboard" } + a(href = "overall-board", + class = "mt-3 text-gray-600 hover:underline sm:mx-3 sm:mt-0" + ) { "Overall Leaderboard" } + } + } + } + } + // Actual body div(class = "container mx-auto px-6") { div(class = "md:flex mt-8 md:-mx-4") { div(class = "rounded-md overflow-hidden bg-cover bg-center") { diff --git a/src/templates/global_state.rs b/src/templates/global_state.rs index c6a7b2d..7c71814 100644 --- a/src/templates/global_state.rs +++ b/src/templates/global_state.rs @@ -11,25 +11,54 @@ cfg_if::cfg_if! { #[derive(Serialize, Deserialize, ReactiveState, Clone)] #[rx(alias = "AppStateRx")] -pub struct AppState {} - -pub fn get_global_state_creator() -> GlobalStateCreator { - GlobalStateCreator::new() - .build_state_fn(get_build_state) - .request_state_fn(get_request_state) +pub struct AppState { + #[rx(nested)] + pub auth: AuthData, } -#[engine_only_fn] -fn get_state() -> AppState { - AppState {} +#[derive(Serialize, Deserialize, Clone)] +pub enum LoginState { + Authenticated, + NotAuthenticated, + Unknown, +} + +#[derive(Serialize, Deserialize, ReactiveState, Clone)] +#[rx(alias = "AuthDataRx")] +pub struct AuthData { + pub state: LoginState, + pub username: Option, + pub claims: Claims, +} + +#[derive(Serialize, Deserialize, ReactiveState, Clone)] +#[rx(alias = "ClaimsRx")] +pub struct Claims {} + +pub fn get_global_state_creator() -> GlobalStateCreator { + GlobalStateCreator::new().build_state_fn(get_build_state) } #[engine_only_fn] pub async fn get_build_state() -> AppState { - get_state() + AppState { + auth: AuthData { + state: LoginState::Unknown, + username: None, + claims: Claims {}, + }, + } } -#[engine_only_fn] -pub async fn get_request_state(_req: Request) -> AppState { - get_state() +// Client only code to check if they're authenticated +#[cfg(client)] +impl AuthDataRx { + pub fn detect_state(&self) { + // If the user is in a known state, return + if let LoginState::Authenticated | LoginState::NotAuthenticated = *self.state.get() { + return; + } + // TODO -> Get state from storage + self.state.set(LoginState::NotAuthenticated); + } }