From 86fd4b73b6ad1699b067994c49ebe174a65a3820 Mon Sep 17 00:00:00 2001 From: juancwu <46619361+juancwu@users.noreply.github.com> Date: Tue, 16 Dec 2025 16:28:11 -0500 Subject: [PATCH] render auth and password pages --- internal/handler/auth.go | 14 +++- internal/routes/routes.go | 1 + internal/ui/pages/auth_password.templ | 96 +++++++++++++++++++++++++++ internal/ui/render.go | 48 ++++++++++++++ 4 files changed, 156 insertions(+), 3 deletions(-) create mode 100644 internal/ui/pages/auth_password.templ create mode 100644 internal/ui/render.go diff --git a/internal/handler/auth.go b/internal/handler/auth.go index ae95fd5..949eca4 100644 --- a/internal/handler/auth.go +++ b/internal/handler/auth.go @@ -1,6 +1,11 @@ package handler -import "net/http" +import ( + "net/http" + + "git.juancwu.dev/juancwu/budgit/internal/ui" + "git.juancwu.dev/juancwu/budgit/internal/ui/pages" +) type authHandler struct { } @@ -10,6 +15,9 @@ func NewAuthHandler() *authHandler { } func (h *authHandler) AuthPage(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(http.StatusOK) - w.Write([]byte("200 OK")) + ui.Render(w, r, pages.Auth("")) +} + +func (h *authHandler) PasswordPage(w http.ResponseWriter, r *http.Request) { + ui.Render(w, r, pages.AuthPassword("")) } diff --git a/internal/routes/routes.go b/internal/routes/routes.go index 3472356..be009d2 100644 --- a/internal/routes/routes.go +++ b/internal/routes/routes.go @@ -26,6 +26,7 @@ func SetupRoutes(a *app.App) http.Handler { // Auth pages mux.HandleFunc("GET /auth", middleware.RequireGuest(auth.AuthPage)) + mux.HandleFunc("GET /auth/password", middleware.RequireGuest(auth.PasswordPage)) // 404 mux.HandleFunc("/{path...}", home.NotFoundPage) diff --git a/internal/ui/pages/auth_password.templ b/internal/ui/pages/auth_password.templ new file mode 100644 index 0000000..17ee020 --- /dev/null +++ b/internal/ui/pages/auth_password.templ @@ -0,0 +1,96 @@ +package pages + +import ( + "git.juancwu.dev/juancwu/budgit/internal/ctxkeys" + "git.juancwu.dev/juancwu/budgit/internal/ui/components/button" + "git.juancwu.dev/juancwu/budgit/internal/ui/components/csrf" + "git.juancwu.dev/juancwu/budgit/internal/ui/components/form" + "git.juancwu.dev/juancwu/budgit/internal/ui/components/icon" + "git.juancwu.dev/juancwu/budgit/internal/ui/components/input" + "git.juancwu.dev/juancwu/budgit/internal/ui/components/label" + "git.juancwu.dev/juancwu/budgit/internal/ui/layouts" +) + +templ AuthPassword(errorMsg string) { + {{ cfg := ctxkeys.Config(ctx) }} + @layouts.Auth(layouts.SEOProps{ + Title: "Sign In", + Description: "Sign in with your password", + Path: ctxkeys.URLPath(ctx), + }) { +
+
+
+
+ @button.Button(button.Props{ + Variant: button.VariantSecondary, + Size: button.SizeLg, + Href: "/", + }) { + @icon.Layers() + { cfg.AppName } + } +
+

Sign In

+

Sign in with your password

+
+
+ @csrf.Token() + @form.Item() { + @label.Label(label.Props{ + For: "email", + Class: "block mb-2", + }) { + Email + } + @input.Input(input.Props{ + ID: "email", + Name: "email", + Type: input.TypeEmail, + Placeholder: "name@example.com", + HasError: errorMsg != "", + Attributes: templ.Attributes{"autofocus": ""}, + }) + if errorMsg != "" { + @form.Message(form.MessageProps{Variant: form.MessageVariantError}) { + { errorMsg } + } + } + } + @form.Item() { + @label.Label(label.Props{ + For: "password", + Class: "block mb-2", + }) { + Password + } + @input.Input(input.Props{ + ID: "password", + Name: "password", + Type: input.TypePassword, + Placeholder: "••••••••", + }) + + } + @button.Button(button.Props{ + Type: button.TypeSubmit, + FullWidth: true, + }) { + Sign In + } +
+ +

+ Don't have an account? + + Sign up with magic link + +

+
+
+ } +} diff --git a/internal/ui/render.go b/internal/ui/render.go new file mode 100644 index 0000000..bfc90db --- /dev/null +++ b/internal/ui/render.go @@ -0,0 +1,48 @@ +package ui + +import ( + "fmt" + "log/slog" + "net/http" + + "github.com/a-h/templ" +) + +func Render(w http.ResponseWriter, r *http.Request, c templ.Component) { + err := c.Render(r.Context(), w) + if err != nil { + slog.Error("render failed", "error", err) + http.Error(w, "internal server error", http.StatusInternalServerError) + } +} + +func RenderFragment(w http.ResponseWriter, r *http.Request, c templ.Component, fragmentIDs ...any) { + err := templ.RenderFragments(r.Context(), w, c, fragmentIDs...) + if err != nil { + slog.Error("render fragment failed", "error", err) + http.Error(w, "internal server error", http.StatusInternalServerError) + } +} + +func RenderOOB(w http.ResponseWriter, r *http.Request, c templ.Component, target string) { + // Write OOB wrapper start + _, err := fmt.Fprintf(w, `
`, target) + if err != nil { + slog.Error("render oob write wrapper start failed", "error", err) + return + } + + // Render component + err = c.Render(r.Context(), w) + if err != nil { + slog.Error("render oob component render failed", "error", err) + http.Error(w, "internal server error", http.StatusInternalServerError) + return + } + + // Write OOB wrapper end + _, err = w.Write([]byte(`
`)) + if err != nil { + slog.Error("render oob write wrapper end failed", "error", err) + } +}