Compare commits
3 Commits
4bacbfd1ab
...
203760ff14
| Author | SHA1 | Date | |
|---|---|---|---|
|
203760ff14
|
|||
|
8348277a87
|
|||
|
e7c43e7a47
|
@@ -86,3 +86,9 @@ pub async fn logout() -> Result<()> {
|
|||||||
pub async fn get_user() -> Result<Option<User>> {
|
pub async fn get_user() -> Result<Option<User>> {
|
||||||
Ok(auth.user.clone().map(Into::into))
|
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
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}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -9,20 +9,25 @@ pub fn FormInput(
|
|||||||
#[props(default)] name: Option<String>,
|
#[props(default)] name: Option<String>,
|
||||||
#[props(default)] required: bool,
|
#[props(default)] required: bool,
|
||||||
#[props(default)] r#type: String,
|
#[props(default)] r#type: String,
|
||||||
|
children: Element,
|
||||||
) -> Element {
|
) -> Element {
|
||||||
let name = name.unwrap_or(label.to_lowercase());
|
let name = name.unwrap_or(label.to_lowercase());
|
||||||
|
|
||||||
rsx! {
|
rsx! {
|
||||||
div {
|
div {
|
||||||
class: "group",
|
class: "group/field",
|
||||||
|
|
||||||
label {
|
p {
|
||||||
class: "label opacity-0 not-group-has-placeholder-shown:opacity-100 transition-opacity pl-2",
|
class: "opacity-0 not-group-has-placeholder-shown/field:opacity-70 transition-opacity pl-2",
|
||||||
{label.clone()}
|
{label.clone()}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
label {
|
||||||
|
class: "input w-full",
|
||||||
|
|
||||||
|
{children}
|
||||||
|
|
||||||
input {
|
input {
|
||||||
class: "input",
|
|
||||||
name,
|
name,
|
||||||
required,
|
required,
|
||||||
r#type,
|
r#type,
|
||||||
@@ -30,4 +35,5 @@ pub fn FormInput(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,2 +1,3 @@
|
|||||||
pub mod footer;
|
pub mod footer;
|
||||||
|
pub mod form;
|
||||||
pub mod form_input;
|
pub mod form_input;
|
||||||
|
|||||||
Reference in New Issue
Block a user