- Replace events+expenses with flat purchases table - Add warranty_months, return_days, product_name fields - Remove currency conversion, CSV/PDF reporting, event filing - Simplify auth (no onboarding/department/profile) - Update AI extraction prompts for product/warranty info - Update all branding: templates, install.sh, Makefile, service file
63 lines
2.2 KiB
Go
63 lines
2.2 KiB
Go
package handlers
|
|
|
|
import (
|
|
"html/template"
|
|
"log"
|
|
"path/filepath"
|
|
"sync"
|
|
)
|
|
|
|
var (
|
|
templatesOnce sync.Once
|
|
templates map[string]*template.Template
|
|
)
|
|
|
|
// getTemplate returns a cached template by filename (e.g. "dashboard.html").
|
|
// Templates are parsed once from the templates/ directory on first call.
|
|
func getTemplate(name string) *template.Template {
|
|
templatesOnce.Do(loadTemplates)
|
|
t := templates[name]
|
|
if t == nil {
|
|
log.Panicf("template %q not found in cache — did you delete templates/%s?", name, name)
|
|
}
|
|
return t
|
|
}
|
|
|
|
// loadTemplates walks the templates/ directory and pre-parses all .html files.
|
|
func loadTemplates() {
|
|
templates = make(map[string]*template.Template)
|
|
|
|
files, err := filepath.Glob("templates/*.html")
|
|
if err != nil {
|
|
log.Panicf("list templates: %v", err)
|
|
}
|
|
|
|
// Parse each file into its own named template.
|
|
for _, f := range files {
|
|
name := filepath.Base(f)
|
|
t, err := template.ParseFiles(f)
|
|
if err != nil {
|
|
log.Panicf("parse template %s: %v", f, err)
|
|
}
|
|
templates[name] = t
|
|
}
|
|
|
|
// Also register the inline OTP form template.
|
|
otpTmpl := template.Must(template.New("otp_form").Parse(otpFormHTML))
|
|
templates["otp_form"] = otpTmpl
|
|
|
|
log.Printf("Loaded %d templates", len(templates))
|
|
}
|
|
|
|
// otpFormHTML is the inline OTP form template fragment with single 6-digit field.
|
|
const otpFormHTML = `
|
|
<form hx-post="/verify-otp" hx-target="#otp-form" hx-swap="innerHTML">
|
|
<input type="hidden" name="email" value="{{.Email}}">
|
|
{{if .Error}}<div class="error-message" style="color: #fca5a5; background: #450a0a; border: 1px solid #7f1d1d; padding: 0.75rem; border-radius: 0.5rem; margin-bottom: 1rem;">{{.Error}}</div>{{end}}
|
|
<div class="form-group" style="margin: 1rem 0;">
|
|
<input type="text" name="otp_code" inputmode="numeric" pattern="[0-9]{6}" maxlength="6" autocomplete="one-time-code" required
|
|
placeholder="Enter 6-digit code"
|
|
style="width: 100%; padding: 1rem; font-size: 1.5rem; text-align: center; letter-spacing: 0.75rem; border: 2px solid #475569; border-radius: 0.5rem; background: #1e293b; color: #f8fafc; box-sizing: border-box;">
|
|
</div>
|
|
<button type="submit" class="btn btn-primary btn-block">Verify Code</button>
|
|
</form>`
|