Initial Commit

This commit is contained in:
2024-10-12 14:36:36 +02:00
commit bfc5cbf624
67 changed files with 10860 additions and 0 deletions

View 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()
}

View 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()
}

View 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()
}

View 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>
}
}

View File

@@ -0,0 +1,6 @@
pub mod auth_base;
pub mod forgot;
pub mod login;
pub mod logout;
pub mod register;
pub mod reset;

View 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()
}

View 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()
}