## Adding a New Page — Step by Step ### Step 1 — Choose a layout Declare the layout in your template's very first line using a Go comment: ```html {{/* layout: base_public.html */}} ``` If you omit this line, `base.html` (sidebar layout) is used automatically. **Built-in layouts:** | Layout file | Navigation | Best for | |---|---|---| | `base.html` | Left sidebar | Authenticated app pages (dashboard, profile, etc.) | | `base_public.html` | Top bar | Public/marketing pages, landing pages | | `base_bare.html` | None | Minimal pages, print view, embeds | **Custom layout:** Create `web/templates/layouts/my_layout.html`, then declare `{{/* layout: my_layout.html */}}` in your page. Any layout filename works as long as it lives in the `layouts/` directory and contains `{{define "base"}} ... {{end}}`. ### Step 2 — Create the template `web/templates/pages/mypage.html`: ```html {{/* layout: base.html */}} {{template "base" .}} {{define "title"}}My Page — GoApp{{end}} {{define "content"}}

{{.Title}}

{{range .Items}}

{{.}}

{{end}}
{{end}} {{/* Optional: page-specific additions */}} {{define "head"}}{{end}} {{/* Optional: page-specific scripts */}} {{define "scripts"}}{{end}} ``` The `{{/* layout: ... */}}` comment is read only by Go at startup — it never appears in the HTML output. ### Step 3 — Create the handler `internal/handlers/mypage.go`: ```go package handlers import "net/http" // MyPageData holds everything the template needs. // Always embed PageData — it carries User, Nav, flash messages, etc. type MyPageData struct { PageData Items []string // your page-specific data } type MyPageHandler struct { tmpl *Renderer // Add other dependencies here: *db.DB, *mailer.Mailer, *logger.Logger } func NewMyPageHandler(r *Renderer) *MyPageHandler { return &MyPageHandler{tmpl: r} } func (h *MyPageHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodGet { http.Error(w, "method not allowed", http.StatusMethodNotAllowed) return } data := MyPageData{ PageData: NewPageData(r, "My Page"), Items: []string{"hello", "world"}, } h.tmpl.Render(w, "mypage", data) } ``` ### Step 4 — Register the route Add one line to `internal/router/router.go`: ```go // Public page (no login required): mux.Handle("/mypage", handlers.NewMyPageHandler(renderer)) // Authenticated page (redirects to /login if not signed in): mux.Handle("/mypage", middleware.RequireAuth(handlers.NewMyPageHandler(renderer))) // Admin-only page: mux.Handle("/mypage", adminMW(handlers.NewMyPageHandler(renderer))) ``` ### Step 5 — (Optional) Add to sidebar navigation In `internal/handlers/renderer.go`, append to `DefaultNav`: ```go var DefaultNav = []NavItem{ // existing items ... {Label: "My Page", Href: "/mypage", Icon: "info"}, // all users {Label: "Admin Page", Href: "/mypage", Icon: "shield", AdminOnly: true}, // admins only } ``` Available icon names: `home`, `info`, `mail`, `users`, `activity`, `settings`, `logout`, `shield`, `key`, `mfa` --- ## Email — Using the Mailer ### Configure in `data/config.json` ```json "email": { "enabled": true, "smtp_host": "smtp.gmail.com", "smtp_port": 587, "encryption": "starttls", "auth": true, "username": "you@gmail.com", "password": "your-app-password", "from_address": "GoApp " } ``` **Gmail tip:** Create an [App Password](https://myaccount.google.com/apppasswords) (not your main password). Requires 2FA enabled on your Google account. ### Wire the Mailer into a handler ```go // In router.go (m is already created from cfg.Email): mux.Handle("/notify", handlers.NewNotifyHandler(renderer, m, log)) // In your handler: type NotifyHandler struct { tmpl *Renderer mailer *mailer.Mailer log *logger.Logger } func NewNotifyHandler(r *Renderer, m *mailer.Mailer, l *logger.Logger) *NotifyHandler { return &NotifyHandler{tmpl: r, mailer: m, log: l} } ``` ### Send plain-text email ```go err := h.mailer.Send(mailer.Message{ To: []string{"user@example.com"}, Subject: "Welcome to GoApp", Body: "Hello! Your account is ready.", }) if err != nil { h.log.Warn("send welcome email", "err", err) // Don't abort — email failure should not break the UX } ``` ### Send HTML email ```go err := h.mailer.Send(mailer.Message{ To: []string{"user@example.com"}, Subject: "Welcome to GoApp", Body: "

Welcome!

Your account is ready.

", IsHTML: true, }) ``` ### Multiple recipients + CC ```go err := h.mailer.Send(mailer.Message{ To: []string{"alice@example.com", "bob@example.com"}, CC: []string{"manager@example.com"}, Subject: "Team notification", Body: "Something happened that you should know about.", }) ``` ### Safe error handling pattern When `email.enabled` is `false`, `Send()` returns `nil` immediately — the rest of your handler works normally. Always log email errors rather than aborting the request: ```go if err := h.mailer.Send(msg); err != nil { h.log.Warn("email send failed", "err", err) // continue — don't return an error page to the user } ```