Initial Commit
This commit is contained in:
36
src/lib/domain/leptos/app/pages/auth/auth_base.rs
Normal file
36
src/lib/domain/leptos/app/pages/auth/auth_base.rs
Normal file
@@ -0,0 +1,36 @@
|
||||
use leptos::*;
|
||||
|
||||
/// Renders the home page of your application.
|
||||
#[component]
|
||||
pub fn AuthBase(children: Children) -> impl IntoView {
|
||||
view! {
|
||||
<div class="h-full flex flex-row">
|
||||
|
||||
<div class="w-full md:basis-1/3 2xl:basis-1/4 flex h-full flex-col px-4 py-16">
|
||||
<div class="flex h-full grow-0 flex-col px-4 py-16">
|
||||
|
||||
<div class="mt-auto">
|
||||
|
||||
<img class="mx-auto" src="/android-chrome-192x192.png" alt={ crate::PROJECT_NAME }/>
|
||||
|
||||
{ children() }
|
||||
</div>
|
||||
|
||||
<div class="mt-auto text-center text-sm text-gray-500">
|
||||
<p>{ crate::COPYRIGHT }</p>
|
||||
<p class="">
|
||||
<span class="px-1"><a href="https://git.avii.nl/AVAM/avam" class="link" target="_BLANK"><i class="fab fa-git-alt"></i></a></span>
|
||||
<span class="px-1"><a href="#" class="link" target="_BLANK"><i class="fab fa-discord"></i></a></span>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="md:basis-2/3 2xl:basis-3/4 bg-cover bg-center bg-auth"></div>
|
||||
|
||||
</div>
|
||||
|
||||
}
|
||||
.into_view()
|
||||
}
|
81
src/lib/domain/leptos/app/pages/auth/forgot.rs
Normal file
81
src/lib/domain/leptos/app/pages/auth/forgot.rs
Normal file
@@ -0,0 +1,81 @@
|
||||
use leptos::*;
|
||||
use leptos_router::*;
|
||||
|
||||
use super::auth_base::AuthBase;
|
||||
|
||||
#[server]
|
||||
async fn forgot_action(email: String) -> Result<(), ServerFnError<String>> {
|
||||
use crate::domain::api::prelude::*;
|
||||
|
||||
let email = EmailAddress::new(&email).map_err(|_| format!("\"{}\" is not a email address", email))?;
|
||||
|
||||
let app = use_context::<AppService>().unwrap();
|
||||
let flashbag = use_context::<FlashBag>().unwrap();
|
||||
|
||||
// If the email address is known, we'll set a recovery link, if it's not, we wont, but the flash remains the same.
|
||||
app.forgot_password(&email).await;
|
||||
|
||||
let flash = FlashMessage::new("login",
|
||||
format!(
|
||||
"An e-mail has been sent to {} with a link to reset your password.",
|
||||
email
|
||||
)).with_alert(Alert::Success);
|
||||
|
||||
flashbag.set(flash);
|
||||
|
||||
leptos_axum::redirect("/auth/login");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Renders the home page of your application.
|
||||
#[component]
|
||||
pub fn ForgotPage() -> impl IntoView {
|
||||
let submit = Action::<ForgotAction, _>::server();
|
||||
let response = submit.value().read_only();
|
||||
|
||||
view! {
|
||||
<AuthBase>
|
||||
|
||||
<Suspense>
|
||||
<Show when=move || response.get().is_some_and(|e| e.is_err())>
|
||||
<div role="alert" class="alert alert-error my-2">
|
||||
<i class="fas fa-exclamation-circle"></i>
|
||||
<span>
|
||||
{ move || if let Some(Err(e)) = response.get() {
|
||||
{format!("{}", e)}.into_view()
|
||||
} else {
|
||||
().into_view()
|
||||
}}
|
||||
</span>
|
||||
</div>
|
||||
</Show>
|
||||
</Suspense>
|
||||
|
||||
<ActionForm action=submit class="w-full">
|
||||
<div class="flex flex-col gap-2">
|
||||
<label class="input input-bordered flex items-center gap-2">
|
||||
<i class="fas fa-envelope"></i>
|
||||
<input type="text" placeholder="E-mail" name="email" class="grow" />
|
||||
</label>
|
||||
|
||||
<div>
|
||||
<input type="submit" value="Request Password Reset" class="btn btn-primary btn-block" />
|
||||
</div>
|
||||
|
||||
<div class="text-center text-sm">
|
||||
"New Account? Sign up "<a href="/auth/register" class="link">"here"</a>"!"
|
||||
</div>
|
||||
|
||||
<div class="text-center text-sm">
|
||||
"Remembered your password? Login "<a href="/auth/login" class="link">"here"</a>"!"
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</ActionForm>
|
||||
|
||||
</AuthBase>
|
||||
|
||||
}
|
||||
.into_view()
|
||||
}
|
106
src/lib/domain/leptos/app/pages/auth/login.rs
Normal file
106
src/lib/domain/leptos/app/pages/auth/login.rs
Normal file
@@ -0,0 +1,106 @@
|
||||
use leptos::*;
|
||||
use leptos_router::*;
|
||||
|
||||
use crate::domain::leptos::app::components::alert::Alert;
|
||||
|
||||
use super::auth_base::AuthBase;
|
||||
|
||||
#[server]
|
||||
async fn login_action(email: String, password: String) -> Result<(), ServerFnError<String>> {
|
||||
use crate::domain::api::prelude::*;
|
||||
|
||||
let app = use_context::<AppService>().unwrap();
|
||||
let session = use_context::<Session>().unwrap();
|
||||
|
||||
let email = email.try_into().map_err(|e| format!("{}", e))?;
|
||||
|
||||
let user = match app.user_login(UserLoginRequest::new(email, password)).await {
|
||||
Ok(u) => u,
|
||||
Err(_) => return Err(ServerFnError::WrappedServerError("Username or password incorrect".into())),
|
||||
};
|
||||
|
||||
session.set("user", user.id());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Renders the home page of your application.
|
||||
#[component]
|
||||
pub fn LoginPage(user_signal: RwSignal<bool>) -> impl IntoView {
|
||||
let submit = Action::<LoginAction, _>::server();
|
||||
let response = submit.value().read_only();
|
||||
|
||||
create_effect(move |_| {
|
||||
if response.get().is_some() {
|
||||
user_signal.set(!user_signal.get_untracked());
|
||||
}
|
||||
});
|
||||
|
||||
let flash = create_resource(
|
||||
|| (),
|
||||
move |_| async move {
|
||||
use crate::domain::leptos::get_flash;
|
||||
|
||||
get_flash("login".to_string()).await.unwrap()
|
||||
},
|
||||
);
|
||||
|
||||
view! {
|
||||
<AuthBase>
|
||||
|
||||
<Suspense>
|
||||
<Show when=move || response.get().is_some_and(|e| e.is_err())>
|
||||
<div role="alert" class="alert alert-error my-2">
|
||||
<i class="fas fa-exclamation-circle"></i>
|
||||
<span>
|
||||
{ move || if let Some(Err(e)) = response.get() {
|
||||
{format!("{}", e)}.into_view()
|
||||
} else {
|
||||
().into_view()
|
||||
}}
|
||||
</span>
|
||||
</div>
|
||||
</Show>
|
||||
</Suspense>
|
||||
|
||||
<Suspense>
|
||||
<Show when=move || flash().is_some_and(|u| u.is_some())>
|
||||
|
||||
<Alert alert={ flash().unwrap().unwrap().alert() }>
|
||||
{ flash().unwrap().unwrap().message() }
|
||||
</Alert>
|
||||
|
||||
</Show>
|
||||
</Suspense>
|
||||
|
||||
<ActionForm action=submit class="w-full">
|
||||
<div class="flex flex-col gap-2">
|
||||
<label class="input input-bordered flex items-center gap-2">
|
||||
<i class="fas fa-envelope"></i>
|
||||
<input type="text" placeholder="E-mail" name="email" class="grow" />
|
||||
</label>
|
||||
|
||||
<label class="input input-bordered flex items-center gap-2">
|
||||
<i class="fas fa-lock"></i>
|
||||
<input type="password" placeholder="●●●●●●●●" name="password" class="grow" on:input=move |_| submit.value().set(None) />
|
||||
</label>
|
||||
|
||||
<div>
|
||||
<input type="submit" value="Login" class="btn btn-primary btn-block" />
|
||||
</div>
|
||||
|
||||
<div class="text-center text-sm">
|
||||
"New Account? Sign up "<a href="/auth/register" class="link">"here"</a>"!"
|
||||
</div>
|
||||
<div class="text-center text-sm">
|
||||
<a href="/auth/forgot" class="link">"Forgot password"</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</ActionForm>
|
||||
|
||||
</AuthBase>
|
||||
|
||||
}
|
||||
.into_view()
|
||||
}
|
34
src/lib/domain/leptos/app/pages/auth/logout.rs
Normal file
34
src/lib/domain/leptos/app/pages/auth/logout.rs
Normal file
@@ -0,0 +1,34 @@
|
||||
use leptos::*;
|
||||
use leptos_router::Redirect;
|
||||
|
||||
#[server]
|
||||
async fn logout_action() -> Result<(), ServerFnError<String>> {
|
||||
use crate::domain::api::prelude::*;
|
||||
|
||||
let session = use_context::<Session>().unwrap();
|
||||
session.clear();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[component]
|
||||
pub fn LogoutPage(user_signal: RwSignal<bool>) -> impl IntoView {
|
||||
let submit = Action::<LogoutAction, _>::server();
|
||||
let response = submit.value().read_only();
|
||||
|
||||
create_effect(move |_| {
|
||||
if response.get().is_some() {
|
||||
user_signal.set(!user_signal.get_untracked());
|
||||
}
|
||||
});
|
||||
|
||||
submit.dispatch(LogoutAction {});
|
||||
|
||||
view! {
|
||||
<Suspense>
|
||||
<Show when=move || response().is_some()>
|
||||
<Redirect path="/" />
|
||||
</Show>
|
||||
</Suspense>
|
||||
}
|
||||
}
|
6
src/lib/domain/leptos/app/pages/auth/mod.rs
Normal file
6
src/lib/domain/leptos/app/pages/auth/mod.rs
Normal file
@@ -0,0 +1,6 @@
|
||||
pub mod auth_base;
|
||||
pub mod forgot;
|
||||
pub mod login;
|
||||
pub mod logout;
|
||||
pub mod register;
|
||||
pub mod reset;
|
98
src/lib/domain/leptos/app/pages/auth/register.rs
Normal file
98
src/lib/domain/leptos/app/pages/auth/register.rs
Normal file
@@ -0,0 +1,98 @@
|
||||
use leptos::*;
|
||||
use leptos_router::*;
|
||||
|
||||
|
||||
use super::auth_base::AuthBase;
|
||||
|
||||
|
||||
|
||||
#[server]
|
||||
async fn register_action(
|
||||
email: String,
|
||||
password: String,
|
||||
confirm_password: String,
|
||||
) -> Result<(), ServerFnError<String>> {
|
||||
use crate::domain::api::prelude::*;
|
||||
|
||||
let app = use_context::<AppService>().unwrap();
|
||||
let flashbag = use_context::<FlashBag>().unwrap();
|
||||
|
||||
if password != confirm_password {
|
||||
return Err("Passwords don't match".to_string().into());
|
||||
}
|
||||
|
||||
let email = EmailAddress::new(&email).map_err(|_| format!("\"{}\" is not a email address", email))?;
|
||||
let password = Password::new(&password).map_err(|e| format!("{}", e))?;
|
||||
|
||||
let user = app.create_user(CreateUserRequest::new(email, password))
|
||||
.await
|
||||
.map_err(|e| format!("{}", e))?;
|
||||
|
||||
let flash = FlashMessage::new("login",
|
||||
format!(
|
||||
"An e-mail has been sent to {} with a link to activate your account.",
|
||||
user.email()
|
||||
)).with_alert(Alert::Success);
|
||||
|
||||
flashbag.set(flash);
|
||||
|
||||
leptos_axum::redirect("/auth/login");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Renders the home page of your application.
|
||||
#[component]
|
||||
pub fn RegisterPage() -> impl IntoView {
|
||||
let submit = Action::<RegisterAction, _>::server();
|
||||
|
||||
let response = submit.value().read_only();
|
||||
|
||||
view! {
|
||||
<AuthBase>
|
||||
|
||||
<Suspense>
|
||||
<Show when=move || response.get().is_some_and(|e| e.is_err())>
|
||||
<div role="alert" class="alert alert-error my-2">
|
||||
<i class="fas fa-exclamation-circle"></i>
|
||||
<span>
|
||||
{ move || if let Some(Err(e)) = response.get() {
|
||||
{format!("{}", e)}.into_view()
|
||||
} else {
|
||||
().into_view()
|
||||
}}
|
||||
</span>
|
||||
</div>
|
||||
</Show>
|
||||
</Suspense>
|
||||
|
||||
<ActionForm action=submit class="w-full">
|
||||
<div class="flex flex-col gap-2">
|
||||
<label class="input input-bordered flex items-center gap-2">
|
||||
<i class="fas fa-envelope"></i>
|
||||
<input type="text" placeholder="E-mail" name="email" class="grow" />
|
||||
</label>
|
||||
|
||||
<label class="input input-bordered flex items-center gap-2">
|
||||
<i class="fas fa-lock"></i>
|
||||
<input type="password" placeholder="Password" name="password" class="grow" />
|
||||
</label>
|
||||
|
||||
<label class="input input-bordered flex items-center gap-2">
|
||||
<i class="fas fa-lock"></i>
|
||||
<input type="password" placeholder="Confirm Password" name="confirm_password" class="grow" />
|
||||
</label>
|
||||
|
||||
<div>
|
||||
<input type="submit" value="Sign Up!" class="btn btn-primary btn-block" />
|
||||
</div>
|
||||
|
||||
<div class="text-center text-sm">
|
||||
"Already have an Account? Login "<a href="/auth/login" class="link">"here"</a>"!"
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</ActionForm>
|
||||
</AuthBase>
|
||||
}.into_view()
|
||||
}
|
108
src/lib/domain/leptos/app/pages/auth/reset.rs
Normal file
108
src/lib/domain/leptos/app/pages/auth/reset.rs
Normal file
@@ -0,0 +1,108 @@
|
||||
use leptos::*;
|
||||
use leptos_router::*;
|
||||
|
||||
use super::auth_base::AuthBase;
|
||||
|
||||
#[server]
|
||||
async fn reset_action(token: String, password: String, confirm_password: String) -> Result<(), ServerFnError<String>> {
|
||||
use crate::domain::api::prelude::*;
|
||||
|
||||
let app = use_context::<AppService>().unwrap();
|
||||
let flashbag = use_context::<FlashBag>().unwrap();
|
||||
|
||||
if password != confirm_password {
|
||||
return Err("Passwords don't match".to_string().into());
|
||||
}
|
||||
|
||||
let password = Password::new(&password).map_err(|e| format!("{}", e))?;
|
||||
|
||||
app.reset_password(&token.into(), &password).await.map_err(|e| format!("{}", e))?;
|
||||
|
||||
let flash = FlashMessage::new("login",
|
||||
format!(
|
||||
"Your password has been reset."
|
||||
)).with_alert(Alert::Success);
|
||||
|
||||
flashbag.set(flash);
|
||||
|
||||
leptos_axum::redirect("/auth/login");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Renders the home page of your application.
|
||||
#[component]
|
||||
pub fn ResetPage() -> impl IntoView {
|
||||
let submit = Action::<ResetAction, _>::server();
|
||||
let response = submit.value().read_only();
|
||||
|
||||
let params = use_params_map();
|
||||
|
||||
let token = move || {
|
||||
params.with(|params| params.get("token").cloned())
|
||||
};
|
||||
|
||||
let user = create_resource(token, move |_| async move {
|
||||
crate::domain::leptos::check_forgot_password_token(token()).await.unwrap()
|
||||
});
|
||||
|
||||
view! {
|
||||
<AuthBase>
|
||||
|
||||
<Suspense>
|
||||
<Show when=move || response.get().is_some_and(|e| e.is_err())>
|
||||
<div role="alert" class="alert alert-error my-2">
|
||||
<i class="fas fa-exclamation-circle"></i>
|
||||
<span>
|
||||
{ move || if let Some(Err(e)) = response.get() {
|
||||
{format!("{}", e)}.into_view()
|
||||
} else {
|
||||
().into_view()
|
||||
}}
|
||||
</span>
|
||||
</div>
|
||||
</Show>
|
||||
</Suspense>
|
||||
|
||||
<ActionForm action=submit class="w-full">
|
||||
<div class="flex flex-col gap-2">
|
||||
|
||||
<Suspense>
|
||||
<Show when=move || user().is_some_and(|e| e.is_none())>
|
||||
<Redirect path="/auth/login"/>
|
||||
</Show>
|
||||
<Show when=move || user().is_some_and(|e| e.is_some())>
|
||||
<label class="input input-bordered flex items-center gap-2">
|
||||
<i class="fas fa-envelope"></i>
|
||||
<input type="text" class="grow" value={user().unwrap().unwrap().email().to_string()} disabled />
|
||||
</label>
|
||||
</Show>
|
||||
</Suspense>
|
||||
|
||||
<label class="input input-bordered flex items-center gap-2">
|
||||
<i class="fas fa-lock"></i>
|
||||
<input type="password" placeholder="New Password" name="password" class="grow" />
|
||||
</label>
|
||||
|
||||
<label class="input input-bordered flex items-center gap-2">
|
||||
<i class="fas fa-lock"></i>
|
||||
<input type="password" placeholder="Confirm Password" name="confirm_password" class="grow" />
|
||||
</label>
|
||||
|
||||
<div>
|
||||
<input type="hidden" name="token" value={token()} />
|
||||
<input type="submit" value="Reset!" class="btn btn-primary btn-block" />
|
||||
</div>
|
||||
|
||||
<div class="text-center text-sm">
|
||||
"Remembered your password? Login "<a href="/auth/login" class="link">"here"</a>"!"
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</ActionForm>
|
||||
|
||||
</AuthBase>
|
||||
|
||||
}
|
||||
.into_view()
|
||||
}
|
Reference in New Issue
Block a user