feat(server): add error feedback to signup form

main
Jef Roosens 2025-08-28 13:27:09 +02:00
parent 4902f4d1fe
commit 5017bfb710
Signed by: Jef Roosens
GPG Key ID: 02D4C0997E74717B
4 changed files with 42 additions and 4 deletions

View File

@ -195,6 +195,7 @@ async fn get_signup(State(ctx): State<Context>, headers: HeaderMap, jar: CookieJ
Redirect::to("/").into_response()
} else {
View::Signup {
username: None,
username_available: true,
passwords_match: true,
}
@ -207,10 +208,13 @@ async fn get_signup(State(ctx): State<Context>, headers: HeaderMap, jar: CookieJ
async fn post_signup(
State(ctx): State<Context>,
jar: CookieJar,
headers: HeaderMap,
user_agent: Option<TypedHeader<UserAgent>>,
Form(signup): Form<SignupForm>,
) -> AppResult<Response> {
if signup.validate(&ctx)?.valid() {
let validation = signup.validate(&ctx)?;
if validation.valid() {
// Create the user and log them in
match tokio::task::spawn_blocking(move || {
let user = ctx.store.create_user(&signup.username, &signup.password)?;
@ -238,6 +242,13 @@ async fn post_signup(
Err(err) => Err(AppError::from(err)),
}
} else {
todo!("return form with error messages")
Ok(View::Signup {
username: Some(signup.username),
username_available: validation.username_available,
passwords_match: validation.passwords_match,
}
.page(&headers)
.response(&ctx.tera)
.into_response())
}
}

View File

@ -91,6 +91,7 @@ pub fn initialize_tera() -> tera::Result<tera::Tera> {
),
(
View::Signup {
username: None,
username_available: true,
passwords_match: true,
}

View File

@ -1,11 +1,34 @@
<article>
<form hx-post hx-target="#inner">
<label for="username">Username:</label>
<input type="text" id="username" name="username">
<input
type="text"
id="username"
name="username"
value="{{ username }}"
{% if not username_available %}
aria-invalid="true"
aria-describedby="username-helper"
{% endif %}
>
{% if not username_available %}
<small id="username-helper">Username not available</small>
{% endif %}
<label for="password">Password:</label>
<input type="password" id="password" name="password">
<label for="password_confirm">Confirm password:</label>
<input type="password" id="password_confirm" name="password_confirm">
<input
type="password"
id="password_confirm"
name="password_confirm"
{% if not passwords_match %}
aria-invalid="true"
aria-describedby="password-helper"
{% endif %}
>
{% if not passwords_match %}
<small id="password-helper">Passwords don't match</small>
{% endif %}
<input type="submit" value="Sign Up">
</form>
</article>

View File

@ -9,6 +9,7 @@ pub enum View {
Sessions(Vec<gpodder::Session>, i64, Option<Query>),
Users(Vec<gpodder::User>, i64, Option<Query>),
Signup {
username: Option<String>,
username_available: bool,
passwords_match: bool,
},
@ -67,9 +68,11 @@ impl Template for View {
}
}
Self::Signup {
username,
username_available,
passwords_match,
} => {
ctx.insert("username", &username);
ctx.insert("username_available", &username_available);
ctx.insert("passwords_match", &passwords_match);
}