From 7a439a40cc87cd68b593e14ec6a7c49fa989a9dd Mon Sep 17 00:00:00 2001 From: jordi fita mas Date: Wed, 1 Feb 2023 14:34:40 +0100 Subject: [PATCH] =?UTF-8?q?Use=20a=20proper=20struct=20for=20the=20contact?= =?UTF-8?q?=E2=80=99s=20form?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Our company is a kind-of contact, although it does not appear in the contact section, thus i could embed the contact form inside the tax details form to reuse all the common fields. --- pkg/company.go | 137 +--------------------- pkg/contacts.go | 194 +++++++++++++++++++++++++------ web/template/contacts-new.gohtml | 78 ++++--------- 3 files changed, 186 insertions(+), 223 deletions(-) diff --git a/pkg/company.go b/pkg/company.go index e7d7a64..c2e19d8 100644 --- a/pkg/company.go +++ b/pkg/company.go @@ -84,109 +84,13 @@ type Tax struct { } type taxDetailsForm struct { - locale *Locale - BusinessName *InputField - VATIN *InputField - TradeName *InputField - Phone *InputField - Email *InputField - Web *InputField - Address *InputField - City *InputField - Province *InputField - PostalCode *InputField - Country *SelectField - Currency *SelectField + *contactForm + Currency *SelectField } func newTaxDetailsForm(ctx context.Context, conn *Conn, locale *Locale) *taxDetailsForm { return &taxDetailsForm{ - locale: locale, - BusinessName: &InputField{ - Name: "business_name", - Label: pgettext("input", "Business name", locale), - Type: "text", - Required: true, - Attributes: []template.HTMLAttr{ - `autocomplete="organization"`, - }, - }, - VATIN: &InputField{ - Name: "vatin", - Label: pgettext("input", "VAT number", locale), - Type: "text", - Required: true, - }, - TradeName: &InputField{ - Name: "trade_name", - Label: pgettext("input", "Trade name", locale), - Type: "text", - }, - Phone: &InputField{ - Name: "phone", - Label: pgettext("input", "Phone", locale), - Type: "tel", - Required: true, - Attributes: []template.HTMLAttr{ - `autocomplete="tel"`, - }, - }, - Email: &InputField{ - Name: "email", - Label: pgettext("input", "Email", locale), - Type: "email", - Required: true, - Attributes: []template.HTMLAttr{ - `autocomplete="email"`, - }, - }, - Web: &InputField{ - Name: "web", - Label: pgettext("input", "Web", locale), - Type: "url", - Attributes: []template.HTMLAttr{ - `autocomplete="url"`, - }, - }, - Address: &InputField{ - Name: "address", - Label: pgettext("input", "Address", locale), - Type: "text", - Required: true, - Attributes: []template.HTMLAttr{ - `autocomplete="address-line1"`, - }, - }, - City: &InputField{ - Name: "city", - Label: pgettext("input", "City", locale), - Type: "text", - Required: true, - }, - Province: &InputField{ - Name: "province", - Label: pgettext("input", "Province", locale), - Type: "text", - Required: true, - }, - PostalCode: &InputField{ - Name: "postal_code", - Label: pgettext("input", "Postal code", locale), - Type: "text", - Required: true, - Attributes: []template.HTMLAttr{ - `autocomplete="postal-code"`, - }, - }, - Country: &SelectField{ - Name: "country", - Label: pgettext("input", "Country", locale), - Options: mustGetCountryOptions(ctx, conn, locale), - Selected: "ES", - Attributes: []template.HTMLAttr{ - `autocomplete="country"`, - }, - }, + contactForm: newContactForm(ctx, conn, locale), Currency: &SelectField{ Name: "currency", Label: pgettext("input", "Currency", locale), @@ -197,48 +101,17 @@ func newTaxDetailsForm(ctx context.Context, conn *Conn, locale *Locale) *taxDeta } func (form *taxDetailsForm) Parse(r *http.Request) error { - if err := r.ParseForm(); err != nil { + if err := form.contactForm.Parse(r); err != nil { return err } - form.BusinessName.FillValue(r) - form.VATIN.FillValue(r) - form.TradeName.FillValue(r) - form.Phone.FillValue(r) - form.Email.FillValue(r) - form.Web.FillValue(r) - form.Address.FillValue(r) - form.City.FillValue(r) - form.Province.FillValue(r) - form.PostalCode.FillValue(r) - form.Country.FillValue(r) form.Currency.FillValue(r) return nil } func (form *taxDetailsForm) Validate(ctx context.Context, conn *Conn) bool { validator := newFormValidator() - validator.CheckRequiredInput(form.BusinessName, gettext("Business name can not be empty.", form.locale)) - if validator.CheckRequiredInput(form.VATIN, gettext("VAT number can not be empty.", form.locale)) { - validator.CheckValidVATINInput(form.VATIN, form.Country.Selected, gettext("This value is not a valid VAT number.", form.locale)) - } - if validator.CheckRequiredInput(form.Phone, gettext("Phone can not be empty.", form.locale)) { - validator.CheckValidPhoneInput(form.Phone, form.Country.Selected, gettext("This value is not a valid phone number.", form.locale)) - } - if validator.CheckRequiredInput(form.Email, gettext("Email can not be empty.", form.locale)) { - validator.CheckValidEmailInput(form.Email, gettext("This value is not a valid email. It should be like name@domain.com.", form.locale)) - } - if form.Web.Val != "" { - validator.checkValidURL(form.Web, gettext("This value is not a valid web address. It should be like https://domain.com/.", form.locale)) - } - validator.CheckRequiredInput(form.Address, gettext("Address can not be empty.", form.locale)) - validator.CheckRequiredInput(form.City, gettext("City can not be empty.", form.locale)) - validator.CheckRequiredInput(form.Province, gettext("Province can not be empty.", form.locale)) - if validator.CheckRequiredInput(form.PostalCode, gettext("Postal code can not be empty.", form.locale)) { - validator.CheckValidPostalCode(ctx, conn, form.PostalCode, form.Country.Selected, gettext("This value is not a valid postal code.", form.locale)) - } - validator.CheckValidSelectOption(form.Country, gettext("Selected country is not valid.", form.locale)) validator.CheckValidSelectOption(form.Currency, gettext("Selected currency is not valid.", form.locale)) - return validator.AllOK() + return form.contactForm.Validate(ctx, conn) && validator.AllOK() } func (form *taxDetailsForm) mustFillFromDatabase(ctx context.Context, conn *Conn, company *Company) *taxDetailsForm { diff --git a/pkg/contacts.go b/pkg/contacts.go index 2f40d45..861e0eb 100644 --- a/pkg/contacts.go +++ b/pkg/contacts.go @@ -2,6 +2,7 @@ package pkg import ( "context" + "html/template" "net/http" ) @@ -20,22 +21,18 @@ func ContactsHandler() http.Handler { conn := getConn(r) company := getCompany(r) if r.Method == "POST" { - r.ParseForm() - page := &NewContactPage{ - BusinessName: r.FormValue("business_name"), - VATIN: r.FormValue("vatin"), - TradeName: r.FormValue("trade_name"), - Phone: r.FormValue("phone"), - Email: r.FormValue("email"), - Web: r.FormValue("web"), - Address: r.FormValue("address"), - City: r.FormValue("city"), - Province: r.FormValue("province"), - PostalCode: r.FormValue("postal_code"), - CountryCode: r.FormValue("country"), + locale := getLocale(r) + form := newContactForm(r.Context(), conn, locale) + if err := form.Parse(r); err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + if form.Validate(r.Context(), conn) { + conn.MustExec(r.Context(), "insert into contact (company_id, business_name, vatin, trade_name, phone, email, web, address, province, city, postal_code, country_code) values ($1, $2, ($12 || $3)::vatin, $4, parse_packed_phone_number($5, $12), $6, $7, $8, $9, $10, $11, $12)", company.Id, form.BusinessName, form.VATIN, form.TradeName, form.Phone, form.Email, form.Web, form.Address, form.City, form.Province, form.PostalCode, form.Country) + http.Redirect(w, r, "/company/"+company.Slug+"/contacts", http.StatusSeeOther) + } else { + mustRenderAppTemplate(w, r, "contacts-new.gohtml", form) } - conn.MustExec(r.Context(), "insert into contact (company_id, business_name, vatin, trade_name, phone, email, web, address, province, city, postal_code, country_code) values ($1, $2, ($12 || $3)::vatin, $4, parse_packed_phone_number($5, $12), $6, $7, $8, $9, $10, $11, $12)", company.Id, page.BusinessName, page.VATIN, page.TradeName, page.Phone, page.Email, page.Web, page.Address, page.City, page.Province, page.PostalCode, page.CountryCode) - http.Redirect(w, r, "/company/"+company.Slug+"/contacts", http.StatusSeeOther) } else { page := &ContactsIndexPage{ Contacts: mustGetContactEntries(r.Context(), conn, company), @@ -68,29 +65,160 @@ func mustGetContactEntries(ctx context.Context, conn *Conn, company *Company) [] return entries } -type NewContactPage struct { - BusinessName string - VATIN string - TradeName string - Phone string - Email string - Web string - Address string - City string - Province string - PostalCode string - CountryCode string - Countries []*SelectOption +type contactForm struct { + locale *Locale + BusinessName *InputField + VATIN *InputField + TradeName *InputField + Phone *InputField + Email *InputField + Web *InputField + Address *InputField + City *InputField + Province *InputField + PostalCode *InputField + Country *SelectField +} + +func newContactForm(ctx context.Context, conn *Conn, locale *Locale) *contactForm { + return &contactForm{ + locale: locale, + BusinessName: &InputField{ + Name: "business_name", + Label: pgettext("input", "Business name", locale), + Type: "text", + Required: true, + Attributes: []template.HTMLAttr{ + `autocomplete="organization"`, + }, + }, + VATIN: &InputField{ + Name: "vatin", + Label: pgettext("input", "VAT number", locale), + Type: "text", + Required: true, + }, + TradeName: &InputField{ + Name: "trade_name", + Label: pgettext("input", "Trade name", locale), + Type: "text", + }, + Phone: &InputField{ + Name: "phone", + Label: pgettext("input", "Phone", locale), + Type: "tel", + Required: true, + Attributes: []template.HTMLAttr{ + `autocomplete="tel"`, + }, + }, + Email: &InputField{ + Name: "email", + Label: pgettext("input", "Email", locale), + Type: "email", + Required: true, + Attributes: []template.HTMLAttr{ + `autocomplete="email"`, + }, + }, + Web: &InputField{ + Name: "web", + Label: pgettext("input", "Web", locale), + Type: "url", + Attributes: []template.HTMLAttr{ + `autocomplete="url"`, + }, + }, + Address: &InputField{ + Name: "address", + Label: pgettext("input", "Address", locale), + Type: "text", + Required: true, + Attributes: []template.HTMLAttr{ + `autocomplete="address-line1"`, + }, + }, + City: &InputField{ + Name: "city", + Label: pgettext("input", "City", locale), + Type: "text", + Required: true, + }, + Province: &InputField{ + Name: "province", + Label: pgettext("input", "Province", locale), + Type: "text", + Required: true, + }, + PostalCode: &InputField{ + Name: "postal_code", + Label: pgettext("input", "Postal code", locale), + Type: "text", + Required: true, + Attributes: []template.HTMLAttr{ + `autocomplete="postal-code"`, + }, + }, + Country: &SelectField{ + Name: "country", + Label: pgettext("input", "Country", locale), + Options: mustGetCountryOptions(ctx, conn, locale), + Selected: "ES", + Attributes: []template.HTMLAttr{ + `autocomplete="country"`, + }, + }, + } +} + +func (form *contactForm) Parse(r *http.Request) error { + if err := r.ParseForm(); err != nil { + return err + } + form.BusinessName.FillValue(r) + form.VATIN.FillValue(r) + form.TradeName.FillValue(r) + form.Phone.FillValue(r) + form.Email.FillValue(r) + form.Web.FillValue(r) + form.Address.FillValue(r) + form.City.FillValue(r) + form.Province.FillValue(r) + form.PostalCode.FillValue(r) + form.Country.FillValue(r) + return nil +} + +func (form *contactForm) Validate(ctx context.Context, conn *Conn) bool { + validator := newFormValidator() + validator.CheckRequiredInput(form.BusinessName, gettext("Business name can not be empty.", form.locale)) + if validator.CheckRequiredInput(form.VATIN, gettext("VAT number can not be empty.", form.locale)) { + validator.CheckValidVATINInput(form.VATIN, form.Country.Selected, gettext("This value is not a valid VAT number.", form.locale)) + } + if validator.CheckRequiredInput(form.Phone, gettext("Phone can not be empty.", form.locale)) { + validator.CheckValidPhoneInput(form.Phone, form.Country.Selected, gettext("This value is not a valid phone number.", form.locale)) + } + if validator.CheckRequiredInput(form.Email, gettext("Email can not be empty.", form.locale)) { + validator.CheckValidEmailInput(form.Email, gettext("This value is not a valid email. It should be like name@domain.com.", form.locale)) + } + if form.Web.Val != "" { + validator.checkValidURL(form.Web, gettext("This value is not a valid web address. It should be like https://domain.com/.", form.locale)) + } + validator.CheckRequiredInput(form.Address, gettext("Address can not be empty.", form.locale)) + validator.CheckRequiredInput(form.City, gettext("City can not be empty.", form.locale)) + validator.CheckRequiredInput(form.Province, gettext("Province can not be empty.", form.locale)) + if validator.CheckRequiredInput(form.PostalCode, gettext("Postal code can not be empty.", form.locale)) { + validator.CheckValidPostalCode(ctx, conn, form.PostalCode, form.Country.Selected, gettext("This value is not a valid postal code.", form.locale)) + } + validator.CheckValidSelectOption(form.Country, gettext("Selected country is not valid.", form.locale)) + return validator.AllOK() } func NewContactHandler() http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { locale := getLocale(r) conn := getConn(r) - page := &NewContactPage{ - CountryCode: "ES", - Countries: mustGetCountryOptions(r.Context(), conn, locale), - } - mustRenderAppTemplate(w, r, "contacts-new.gohtml", page) + form := newContactForm(r.Context(), conn, locale) + mustRenderAppTemplate(w, r, "contacts-new.gohtml", form) }) } diff --git a/web/template/contacts-new.gohtml b/web/template/contacts-new.gohtml index 6a4adac..6757ba6 100644 --- a/web/template/contacts-new.gohtml +++ b/web/template/contacts-new.gohtml @@ -1,65 +1,27 @@ {{ define "title" -}} -{{( pgettext "New Contact" "title" )}} + {{( pgettext "New Contact" "title" )}} {{- end }} {{ define "content" }} -
-

{{(pgettext "New Contact" "title")}}

-
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
+
+

{{(pgettext "New Contact" "title")}}

+ + {{ template "input-field" .BusinessName | addInputAttr "autofocus" }} + {{ template "input-field" .VATIN }} + {{ template "input-field" .TradeName }} + {{ template "input-field" .Phone }} + {{ template "input-field" .Email }} + {{ template "input-field" .Web }} + {{ template "input-field" .Address | addInputAttr `class="width-2x"` }} + {{ template "input-field" .City }} + {{ template "input-field" .Province }} + {{ template "input-field" .PostalCode }} + {{ template "select-field" .Country | addSelectAttr `class="width-fixed"` }} -
- - -
+
+ +
-
- -
- - -
+ +
{{- end }}