feat(14-02): migrate auth_signup.templ to AuthLayout with ui.FormField inputs

- Replace Layout+Card pattern with AuthLayout("Create your account", csrfToken)
- Wire GoogleButton and AuthDivider into SignupPage body
- Replace raw <input> elements with @ui.FormField/@ui.Input design system components
- Password placeholder "12 characters minimum", autocomplete="new-password"
- Add signup-copy nav link ("Already have an account? Sign in") pointing to /login
- Preserve HTMX swap: hx-post="/signup" hx-target="#signup-form" hx-swap="outerHTML"
- Remove signupCardBody helper
This commit is contained in:
Arthur Belleville 2026-05-16 19:10:12 +02:00
parent 808eaecc85
commit 65e3dbfd03
No known key found for this signature in database

View file

@ -2,24 +2,25 @@ package templates
import "backend/internal/web/ui" import "backend/internal/web/ui"
// SignupPage renders the full /signup page wrapped in the base Layout. // SignupPage renders the full /signup page wrapped in AuthLayout.
// It delegates the form section to SignupFormFragment so HTMX can swap just the // It delegates the form section to SignupFormFragment so HTMX can swap just the
// form on validation errors without re-rendering the surrounding shell. // form on validation errors without re-rendering the surrounding shell.
templ SignupPage(form SignupForm, errs SignupErrors, csrfToken string, providers AuthProviderButtons) { templ SignupPage(form SignupForm, errs SignupErrors, csrfToken string, providers AuthProviderButtons) {
@Layout("Sign up", nil, csrfToken) { @AuthLayout("Create your account", csrfToken) {
<div class="flex min-h-[60vh] items-start justify-center pt-16"> <div class="auth-card-topbar"></div>
@ui.Card(ui.CardProps{Body: signupCardBody(form, errs, csrfToken, providers)}) <div class="brand-header">
<img class="brand-logo" src="/static/logo_dark.png" alt="Xtablo"/>
</div> </div>
} <div class="title-group">
} <h1>Create your account</h1>
</div>
templ signupCardBody(form SignupForm, errs SignupErrors, csrfToken string, providers AuthProviderButtons) { <div class="auth-body">
<div class="w-full max-w-sm px-6 py-8"> @GoogleButton(providers.Google.StartURL, providers.Google.Configured)
<h1 class="mb-6 text-2xl font-semibold">Create your account</h1> @AuthDivider()
@AuthProviderButtonsBlock(providers)
@SignupFormFragment(form, errs, csrfToken) @SignupFormFragment(form, errs, csrfToken)
</div> </div>
} }
}
// SignupFormFragment is the bare form used for HTMX swaps. // SignupFormFragment is the bare form used for HTMX swaps.
// hx-post targets this component itself so the form can be replaced inline // hx-post targets this component itself so the form can be replaced inline
@ -33,37 +34,37 @@ templ SignupFormFragment(form SignupForm, errs SignupErrors, csrfToken string) {
hx-post="/signup" hx-post="/signup"
hx-target="#signup-form" hx-target="#signup-form"
hx-swap="outerHTML" hx-swap="outerHTML"
class="space-y-5" class="login-form"
> >
@ui.CSRFField(csrfToken) @ui.CSRFField(csrfToken)
@GeneralError(errs.General) @GeneralError(errs.General)
<div> @ui.FormField(ui.FormFieldProps{
<label for="email" class="block text-sm font-medium text-slate-700">Email address</label> Label: "Email address",
<input For: "email",
id="email" Field: ui.Input(ui.InputProps{
type="email" ID: "email",
name="email" Name: "email",
value={ form.Email } Type: "email",
required Placeholder: "you@example.com",
autocomplete="email" Value: form.Email,
class="mt-1 block w-full rounded border border-slate-300 px-3 py-2 text-sm placeholder-slate-400 focus:border-slate-500 focus:outline-none" Required: true,
placeholder="you@example.com" Attrs: templ.Attributes{"autocomplete": "email"},
/> }),
@FieldError(errs.Email) Error: errs.Email,
</div> })
<div> @ui.FormField(ui.FormFieldProps{
<label for="password" class="block text-sm font-medium text-slate-700">Password</label> Label: "Password",
<input For: "password",
id="password" Field: ui.Input(ui.InputProps{
type="password" ID: "password",
name="password" Name: "password",
required Type: "password",
autocomplete="new-password" Placeholder: "12 characters minimum",
class="mt-1 block w-full rounded border border-slate-300 px-3 py-2 text-sm placeholder-slate-400 focus:border-slate-500 focus:outline-none" Required: true,
placeholder="12 characters minimum" Attrs: templ.Attributes{"autocomplete": "new-password"},
/> }),
@FieldError(errs.Password) Error: errs.Password,
</div> })
@ui.Button(ui.ButtonProps{ @ui.Button(ui.ButtonProps{
Label: "Create account", Label: "Create account",
Variant: ui.ButtonVariantDefault, Variant: ui.ButtonVariantDefault,
@ -71,5 +72,9 @@ templ SignupFormFragment(form SignupForm, errs SignupErrors, csrfToken string) {
Size: ui.SizeMD, Size: ui.SizeMD,
Type: "submit", Type: "submit",
}) })
<p class="signup-copy">
Already have an account?
<a class="signup-link" href="/login">Sign in</a>
</p>
</form> </form>
} }