dialog manager extended to be able to produce the New Account dialog
This commit is contained in:
@@ -9,7 +9,9 @@
|
|||||||
// Package main contains the high-level Amsterdam logic.
|
// Package main contains the high-level Amsterdam logic.
|
||||||
package main
|
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.
|
/* NotImplPage is used for all TODO links, to show that something hasn't yet been implemented.
|
||||||
* Parameters:
|
* Parameters:
|
||||||
|
|||||||
@@ -13,19 +13,19 @@ require (
|
|||||||
require (
|
require (
|
||||||
filippo.io/edwards25519 v1.1.0 // indirect
|
filippo.io/edwards25519 v1.1.0 // indirect
|
||||||
github.com/CloudyKit/fastprinter v0.0.0-20200109182630-33d98a066a53 // 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/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/context v1.1.2 // indirect
|
||||||
github.com/gorilla/securecookie 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 v1.0.2
|
||||||
github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect
|
github.com/jmoiron/sqlx v1.4.0
|
||||||
github.com/jmoiron/sqlx v1.4.0 // indirect
|
github.com/labstack/echo-contrib v0.17.4
|
||||||
github.com/labstack/echo-contrib v0.17.4 // indirect
|
|
||||||
github.com/mattn/go-colorable v0.1.14 // indirect
|
github.com/mattn/go-colorable v0.1.14 // indirect
|
||||||
github.com/mattn/go-isatty v0.0.20 // 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/bytebufferpool v1.0.0 // indirect
|
||||||
github.com/valyala/fasttemplate v1.2.2 // indirect
|
github.com/valyala/fasttemplate v1.2.2 // indirect
|
||||||
golang.org/x/crypto v0.38.0 // indirect
|
golang.org/x/crypto v0.38.0 // indirect
|
||||||
|
|||||||
@@ -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-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 h1:WR7JPKkeNpnYIOfHRa7ivM21aWAdHD0gEWHCx+WQBRw=
|
||||||
github.com/alexflint/go-scalar v1.2.0/go.mod h1:LoFvNMqS1CPrMVltza4LvnGKhaSpc3oyLEBUZVhhS2o=
|
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.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 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
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.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 h1:U/N249h2WzJ3Ukj8SowVFjdtZKfu9vlLZxjPXV1aweo=
|
||||||
github.com/go-sql-driver/mysql v1.9.3/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI64Gl8i5p1WMU=
|
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 h1:WRkNAv2uoa03QNIc1A6u4O7DAGMUVoopZhkiXWA2V1o=
|
||||||
github.com/gorilla/context v1.1.2/go.mod h1:KDPwT9i/MeWHiLl90fuTgrt4/wPcv75vFAZLaOOcbxM=
|
github.com/gorilla/context v1.1.2/go.mod h1:KDPwT9i/MeWHiLl90fuTgrt4/wPcv75vFAZLaOOcbxM=
|
||||||
github.com/gorilla/securecookie v1.1.2 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kXD8ePA=
|
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/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 h1:dV3g9Z/unq5DpblPpw+Oqcv4dU/1omnb4Ok8iPY6p1c=
|
||||||
github.com/hashicorp/golang-lru v1.0.2/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
|
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 h1:1PLqN7S1UYp5t4SrVVnt4nUVNemrDAtxlulVe+Qgm3o=
|
||||||
github.com/jmoiron/sqlx v1.4.0/go.mod h1:ZrZ7UsYB/weZdl2Bxg6jCRO9c3YHl8r3ahlKmRT4JLY=
|
github.com/jmoiron/sqlx v1.4.0/go.mod h1:ZrZ7UsYB/weZdl2Bxg6jCRO9c3YHl8r3ahlKmRT4JLY=
|
||||||
github.com/labstack/echo-contrib v0.17.4 h1:g5mfsrJfJTKv+F5uNKCyrjLK7js+ZW6HTjg4FnDxxgk=
|
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/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 h1:F8qTUNXgG1+6WQmqoUWnz8WiEU60mXVVw0P4ht1WRA0=
|
||||||
github.com/labstack/gommon v0.4.2/go.mod h1:QlUFxVM+SNXhDL/Z7YhocGIBYOiwB0mXm1+1bAPHPyU=
|
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/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 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
|
||||||
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
|
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 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
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/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 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
|||||||
@@ -40,3 +40,21 @@ func NewAccountUserAgreement(ctxt ui.AmContext) (string, any, error) {
|
|||||||
ctxt.VarMap().Set("amsterdam_pageTitle", "New Account User Agreement")
|
ctxt.VarMap().Set("amsterdam_pageTitle", "New Account User Agreement")
|
||||||
return "framed_template", "agreement.jet", nil
|
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)
|
||||||
|
}
|
||||||
|
|||||||
@@ -44,6 +44,7 @@ func setupEcho() *echo.Echo {
|
|||||||
e.GET("/about", ui.AmWrap(AboutPage))
|
e.GET("/about", ui.AmWrap(AboutPage))
|
||||||
e.GET("/login", ui.AmWrap(LoginForm))
|
e.GET("/login", ui.AmWrap(LoginForm))
|
||||||
e.GET("/newacct", ui.AmWrap(NewAccountUserAgreement))
|
e.GET("/newacct", ui.AmWrap(NewAccountUserAgreement))
|
||||||
|
e.GET("/newacct2", ui.AmWrap(NewAccountForm))
|
||||||
|
|
||||||
return e
|
return e
|
||||||
}
|
}
|
||||||
|
|||||||
+41
-7
@@ -19,13 +19,15 @@ import (
|
|||||||
|
|
||||||
// DialogItem holds the dialog item definition.
|
// DialogItem holds the dialog item definition.
|
||||||
type DialogItem struct {
|
type DialogItem struct {
|
||||||
Type string `yaml:"type"`
|
Type string `yaml:"type"`
|
||||||
Name string `yaml:"name"`
|
Name string `yaml:"name"`
|
||||||
Caption string `yaml:"caption,omitempty"`
|
Caption string `yaml:"caption,omitempty"`
|
||||||
Size int `yaml:"size,omitempty"`
|
Subcaption string `yaml:"subcaption,omitempty"`
|
||||||
MaxLength int `yaml:"maxlength,omitempty"`
|
Required bool `yaml:"required,omitempty"`
|
||||||
Value string `yaml:"value,omitempty"`
|
Size int `yaml:"size,omitempty"`
|
||||||
Tone string `yaml:"tone,omitempty"`
|
MaxLength int `yaml:"maxlength,omitempty"`
|
||||||
|
Value string `yaml:"value,omitempty"`
|
||||||
|
Param string `yaml:"param,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Dialog holds the dialog definition.
|
// Dialog holds the dialog definition.
|
||||||
@@ -52,15 +54,39 @@ func AmLoadDialog(name string) (*Dialog, error) {
|
|||||||
var d Dialog
|
var d Dialog
|
||||||
err = yaml.Unmarshal(b, &d)
|
err = yaml.Unmarshal(b, &d)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
|
// "nil-patch" certain fields
|
||||||
if d.MenuSelector == "" {
|
if d.MenuSelector == "" {
|
||||||
d.MenuSelector = "nochange"
|
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 &d, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil, err
|
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.
|
/* Render sets up the rendering parameters to send this dialog to the output.
|
||||||
* Parameters:
|
* Parameters:
|
||||||
* ctxt - The AmContext for this request.
|
* ctxt - The AmContext for this request.
|
||||||
@@ -70,6 +96,14 @@ func AmLoadDialog(name string) (*Dialog, error) {
|
|||||||
* Standard Go error status.
|
* Standard Go error status.
|
||||||
*/
|
*/
|
||||||
func (d *Dialog) Render(ctxt AmContext) (string, any, error) {
|
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)
|
ctxt.VarMap().Set("amsterdam_dialog", d)
|
||||||
return "framed_template", "dialog.jet", nil
|
return "framed_template", "dialog.jet", nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ fields:
|
|||||||
- type: "hidden"
|
- type: "hidden"
|
||||||
name: "tgt"
|
name: "tgt"
|
||||||
value: ""
|
value: ""
|
||||||
- type: "veniceid"
|
- type: "ams_id"
|
||||||
name: "user"
|
name: "user"
|
||||||
caption: "User Name"
|
caption: "User Name"
|
||||||
size: 32
|
size: 32
|
||||||
@@ -34,12 +34,12 @@ fields:
|
|||||||
- type: "button"
|
- type: "button"
|
||||||
name: "login"
|
name: "login"
|
||||||
caption: "Log In"
|
caption: "Log In"
|
||||||
tone: "blue"
|
param: "blue"
|
||||||
- type: "button"
|
- type: "button"
|
||||||
name: "remind"
|
name: "remind"
|
||||||
caption: "Password Reminder"
|
caption: "Password Reminder"
|
||||||
tone: "gray"
|
param: "gray"
|
||||||
- type: "button"
|
- type: "button"
|
||||||
name: "cancel"
|
name: "cancel"
|
||||||
caption: "Cancel"
|
caption: "Cancel"
|
||||||
tone: "red"
|
param: "red"
|
||||||
|
|||||||
@@ -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"
|
||||||
+107
@@ -12,12 +12,21 @@ package ui
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"embed"
|
"embed"
|
||||||
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"reflect"
|
||||||
|
"regexp"
|
||||||
|
"slices"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
"git.erbosoft.com/amy/amsterdam/config"
|
"git.erbosoft.com/amy/amsterdam/config"
|
||||||
"github.com/CloudyKit/jet/v6"
|
"github.com/CloudyKit/jet/v6"
|
||||||
"github.com/CloudyKit/jet/v6/loaders/embedfs"
|
"github.com/CloudyKit/jet/v6/loaders/embedfs"
|
||||||
"github.com/CloudyKit/jet/v6/loaders/multi"
|
"github.com/CloudyKit/jet/v6/loaders/multi"
|
||||||
|
"github.com/biter777/countries"
|
||||||
"github.com/labstack/echo/v4"
|
"github.com/labstack/echo/v4"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -27,6 +36,96 @@ var static_views embed.FS
|
|||||||
// views is the main Jet template repository.
|
// views is the main Jet template repository.
|
||||||
var views *jet.Set
|
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.
|
// SetupTemplates is called to set up the template renderer after the configuration is loaded.
|
||||||
func SetupTemplates() {
|
func SetupTemplates() {
|
||||||
views = jet.NewSet(
|
views = jet.NewSet(
|
||||||
@@ -39,6 +138,13 @@ func SetupTemplates() {
|
|||||||
views.AddGlobal("AmsterdamVersion", config.AMSTERDAM_VERSION)
|
views.AddGlobal("AmsterdamVersion", config.AMSTERDAM_VERSION)
|
||||||
views.AddGlobal("AmsterdamCopyright", config.AMSTERDAM_COPYRIGHT)
|
views.AddGlobal("AmsterdamCopyright", config.AMSTERDAM_COPYRIGHT)
|
||||||
views.AddGlobal("GlobalConfig", config.GlobalConfig)
|
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.
|
// 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 {
|
func (r *TemplateRenderer) Render(w io.Writer, name string, data any, c echo.Context) error {
|
||||||
view, err := views.GetTemplate(name)
|
view, err := views.GetTemplate(name)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,7 +16,7 @@
|
|||||||
</p>
|
</p>
|
||||||
<div class="flex justify-center gap-4 mt-6">
|
<div class="flex justify-center gap-4 mt-6">
|
||||||
<button type="button" class="bg-blue-600 hover:bg-blue-700 text-white px-6 py-2 rounded font-medium transition-colors"
|
<button type="button" class="bg-blue-600 hover:bg-blue-700 text-white px-6 py-2 rounded font-medium transition-colors"
|
||||||
onclick="window.location.assign('/TODO/newacct2')">I Accept</button>
|
onclick="window.location.assign('/newacct2')">I Accept</button>
|
||||||
<button type="button" class="bg-red-600 hover:bg-red-700 text-white px-6 py-2 rounded font-medium transition-colors"
|
<button type="button" class="bg-red-600 hover:bg-red-700 text-white px-6 py-2 rounded font-medium transition-colors"
|
||||||
onclick="window.location.assign('/')">I Decline</button>
|
onclick="window.location.assign('/')">I Decline</button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
+65
-5
@@ -21,13 +21,19 @@
|
|||||||
{{ if amsterdam_dialog.Instructions != "" }}
|
{{ if amsterdam_dialog.Instructions != "" }}
|
||||||
<p class="text-black text-sm mb-6">{{ amsterdam_dialog.Instructions | raw }}</p>
|
<p class="text-black text-sm mb-6">{{ amsterdam_dialog.Instructions | raw }}</p>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
|
{{ if amsterdam_required }}
|
||||||
|
<p class="text-black text-sm mb-6">Required fields are marked with a <span class="text-red-600">*</span>.</p>
|
||||||
|
{{ end }}
|
||||||
|
|
||||||
<div class="bg-gray-50 p-6 rounded-lg">
|
<div class="bg-gray-50 p-6 rounded-lg">
|
||||||
<div class="space-y-4">
|
<div class="space-y-4">
|
||||||
{{ range amsterdam_dialog.Fields }}
|
{{ range amsterdam_dialog.Fields }}
|
||||||
{{ if .Type == "veniceid" }}
|
{{ if .Type == "text" || .Type == "ams_id" || .Type == "email" }}
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
<label for="{{ .Name }}" class="w-24 text-right pr-4 text-black text-sm">{{ .Caption }}:</label>
|
<label for="{{ .Name }}" class="w-64 text-right pr-4 text-black text-sm">
|
||||||
|
{{ .Caption }}{{ if .Subcaption != "" }} {{ .Subcaption }}{{ end }}:
|
||||||
|
{{ if .Required }}<span class="text-red-600">*</span>{{ end }}
|
||||||
|
</label>
|
||||||
<input type="text" id="{{ .Name }}" name="{{ .Name }}"
|
<input type="text" id="{{ .Name }}" name="{{ .Name }}"
|
||||||
{{ if .Size > 0 }}size="{{ .Size }}"{{ end }}
|
{{ if .Size > 0 }}size="{{ .Size }}"{{ end }}
|
||||||
{{ if .MaxLength > 0 }}maxlength="{{ .MaxLength }}"{{ end }}
|
{{ if .MaxLength > 0 }}maxlength="{{ .MaxLength }}"{{ end }}
|
||||||
@@ -36,7 +42,10 @@
|
|||||||
</div>
|
</div>
|
||||||
{{ else if .Type == "password" }}
|
{{ else if .Type == "password" }}
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
<label for="{{ .Name }}" class="w-24 text-right pr-4 text-black text-sm">{{ .Caption }}:</label>
|
<label for="{{ .Name }}" class="w-64 text-right pr-4 text-black text-sm">
|
||||||
|
{{ .Caption }}{{ if .Subcaption != "" }} {{ .Subcaption }}{{ end }}:
|
||||||
|
{{ if .Required }}<span class="text-red-600">*</span>{{ end }}
|
||||||
|
</label>
|
||||||
<input type="password" id="{{ .Name }}" name="{{ .Name }}"
|
<input type="password" id="{{ .Name }}" name="{{ .Name }}"
|
||||||
{{ if .Size > 0 }}size="{{ .Size }}"{{ end }}
|
{{ if .Size > 0 }}size="{{ .Size }}"{{ end }}
|
||||||
{{ if .MaxLength > 0 }}maxlength="{{ .MaxLength }}"{{ end }}
|
{{ if .MaxLength > 0 }}maxlength="{{ .MaxLength }}"{{ end }}
|
||||||
@@ -49,15 +58,66 @@
|
|||||||
<input type="checkbox" id="{{ .Name }}" name="{{ .Name }}"
|
<input type="checkbox" id="{{ .Name }}" name="{{ .Name }}"
|
||||||
value="Y" class="w-4 h-4 text-blue-600 border-gray-300 rounded focus:ring-blue-500" />
|
value="Y" class="w-4 h-4 text-blue-600 border-gray-300 rounded focus:ring-blue-500" />
|
||||||
</div>
|
</div>
|
||||||
<label for="{{ .Name }}" class="flex-1 text-black text-sm">{{ .Caption }}</label>
|
<label for="{{ .Name }}" class="flex-1 text-black text-sm">
|
||||||
|
{{ .Caption }}{{ if .Subcaption != "" }} {{ .Subcaption }}{{ end }}
|
||||||
|
{{ if .Required }}<span class="text-red-600">*</span>{{ end }}
|
||||||
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
{{ else if .Type == "countrylist" }}
|
||||||
|
<div class="flex items-center">
|
||||||
|
<label for="{{ .Name }}" class="w-64 text-right pr-4 text-black text-sm">
|
||||||
|
{{ .Caption }}{{ if .Subcaption != "" }} {{ .Subcaption }}{{ end }}
|
||||||
|
{{ if .Required }}<span class="text-red-600">*</span>{{ end }}
|
||||||
|
</label>
|
||||||
|
{{ v := .Value }}
|
||||||
|
<select id="{{ .Name }}" name="{{ .Name }}" {{ if .Required }}required{{ end }}
|
||||||
|
class="flex-1 px-3 py-2 border border-gray-300 rounded focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500">
|
||||||
|
<option value="XX" {{ if v == "XX" }}selected{{ end }}>🏳️ (unknown)</option>
|
||||||
|
{{ range GetCountryList() }}
|
||||||
|
{{ cc := .Alpha2() }}
|
||||||
|
<option value="{{ cc }}" {{ if cc == v }}selected{{ end }}>{{ .Emoji() }} {{ .Info().Name }}</option>
|
||||||
|
{{ end }}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
{{ else if .Type == "date" }}
|
||||||
|
<div class="flex items-center">
|
||||||
|
<label class="w-64 text-right pr-4 text-black text-sm">
|
||||||
|
{{ .Caption }}{{ if .Subcaption != "" }} {{ .Subcaption }}{{ end }}
|
||||||
|
{{ if .Required }}<span class="text-red-600">*</span>{{ end }}
|
||||||
|
</label>
|
||||||
|
<div class="flex gap-2">
|
||||||
|
<select name="{{ .Name }}_month"
|
||||||
|
class="px-3 py-2 border border-gray-300 rounded focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500">
|
||||||
|
<option value="-1" selected>---</option>
|
||||||
|
{{ range i := GetMonthList() }}
|
||||||
|
<option value="{{ i + 1 }}">{{ . }}</option>
|
||||||
|
{{ end }}
|
||||||
|
</select>
|
||||||
|
<select name="{{ .Name }}_day" class="px-3 py-2 border border-gray-300 rounded focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500">
|
||||||
|
<option value="-1" selected>---</option>
|
||||||
|
{{ range MakeIntRange(1, 32, 1) }}
|
||||||
|
<option value="{{ . }}">{{ . }}</option>
|
||||||
|
{{ end }}
|
||||||
|
</select>
|
||||||
|
<select name="{{ .Name }}_year" class="px-3 py-2 border border-gray-300 rounded focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500">
|
||||||
|
<option value="-1" selected>---</option>
|
||||||
|
{{ range MakeYearRange(.Param) }}
|
||||||
|
<option value="{{ . }}">{{ . }}</option>
|
||||||
|
{{ end }}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{ else if .Type == "header" }}
|
||||||
|
<h2 class="text-lg font-bold text-black mb-4">{{ .Caption }}</h2>
|
||||||
|
{{ else if .Type != "hidden" && .Type != "button" }}
|
||||||
|
BARF! I don't understand {{ .Type }} (field {{ .Name }})
|
||||||
{{ end }}
|
{{ end }}
|
||||||
{{ end }}
|
{{ end }}
|
||||||
</div>
|
</div>
|
||||||
<div class="flex justify-center gap-4 mt-6">
|
<div class="flex justify-center gap-4 mt-6">
|
||||||
{{ range amsterdam_dialog.Fields }}
|
{{ range amsterdam_dialog.Fields }}
|
||||||
{{ if .Type == "button" }}
|
{{ if .Type == "button" }}
|
||||||
{{ clstmp := "bg-" + .Tone + "-600 hover:bg-" + .Tone + "-700" }}
|
{{ clstmp := "bg-" + .Param + "-600 hover:bg-" + .Param + "-700" }}
|
||||||
<button type="submit" name="{{ .Name }}"
|
<button type="submit" name="{{ .Name }}"
|
||||||
class="{{ clstmp }} text-white px-6 py-2 rounded font-medium transition-colors">{{ .Caption }}</button>
|
class="{{ clstmp }} text-white px-6 py-2 rounded font-medium transition-colors">{{ .Caption }}</button>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
|
|||||||
Reference in New Issue
Block a user