From be56b06d7acdfba8c38e732654ab4402e18c49cb Mon Sep 17 00:00:00 2001
From: Amy Gale Ruth Bowersox
Date: Thu, 25 Sep 2025 22:49:41 -0600
Subject: [PATCH] dialog manager extended to be able to produce the New Account
dialog
---
errors.go | 4 +-
go.mod | 14 ++--
go.sum | 8 ++-
login.go | 18 ++++++
main.go | 1 +
ui/dialog.go | 48 ++++++++++++--
ui/dialogs/login.yaml | 8 +--
ui/dialogs/newaccount.yaml | 127 +++++++++++++++++++++++++++++++++++++
ui/templates.go | 107 +++++++++++++++++++++++++++++++
ui/views/agreement.jet | 2 +-
ui/views/dialog.jet | 70 ++++++++++++++++++--
11 files changed, 380 insertions(+), 27 deletions(-)
create mode 100644 ui/dialogs/newaccount.yaml
diff --git a/errors.go b/errors.go
index 235a67e..fa0f0fa 100644
--- a/errors.go
+++ b/errors.go
@@ -9,7 +9,9 @@
// Package main contains the high-level Amsterdam logic.
package main
-import "git.erbosoft.com/amy/amsterdam/ui"
+import (
+ "git.erbosoft.com/amy/amsterdam/ui"
+)
/* NotImplPage is used for all TODO links, to show that something hasn't yet been implemented.
* Parameters:
diff --git a/go.mod b/go.mod
index 5ce402e..b807fbd 100644
--- a/go.mod
+++ b/go.mod
@@ -13,19 +13,19 @@ require (
require (
filippo.io/edwards25519 v1.1.0 // indirect
github.com/CloudyKit/fastprinter v0.0.0-20200109182630-33d98a066a53 // indirect
- github.com/alexflint/go-arg v1.6.0 // indirect
+ github.com/alexflint/go-arg v1.6.0
github.com/alexflint/go-scalar v1.2.0 // indirect
- github.com/go-sql-driver/mysql v1.9.3 // indirect
+ github.com/biter777/countries v1.7.5
+ github.com/go-sql-driver/mysql v1.9.3
github.com/gorilla/context v1.1.2 // indirect
github.com/gorilla/securecookie v1.1.2 // indirect
- github.com/gorilla/sessions v1.4.0 // indirect
+ github.com/gorilla/sessions v1.4.0
github.com/hashicorp/golang-lru v1.0.2
- github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect
- github.com/jmoiron/sqlx v1.4.0 // indirect
- github.com/labstack/echo-contrib v0.17.4 // indirect
+ github.com/jmoiron/sqlx v1.4.0
+ github.com/labstack/echo-contrib v0.17.4
github.com/mattn/go-colorable v0.1.14 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
- github.com/quasoft/memstore v0.0.0-20191010062613-2bce066d2b0b // indirect
+ github.com/quasoft/memstore v0.0.0-20191010062613-2bce066d2b0b
github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/valyala/fasttemplate v1.2.2 // indirect
golang.org/x/crypto v0.38.0 // indirect
diff --git a/go.sum b/go.sum
index 1c7f520..ed05acd 100644
--- a/go.sum
+++ b/go.sum
@@ -8,12 +8,16 @@ github.com/alexflint/go-arg v1.6.0 h1:wPP9TwTPO54fUVQl4nZoxbFfKCcy5E6HBCumj1XVRS
github.com/alexflint/go-arg v1.6.0/go.mod h1:A7vTJzvjoaSTypg4biM5uYNTkJ27SkNTArtYXnlqVO8=
github.com/alexflint/go-scalar v1.2.0 h1:WR7JPKkeNpnYIOfHRa7ivM21aWAdHD0gEWHCx+WQBRw=
github.com/alexflint/go-scalar v1.2.0/go.mod h1:LoFvNMqS1CPrMVltza4LvnGKhaSpc3oyLEBUZVhhS2o=
+github.com/biter777/countries v1.7.5 h1:MJ+n3+rSxWQdqVJU8eBy9RqcdH6ePPn4PJHocVWUa+Q=
+github.com/biter777/countries v1.7.5/go.mod h1:1HSpZ526mYqKJcpT5Ti1kcGQ0L0SrXWIaptUWjFfv2E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=
github.com/go-sql-driver/mysql v1.9.3 h1:U/N249h2WzJ3Ukj8SowVFjdtZKfu9vlLZxjPXV1aweo=
github.com/go-sql-driver/mysql v1.9.3/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI64Gl8i5p1WMU=
+github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
+github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/gorilla/context v1.1.2 h1:WRkNAv2uoa03QNIc1A6u4O7DAGMUVoopZhkiXWA2V1o=
github.com/gorilla/context v1.1.2/go.mod h1:KDPwT9i/MeWHiLl90fuTgrt4/wPcv75vFAZLaOOcbxM=
github.com/gorilla/securecookie v1.1.2 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kXD8ePA=
@@ -22,8 +26,6 @@ github.com/gorilla/sessions v1.4.0 h1:kpIYOp/oi6MG/p5PgxApU8srsSw9tuFbt46Lt7auzq
github.com/gorilla/sessions v1.4.0/go.mod h1:FLWm50oby91+hl7p/wRxDth9bWSuk0qVL2emc7lT5ik=
github.com/hashicorp/golang-lru v1.0.2 h1:dV3g9Z/unq5DpblPpw+Oqcv4dU/1omnb4Ok8iPY6p1c=
github.com/hashicorp/golang-lru v1.0.2/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
-github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
-github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
github.com/jmoiron/sqlx v1.4.0 h1:1PLqN7S1UYp5t4SrVVnt4nUVNemrDAtxlulVe+Qgm3o=
github.com/jmoiron/sqlx v1.4.0/go.mod h1:ZrZ7UsYB/weZdl2Bxg6jCRO9c3YHl8r3ahlKmRT4JLY=
github.com/labstack/echo-contrib v0.17.4 h1:g5mfsrJfJTKv+F5uNKCyrjLK7js+ZW6HTjg4FnDxxgk=
@@ -32,11 +34,13 @@ github.com/labstack/echo/v4 v4.13.4 h1:oTZZW+T3s9gAu5L8vmzihV7/lkXGZuITzTQkTEhcX
github.com/labstack/echo/v4 v4.13.4/go.mod h1:g63b33BZ5vZzcIUF8AtRH40DrTlXnx4UMC8rBdndmjQ=
github.com/labstack/gommon v0.4.2 h1:F8qTUNXgG1+6WQmqoUWnz8WiEU60mXVVw0P4ht1WRA0=
github.com/labstack/gommon v0.4.2/go.mod h1:QlUFxVM+SNXhDL/Z7YhocGIBYOiwB0mXm1+1bAPHPyU=
+github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
+github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU=
github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
diff --git a/login.go b/login.go
index fb23845..cd4e384 100644
--- a/login.go
+++ b/login.go
@@ -40,3 +40,21 @@ func NewAccountUserAgreement(ctxt ui.AmContext) (string, any, error) {
ctxt.VarMap().Set("amsterdam_pageTitle", "New Account User Agreement")
return "framed_template", "agreement.jet", nil
}
+
+/* NewAccountUserAgreement renders the Amsterdam account creation form.
+ * Parameters:
+ * ctxt - The AmContext for the request.
+ * Returns:
+ * Command string dictating what to be rendered.
+ * Data as a parameter for the command string.
+ * Standard Go error status.
+ */
+func NewAccountForm(ctxt ui.AmContext) (string, any, error) {
+ dlg, err := ui.AmLoadDialog("newaccount")
+ if err == nil {
+ dlg.Field("country").Value = "XX"
+ ctxt.VarMap().Set("amsterdam_pageTitle", "Create New Account")
+ return dlg.Render(ctxt)
+ }
+ return ui.ErrorPage(ctxt, err)
+}
diff --git a/main.go b/main.go
index b2e2dd4..8ecc6ed 100644
--- a/main.go
+++ b/main.go
@@ -44,6 +44,7 @@ func setupEcho() *echo.Echo {
e.GET("/about", ui.AmWrap(AboutPage))
e.GET("/login", ui.AmWrap(LoginForm))
e.GET("/newacct", ui.AmWrap(NewAccountUserAgreement))
+ e.GET("/newacct2", ui.AmWrap(NewAccountForm))
return e
}
diff --git a/ui/dialog.go b/ui/dialog.go
index ea6baad..22d93fe 100644
--- a/ui/dialog.go
+++ b/ui/dialog.go
@@ -19,13 +19,15 @@ import (
// DialogItem holds the dialog item definition.
type DialogItem struct {
- Type string `yaml:"type"`
- Name string `yaml:"name"`
- Caption string `yaml:"caption,omitempty"`
- Size int `yaml:"size,omitempty"`
- MaxLength int `yaml:"maxlength,omitempty"`
- Value string `yaml:"value,omitempty"`
- Tone string `yaml:"tone,omitempty"`
+ Type string `yaml:"type"`
+ Name string `yaml:"name"`
+ Caption string `yaml:"caption,omitempty"`
+ Subcaption string `yaml:"subcaption,omitempty"`
+ Required bool `yaml:"required,omitempty"`
+ Size int `yaml:"size,omitempty"`
+ MaxLength int `yaml:"maxlength,omitempty"`
+ Value string `yaml:"value,omitempty"`
+ Param string `yaml:"param,omitempty"`
}
// Dialog holds the dialog definition.
@@ -52,15 +54,39 @@ func AmLoadDialog(name string) (*Dialog, error) {
var d Dialog
err = yaml.Unmarshal(b, &d)
if err == nil {
+ // "nil-patch" certain fields
if d.MenuSelector == "" {
d.MenuSelector = "nochange"
}
+ for _, fld := range d.Fields {
+ if fld.Type == "button" && fld.Param == "" {
+ fld.Param = "blue"
+ }
+ if fld.Type == "date" && fld.Param == "" {
+ fld.Param = "year:-100"
+ }
+ }
return &d, nil
}
}
return nil, err
}
+/* Field returns a pointer to a dialog's field, given its name.
+ * Parameters:
+ * name - The name of the field to find.
+ * Returns:
+ * Pointer to the field, or nil.
+ */
+func (d *Dialog) Field(name string) *DialogItem {
+ for i := 0; i < len(d.Fields); i++ {
+ if d.Fields[i].Name == name {
+ return &(d.Fields[i])
+ }
+ }
+ return nil
+}
+
/* Render sets up the rendering parameters to send this dialog to the output.
* Parameters:
* ctxt - The AmContext for this request.
@@ -70,6 +96,14 @@ func AmLoadDialog(name string) (*Dialog, error) {
* Standard Go error status.
*/
func (d *Dialog) Render(ctxt AmContext) (string, any, error) {
+ required := false
+ for _, fld := range d.Fields {
+ if fld.Required {
+ required = true
+ break
+ }
+ }
+ ctxt.VarMap().Set("amsterdam_required", required)
ctxt.VarMap().Set("amsterdam_dialog", d)
return "framed_template", "dialog.jet", nil
}
diff --git a/ui/dialogs/login.yaml b/ui/dialogs/login.yaml
index aec6b24..29c99f8 100644
--- a/ui/dialogs/login.yaml
+++ b/ui/dialogs/login.yaml
@@ -18,7 +18,7 @@ fields:
- type: "hidden"
name: "tgt"
value: ""
- - type: "veniceid"
+ - type: "ams_id"
name: "user"
caption: "User Name"
size: 32
@@ -34,12 +34,12 @@ fields:
- type: "button"
name: "login"
caption: "Log In"
- tone: "blue"
+ param: "blue"
- type: "button"
name: "remind"
caption: "Password Reminder"
- tone: "gray"
+ param: "gray"
- type: "button"
name: "cancel"
caption: "Cancel"
- tone: "red"
+ param: "red"
diff --git a/ui/dialogs/newaccount.yaml b/ui/dialogs/newaccount.yaml
new file mode 100644
index 0000000..7755fe1
--- /dev/null
+++ b/ui/dialogs/newaccount.yaml
@@ -0,0 +1,127 @@
+#
+# Amsterdam Web Communities System
+# Copyright (c) 2025 Erbosoft Metaverse Design Solutions, All Rights Reserved
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at https://mozilla.org/MPL/2.0/.
+#
+name: "newaccount"
+formName: "createform"
+menuSelector: "top"
+title: "Create Account"
+action: "/TODO/newacct2"
+instructions: >
+ To create a new account, please enter your information below.
+fields:
+ - type: "hidden"
+ name: "tgt"
+ value: ""
+ - type: "header"
+ name: "header1"
+ caption: "Name"
+ - type: "text"
+ name: "prefix"
+ caption: "Prefix"
+ subcaption: "(Mr., Ms., etc.)"
+ size: 8
+ maxlength: 8
+ - type: "text"
+ name: "first"
+ caption: "First Name"
+ required: true
+ size: 32
+ maxlength: 64
+ - type: "text"
+ name: "mid"
+ caption: "Middle Initial"
+ size: 1
+ maxlength: 1
+ - type: "text"
+ name: "last"
+ caption: "Last Name"
+ required: true
+ size: 32
+ maxlength: 64
+ - type: "text"
+ name: "suffix"
+ caption: "Suffix"
+ subcaption: "(Jr., III, etc.)"
+ size: 8
+ maxlength: 8
+ - type: "header"
+ name: "header2"
+ caption: "Location"
+ - type: "text"
+ name: "loc"
+ caption: "City"
+ required: true
+ size: 32
+ maxlength: 64
+ - type: "text"
+ name: "reg"
+ caption: "State/Province"
+ required: true
+ size: 32
+ maxlength: 64
+ - type: "text"
+ name: "pcode"
+ caption: "Zip/Postal Code"
+ required: true
+ size: 32
+ maxlength: 64
+ - type: "countrylist"
+ name: "country"
+ caption: "Country"
+ required: true
+ - type: "header"
+ name: "header3"
+ caption: "E-Mail"
+ - type: "email"
+ name: "email"
+ caption: "E-Mail Address"
+ required: true
+ size: 32
+ maxlength: 255
+ - type: "header"
+ name: "header4"
+ caption: "Other Information"
+ - type: "date"
+ name: "dob"
+ caption: "Date of Birth"
+ param: "year:-100"
+ - type: "header"
+ name: "header5"
+ caption: "Account Information"
+ - type: "ams_id"
+ name: "user"
+ caption: "User Name"
+ required: true
+ size: 32
+ maxlength: 64
+ - type: "password"
+ name: "pass1"
+ caption: "Password"
+ required: true
+ size: 32
+ maxlength: 128
+ - type: "password"
+ name: "pass2"
+ caption: "Password"
+ subcaption: "(retype)"
+ required: true
+ size: 32
+ maxlength: 128
+ - type: "text"
+ name: "remind"
+ caption: "Password reminder phrase"
+ size: 32
+ maxlength: 255
+ - type: "button"
+ name: "create"
+ caption: "Create"
+ param: "blue"
+ - type: "button"
+ name: "cancel"
+ caption: "Cancel"
+ param: "red"
diff --git a/ui/templates.go b/ui/templates.go
index 4233149..75153cf 100644
--- a/ui/templates.go
+++ b/ui/templates.go
@@ -12,12 +12,21 @@ package ui
import (
"embed"
+ "fmt"
"io"
+ "reflect"
+ "regexp"
+ "slices"
+ "strconv"
+ "strings"
+ "sync"
+ "time"
"git.erbosoft.com/amy/amsterdam/config"
"github.com/CloudyKit/jet/v6"
"github.com/CloudyKit/jet/v6/loaders/embedfs"
"github.com/CloudyKit/jet/v6/loaders/multi"
+ "github.com/biter777/countries"
"github.com/labstack/echo/v4"
)
@@ -27,6 +36,96 @@ var static_views embed.FS
// views is the main Jet template repository.
var views *jet.Set
+var cachedCountryList []countries.CountryCode = nil
+
+var countryListMutex sync.Mutex
+
+func internalGetCountryList() []countries.CountryCode {
+ countryListMutex.Lock()
+ defer countryListMutex.Unlock()
+ if cachedCountryList == nil {
+ c := countries.All()
+ slices.SortFunc(c, func(a countries.CountryCode, b countries.CountryCode) int {
+ return strings.Compare(a.Info().Name, b.Info().Name)
+ })
+ cachedCountryList = c
+ }
+ return cachedCountryList
+}
+
+// getCountryList is a wrapper around countries.All() to be added to the template context.
+func getCountryList(a jet.Arguments) reflect.Value {
+ return reflect.ValueOf(internalGetCountryList())
+}
+
+// getMonthList is a simple wrapper that returns the names of the months to the template context.
+func getMonthList(a jet.Arguments) reflect.Value {
+ rc := make([]string, 12)
+ for m := time.January; m <= time.December; m++ {
+ rc[m-time.January] = m.String()
+ }
+ return reflect.ValueOf(rc)
+}
+
+// countRanger is a Ranger that can count from one value to another with a certain step.
+type countRanger struct {
+ i int
+ val int64
+ step int64
+ to int64
+}
+
+/* Range (from Ranger) returns the "next" value of this iterator.
+ * Returns:
+ * Next index of the returned value
+ * Next returned value
+ * true if this is the last iteration, false if not
+ */
+func (r *countRanger) Range() (reflect.Value, reflect.Value, bool) {
+ r.i++
+ r.val += r.step
+ var end bool
+ if r.step < 0 {
+ end = r.val <= r.to
+ } else {
+ end = r.val >= r.to
+ }
+ return reflect.ValueOf(r.i), reflect.ValueOf(r.val), end
+}
+
+// ProvidesIndex (from Ranger) returns true to indicate that this Ranger has indexes.
+func (r *countRanger) ProvidesIndex() bool {
+ return true
+}
+
+// makeIntRange creates and returns a countRanger.
+func makeIntRange(a jet.Arguments) reflect.Value {
+ from := a.Get(0).Convert(reflect.TypeFor[int64]()).Int()
+ to := a.Get(1).Convert(reflect.TypeFor[int64]()).Int()
+ step := a.Get(2).Convert(reflect.TypeFor[int64]()).Int()
+ rc := &countRanger{i: -1, val: from - step, step: step, to: to}
+ return reflect.ValueOf(rc).Convert(reflect.TypeFor[jet.Ranger]())
+}
+
+// makeYearRange parses a year parameter and creates a countRanger that reflects it.
+func makeYearRange(a jet.Arguments) reflect.Value {
+ param := a.Get(0).Convert(reflect.TypeFor[string]()).String()
+ yearRegex, _ := regexp.Compile(`year:(\S+)(\s+.+)?$`)
+ m := yearRegex.FindStringSubmatch(param)
+ if m != nil {
+ count, err := strconv.Atoi(m[1])
+ if err == nil {
+ start_year := time.Now().Year()
+ rc := &countRanger{i: -1, val: int64(start_year) + 1, step: -1, to: int64(start_year + count - 1)}
+ return reflect.ValueOf(rc).Convert(reflect.TypeFor[jet.Ranger]())
+ } else {
+ return reflect.ValueOf(err)
+ }
+ } else {
+ return reflect.ValueOf(fmt.Errorf("cannot locate year: marker in param"))
+ }
+}
+
// SetupTemplates is called to set up the template renderer after the configuration is loaded.
func SetupTemplates() {
views = jet.NewSet(
@@ -39,6 +138,13 @@ func SetupTemplates() {
views.AddGlobal("AmsterdamVersion", config.AMSTERDAM_VERSION)
views.AddGlobal("AmsterdamCopyright", config.AMSTERDAM_COPYRIGHT)
views.AddGlobal("GlobalConfig", config.GlobalConfig)
+ views.AddGlobalFunc("GetCountryList", getCountryList)
+ views.AddGlobalFunc("GetMonthList", getMonthList)
+ views.AddGlobalFunc("MakeIntRange", makeIntRange)
+ views.AddGlobalFunc("MakeYearRange", makeYearRange)
+
+ // preload the country list in the background
+ go internalGetCountryList()
}
// TemplateRenderer is the Renderer instance set into the Echo context at creation time, to render Jet templates.
@@ -55,6 +161,7 @@ type TemplateRenderer struct{}
*/
func (r *TemplateRenderer) Render(w io.Writer, name string, data any, c echo.Context) error {
view, err := views.GetTemplate(name)
+
if err != nil {
return err
}
diff --git a/ui/views/agreement.jet b/ui/views/agreement.jet
index 7ce5eee..0e27808 100644
--- a/ui/views/agreement.jet
+++ b/ui/views/agreement.jet
@@ -16,7 +16,7 @@
+ onclick="window.location.assign('/newacct2')">I Accept
diff --git a/ui/views/dialog.jet b/ui/views/dialog.jet
index a6f3490..ce099cd 100644
--- a/ui/views/dialog.jet
+++ b/ui/views/dialog.jet
@@ -21,13 +21,19 @@
{{ if amsterdam_dialog.Instructions != "" }}
{{ amsterdam_dialog.Instructions | raw }}
{{ end }}
+ {{ if amsterdam_required }}
+ Required fields are marked with a *.
+ {{ end }}
+ {{ else if .Type == "countrylist" }}
+
+
+ {{ v := .Value }}
+
+
+ {{ else if .Type == "date" }}
+
+
+
+
+
+
+
+
+ {{ else if .Type == "header" }}
+
{{ .Caption }}
+ {{ else if .Type != "hidden" && .Type != "button" }}
+ BARF! I don't understand {{ .Type }} (field {{ .Name }})
{{ end }}
{{ end }}
{{ range amsterdam_dialog.Fields }}
{{ if .Type == "button" }}
- {{ clstmp := "bg-" + .Tone + "-600 hover:bg-" + .Tone + "-700" }}
+ {{ clstmp := "bg-" + .Param + "-600 hover:bg-" + .Param + "-700" }}
{{ end }}