simple-web-app

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README

commit 6bdc76dd9494c13a72291f5969c9831c5498f7b1
parent 3e9d8a750032c40e26bfa1c91925311c251944fb
Author: Silas Brack <silasbrack@gmail.com>
Date:   Sun,  7 Jun 2026 20:11:17 +0200

feat: add resend verification email page

Users who registered but can't login can request a new verification
email via /resend-verification. Link added to the login page.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

Diffstat:
Msrc/handlers/auth.rs | 45++++++++++++++++++++++++++++++++++++++++++++-
Msrc/routes.rs | 2++
Msrc/templates.rs | 7+++++++
Mtemplates/login.html | 1+
Atemplates/resend_verification.html | 28++++++++++++++++++++++++++++
5 files changed, 82 insertions(+), 1 deletion(-)

diff --git a/src/handlers/auth.rs b/src/handlers/auth.rs @@ -9,7 +9,7 @@ use serde::Deserialize; use crate::error::AppError; use crate::state::AppState; -use crate::templates::{ApplicationTemplate, LoginTemplate, RegisterTemplate}; +use crate::templates::{ApplicationTemplate, LoginTemplate, RegisterTemplate, ResendVerificationTemplate}; use crate::trailbase::{self, StoredTokens}; const AUTH_COOKIE: &str = "auth_tokens"; @@ -169,6 +169,49 @@ pub async fn register( } } +pub async fn show_resend_verification() -> Result<Html<String>, AppError> { + let template = ResendVerificationTemplate { + error: None, + message: None, + }; + let app = ApplicationTemplate { + content: template.render()?, + }; + Ok(Html(app.render()?)) +} + +#[derive(Debug, Deserialize)] +pub struct ResendVerificationForm { + pub email: String, +} + +pub async fn resend_verification( + State(state): State<AppState>, + Form(form): Form<ResendVerificationForm>, +) -> Result<Html<String>, AppError> { + let url = format!("{}api/auth/v1/verify_email/trigger?email={}", state.trailbase_url, url::form_urlencoded::byte_serialize(form.email.as_bytes()).collect::<String>()); + let response = state.http_client.get(&url).send().await; + + let (message, error) = match response { + Ok(resp) if resp.status().is_success() => { + (Some("Verification email sent! Check your inbox.".to_string()), None) + } + Ok(resp) => { + let body = resp.text().await.unwrap_or_default(); + (None, Some(format!("Failed to send: {}", body))) + } + Err(e) => { + (None, Some(format!("Request failed: {}", e))) + } + }; + + let template = ResendVerificationTemplate { error, message }; + let app = ApplicationTemplate { + content: template.render()?, + }; + Ok(Html(app.render()?)) +} + pub async fn logout(jar: CookieJar) -> (CookieJar, Redirect) { let jar = clear_auth_cookie(jar); (jar, Redirect::to("/")) diff --git a/src/routes.rs b/src/routes.rs @@ -86,6 +86,8 @@ pub fn create_router(state: AppState) -> Router { .route("/register", get(handlers::show_register)) .route("/register", post(handlers::register)) .route("/logout", get(handlers::logout)) + .route("/resend-verification", get(handlers::show_resend_verification)) + .route("/resend-verification", post(handlers::resend_verification)) // Static files .route("/static/style.css", get(serve_style)) diff --git a/src/templates.rs b/src/templates.rs @@ -131,3 +131,10 @@ pub struct RegisterTemplate { pub username: String, pub email: String, } + +#[derive(Template)] +#[template(path = "resend_verification.html")] +pub struct ResendVerificationTemplate { + pub error: Option<String>, + pub message: Option<String>, +} diff --git a/templates/login.html b/templates/login.html @@ -24,4 +24,5 @@ </form> <p class="auth-link">Don't have an account? <a href="/register">Register</a></p> + <p class="auth-link">Registered but can't login? <a href="/resend-verification">Resend verification email</a></p> </div> diff --git a/templates/resend_verification.html b/templates/resend_verification.html @@ -0,0 +1,28 @@ +<div class="auth-page"> + <h2>Resend Verification Email</h2> + + {% match message %} + {% when Some with (msg) %} + <div class="success-message">{{ msg }}</div> + {% when None %} + {% endmatch %} + + {% match error %} + {% when Some with (err) %} + <div class="error-message">{{ err }}</div> + {% when None %} + {% endmatch %} + + <form method="post" action="/resend-verification" class="auth-form"> + <div class="form-group"> + <label for="email">Email</label> + <input type="email" id="email" name="email" required autocomplete="email" /> + </div> + + <div class="form-actions"> + <button type="submit">Resend</button> + </div> + </form> + + <p class="auth-link"><a href="/login">Back to Login</a></p> +</div>