From 242f9b1218b29f65ee75e7feaa14589d20fcf1bb Mon Sep 17 00:00:00 2001 From: Matthew Kaminski Date: Mon, 26 Aug 2024 20:15:48 -0400 Subject: [PATCH] Add initial forgot password form --- src/capsules/forgot_password_form.rs | 120 +++++++++++++++++++++++++++ src/capsules/login_form.rs | 38 +++++++-- src/components/header.rs | 17 +++- src/global_state.rs | 2 + src/main.rs | 1 + 5 files changed, 170 insertions(+), 8 deletions(-) diff --git a/src/capsules/forgot_password_form.rs b/src/capsules/forgot_password_form.rs index e69de29..fa7c7cf 100644 --- a/src/capsules/forgot_password_form.rs +++ b/src/capsules/forgot_password_form.rs @@ -0,0 +1,120 @@ +use lazy_static::lazy_static; +use perseus::prelude::*; +use serde::{Deserialize, Serialize}; +use sycamore::prelude::*; +use web_sys::Event; + +cfg_if::cfg_if! { + if #[cfg(client)] { + use crate::{ + state_enums::{ OpenState}, + templates::{get_api_path}, + global_state::{self, AppStateRx}, + }; + use reqwest::StatusCode; + } +} + +lazy_static! { + pub static ref FORGOT_PASSWORD_FORM: Capsule = + get_capsule(); +} + +#[derive(Serialize, Deserialize, Clone, ReactiveState)] +#[rx(alias = "ForgotPasswordFormStateRx")] +struct ForgotPasswordFormState { + username: String, + how_to_reach: String, +} + +impl ForgotPasswordFormStateRx { + #[cfg(client)] + fn reset(&self) { + self.username.set(String::new()); + self.how_to_reach.set(String::new()); + } +} + +#[derive(Clone)] +pub struct ForgotPasswordFormProps {} + +#[auto_scope] +fn forgot_password_form_capsule( + cx: Scope, + state: &ForgotPasswordFormStateRx, + _props: ForgotPasswordFormProps, +) -> View { + let close_modal = move |_event: Event| { + #[cfg(client)] + { + spawn_local_scoped(cx, async move { + let global_state = Reactor::::from_cx(cx).get_global_state::(cx); + state.reset(); + global_state + .modals_open + .forgot_password + .set(OpenState::Closed) + }); + } + }; + let handle_submit = move |_event: Event| { + #[cfg(client)] + { + spawn_local_scoped(cx, async move { + let global_state = Reactor::::from_cx(cx).get_global_state::(cx); + + // Close modal + state.reset(); + global_state + .modals_open + .forgot_password + .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 md:mx-auto w-full md:w-1/2 lg:w-1/3 z-0 my-10") { + div (class="bg-white rounded-lg shadow relative dark:bg-gray-700"){ + div (class="flex justify-end p-2"){ + 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" + } + } + div (class="space-y-6 px-6 lg:px-8 pb-4 sm:pb-6 xl:pb-8") { + h3 (class="text-xl font-medium text-gray-900 dark:text-white"){"Sign in to our platform"} + div { + label (class="text-sm font-medium text-gray-900 block mb-2 dark:text-gray-300") {"Username"} + input (bind:value = state.username, class="bg-gray-50 border border-gray-300 text-gray-900 sm:text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-600 dark:border-gray-500 dark:placeholder-gray-400 dark:text-white") {} + } + div { + label (class="text-sm font-medium text-gray-900 block mb-2 dark:text-gray-300"){"How to contact you with new password"} + input (bind:value = state.how_to_reach, class="bg-gray-50 border border-gray-300 text-gray-900 sm:text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-600 dark:border-gray-500 dark:placeholder-gray-400 dark:text-white"){} + } + + button (on:click = handle_submit, class="w-full 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 text-center dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800"){"Log in"} + div (class="text-sm font-medium text-gray-500 dark:text-gray-300"){ + a (class="text-blue-700 hover:underline dark:text-blue-500"){"Submit"} + } + } + } + } + } + } +} + +pub fn get_capsule() -> Capsule { + Capsule::build(Template::build("forgot_password_form").build_state_fn(get_build_state)) + .empty_fallback() + .view_with_state(forgot_password_form_capsule) + .build() +} + +#[engine_only_fn] +async fn get_build_state(_info: StateGeneratorInfo<()>) -> ForgotPasswordFormState { + ForgotPasswordFormState { + username: "".to_owned(), + how_to_reach: "".to_owned(), + } +} diff --git a/src/capsules/login_form.rs b/src/capsules/login_form.rs index ba91059..414b4de 100644 --- a/src/capsules/login_form.rs +++ b/src/capsules/login_form.rs @@ -1,5 +1,3 @@ -use std::arch::global_asm; - use lazy_static::lazy_static; use perseus::prelude::*; use serde::{Deserialize, Serialize}; @@ -14,7 +12,7 @@ cfg_if::cfg_if! { state_enums::{LoginState, OpenState}, templates::{get_api_path}, global_state::{self, AppStateRx}, - models::auth::WebAuthInfo, + models::auth::WebAuthInfo, }; use reqwest::StatusCode; } @@ -32,6 +30,15 @@ struct LoginFormState { remember_me: bool, } +impl LoginFormStateRx { + #[cfg(client)] + fn reset(&self) { + self.username.set(String::new()); + self.password.set(String::new()); + self.remember_me.set(false); + } +} + #[derive(Clone)] pub struct LoginFormProps { pub remember_me: bool, @@ -53,6 +60,22 @@ fn login_form_capsule( } }; + let handle_forgot_password = move |_event: Event| { + #[cfg(client)] + { + spawn_local_scoped(cx, async move { + let global_state = Reactor::::from_cx(cx).get_global_state::(cx); + global_state + .modals_open + .forgot_password + .set(OpenState::Open); + // Close modal + state.reset(); + global_state.modals_open.login.set(OpenState::Closed); + }); + } + }; + let handle_log_in = move |_event: Event| { #[cfg(client)] { @@ -93,6 +116,7 @@ fn login_form_capsule( }); // Close modal + state.reset(); global_state.modals_open.login.set(OpenState::Closed); }); } @@ -124,14 +148,14 @@ fn login_form_capsule( div (class="flex items-center h-5"){ input (bind:checked = state.remember_me, type = "checkbox", class="w-4 h-4 text-blue-600 bg-gray-100 border-gray-300 rounded focus:ring-blue-500 dark:focus:ring-blue-600 dark:ring-offset-gray-800 focus:ring-2 dark:bg-gray-700 dark:border-gray-600") {} } - } - div (class="text-sm ml-3"){ - label (class="font-medium text-gray-900 dark:text-gray-300"){"Remember me"} + div (class="text-sm ml-3"){ + label (class="font-medium text-gray-900 dark:text-gray-300"){"Remember me"} + } } }}, false => view!{cx, }, }) - a (class="text-sm text-blue-700 hover:underline dark:text-blue-500"){"Lost Password?"} + a (on:click = handle_forgot_password, class="text-sm text-blue-700 hover:underline dark:text-blue-500"){"Lost Password?"} } button (on:click = handle_log_in, class="w-full 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 text-center dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800"){"Log in"} div (class="text-sm font-medium text-gray-500 dark:text-gray-300"){ diff --git a/src/components/header.rs b/src/components/header.rs index eef573b..cb862bc 100644 --- a/src/components/header.rs +++ b/src/components/header.rs @@ -5,7 +5,10 @@ use sycamore::prelude::*; use web_sys::Event; use crate::{ - capsules::login_form::{LoginFormProps, LOGIN_FORM}, + capsules::{ + forgot_password_form::{ForgotPasswordFormProps, FORGOT_PASSWORD_FORM}, + login_form::{LoginFormProps, LOGIN_FORM}, + }, endpoints::LOGIN, global_state::AppStateRx, models::auth::LoginInfo, @@ -105,6 +108,18 @@ pub fn Header<'a, G: Html>(cx: Scope<'a>, HeaderProps { game, title }: HeaderPro view!{ cx, } } }) + (match *global_state.modals_open.forgot_password.get() { + OpenState::Open => { + view! { cx, + (FORGOT_PASSWORD_FORM.widget(cx, "", + ForgotPasswordFormProps{} + )) + } + } + OpenState::Closed => { + view!{ cx, } + } + }) } } } diff --git a/src/global_state.rs b/src/global_state.rs index c466e36..f25fb3e 100644 --- a/src/global_state.rs +++ b/src/global_state.rs @@ -81,6 +81,7 @@ impl AuthDataRx { #[rx(alias = "ModalOpenDataRx")] pub struct ModalOpenData { pub login: OpenState, + pub forgot_password: OpenState, } pub fn get_global_state_creator() -> GlobalStateCreator { @@ -98,6 +99,7 @@ pub async fn get_build_state() -> AppState { }, modals_open: ModalOpenData { login: OpenState::Closed, + forgot_password: OpenState::Closed, }, } } diff --git a/src/main.rs b/src/main.rs index 6b96339..39df0d3 100644 --- a/src/main.rs +++ b/src/main.rs @@ -66,6 +66,7 @@ pub fn main() -> PerseusApp { .template(crate::templates::one_v_one_board::get_template()) .template(crate::templates::overall_board::get_template()) .capsule_ref(&*crate::capsules::login_form::LOGIN_FORM) + .capsule_ref(&*crate::capsules::forgot_password_form::FORGOT_PASSWORD_FORM) .error_views(crate::error_views::get_error_views()) .index_view(|cx| { view! { cx,