Validate profile form and use templates for fields
Let’s start first with a non-fancy validation method with just if conditionals instead of bringing yet another complicated library. I hope i do not regret it. I wanted to move all the input field to a template because all that gobbledygook with the .input div and repeating the label in the placeholder was starting to annoy me. Now with error messages was even more concerning. I did not know whether the label should be a part of the input fields or something that the template should do. At the end i decided that it makes more sense to be part of the input field because in the error messages i use that same label, thus the template does not have a say in that, and, besides, it was just easier to write the template. The same with the error messages: i’ve seen frameworks that have a map with the field’s id/name to the error slice, but then it would be a bit harder to write the template. I added AddError functions instead of just using append inside the validator function, and have a local variable for whether it all went OK, because i was worried that i would leave out the `ok = false` in some conditions. I had started writing “constructors” functions for InputField and SelectField, but then had to add other methods to change the required field and who knows what else, and in the end it was easier to just construct the field inline.
This commit is contained in:
parent
89256d5b4c
commit
9f17f55547
|
@ -0,0 +1,73 @@
|
|||
package pkg
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/mail"
|
||||
)
|
||||
|
||||
type InputField struct {
|
||||
Name string
|
||||
Label string
|
||||
Type string
|
||||
Value string
|
||||
Required bool
|
||||
Errors []error
|
||||
}
|
||||
|
||||
func (field *InputField) Equals(other *InputField) bool {
|
||||
return field.Value == other.Value
|
||||
}
|
||||
|
||||
func (field *InputField) IsEmpty() bool {
|
||||
return field.Value == ""
|
||||
}
|
||||
|
||||
func (field *InputField) HasValidEmail() bool {
|
||||
_, err := mail.ParseAddress(field.Value)
|
||||
return err == nil
|
||||
}
|
||||
|
||||
type SelectOption struct {
|
||||
Value string
|
||||
Label string
|
||||
}
|
||||
|
||||
type SelectField struct {
|
||||
Name string
|
||||
Label string
|
||||
Selected string
|
||||
Options []*SelectOption
|
||||
Errors []error
|
||||
}
|
||||
|
||||
func (field *SelectField) HasValidOption() bool {
|
||||
for _, option := range field.Options {
|
||||
if option.Value == field.Selected {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func MustGetOptions(ctx context.Context, conn *Conn, sql string, args ...interface{}) []*SelectOption {
|
||||
rows, err := conn.Query(ctx, sql, args...)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var options []*SelectOption
|
||||
for rows.Next() {
|
||||
option := &SelectOption{}
|
||||
err = rows.Scan(&option.Value, &option.Label)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
options = append(options, option)
|
||||
}
|
||||
if rows.Err() != nil {
|
||||
panic(rows.Err())
|
||||
}
|
||||
|
||||
return options
|
||||
}
|
|
@ -66,6 +66,10 @@ func pgettext(context string, str string, locale *Locale) string {
|
|||
return locale.GetC(str, context)
|
||||
}
|
||||
|
||||
func gettext(str string, locale *Locale) string {
|
||||
return locale.Get(str)
|
||||
}
|
||||
|
||||
func mustGetAvailableLanguages(db *Db) []language.Tag {
|
||||
rows, err := db.Query(context.Background(), "select lang_tag from language where selectable")
|
||||
if err != nil {
|
||||
|
|
155
pkg/profile.go
155
pkg/profile.go
|
@ -2,7 +2,9 @@ package pkg
|
|||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type LanguageOption struct {
|
||||
|
@ -10,66 +12,117 @@ type LanguageOption struct {
|
|||
Name string
|
||||
}
|
||||
|
||||
type ProfilePage struct {
|
||||
Name string
|
||||
Email string
|
||||
Password string
|
||||
PasswordConfirm string
|
||||
Language string
|
||||
Languages []LanguageOption
|
||||
type profileForm struct {
|
||||
Name *InputField
|
||||
Email *InputField
|
||||
Password *InputField
|
||||
PasswordConfirm *InputField
|
||||
Language *SelectField
|
||||
Valid bool
|
||||
}
|
||||
|
||||
func newProfileForm(ctx context.Context, conn *Conn, locale *Locale) *profileForm {
|
||||
automaticOption := pgettext("language option", "Automatic", locale)
|
||||
languages := MustGetOptions(ctx, conn, "select 'und', $1 union all select lang_tag, endonym from language where selectable", automaticOption)
|
||||
return &profileForm{
|
||||
Name: &InputField{
|
||||
Name: "name",
|
||||
Label: pgettext("input", "User name", locale),
|
||||
Type: "text",
|
||||
Required: true,
|
||||
},
|
||||
Email: &InputField{
|
||||
Name: "email",
|
||||
Label: pgettext("input", "Email", locale),
|
||||
Type: "email",
|
||||
Required: true,
|
||||
},
|
||||
Password: &InputField{
|
||||
Name: "password",
|
||||
Label: pgettext("input", "Password", locale),
|
||||
Type: "password",
|
||||
},
|
||||
PasswordConfirm: &InputField{
|
||||
Name: "password_confirm",
|
||||
Label: pgettext("input", "Password Confirmation", locale),
|
||||
Type: "password",
|
||||
},
|
||||
Language: &SelectField{
|
||||
Name: "language",
|
||||
Label: pgettext("input", "Language", locale),
|
||||
Options: languages,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (form *profileForm) Parse(r *http.Request) error {
|
||||
if err := r.ParseForm(); err != nil {
|
||||
return err
|
||||
}
|
||||
form.Email.Value = strings.TrimSpace(r.FormValue("email"))
|
||||
form.Name.Value = strings.TrimSpace(r.FormValue("name"))
|
||||
form.Password.Value = strings.TrimSpace(r.FormValue("password"))
|
||||
form.PasswordConfirm.Value = strings.TrimSpace(r.FormValue("password_confirm"))
|
||||
form.Language.Selected = r.FormValue("language")
|
||||
return nil
|
||||
}
|
||||
|
||||
func (form *profileForm) Validate(locale *Locale) bool {
|
||||
form.Valid = true
|
||||
if form.Email.IsEmpty() {
|
||||
form.AppendInputError(form.Email, errors.New(gettext("Email can not be empty.", locale)))
|
||||
} else if !form.Email.HasValidEmail() {
|
||||
form.AppendInputError(form.Email, errors.New(gettext("This value is not a valid email. It should be like name@domain.com.", locale)))
|
||||
}
|
||||
if form.Name.IsEmpty() {
|
||||
form.AppendInputError(form.Name, errors.New(gettext("Name can not be empty.", locale)))
|
||||
}
|
||||
if !form.PasswordConfirm.Equals(form.Password) {
|
||||
form.AppendInputError(form.PasswordConfirm, errors.New(gettext("Confirmation does not match password.", locale)))
|
||||
}
|
||||
if !form.Language.HasValidOption() {
|
||||
form.AppendSelectError(form.Language, errors.New(gettext("Selected language is not valid.", locale)))
|
||||
}
|
||||
return form.Valid
|
||||
}
|
||||
|
||||
func (form *profileForm) AppendInputError(field *InputField, err error) {
|
||||
field.Errors = append(field.Errors, err)
|
||||
form.Valid = false
|
||||
}
|
||||
|
||||
func (form *profileForm) AppendSelectError(field *SelectField, err error) {
|
||||
field.Errors = append(field.Errors, err)
|
||||
form.Valid = false
|
||||
}
|
||||
|
||||
func ProfileHandler() http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
user := getUser(r)
|
||||
conn := getConn(r)
|
||||
page := ProfilePage{
|
||||
Email: user.Email,
|
||||
Language: user.Language.String(),
|
||||
}
|
||||
locale := getLocale(r)
|
||||
form := newProfileForm(r.Context(), conn, locale)
|
||||
if r.Method == "POST" {
|
||||
r.ParseForm()
|
||||
page.Email = r.FormValue("email")
|
||||
page.Name = r.FormValue("name")
|
||||
page.Password = r.FormValue("password")
|
||||
page.PasswordConfirm = r.FormValue("password_confirm")
|
||||
page.Language = r.FormValue("language")
|
||||
cookie := conn.MustGetText(r.Context(), "", "update user_profile set name = $1, email = $2, lang_tag = $3 returning build_cookie()", page.Name, page.Email, page.Language)
|
||||
setSessionCookie(w, cookie)
|
||||
if page.Password != "" && page.Password == page.PasswordConfirm {
|
||||
conn.MustExec(r.Context(), "select change_password($1)", page.Password)
|
||||
}
|
||||
http.Redirect(w, r, "/profile", http.StatusSeeOther)
|
||||
if err := form.Parse(r); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
if ok := form.Validate(locale); ok {
|
||||
cookie := conn.MustGetText(r.Context(), "", "update user_profile set name = $1, email = $2, lang_tag = $3 returning build_cookie()", form.Name.Value, form.Email.Value, form.Language.Selected)
|
||||
setSessionCookie(w, cookie)
|
||||
if form.Password.Value != "" {
|
||||
conn.MustExec(r.Context(), "select change_password($1)", form.Password.Value)
|
||||
}
|
||||
company := getCompany(r)
|
||||
http.Redirect(w, r, "/company/"+company.Slug+"/profile", http.StatusSeeOther)
|
||||
return
|
||||
}
|
||||
w.WriteHeader(http.StatusUnprocessableEntity)
|
||||
} else {
|
||||
page.Languages = mustGetLanguageOptions(r.Context(), conn)
|
||||
if err := conn.QueryRow(r.Context(), "select name from user_profile").Scan(&page.Name); err != nil {
|
||||
panic(nil)
|
||||
form.Name.Value = conn.MustGetText(r.Context(), "", "select name from user_profile")
|
||||
form.Email.Value = user.Email
|
||||
form.Language.Selected = user.Language.String()
|
||||
}
|
||||
}
|
||||
mustRenderAppTemplate(w, r, "profile.gohtml", page)
|
||||
mustRenderAppTemplate(w, r, "profile.gohtml", form)
|
||||
})
|
||||
}
|
||||
|
||||
func mustGetLanguageOptions(ctx context.Context, conn *Conn) []LanguageOption {
|
||||
rows, err := conn.Query(ctx, "select lang_tag, endonym from language where selectable")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var langs []LanguageOption
|
||||
for rows.Next() {
|
||||
var lang LanguageOption
|
||||
err = rows.Scan(&lang.Tag, &lang.Name)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
langs = append(langs, lang)
|
||||
}
|
||||
if rows.Err() != nil {
|
||||
panic(rows.Err())
|
||||
}
|
||||
|
||||
return langs
|
||||
}
|
||||
|
|
|
@ -24,7 +24,7 @@ func mustRenderTemplate(wr io.Writer, r *http.Request, layout string, filename s
|
|||
return "/company/" + company.Slug + uri
|
||||
},
|
||||
})
|
||||
if _, err := t.ParseFiles(templateFile(filename), templateFile(layout)); err != nil {
|
||||
if _, err := t.ParseFiles(templateFile(filename), templateFile(layout), templateFile("form.gohtml")); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if err := t.ExecuteTemplate(wr, layout, data); err != nil {
|
||||
|
|
71
po/ca.po
71
po/ca.po
|
@ -8,7 +8,7 @@ msgid ""
|
|||
msgstr ""
|
||||
"Project-Id-Version: numerus\n"
|
||||
"Report-Msgid-Bugs-To: jordi@tandem.blog\n"
|
||||
"POT-Creation-Date: 2023-01-31 13:28+0100\n"
|
||||
"POT-Creation-Date: 2023-01-31 15:17+0100\n"
|
||||
"PO-Revision-Date: 2023-01-18 17:08+0100\n"
|
||||
"Last-Translator: jordi fita mas <jordi@tandem.blog>\n"
|
||||
"Language-Team: Catalan <ca@dodds.net>\n"
|
||||
|
@ -56,13 +56,13 @@ msgstr "Entrada"
|
|||
msgid "Invalid user or password"
|
||||
msgstr "Nom d’usuari o contrasenya incorrectes"
|
||||
|
||||
#: web/template/login.gohtml:17 web/template/profile.gohtml:19
|
||||
#: web/template/tax-details.gohtml:27 web/template/contacts-new.gohtml:27
|
||||
#: web/template/login.gohtml:17 web/template/tax-details.gohtml:27
|
||||
#: web/template/contacts-new.gohtml:27 pkg/profile.go:37
|
||||
msgctxt "input"
|
||||
msgid "Email"
|
||||
msgstr "Correu-e"
|
||||
|
||||
#: web/template/login.gohtml:22 web/template/profile.gohtml:27
|
||||
#: web/template/login.gohtml:22 pkg/profile.go:43
|
||||
msgctxt "input"
|
||||
msgid "Password"
|
||||
msgstr "Contrasenya"
|
||||
|
@ -82,32 +82,17 @@ msgctxt "title"
|
|||
msgid "User Access Data"
|
||||
msgstr "Dades accés usuari"
|
||||
|
||||
#: web/template/profile.gohtml:14
|
||||
msgctxt "input"
|
||||
msgid "User name"
|
||||
msgstr "Nom d’usuari"
|
||||
|
||||
#: web/template/profile.gohtml:23
|
||||
#: web/template/profile.gohtml:16
|
||||
msgctxt "title"
|
||||
msgid "Password Change"
|
||||
msgstr "Canvi contrasenya"
|
||||
|
||||
#: web/template/profile.gohtml:32
|
||||
msgctxt "input"
|
||||
msgid "Password Confirmation"
|
||||
msgstr "Confirmació contrasenya"
|
||||
|
||||
#: web/template/profile.gohtml:37
|
||||
msgctxt "input"
|
||||
#: web/template/profile.gohtml:23
|
||||
msgctxt "title"
|
||||
msgid "Language"
|
||||
msgstr "Idioma"
|
||||
|
||||
#: web/template/profile.gohtml:40
|
||||
msgctxt "language option"
|
||||
msgid "Automatic"
|
||||
msgstr "Automàtic"
|
||||
|
||||
#: web/template/profile.gohtml:46 web/template/tax-details.gohtml:133
|
||||
#: web/template/profile.gohtml:27 web/template/tax-details.gohtml:133
|
||||
msgctxt "action"
|
||||
msgid "Save changes"
|
||||
msgstr "Desa canvis"
|
||||
|
@ -244,3 +229,43 @@ msgstr "Afegeix nou impost"
|
|||
msgctxt "title"
|
||||
msgid "New Contact"
|
||||
msgstr "Nou contacte"
|
||||
|
||||
#: pkg/profile.go:26
|
||||
msgctxt "language option"
|
||||
msgid "Automatic"
|
||||
msgstr "Automàtic"
|
||||
|
||||
#: pkg/profile.go:31
|
||||
msgctxt "input"
|
||||
msgid "User name"
|
||||
msgstr "Nom d’usuari"
|
||||
|
||||
#: pkg/profile.go:48
|
||||
msgctxt "input"
|
||||
msgid "Password Confirmation"
|
||||
msgstr "Confirmació contrasenya"
|
||||
|
||||
#: pkg/profile.go:53
|
||||
msgctxt "input"
|
||||
msgid "Language"
|
||||
msgstr "Idioma"
|
||||
|
||||
#: pkg/profile.go:74
|
||||
msgid "Email can not be empty."
|
||||
msgstr "No podeu deixar el correu-e en blanc."
|
||||
|
||||
#: pkg/profile.go:76
|
||||
msgid "This value is not a valid email. It should be like name@domain.com."
|
||||
msgstr "Aquest valor no és un correu-e vàlid. Hauria de ser similar a nom@domini.cat."
|
||||
|
||||
#: pkg/profile.go:79
|
||||
msgid "Name can not be empty."
|
||||
msgstr "No podeu deixar el nom en blanc."
|
||||
|
||||
#: pkg/profile.go:82
|
||||
msgid "Confirmation does not match password."
|
||||
msgstr "La confirmació no és igual a la contrasenya."
|
||||
|
||||
#: pkg/profile.go:85
|
||||
msgid "Selected language is not valid."
|
||||
msgstr "Heu seleccionat un idioma que no és vàlid."
|
||||
|
|
71
po/es.po
71
po/es.po
|
@ -7,7 +7,7 @@ msgid ""
|
|||
msgstr ""
|
||||
"Project-Id-Version: numerus\n"
|
||||
"Report-Msgid-Bugs-To: jordi@tandem.blog\n"
|
||||
"POT-Creation-Date: 2023-01-31 13:28+0100\n"
|
||||
"POT-Creation-Date: 2023-01-31 15:17+0100\n"
|
||||
"PO-Revision-Date: 2023-01-18 17:45+0100\n"
|
||||
"Last-Translator: jordi fita mas <jordi@tandem.blog>\n"
|
||||
"Language-Team: Spanish <es@tp.org.es>\n"
|
||||
|
@ -56,13 +56,13 @@ msgstr "Entrada"
|
|||
msgid "Invalid user or password"
|
||||
msgstr "Nombre de usuario o contraseña inválido"
|
||||
|
||||
#: web/template/login.gohtml:17 web/template/profile.gohtml:19
|
||||
#: web/template/tax-details.gohtml:27 web/template/contacts-new.gohtml:27
|
||||
#: web/template/login.gohtml:17 web/template/tax-details.gohtml:27
|
||||
#: web/template/contacts-new.gohtml:27 pkg/profile.go:37
|
||||
msgctxt "input"
|
||||
msgid "Email"
|
||||
msgstr "Correo-e"
|
||||
|
||||
#: web/template/login.gohtml:22 web/template/profile.gohtml:27
|
||||
#: web/template/login.gohtml:22 pkg/profile.go:43
|
||||
msgctxt "input"
|
||||
msgid "Password"
|
||||
msgstr "Contraseña"
|
||||
|
@ -82,32 +82,17 @@ msgctxt "title"
|
|||
msgid "User Access Data"
|
||||
msgstr "Datos acceso usuario"
|
||||
|
||||
#: web/template/profile.gohtml:14
|
||||
msgctxt "input"
|
||||
msgid "User name"
|
||||
msgstr "Nombre de usuario"
|
||||
|
||||
#: web/template/profile.gohtml:23
|
||||
#: web/template/profile.gohtml:16
|
||||
msgctxt "title"
|
||||
msgid "Password Change"
|
||||
msgstr "Cambio de contraseña"
|
||||
|
||||
#: web/template/profile.gohtml:32
|
||||
msgctxt "input"
|
||||
msgid "Password Confirmation"
|
||||
msgstr "Confirmación contrasenya"
|
||||
|
||||
#: web/template/profile.gohtml:37
|
||||
msgctxt "input"
|
||||
#: web/template/profile.gohtml:23
|
||||
msgctxt "title"
|
||||
msgid "Language"
|
||||
msgstr "Idioma"
|
||||
|
||||
#: web/template/profile.gohtml:40
|
||||
msgctxt "language option"
|
||||
msgid "Automatic"
|
||||
msgstr "Automático"
|
||||
|
||||
#: web/template/profile.gohtml:46 web/template/tax-details.gohtml:133
|
||||
#: web/template/profile.gohtml:27 web/template/tax-details.gohtml:133
|
||||
msgctxt "action"
|
||||
msgid "Save changes"
|
||||
msgstr "Guardar cambios"
|
||||
|
@ -244,3 +229,43 @@ msgstr "Añadir nuevo impuesto"
|
|||
msgctxt "title"
|
||||
msgid "New Contact"
|
||||
msgstr "Nuevo contacto"
|
||||
|
||||
#: pkg/profile.go:26
|
||||
msgctxt "language option"
|
||||
msgid "Automatic"
|
||||
msgstr "Automático"
|
||||
|
||||
#: pkg/profile.go:31
|
||||
msgctxt "input"
|
||||
msgid "User name"
|
||||
msgstr "Nombre de usuario"
|
||||
|
||||
#: pkg/profile.go:48
|
||||
msgctxt "input"
|
||||
msgid "Password Confirmation"
|
||||
msgstr "Confirmación contrasenya"
|
||||
|
||||
#: pkg/profile.go:53
|
||||
msgctxt "input"
|
||||
msgid "Language"
|
||||
msgstr "Idioma"
|
||||
|
||||
#: pkg/profile.go:74
|
||||
msgid "Email can not be empty."
|
||||
msgstr "No podéis dejar el correo-e en blanco."
|
||||
|
||||
#: pkg/profile.go:76
|
||||
msgid "This value is not a valid email. It should be like name@domain.com."
|
||||
msgstr "Este valor no es un correo-e válido. Tiene que ser parecido a nombre@dominio.es."
|
||||
|
||||
#: pkg/profile.go:79
|
||||
msgid "Name can not be empty."
|
||||
msgstr "No podéis dejar el nombre en blanco."
|
||||
|
||||
#: pkg/profile.go:82
|
||||
msgid "Confirmation does not match password."
|
||||
msgstr "La confirmación no corresponde con la contraseña."
|
||||
|
||||
#: pkg/profile.go:85
|
||||
msgid "Selected language is not valid."
|
||||
msgstr "Habéis escogido un idioma que no es válido."
|
||||
|
|
|
@ -320,6 +320,12 @@ input.width-2x {
|
|||
top: 1rem;
|
||||
}
|
||||
|
||||
.input ul {
|
||||
font-size: .8em;
|
||||
padding-left: 1em;
|
||||
color: var(--numerus--color--red);
|
||||
}
|
||||
|
||||
[lang="en"] input:not([required]) + label::after {
|
||||
content: " (optional)"
|
||||
}
|
||||
|
|
|
@ -0,0 +1,33 @@
|
|||
{{ define "input-field" -}}
|
||||
<div class="input {{ if .Errors }}has-errors{{ end }}">
|
||||
<input type="{{ .Type }}" name="{{ .Name }}" id="{{ .Name }}-field"
|
||||
{{ if .Required }}required="required"{{ end }} value="{{ .Value }}" placeholder="{{ .Label }}">
|
||||
<label for="{{ .Name }}-field">{{ .Label }}</label>
|
||||
{{ if .Errors }}
|
||||
<ul>
|
||||
{{- range $error := .Errors }}
|
||||
<li>{{ . }}</li>
|
||||
{{- end }}
|
||||
</ul>
|
||||
{{ end }}
|
||||
</div>
|
||||
{{- end }}
|
||||
|
||||
{{ define "select-field" -}}
|
||||
<div class="input {{ if .Errors }}has-errors{{ end }}">
|
||||
<select id="{{ .Name }}-field" name="{{ .Name }}">
|
||||
{{- range $option := .Options }}
|
||||
<option value="{{ .Value }}"
|
||||
{{ if eq .Value $.Selected }}selected="selected"{{ end }}>{{ .Label }}</option>
|
||||
{{- end }}
|
||||
</select>
|
||||
<label for="{{ .Name }}-field">{{ .Label }}</label>
|
||||
{{ if .Errors }}
|
||||
<ul>
|
||||
{{- range $error := .Errors }}
|
||||
<li>{{ . }}</li>
|
||||
{{- end }}
|
||||
</ul>
|
||||
{{ end }}
|
||||
</div>
|
||||
{{- end }}
|
|
@ -1,47 +1,28 @@
|
|||
{{ define "title" -}}
|
||||
{{( pgettext "User Settings" "title" )}}
|
||||
{{( pgettext "User Settings" "title" )}}
|
||||
{{- end }}
|
||||
|
||||
{{ define "content" }}
|
||||
<section class="dialog-content">
|
||||
<h2>{{(pgettext "User Settings" "title")}}</h2>
|
||||
<form method="POST" action="/profile">
|
||||
<form method="POST">
|
||||
<fieldset class="full-width">
|
||||
<legend>{{( pgettext "User Access Data" "title" )}}</legend>
|
||||
|
||||
<div class="input">
|
||||
<input type="text" name="name" id="name" required="required" value="{{ .Name }}" placeholder="{{( pgettext "User name" "input" )}}">
|
||||
<label for="name">{{( pgettext "User name" "input" )}}</label>
|
||||
</div>
|
||||
|
||||
<div class="input">
|
||||
<input type="email" name="email" id="email" required="required" value="{{ .Email }}" placeholder="{{( pgettext "Email" "input" )}}">
|
||||
<label for="email">{{( pgettext "Email" "input" )}}</label>
|
||||
</div>
|
||||
{{ template "input-field" .Name }}
|
||||
{{ template "input-field" .Email }}
|
||||
</fieldset>
|
||||
<fieldset class="full-width">
|
||||
<legend>{{( pgettext "Password Change" "title" )}}</legend>
|
||||
|
||||
<div class="input">
|
||||
<input type="password" name="password" id="password" value="{{ .Password }}" placeholder="{{( pgettext "Password" "input" )}}">
|
||||
<label for="password">{{( pgettext "Password" "input" )}}</label>
|
||||
</div>
|
||||
|
||||
<div class="input">
|
||||
<input type="password" name="password_confirm" id="password_confirm" value="{{ .PasswordConfirm }}" placeholder="{{( pgettext "Password Confirmation" "input" )}}">
|
||||
<label for="password_confirm">{{( pgettext "Password Confirmation" "input" )}}</label>
|
||||
</div>
|
||||
{{ template "input-field" .Password }}
|
||||
{{ template "input-field" .PasswordConfirm }}
|
||||
</fieldset>
|
||||
|
||||
<fieldset>
|
||||
<legend id="language-legend">{{( pgettext "Language" "input" )}}</legend>
|
||||
<legend id="language-legend">{{( pgettext "Language" "title" )}}</legend>
|
||||
|
||||
<select id="language" name="language" aria-labelledby="language-legend">
|
||||
<option value="und">{{( pgettext "Automatic" "language option" )}}</option>
|
||||
{{- range $language := .Languages }}
|
||||
<option value="{{ .Tag }}" {{ if eq .Tag $.Language }}selected="selected"{{ end }}>{{ .Name }}</option>
|
||||
{{- end }}
|
||||
</select>
|
||||
{{ template "select-field" .Language }}
|
||||
|
||||
<button type="submit">{{( pgettext "Save changes" "action" )}}</button>
|
||||
</fieldset>
|
||||
|
|
Loading…
Reference in New Issue