notwork - login form capsule with rcsignal

This commit is contained in:
2024-08-25 14:07:27 -04:00
parent c2ecce0324
commit 462ca81a15
12 changed files with 180 additions and 100 deletions

View File

@@ -19,7 +19,6 @@ once_cell = "1.18.0"
web-sys = "0.3.64"
cfg-if = "1.0.0"
chrono = { version = "0.4.38", features = ["serde", "wasm-bindgen"] }
axum-login = "0.15.3"
password-auth = "1.0.0"
lazy_static = "1.5"

View File

@@ -2,24 +2,53 @@ use lazy_static::lazy_static;
use perseus::prelude::*;
use serde::{Deserialize, Serialize};
use sycamore::prelude::*;
use web_sys::Event;
use crate::state_enums::OpenState;
lazy_static! {
pub static ref LOGIN_FORM: Capsule<PerseusNodeType, LoginFormProps> = get_capsule();
}
#[derive(Serialize, Deserialize, Clone, ReactiveState)]
#[rx(alias = "LoginFormStateRx")]
struct LoginFormState {
username: String,
password: String,
}
#[derive(Clone)]
pub struct LoginFormProps {
pub open_state: RcSignal<OpenState>,
pub remember_me: bool,
pub endpoint: String,
pub lost_password_url: Option<String>,
pub forgot_password_url: Option<String>,
}
#[auto_scope]
fn login_form_capsule<G: Html>(
cx: Scope,
state: &LoginFormStateRx,
props: LoginFormProps,
) -> View<G> {
let close_modal = move |_event: Event| {
#[cfg(client)]
{
let open_state = props.open_state.clone();
spawn_local_scoped(cx, async move {
open_state.set(OpenState::Closed);
});
}
};
view! {
cx,
div (class="overflow-x-hidden overflow-y-auto fixed h-modal md:h-full top-4 left-0 right-0 md:inset-0 z-50 justify-center items-center"){
div (class="relative w-full max-w-md px-4 h-full md:h-auto") {
div (class="bg-white rounded-lg shadow relative dark:bg-gray-700"){
div (class="flex justify-end p-2"){
button (class="text-gray-400 bg-transparent hover:bg-gray-200 hover:text-gray-900 rounded-lg text-sm p-1.5 ml-auto inline-flex items-center dark:hover:bg-gray-800 dark:hover:text-white"){
button (on:click = close_modal, class="text-gray-400 bg-transparent hover:bg-gray-200 hover:text-gray-900 rounded-lg text-sm p-1.5 ml-auto inline-flex items-center dark:hover:bg-gray-800 dark:hover:text-white"){
"Back"
}
}
@@ -55,21 +84,6 @@ fn login_form_capsule<G: Html>(
}
}
#[derive(Serialize, Deserialize, Clone, ReactiveState)]
#[rx(alias = "LoginFormStateRx")]
struct LoginFormState {
username: String,
password: String,
}
#[derive(Clone)]
pub struct LoginFormProps {
pub remember_me: bool,
pub endpoint: String,
pub lost_password_url: Option<String>,
pub forgot_password_url: Option<String>,
}
pub fn get_capsule<G: Html>() -> Capsule<G, LoginFormProps> {
Capsule::build(Template::build("login_form").build_state_fn(get_build_state))
.empty_fallback()

103
src/components/header.rs Normal file
View File

@@ -0,0 +1,103 @@
use perseus::prelude::*;
use sycamore::prelude::*;
use web_sys::Event;
use crate::{
capsules::login_form::{LoginFormProps, LOGIN_FORM},
state_enums::{GameState, LoginState, OpenState},
templates::global_state::AppStateRx,
};
#[derive(Prop)]
pub struct HeaderProps<'a> {
pub game: GameState,
pub title: &'a str,
}
#[component]
pub fn Header<'a, G: Html>(cx: Scope<'a>, HeaderProps { game, title }: HeaderProps<'a>) -> View<G> {
// Get global state to get authentication info
let global_state = Reactor::<G>::from_cx(cx).get_global_state::<AppStateRx>(cx);
// Create signal for opening/closing the login modal
let login_modal_state = create_rc_signal(OpenState::Closed);
let handle_log_in = {
let login_modal_state = login_modal_state.clone();
move |_event: Event| {
#[cfg(client)]
{
spawn_local_scoped(cx, async move {
login_modal_state.set(OpenState::Open);
});
}
}
};
view! { cx,
header {
div (class = "flex items-center justify-between w-full md:text-center h-20") {
div(class = "flex-1") {}
// Title
div(class = "text-gray-700 text-2xl font-semibold py-2") {
"Pool Elo - Season 1"
}
// Login / register or user buttons
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..."
}
}
},
})
}
}
}
section {
div(class = "flex-1 py-2") {(
match *login_modal_state.get() {
OpenState::Open => {
let login_modal_state = login_modal_state.clone();
view! { cx,
(LOGIN_FORM.widget(cx, "",
LoginFormProps{
open_state: login_modal_state.clone(),
remember_me: true,
endpoint: "".to_string(),
lost_password_url: Some("".to_string()),
forgot_password_url: Some("".to_string()),
}
))
}
}
OpenState::Closed => {
view!{ cx, }
}
})
}
}
}
}

View File

@@ -1,6 +1,8 @@
use crate::{
capsules::login_form::{LoginFormProps, LOGIN_FORM},
templates::global_state::{AppStateRx, LoginState},
components::header::{Header, HeaderProps},
state_enums::{GameState, LoginState},
templates::global_state::AppStateRx,
};
use perseus::prelude::*;
use sycamore::prelude::*;
@@ -8,7 +10,8 @@ use web_sys::Event;
#[derive(Prop)]
pub struct LayoutProps<'a, G: Html> {
pub _title: &'a str,
pub game: GameState,
pub title: &'a str,
pub children: Children<'a, G>,
}
@@ -18,86 +21,26 @@ pub struct LayoutProps<'a, G: Html> {
pub fn Layout<'a, G: Html>(
cx: Scope<'a>,
LayoutProps {
_title: _,
game,
title,
children,
}: LayoutProps<'a, G>,
) -> View<G> {
let children = children.call(cx);
// Get global state to get authentication info
#[cfg(client)]
let global_state = Reactor::<G>::from_cx(cx).get_global_state::<AppStateRx>(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::<G>::from_cx(cx).get_global_state::<AppStateRx>(cx);
global_state.auth.state.set(LoginState::Authenticated);
});
}
};
view! { cx,
// Main page header
header {
div (class = "flex items-center justify-between w-full md:text-center h-20") {
div(class = "flex-1") {}
div(class = "text-gray-700 text-2xl font-semibold py-2") {
"Pool Elo - Season 1"
}
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 page header, including login functionality
Header(game = game, title = title)
main(style = "my-8") {
(
match *global_state.auth.state.get() {
LoginState::Authenticated => { view! { cx,
(LOGIN_FORM.widget(cx, "",
LoginFormProps{
remember_me: true,
endpoint: "".to_string(),
lost_password_url: Some("".to_string()),
forgot_password_url: Some("".to_string())
})
)
}},
_ => { view! { cx, div {} } }})
// Body header
div {
div (class = "container mx-auto px-6 py-3") {

View File

@@ -1 +1,2 @@
mod header;
pub mod layout;

View File

@@ -6,6 +6,7 @@ mod entity;
mod error_views;
#[cfg(engine)]
mod server;
mod state_enums;
mod templates;
use perseus::prelude::*;

22
src/state_enums.rs Normal file
View File

@@ -0,0 +1,22 @@
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, Clone)]
pub enum LoginState {
Authenticated,
NotAuthenticated,
Unknown,
}
#[derive(Serialize, Deserialize, Clone)]
pub enum GameState {
None,
Pool,
Pickleball,
TableTennis,
}
#[derive(Serialize, Deserialize, Clone)]
pub enum OpenState {
Open,
Closed,
}

View File

@@ -1,4 +1,4 @@
use crate::components::layout::Layout;
use crate::{components::layout::Layout, state_enums::GameState};
use perseus::prelude::*;
use serde::{Deserialize, Serialize};
use sycamore::prelude::*;
@@ -40,7 +40,7 @@ fn add_game_form_page<'a, G: Html>(cx: BoundedScope<'_, 'a>, state: &'a PageStat
};
view! { cx,
Layout(_title = "Add Game Results") {
Layout(title = "Add Game Results", game = GameState::Pool) {
div (class = "flex flex-wrap") {
select {
option (value="red")

View File

@@ -3,6 +3,8 @@
use perseus::{prelude::*, state::GlobalStateCreator};
use serde::{Deserialize, Serialize};
use crate::state_enums::LoginState;
cfg_if::cfg_if! {
if #[cfg(engine)] {
@@ -16,13 +18,6 @@ pub struct AppState {
pub auth: AuthData,
}
#[derive(Serialize, Deserialize, Clone)]
pub enum LoginState {
Authenticated,
NotAuthenticated,
Unknown,
}
#[derive(Serialize, Deserialize, ReactiveState, Clone)]
#[rx(alias = "AuthDataRx")]
pub struct AuthData {

View File

@@ -1,10 +1,10 @@
use crate::components::layout::Layout;
use crate::{components::layout::Layout, state_enums::GameState};
use perseus::prelude::*;
use sycamore::prelude::*;
fn index_page<G: Html>(cx: Scope) -> View<G> {
view! { cx,
Layout(_title = "Index") {
Layout(title = "Index", game = GameState::Pool) {
// Anything we put in here will be rendered inside the `<main>` block of the layout
p { "Hello World!" }
br {}

View File

@@ -1,4 +1,4 @@
use crate::components::layout::Layout;
use crate::{components::layout::Layout, state_enums::GameState};
use perseus::prelude::*;
use serde::{Deserialize, Serialize};
use sycamore::prelude::*;
@@ -9,7 +9,7 @@ struct PageState {}
fn one_v_one_board_page<'a, G: Html>(cx: BoundedScope<'_, 'a>, _state: &'a PageStateRx) -> View<G> {
view! { cx,
Layout(_title = "1v1 Leaderboard") {
Layout(title = "1v1 Leaderboard", game = GameState::Pool) {
p { "leaderboard" }
}
}

View File

@@ -1,4 +1,6 @@
use crate::{components::layout::Layout, templates::global_state::AppStateRx};
use crate::{
components::layout::Layout, state_enums::GameState, templates::global_state::AppStateRx,
};
use perseus::prelude::*;
use serde::{Deserialize, Serialize};
@@ -12,7 +14,7 @@ fn overall_board_page<'a, G: Html>(cx: BoundedScope<'_, 'a>, _state: &'a PageSta
let _global_state = Reactor::<G>::from_cx(cx).get_global_state::<AppStateRx>(cx);
view! { cx,
Layout(_title = "Overall Leaderboard") {
Layout(title = "Overall Leaderboard", game = GameState::Pool) {
ul {
(View::new_fragment(
vec![],