Compare commits

..

3 Commits

Author SHA1 Message Date
203760ff14 Add form component
Some checks failed
Push Workflows / rustfmt (push) Successful in 5s
Push Workflows / tailwind-build (push) Successful in 6s
Push Workflows / clippy (push) Failing after 1m15s
Push Workflows / test (push) Failing after 1m22s
Push Workflows / docs (push) Successful in 1m25s
Push Workflows / build (push) Failing after 2m8s
Push Workflows / nix-build (push) Failing after 5m6s
2026-07-08 21:12:04 -04:00
8348277a87 Add endpoint to check open signup 2026-07-08 20:47:58 -04:00
e7c43e7a47 Allow icon in form input component
Misc style adjustments
2026-07-08 20:44:38 -04:00
4 changed files with 123 additions and 9 deletions

View File

@@ -86,3 +86,9 @@ pub async fn logout() -> Result<()> {
pub async fn get_user() -> Result<Option<User>> {
Ok(auth.user.clone().map(Into::into))
}
/// Check if open signup is enabled
#[get("/api/v1/auth/open-signup", config: Extension<Config>)]
pub async fn open_signup() -> Result<bool> {
Ok(config.auth.open_signup)
}

101
src/components/form.rs Normal file
View 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}
}
}
}
}
}

View File

@@ -9,24 +9,30 @@ pub fn FormInput(
#[props(default)] name: Option<String>,
#[props(default)] required: bool,
#[props(default)] r#type: String,
children: Element,
) -> Element {
let name = name.unwrap_or(label.to_lowercase());
rsx! {
div {
class: "group",
class: "group/field",
label {
class: "label opacity-0 not-group-has-placeholder-shown:opacity-100 transition-opacity pl-2",
p {
class: "opacity-0 not-group-has-placeholder-shown/field:opacity-70 transition-opacity pl-2",
{label.clone()}
}
input {
class: "input",
name,
required,
r#type,
placeholder: label,
label {
class: "input w-full",
{children}
input {
name,
required,
r#type,
placeholder: label,
}
}
}
}

View File

@@ -1,2 +1,3 @@
pub mod footer;
pub mod form;
pub mod form_input;