Compare commits
8 Commits
203760ff14
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
d5eba9620c
|
|||
|
39afdd83a7
|
|||
|
34b887730a
|
|||
|
f2dc4c908e
|
|||
|
219f1f5e7d
|
|||
|
102387fdad
|
|||
|
87bd1b50a9
|
|||
|
c8cd4b95dc
|
1
Cargo.lock
generated
1
Cargo.lock
generated
@@ -2419,6 +2419,7 @@ dependencies = [
|
||||
"diesel-async",
|
||||
"diesel_migrations",
|
||||
"dioxus",
|
||||
"dioxus-html",
|
||||
"dotenvy",
|
||||
"fred",
|
||||
"getrandom 0.4.3",
|
||||
|
||||
@@ -17,9 +17,10 @@ diesel = { version = "2.3.10", optional = true, features = ["chrono"] }
|
||||
diesel-async = { version = "0.9.1", optional = true, features = ["postgres", "deadpool", "migrations"] }
|
||||
diesel_migrations = { version = "2.3.2", optional = true }
|
||||
dioxus = { version = "0.7.9", features = ["router", "fullstack"] }
|
||||
dioxus-html = "0.7.9"
|
||||
dotenvy = { version = "0.15.7", optional = true }
|
||||
fred = { version = "10.1.0", optional = true }
|
||||
lucide-dioxus = { version = "3.11.0", features = ["notifications"] }
|
||||
lucide-dioxus = { version = "3.11.0", features = ["notifications", "account"] }
|
||||
pbkdf2 = { version = "0.13.0", optional = true, features = ["getrandom", "phc"] }
|
||||
rand = "0.10.1"
|
||||
serde = { version = "1.0.228", features = ["derive"] }
|
||||
|
||||
@@ -14,6 +14,7 @@ enum Route {
|
||||
#[component]
|
||||
pub fn App() -> Element {
|
||||
rsx! {
|
||||
document::Link { rel: "icon", href: LOGO_ICO }
|
||||
document::Link { rel: "stylesheet", href: TAILWIND_CSS }
|
||||
Router::<Route> {}
|
||||
}
|
||||
|
||||
101
src/components/form.rs
Normal file
101
src/components/form.rs
Normal file
@@ -0,0 +1,101 @@
|
||||
use dioxus::prelude::*;
|
||||
use serde::de::DeserializeOwned;
|
||||
|
||||
use crate::util::error::Error;
|
||||
|
||||
/// A simple styled form card. Parses form fields into the provided generic type, and calls the
|
||||
/// callback when submitted via button or enter key.
|
||||
#[component]
|
||||
pub fn Form<T>(
|
||||
/// Heading above the form
|
||||
title: String,
|
||||
/// Form content, placed inside a `fieldset`
|
||||
children: Element,
|
||||
/// Text displayed on the form button
|
||||
action_message: String,
|
||||
/// Form-related error. Set to a parsing error on parse failure, or can be used to display any
|
||||
/// submission error. Clears on each submit. Displays as a card between the inputs and action
|
||||
/// button, opens a modal on click.
|
||||
#[props(default)]
|
||||
error: Signal<Option<Error>>,
|
||||
/// Whether the form should display in a "loading" state, disables the action button and
|
||||
/// displays a loading animation instead. For best UX, especially on pages the user might open
|
||||
/// directly as opposed to navigate to within the app, initialize to true. This component will
|
||||
/// set it to false when run. This sequence will discourage users from submitting the form
|
||||
/// before the page is ready to handle it. Can also be used to indicate background work is
|
||||
/// taking place in the `callback`, but this is not done automatically.
|
||||
#[props(default)]
|
||||
loading: Signal<bool>,
|
||||
/// Content to display below the action button
|
||||
#[props(default)]
|
||||
extra_content: Option<Element>,
|
||||
/// Form submission callback. Called with the deserialized form inputs.
|
||||
onsubmit: Callback<T>,
|
||||
) -> Element
|
||||
where
|
||||
T: DeserializeOwned + 'static,
|
||||
{
|
||||
// Button is initialized to loading, then set back when WASM is finished loading (use_effect
|
||||
// runs only on the client)
|
||||
use_effect(move || loading.set(false));
|
||||
|
||||
rsx! {
|
||||
div {
|
||||
class: "card card-xl w-full sm:w-100 sm:h-fit bg-base-200",
|
||||
|
||||
form {
|
||||
onsubmit: move |evt| {
|
||||
evt.prevent_default();
|
||||
|
||||
let data: T = match evt.data.parsed_values() {
|
||||
Ok(data) => data,
|
||||
Err(e) => {
|
||||
let e = Error::message_here(e.to_string())
|
||||
.with_context("Failed to parse form inputs");
|
||||
|
||||
error.set(Some(e));
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
error.set(None);
|
||||
|
||||
onsubmit.call(data);
|
||||
},
|
||||
|
||||
class: "card-body gap-3",
|
||||
|
||||
h1 {
|
||||
class: "card-title",
|
||||
{title}
|
||||
}
|
||||
|
||||
fieldset {
|
||||
class: "fieldset pt-0 gap-1",
|
||||
{children}
|
||||
}
|
||||
|
||||
{error().map(|e| e.as_alert())}
|
||||
|
||||
div {
|
||||
class: "card-actions gap-4",
|
||||
|
||||
button {
|
||||
class: "btn btn-primary btn-block",
|
||||
disabled: loading(),
|
||||
|
||||
if loading() {
|
||||
span {
|
||||
class: "loading loading-dots"
|
||||
}
|
||||
} else {
|
||||
{action_message}
|
||||
}
|
||||
}
|
||||
|
||||
{extra_content}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -37,3 +37,32 @@ pub fn FormInput(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[component]
|
||||
pub fn UsernameInput() -> Element {
|
||||
rsx! {
|
||||
FormInput {
|
||||
label: "Username",
|
||||
required: true,
|
||||
|
||||
lucide_dioxus::UserRound {
|
||||
class: "opacity-50",
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[component]
|
||||
pub fn PasswordInput() -> Element {
|
||||
rsx! {
|
||||
FormInput {
|
||||
label: "Password",
|
||||
required: true,
|
||||
r#type: "password",
|
||||
|
||||
lucide_dioxus::KeyRound {
|
||||
class: "opacity-50",
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,2 +1,7 @@
|
||||
pub mod footer;
|
||||
pub mod form;
|
||||
pub mod form_input;
|
||||
|
||||
pub use footer::*;
|
||||
pub use form::*;
|
||||
pub use form_input::*;
|
||||
|
||||
@@ -16,6 +16,9 @@ use crate::app::App;
|
||||
fn tracing_setup() {
|
||||
#[cfg(debug_assertions)]
|
||||
dioxus::logger::init(tracing::Level::DEBUG).expect("Failed to initialize tracing logger");
|
||||
|
||||
#[cfg(not(debug_assertions))]
|
||||
dioxus::logger::init(tracing::Level::INFO).expect("Failed to initialize tracing logger");
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "server"))]
|
||||
|
||||
@@ -13,12 +13,13 @@ const ALLOWED_PATH_PREFIX: [&str; 1] = ["/assets/"];
|
||||
#[cfg(debug_assertions)]
|
||||
const ALLOWED_PATH_PREFIX: [&str; 2] = ["/assets/", "/wasm/"];
|
||||
|
||||
const ALLOWED_PATHS: [&str; 5] = [
|
||||
const ALLOWED_PATHS: [&str; 6] = [
|
||||
"/login",
|
||||
"/signup",
|
||||
"/api/v1/auth/login",
|
||||
"/api/v1/auth/signup",
|
||||
"/api/v1/auth/user",
|
||||
"/api/v1/auth/open-signup",
|
||||
];
|
||||
|
||||
/// Axum middleware to redirect an unauthenticated request to /login unless it matches one of the allowed paths
|
||||
|
||||
Reference in New Issue
Block a user