There is still some issues with the price field, because for now it is in cents, but do not have time now to fix that.
277 lines
8.9 KiB
Go
277 lines
8.9 KiB
Go
package pkg
|
|
|
|
import (
|
|
"context"
|
|
"github.com/jackc/pgx/v4"
|
|
"github.com/julienschmidt/httprouter"
|
|
"html/template"
|
|
"net/http"
|
|
)
|
|
|
|
type ContactEntry struct {
|
|
Slug string
|
|
Name string
|
|
Email string
|
|
Phone string
|
|
}
|
|
|
|
type ContactsIndexPage struct {
|
|
Contacts []*ContactEntry
|
|
}
|
|
|
|
func IndexContacts(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
|
|
conn := getConn(r)
|
|
company := mustGetCompany(r)
|
|
page := &ContactsIndexPage{
|
|
Contacts: mustGetContactEntries(r.Context(), conn, company),
|
|
}
|
|
mustRenderAppTemplate(w, r, "contacts/index.gohtml", page)
|
|
}
|
|
|
|
func GetContactForm(w http.ResponseWriter, r *http.Request, params httprouter.Params) {
|
|
locale := getLocale(r)
|
|
conn := getConn(r)
|
|
form := newContactForm(r.Context(), conn, locale)
|
|
slug := params[0].Value
|
|
if slug == "new" {
|
|
w.WriteHeader(http.StatusOK)
|
|
mustRenderNewContactForm(w, r, form)
|
|
return
|
|
}
|
|
err := conn.QueryRow(r.Context(), "select business_name, substr(vatin::text, 3), trade_name, phone, email, web, address, city, province, postal_code, country_code from contact where slug = $1", slug).Scan(form.BusinessName, form.VATIN, form.TradeName, form.Phone, form.Email, form.Web, form.Address, form.City, form.Province, form.PostalCode, form.Country)
|
|
if err != nil {
|
|
if err == pgx.ErrNoRows {
|
|
http.NotFound(w, r)
|
|
return
|
|
} else {
|
|
panic(err)
|
|
}
|
|
}
|
|
w.WriteHeader(http.StatusOK)
|
|
mustRenderEditContactForm(w, r, form)
|
|
}
|
|
|
|
func mustRenderNewContactForm(w http.ResponseWriter, r *http.Request, form *contactForm) {
|
|
mustRenderAppTemplate(w, r, "contacts/new.gohtml", form)
|
|
}
|
|
|
|
func mustRenderEditContactForm(w http.ResponseWriter, r *http.Request, form *contactForm) {
|
|
mustRenderAppTemplate(w, r, "contacts/edit.gohtml", form)
|
|
}
|
|
|
|
func HandleAddContact(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
|
|
conn := getConn(r)
|
|
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 err := verifyCsrfTokenValid(r); err != nil {
|
|
http.Error(w, err.Error(), http.StatusForbidden)
|
|
return
|
|
}
|
|
if !form.Validate(r.Context(), conn) {
|
|
mustRenderNewContactForm(w, r, form)
|
|
return
|
|
}
|
|
company := mustGetCompany(r)
|
|
conn.MustExec(r.Context(), "insert into contact (company_id, business_name, vatin, trade_name, phone, email, web, address, city, province, 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, companyURI(company, "/contacts"), http.StatusSeeOther)
|
|
}
|
|
|
|
func HandleUpdateContact(w http.ResponseWriter, r *http.Request, params httprouter.Params) {
|
|
conn := getConn(r)
|
|
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 err := verifyCsrfTokenValid(r); err != nil {
|
|
http.Error(w, err.Error(), http.StatusForbidden)
|
|
return
|
|
}
|
|
if !form.Validate(r.Context(), conn) {
|
|
mustRenderEditContactForm(w, r, form)
|
|
return
|
|
}
|
|
slug := conn.MustGetText(r.Context(), "", "update contact set business_name = $1, vatin = ($11 || $2)::vatin, trade_name = $3, phone = parse_packed_phone_number($4, $11), email = $5, web = $6, address = $7, city = $8, province = $9, postal_code = $10, country_code = $11 where slug = $12 returning slug", form.BusinessName, form.VATIN, form.TradeName, form.Phone, form.Email, form.Web, form.Address, form.City, form.Province, form.PostalCode, form.Country, params[0].Value)
|
|
if slug == "" {
|
|
http.NotFound(w, r)
|
|
}
|
|
http.Redirect(w, r, companyURI(mustGetCompany(r), "/contacts/"+slug), http.StatusSeeOther)
|
|
}
|
|
|
|
func mustGetContactEntries(ctx context.Context, conn *Conn, company *Company) []*ContactEntry {
|
|
rows, err := conn.Query(ctx, "select slug, business_name, email, phone from contact where company_id = $1 order by business_name", company.Id)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
defer rows.Close()
|
|
|
|
var entries []*ContactEntry
|
|
for rows.Next() {
|
|
entry := &ContactEntry{}
|
|
err = rows.Scan(&entry.Slug, &entry.Name, &entry.Email, &entry.Phone)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
entries = append(entries, entry)
|
|
}
|
|
if rows.Err() != nil {
|
|
panic(rows.Err())
|
|
}
|
|
|
|
return entries
|
|
}
|
|
|
|
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", "Tax", 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()
|
|
}
|