From 2e10966ad71e05c127119ffe7cfa88a5ebe05fdf Mon Sep 17 00:00:00 2001 From: jordi fita mas Date: Fri, 13 Oct 2023 20:30:31 +0200 Subject: [PATCH] Add the list of features for campsite type --- demo/demo.sql | 87 ++++++ deploy/add_campsite_type_feature.sql | 26 ++ deploy/available_icons.sql | 7 +- deploy/campsite_type_feature.sql | 55 ++++ deploy/campsite_type_feature_i18n.sql | 22 ++ deploy/edit_campsite_type_feature.sql | 25 ++ deploy/translate_campsite_type_feature.sql | 24 ++ pkg/campsite/types/admin.go | 2 + pkg/campsite/types/feature.go | 251 ++++++++++++++++++ pkg/campsite/types/l10n.go | 57 ++++ pkg/campsite/types/public.go | 51 +++- pkg/database/db.go | 6 + pkg/database/funcs.go | 13 + po/ca.po | 216 +++++++++------ po/es.po | 218 +++++++++------ revert/add_campsite_type_feature.sql | 7 + revert/campsite_type_feature.sql | 7 + revert/campsite_type_feature_i18n.sql | 7 + revert/edit_campsite_type_feature.sql | 7 + revert/translate_campsite_type_feature.sql | 7 + sqitch.plan | 5 + test/add_campsite_type_feature.sql | 77 ++++++ test/campsite_type_feature.sql | 204 ++++++++++++++ test/campsite_type_feature_i18n.sql | 44 +++ test/edit_campsite_type_feature.sql | 81 ++++++ test/translate_campsite_type_feature.sql | 85 ++++++ verify/add_campsite_type_feature.sql | 7 + verify/available_icons.sql | 5 + verify/campsite_type_feature.sql | 18 ++ verify/campsite_type_feature_i18n.sql | 11 + verify/edit_campsite_type_feature.sql | 7 + verify/translate_campsite_type_feature.sql | 7 + web/static/icons.css | 19 ++ web/static/public.css | 23 +- .../admin/campsite/feature/form.gohtml | 75 ++++++ .../admin/campsite/feature/index.gohtml | 41 +++ .../admin/campsite/feature/l10n.gohtml | 35 +++ .../admin/campsite/type/index.gohtml | 12 +- web/templates/public/campsite/type.gohtml | 13 +- 39 files changed, 1693 insertions(+), 171 deletions(-) create mode 100644 deploy/add_campsite_type_feature.sql create mode 100644 deploy/campsite_type_feature.sql create mode 100644 deploy/campsite_type_feature_i18n.sql create mode 100644 deploy/edit_campsite_type_feature.sql create mode 100644 deploy/translate_campsite_type_feature.sql create mode 100644 pkg/campsite/types/feature.go create mode 100644 revert/add_campsite_type_feature.sql create mode 100644 revert/campsite_type_feature.sql create mode 100644 revert/campsite_type_feature_i18n.sql create mode 100644 revert/edit_campsite_type_feature.sql create mode 100644 revert/translate_campsite_type_feature.sql create mode 100644 test/add_campsite_type_feature.sql create mode 100644 test/campsite_type_feature.sql create mode 100644 test/campsite_type_feature_i18n.sql create mode 100644 test/edit_campsite_type_feature.sql create mode 100644 test/translate_campsite_type_feature.sql create mode 100644 verify/add_campsite_type_feature.sql create mode 100644 verify/campsite_type_feature.sql create mode 100644 verify/campsite_type_feature_i18n.sql create mode 100644 verify/edit_campsite_type_feature.sql create mode 100644 verify/translate_campsite_type_feature.sql create mode 100644 web/templates/admin/campsite/feature/form.gohtml create mode 100644 web/templates/admin/campsite/feature/index.gohtml create mode 100644 web/templates/admin/campsite/feature/l10n.gohtml diff --git a/demo/demo.sql b/demo/demo.sql index a1d9ae2..7e0743d 100644 --- a/demo/demo.sql +++ b/demo/demo.sql @@ -130,6 +130,93 @@ values (72, 'en', 'Plots', '

Camp in the middle of nature

Located on t , (76, 'es', 'Cabañas de madera', '

Alojamientos de lujo

Ubicadas al lado montaña del camping y con vista a la naturaleza que nos rodea.

Dos cabañas de madera maciza de dos plantas y con porche cubierto para disfutrar entre árboles.

', '

Proche/terraza (13 m²)

Planta baja (32 m²)

Planta altillo (16 m²)

', '

El precio incluye

* Toallas: precio extra

', '

Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.

Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.

Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.

') ; +alter table campsite_type_feature alter column campsite_type_feature_id restart with 82; +insert into campsite_type_feature (campsite_type_id, icon_name, name) +values (72, 'person', 'Máx. 5 pers.') + , (72, 'area', 'Cabana 48 m² Porxo 13 m²') + , (72, 'wifi', 'WiFi') + , (72, 'hvac', 'Climatització') + , (72, 'ecofriendly', 'Eco-sostenible') + , (72, 'nopet', 'Gossos NO') + , (73, 'person', 'Máx. 5 pers.') + , (73, 'area', 'Cabana 48 m² Porxo 13 m²') + , (73, 'wifi', 'WiFi') + , (73, 'hvac', 'Climatització') + , (73, 'ecofriendly', 'Eco-sostenible') + , (73, 'nopet', 'Gossos NO') + , (74, 'person', 'Máx. 5 pers.') + , (74, 'area', 'Cabana 48 m² Porxo 13 m²') + , (74, 'wifi', 'WiFi') + , (74, 'hvac', 'Climatització') + , (74, 'ecofriendly', 'Eco-sostenible') + , (74, 'nopet', 'Gossos NO') + , (75, 'person', 'Máx. 5 pers.') + , (75, 'area', 'Cabana 48 m² Porxo 13 m²') + , (75, 'wifi', 'WiFi') + , (75, 'hvac', 'Climatització') + , (75, 'ecofriendly', 'Eco-sostenible') + , (75, 'nopet', 'Gossos NO') + , (76, 'person', 'Máx. 5 pers.') + , (76, 'area', 'Cabana 48 m² Porxo 13 m²') + , (76, 'wifi', 'WiFi') + , (76, 'hvac', 'Climatització') + , (76, 'ecofriendly', 'Eco-sostenible') + , (76, 'nopet', 'Gossos NO') +; + +insert into campsite_type_feature_i18n (campsite_type_feature_id, lang_tag, name) +values (82, 'en', 'Max. 5 pax') + , (82, 'es', 'Máx. 5 pers.') + , (83, 'en', 'Cabin 48 m² Porch 13 m²') + , (83, 'es', 'Cabaña 48 m² Porche 13 m²') + , (85, 'en', 'Climate Control') + , (85, 'es', 'Climatización') + , (86, 'en', 'Eco-sustainable') + , (86, 'es', 'Eco-sostenible') + , (87, 'en', 'Dogs NOT allowed') + , (87, 'es', 'Perros NO') + , (88, 'en', 'Max. 5 pax') + , (88, 'es', 'Máx. 5 pers.') + , (89, 'en', 'Cabin 48 m² Porch 13 m²') + , (89, 'es', 'Cabaña 48 m² Porche 13 m²') + , (91, 'en', 'Climate Control') + , (91, 'es', 'Climatización') + , (92, 'en', 'Eco-sustainable') + , (92, 'es', 'Eco-sostenible') + , (93, 'en', 'Dogs NOT allowed') + , (93, 'es', 'Perros NO') + , (94, 'en', 'Max. 5 pax') + , (94, 'es', 'Máx. 5 pers.') + , (95, 'en', 'Cabin 48 m² Porch 13 m²') + , (95, 'es', 'Cabaña 48 m² Porche 13 m²') + , (97, 'en', 'Climate Control') + , (97, 'es', 'Climatización') + , (98, 'en', 'Eco-sustainable') + , (98, 'es', 'Eco-sostenible') + , (99, 'en', 'Dogs NOT allowed') + , (99, 'es', 'Perros NO') + , (100, 'en', 'Max. 5 pax') + , (100, 'es', 'Máx. 5 pers.') + , (101, 'en', 'Cabin 48 m² Porch 13 m²') + , (101, 'es', 'Cabaña 48 m² Porche 13 m²') + , (103, 'en', 'Climate Control') + , (103, 'es', 'Climatización') + , (104, 'en', 'Eco-sustainable') + , (104, 'es', 'Eco-sostenible') + , (105, 'en', 'Dogs NOT allowed') + , (105, 'es', 'Perros NO') + , (106, 'en', 'Max. 5 pax') + , (106, 'es', 'Máx. 5 pers.') + , (107, 'en', 'Cabin 48 m² Porch 13 m²') + , (107, 'es', 'Cabaña 48 m² Porche 13 m²') + , (109, 'en', 'Climate Control') + , (109, 'es', 'Climatización') + , (110, 'en', 'Eco-sustainable') + , (110, 'es', 'Eco-sostenible') + , (111, 'en', 'Dogs NOT allowed') + , (111, 'es', 'Perros NO') +; + alter table campsite alter column campsite_id restart with 82; select add_campsite(72, '2'); select add_campsite(72, '3'); diff --git a/deploy/add_campsite_type_feature.sql b/deploy/add_campsite_type_feature.sql new file mode 100644 index 0000000..fcb3055 --- /dev/null +++ b/deploy/add_campsite_type_feature.sql @@ -0,0 +1,26 @@ +-- Deploy camper:add_campsite_type_feature to pg +-- requires: roles +-- requires: schema_camper +-- requires: campsite_type_feature +-- requires: campsite_type + +begin; + +set search_path to camper, public; + +create or replace function add_campsite_type_feature(type_slug uuid, icon_name text, name text) returns integer as +$$ + insert into campsite_type_feature (campsite_type_id, icon_name, name) + select campsite_type_id, add_campsite_type_feature.icon_name, add_campsite_type_feature.name + from campsite_type + where slug = type_slug + returning campsite_type_feature_id + ; +$$ + language sql +; + +revoke execute on function add_campsite_type_feature(uuid, text, text) from public; +grant execute on function add_campsite_type_feature(uuid, text, text) to admin; + +commit; diff --git a/deploy/available_icons.sql b/deploy/available_icons.sql index f585ac6..3769f87 100644 --- a/deploy/available_icons.sql +++ b/deploy/available_icons.sql @@ -5,15 +5,20 @@ begin; insert into camper.icon (icon_name) -values ('baby') +values ('area') + , ('baby') , ('ball') , ('bicycle') , ('campfire') , ('castle') + , ('ecofriendly') , ('fridge') + , ('hvac') , ('information') , ('kayak') + , ('nopet') , ('outing') + , ('person') , ('pool') , ('puzzle') , ('restaurant') diff --git a/deploy/campsite_type_feature.sql b/deploy/campsite_type_feature.sql new file mode 100644 index 0000000..b7c11f7 --- /dev/null +++ b/deploy/campsite_type_feature.sql @@ -0,0 +1,55 @@ +-- Deploy camper:campsite_type_feature to pg +-- requires: roles +-- requires: schema_camper +-- requires: campsite_type +-- requires: icon +-- requires: user_profile + +begin; + +set search_path to camper, public; + +create table campsite_type_feature ( + campsite_type_feature_id integer generated by default as identity primary key, + campsite_type_id integer not null references campsite_type, + icon_name text not null references icon, + name text not null constraint name_not_empty check(length(trim(name)) > 0) +); + +grant select on campsite_type_feature to guest; +grant select on campsite_type_feature to employee; +grant select, insert, update, delete on campsite_type_feature to admin; + +alter table campsite_type_feature enable row level security; + +create policy guest_ok +on campsite_type_feature +for select +using (true) +; + +create policy insert_to_company +on campsite_type_feature +for insert +with check ( + exists (select 1 from campsite_type join user_profile using (company_id) where campsite_type.campsite_type_id = campsite_type_feature.campsite_type_id) +) +; + +create policy update_company +on campsite_type_feature +for update +using ( + exists (select 1 from campsite_type join user_profile using (company_id) where campsite_type.campsite_type_id = campsite_type_feature.campsite_type_id) +) +; + +create policy delete_from_company +on campsite_type_feature +for delete +using ( + exists (select 1 from campsite_type join user_profile using (company_id) where campsite_type.campsite_type_id = campsite_type_feature.campsite_type_id) +) +; + +commit; diff --git a/deploy/campsite_type_feature_i18n.sql b/deploy/campsite_type_feature_i18n.sql new file mode 100644 index 0000000..f7ac232 --- /dev/null +++ b/deploy/campsite_type_feature_i18n.sql @@ -0,0 +1,22 @@ +-- Deploy camper:campsite_type_feature_i18n to pg +-- requires: roles +-- requires: schema_camper +-- requires: campsite_type_feature +-- requires: language + +begin; + +set search_path to camper, public; + +create table campsite_type_feature_i18n ( + campsite_type_feature_id integer not null references campsite_type_feature, + lang_tag text not null references language, + name text not null, + primary key (campsite_type_feature_id, lang_tag) +); + +grant select on table campsite_type_feature_i18n to guest; +grant select on table campsite_type_feature_i18n to employee; +grant select, insert, update, delete on table campsite_type_feature_i18n to admin; + +commit; diff --git a/deploy/edit_campsite_type_feature.sql b/deploy/edit_campsite_type_feature.sql new file mode 100644 index 0000000..038e0a7 --- /dev/null +++ b/deploy/edit_campsite_type_feature.sql @@ -0,0 +1,25 @@ +-- Deploy camper:edit_campsite_type_feature to pg +-- requires: roles +-- requires: schema_camper +-- requires: campsite_type_feature + +begin; + +set search_path to camper, public; + +create or replace function edit_campsite_type_feature(feature_id integer, icon_name text, name text) returns integer as +$$ + update campsite_type_feature + set icon_name = edit_campsite_type_feature.icon_name + , name = edit_campsite_type_feature.name + where campsite_type_feature_id = feature_id + returning campsite_type_feature_id + ; +$$ + language sql +; + +revoke execute on function edit_campsite_type_feature(integer, text, text) from public; +grant execute on function edit_campsite_type_feature(integer, text, text) to admin; + +commit; diff --git a/deploy/translate_campsite_type_feature.sql b/deploy/translate_campsite_type_feature.sql new file mode 100644 index 0000000..6246081 --- /dev/null +++ b/deploy/translate_campsite_type_feature.sql @@ -0,0 +1,24 @@ +-- Deploy camper:translate_campsite_type_feature to pg +-- requires: roles +-- requires: schema_camper +-- requires: campsite_type_feature_i18n + +begin; + +set search_path to camper, public; + +create or replace function translate_campsite_type_feature(feature_id integer, lang_tag text, name text) returns void as +$$ + insert into campsite_type_feature_i18n (campsite_type_feature_id, lang_tag, name) + values (feature_id, lang_tag, name) + on conflict (campsite_type_feature_id, lang_tag) do update + set name = excluded.name + ; +$$ + language sql +; + +revoke execute on function translate_campsite_type_feature(integer, text, text) from public; +grant execute on function translate_campsite_type_feature(integer, text, text) to admin; + +commit; diff --git a/pkg/campsite/types/admin.go b/pkg/campsite/types/admin.go index d550e2a..5f98db2 100644 --- a/pkg/campsite/types/admin.go +++ b/pkg/campsite/types/admin.go @@ -91,6 +91,8 @@ func (h *AdminHandler) typeHandler(user *auth.User, company *auth.Company, conn default: httplib.MethodNotAllowed(w, r, http.MethodGet, http.MethodPut) } + case "features": + h.featuresHandler(user, company, conn, f.Slug).ServeHTTP(w, r) case "options": h.optionsHandler(user, company, conn, f.Slug).ServeHTTP(w, r) case "slides": diff --git a/pkg/campsite/types/feature.go b/pkg/campsite/types/feature.go new file mode 100644 index 0000000..2818e83 --- /dev/null +++ b/pkg/campsite/types/feature.go @@ -0,0 +1,251 @@ +/* + * SPDX-FileCopyrightText: 2023 jordi fita mas + * SPDX-License-Identifier: AGPL-3.0-only + */ + +package types + +import ( + "context" + "net/http" + "strconv" + + "github.com/jackc/pgx/v4" + + "dev.tandem.ws/tandem/camper/pkg/auth" + "dev.tandem.ws/tandem/camper/pkg/database" + "dev.tandem.ws/tandem/camper/pkg/form" + httplib "dev.tandem.ws/tandem/camper/pkg/http" + "dev.tandem.ws/tandem/camper/pkg/locale" + "dev.tandem.ws/tandem/camper/pkg/template" +) + +func (h *AdminHandler) featuresHandler(user *auth.User, company *auth.Company, conn *database.Conn, typeSlug string) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + var head string + head, r.URL.Path = httplib.ShiftPath(r.URL.Path) + + switch head { + case "": + switch r.Method { + case http.MethodGet: + serveFeatureIndex(w, r, user, company, conn, typeSlug) + case http.MethodPost: + addFeature(w, r, user, company, conn, typeSlug) + default: + httplib.MethodNotAllowed(w, r, http.MethodGet, http.MethodPost) + } + case "new": + switch r.Method { + case http.MethodGet: + f := newFeatureForm(r.Context(), conn, typeSlug) + f.MustRender(w, r, user, company) + default: + httplib.MethodNotAllowed(w, r, http.MethodGet) + } + default: + id, err := strconv.Atoi(head) + if err != nil { + http.NotFound(w, r) + return + } + f := newFeatureForm(r.Context(), conn, typeSlug) + if err := f.FillFromDatabase(r.Context(), conn, id); err != nil { + if database.ErrorIsNotFound(err) { + http.NotFound(w, r) + return + } + panic(err) + } + h.featureHandler(user, company, conn, f).ServeHTTP(w, r) + } + }) +} + +func (h *AdminHandler) featureHandler(user *auth.User, company *auth.Company, conn *database.Conn, f *featureForm) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + var head string + head, r.URL.Path = httplib.ShiftPath(r.URL.Path) + + switch head { + case "": + switch r.Method { + case http.MethodGet: + f.MustRender(w, r, user, company) + case http.MethodPut: + editFeature(w, r, user, company, conn, f) + default: + httplib.MethodNotAllowed(w, r, http.MethodGet, http.MethodPut) + } + default: + loc, ok := h.locales.Get(head) + if !ok { + http.NotFound(w, r) + return + } + l10n := newFeatureL10nForm(f, loc) + if err := l10n.FillFromDatabase(r.Context(), conn); err != nil { + panic(err) + } + switch r.Method { + case http.MethodGet: + l10n.MustRender(w, r, user, company) + case http.MethodPut: + editFeatureL10n(w, r, user, company, conn, l10n) + default: + httplib.MethodNotAllowed(w, r, http.MethodGet, http.MethodPut) + } + } + }) +} + +func serveFeatureIndex(w http.ResponseWriter, r *http.Request, user *auth.User, company *auth.Company, conn *database.Conn, typeSlug string) { + features, err := collectFeatureEntries(r.Context(), conn, typeSlug) + if err != nil { + panic(err) + } + page := &featureIndex{ + TypeSlug: typeSlug, + Features: features, + } + page.MustRender(w, r, user, company) +} + +func collectFeatureEntries(ctx context.Context, conn *database.Conn, typeSlug string) ([]*featureEntry, error) { + rows, err := conn.Query(ctx, ` + select '/admin/campsites/types/' || campsite_type.slug || '/features/' || campsite_type_feature_id + , feature.icon_name + , feature.name + , array_agg((lang_tag, endonym, not exists (select 1 from campsite_type_feature_i18n as i18n where i18n.campsite_type_feature_id = feature.campsite_type_feature_id and i18n.lang_tag = language.lang_tag)) order by endonym) + from campsite_type_feature as feature + join campsite_type using (campsite_type_id) + join company using (company_id) + , language + where lang_tag <> default_lang_tag + and language.selectable + and campsite_type.slug = $1 + group by campsite_type_feature_id + , campsite_type.slug + , feature.name + order by name + `, pgx.QueryResultFormats{pgx.BinaryFormatCode}, typeSlug) + if err != nil { + return nil, err + } + defer rows.Close() + + var features []*featureEntry + for rows.Next() { + feature := &featureEntry{} + var translations database.RecordArray + if err = rows.Scan(&feature.URL, &feature.Icon, &feature.Name, &translations); err != nil { + return nil, err + } + for _, el := range translations.Elements { + feature.Translations = append(feature.Translations, &locale.Translation{ + URL: feature.URL + "/" + el.Fields[0].Get().(string), + Endonym: el.Fields[1].Get().(string), + Missing: el.Fields[2].Get().(bool), + }) + } + features = append(features, feature) + } + + return features, nil +} + +type featureEntry struct { + URL string + Icon string + Name string + Translations []*locale.Translation +} + +type featureIndex struct { + TypeSlug string + Features []*featureEntry +} + +func (page *featureIndex) MustRender(w http.ResponseWriter, r *http.Request, user *auth.User, company *auth.Company) { + template.MustRenderAdmin(w, r, user, company, "campsite/feature/index.gohtml", page) +} + +func addFeature(w http.ResponseWriter, r *http.Request, user *auth.User, company *auth.Company, conn *database.Conn, typeSlug string) { + f := newFeatureForm(r.Context(), conn, typeSlug) + processFeatureForm(w, r, user, company, f, func(ctx context.Context) (int, error) { + return conn.AddCampsiteTypeFeature(ctx, typeSlug, f.Icon.String(), f.Name.Val) + }) +} + +func editFeature(w http.ResponseWriter, r *http.Request, user *auth.User, company *auth.Company, conn *database.Conn, f *featureForm) { + processFeatureForm(w, r, user, company, f, func(ctx context.Context) (int, error) { + return conn.EditCampsiteTypeFeature(ctx, f.ID, f.Icon.String(), f.Name.Val) + }) +} + +func processFeatureForm(w http.ResponseWriter, r *http.Request, user *auth.User, company *auth.Company, f *featureForm, act func(ctx context.Context) (int, error)) { + if ok, err := form.Handle(f, w, r, user); err != nil { + return + } else if !ok { + f.MustRender(w, r, user, company) + return + } + + if _, err := act(r.Context()); err != nil { + panic(err) + } + httplib.Redirect(w, r, "/admin/campsites/types/"+f.TypeSlug+"/features", http.StatusSeeOther) +} + +type featureForm struct { + ID int + TypeSlug string + Icon *form.Select + Name *form.Input +} + +func newFeatureForm(ctx context.Context, conn *database.Conn, typeSlug string) *featureForm { + return &featureForm{ + TypeSlug: typeSlug, + Icon: &form.Select{ + Name: "icon", + Options: form.MustGetOptions(ctx, conn, "select icon_name, icon_name from icon order by 1"), + }, + Name: &form.Input{ + Name: "name", + }, + } +} + +func (f *featureForm) FillFromDatabase(ctx context.Context, conn *database.Conn, id int) error { + f.ID = id + row := conn.QueryRow(ctx, ` + select array[icon_name] + , name + from campsite_type_feature + where campsite_type_feature_id = $1 + `, id) + return row.Scan(&f.Icon.Selected, &f.Name.Val) +} + +func (f *featureForm) Parse(r *http.Request) error { + if err := r.ParseForm(); err != nil { + return err + } + f.Icon.FillValue(r) + f.Name.FillValue(r) + return nil +} + +func (f *featureForm) Valid(l *locale.Locale) bool { + v := form.NewValidator(l) + v.CheckSelectedOptions(f.Icon, l.GettextNoop("Selected icon is not valid.")) + if v.CheckRequired(f.Name, l.GettextNoop("Name can not be empty.")) { + v.CheckMinLength(f.Name, 1, l.GettextNoop("Name must have at least one letter.")) + } + return v.AllOK +} + +func (f *featureForm) MustRender(w http.ResponseWriter, r *http.Request, user *auth.User, company *auth.Company) { + template.MustRenderAdmin(w, r, user, company, "campsite/feature/form.gohtml", f) +} diff --git a/pkg/campsite/types/l10n.go b/pkg/campsite/types/l10n.go index 023262f..285cff5 100644 --- a/pkg/campsite/types/l10n.go +++ b/pkg/campsite/types/l10n.go @@ -211,3 +211,60 @@ func (l10n *slideL10nForm) Valid(l *locale.Locale) bool { v := form.NewValidator(l) return v.AllOK } + +type featureL10nForm struct { + Locale *locale.Locale + TypeSlug string + ID int + Name *form.L10nInput +} + +func newFeatureL10nForm(f *featureForm, loc *locale.Locale) *featureL10nForm { + return &featureL10nForm{ + Locale: loc, + TypeSlug: f.TypeSlug, + ID: f.ID, + Name: f.Name.L10nInput(), + } +} + +func (l10n *featureL10nForm) FillFromDatabase(ctx context.Context, conn *database.Conn) error { + row := conn.QueryRow(ctx, ` + select coalesce(i18n.name, '') as l10n_name + from campsite_type_feature + left join campsite_type_feature_i18n as i18n on campsite_type_feature.campsite_type_feature_id = i18n.campsite_type_feature_id and i18n.lang_tag = $1 + where campsite_type_feature.campsite_type_feature_id = $2 + `, l10n.Locale.Language, l10n.ID) + return row.Scan(&l10n.Name.Val) +} + +func (l10n *featureL10nForm) MustRender(w http.ResponseWriter, r *http.Request, user *auth.User, company *auth.Company) { + template.MustRenderAdmin(w, r, user, company, "campsite/feature/l10n.gohtml", l10n) +} + +func editFeatureL10n(w http.ResponseWriter, r *http.Request, user *auth.User, company *auth.Company, conn *database.Conn, l10n *featureL10nForm) { + if ok, err := form.Handle(l10n, w, r, user); err != nil { + return + } else if !ok { + l10n.MustRender(w, r, user, company) + return + } + if err := conn.TranslateCampsiteTypeFeature(r.Context(), l10n.ID, l10n.Locale.Language, l10n.Name.Val); err != nil { + panic(err) + } + httplib.Redirect(w, r, "/admin/campsites/types/"+l10n.TypeSlug+"/features", http.StatusSeeOther) +} + +func (l10n *featureL10nForm) Parse(r *http.Request) error { + if err := r.ParseForm(); err != nil { + return err + } + l10n.Name.FillValue(r) + return nil +} + +func (l10n *featureL10nForm) Valid(l *locale.Locale) bool { + v := form.NewValidator(l) + v.CheckRequired(&l10n.Name.Input, l.GettextNoop("Name can not be empty.")) + return v.AllOK +} diff --git a/pkg/campsite/types/public.go b/pkg/campsite/types/public.go index b875f99..a16cb33 100644 --- a/pkg/campsite/types/public.go +++ b/pkg/campsite/types/public.go @@ -11,6 +11,7 @@ import ( "net/http" "github.com/jackc/pgx/v4" + "golang.org/x/text/language" "dev.tandem.ws/tandem/camper/pkg/auth" "dev.tandem.ws/tandem/camper/pkg/carousel" @@ -54,6 +55,7 @@ type publicPage struct { Name string Carousel []*carousel.Slide Prices []*typePrice + Features []*typeFeature Spiel gotemplate.HTML Info gotemplate.HTML Facilities gotemplate.HTML @@ -73,10 +75,25 @@ type optionPrice struct { PricePerNight string } +type typeFeature struct { + Icon string + Name string +} + func newPublicPage(ctx context.Context, conn *database.Conn, loc *locale.Locale, slug string) (*publicPage, error) { + prices, err := collectPrices(ctx, conn, loc.Language, slug) + if err != nil { + return nil, err + } + features, err := collectFeatures(ctx, conn, loc.Language, slug) + if err != nil { + return nil, err + } page := &publicPage{ PublicPage: template.NewPublicPage(), Carousel: mustCollectSlides(ctx, conn, loc, slug), + Prices: prices, + Features: features, } row := conn.QueryRow(ctx, ` select coalesce(i18n.name, campsite_type.name) as l10n_name @@ -93,6 +110,10 @@ func newPublicPage(ctx context.Context, conn *database.Conn, loc *locale.Locale, return nil, err } + return page, nil +} + +func collectPrices(ctx context.Context, conn *database.Conn, language language.Tag, slug string) ([]*typePrice, error) { rows, err := conn.Query(ctx, ` select coalesce(i18n.name, season.name) as l10n_name , to_color(season.color)::text @@ -117,11 +138,12 @@ func newPublicPage(ctx context.Context, conn *database.Conn, loc *locale.Locale, , season.color , cost.min_nights , cost.cost_per_night - `, pgx.QueryResultFormats{pgx.BinaryFormatCode}, loc.Language, slug) + `, pgx.QueryResultFormats{pgx.BinaryFormatCode}, language, slug) if err != nil { return nil, err } + var prices []*typePrice for rows.Next() { price := &typePrice{} var options database.RecordArray @@ -134,10 +156,33 @@ func newPublicPage(ctx context.Context, conn *database.Conn, loc *locale.Locale, PricePerNight: el.Fields[1].Get().(string), }) } - page.Prices = append(page.Prices, price) + prices = append(prices, price) + } + return prices, nil +} + +func collectFeatures(ctx context.Context, conn *database.Conn, language language.Tag, slug string) ([]*typeFeature, error) { + rows, err := conn.Query(ctx, ` + select feature.icon_name + , coalesce(i18n.name, feature.name) as l10n_name + from campsite_type_feature as feature + join campsite_type using (campsite_type_id) + left join campsite_type_feature_i18n as i18n on feature.campsite_type_feature_id = i18n.campsite_type_feature_id and i18n.lang_tag = $1 + where campsite_type.slug = $2 + `, language, slug) + if err != nil { + return nil, err } - return page, nil + var features []*typeFeature + for rows.Next() { + feature := &typeFeature{} + if err := rows.Scan(&feature.Icon, &feature.Name); err != nil { + return nil, err + } + features = append(features, feature) + } + return features, nil } func (p *publicPage) MustRender(w http.ResponseWriter, r *http.Request, user *auth.User, company *auth.Company, conn *database.Conn) { diff --git a/pkg/database/db.go b/pkg/database/db.go index 3a91028..5eec52d 100644 --- a/pkg/database/db.go +++ b/pkg/database/db.go @@ -103,6 +103,12 @@ func (c *Conn) GetBool(ctx context.Context, sql string, args ...interface{}) (bo return result, nil } +func (c *Conn) GetInt(ctx context.Context, sql string, args ...interface{}) (int, error) { + var result int + err := c.QueryRow(ctx, sql, args...).Scan(&result) + return result, err +} + func (c *Conn) GetBytes(ctx context.Context, sql string, args ...interface{}) ([]byte, error) { var result []byte err := c.QueryRow(ctx, sql, args...).Scan(&result) diff --git a/pkg/database/funcs.go b/pkg/database/funcs.go index afb8580..44e76ac 100644 --- a/pkg/database/funcs.go +++ b/pkg/database/funcs.go @@ -45,3 +45,16 @@ func (c *Conn) TranslateCampsiteTypeOption(ctx context.Context, id int, langTag _, err := c.Exec(ctx, "select translate_campsite_type_option($1, $2, $3)", id, langTag, name) return err } + +func (c *Conn) AddCampsiteTypeFeature(ctx context.Context, typeSlug string, iconName string, name string) (int, error) { + return c.GetInt(ctx, "select add_campsite_type_feature($1, $2, $3)", typeSlug, iconName, name) +} + +func (c *Conn) EditCampsiteTypeFeature(ctx context.Context, id int, iconName string, name string) (int, error) { + return c.GetInt(ctx, "select edit_campsite_type_feature($1, $2, $3)", id, iconName, name) +} + +func (c *Conn) TranslateCampsiteTypeFeature(ctx context.Context, id int, langTag language.Tag, name string) error { + _, err := c.Exec(ctx, "select translate_campsite_type_feature($1, $2, $3)", id, langTag, name) + return err +} diff --git a/po/ca.po b/po/ca.po index 04f2b00..11af1cf 100644 --- a/po/ca.po +++ b/po/ca.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: camper\n" "Report-Msgid-Bugs-To: jordi@tandem.blog\n" -"POT-Creation-Date: 2023-10-13 18:05+0200\n" +"POT-Creation-Date: 2023-10-13 20:28+0200\n" "PO-Revision-Date: 2023-07-22 23:45+0200\n" "Last-Translator: jordi fita mas \n" "Language-Team: Catalan \n" @@ -84,29 +84,34 @@ msgctxt "title" msgid "Prices" msgstr "Preus" -#: web/templates/public/campsite/type.gohtml:47 +#: web/templates/public/campsite/type.gohtml:48 msgid "%s €/night" msgstr "%s €/nit" -#: web/templates/public/campsite/type.gohtml:49 +#: web/templates/public/campsite/type.gohtml:50 msgid "%s: %s €/night" msgstr "%s: %s €/nit" -#: web/templates/public/campsite/type.gohtml:52 +#: web/templates/public/campsite/type.gohtml:53 msgid "*Minimum %d nights per stay" msgstr "*Mínim %d nits per estada" -#: web/templates/public/campsite/type.gohtml:61 +#: web/templates/public/campsite/type.gohtml:62 +msgctxt "title" +msgid "Features" +msgstr "Característiques" + +#: web/templates/public/campsite/type.gohtml:73 msgctxt "title" msgid "Info" msgstr "Informació" -#: web/templates/public/campsite/type.gohtml:65 +#: web/templates/public/campsite/type.gohtml:77 msgctxt "title" msgid "Facilities" msgstr "Equipaments" -#: web/templates/public/campsite/type.gohtml:69 +#: web/templates/public/campsite/type.gohtml:81 msgctxt "title" msgid "Description" msgstr "Descripció" @@ -225,6 +230,7 @@ msgid "Caption" msgstr "Llegenda" #: web/templates/admin/carousel/form.gohtml:47 +#: web/templates/admin/campsite/feature/form.gohtml:62 #: web/templates/admin/campsite/carousel/form.gohtml:47 #: web/templates/admin/campsite/form.gohtml:70 #: web/templates/admin/campsite/option/form.gohtml:78 @@ -237,6 +243,7 @@ msgid "Update" msgstr "Actualitza" #: web/templates/admin/carousel/form.gohtml:49 +#: web/templates/admin/campsite/feature/form.gohtml:64 #: web/templates/admin/campsite/carousel/form.gohtml:49 #: web/templates/admin/campsite/form.gohtml:72 #: web/templates/admin/campsite/option/form.gohtml:80 @@ -254,6 +261,7 @@ msgid "Translate Carousel Slide to %s" msgstr "Traducció de la diapositiva del carrusel a %s" #: web/templates/admin/carousel/l10n.gohtml:21 +#: web/templates/admin/campsite/feature/l10n.gohtml:21 #: web/templates/admin/campsite/carousel/l10n.gohtml:21 #: web/templates/admin/campsite/option/l10n.gohtml:21 #: web/templates/admin/campsite/type/l10n.gohtml:21 @@ -268,6 +276,7 @@ msgid "Source:" msgstr "Origen:" #: web/templates/admin/carousel/l10n.gohtml:23 +#: web/templates/admin/campsite/feature/l10n.gohtml:23 #: web/templates/admin/campsite/carousel/l10n.gohtml:23 #: web/templates/admin/campsite/option/l10n.gohtml:23 #: web/templates/admin/campsite/type/l10n.gohtml:23 @@ -283,6 +292,7 @@ msgid "Translation:" msgstr "Traducció:" #: web/templates/admin/carousel/l10n.gohtml:32 +#: web/templates/admin/campsite/feature/l10n.gohtml:32 #: web/templates/admin/campsite/carousel/l10n.gohtml:32 #: web/templates/admin/campsite/option/l10n.gohtml:32 #: web/templates/admin/campsite/type/l10n.gohtml:84 @@ -292,6 +302,80 @@ msgctxt "action" msgid "Translate" msgstr "Tradueix" +#: web/templates/admin/campsite/feature/form.gohtml:8 +#: web/templates/admin/campsite/feature/form.gohtml:25 +msgctxt "title" +msgid "Edit Campsite Type Feature" +msgstr "Edició de les característiques del tipus d’allotjament" + +#: web/templates/admin/campsite/feature/form.gohtml:10 +#: web/templates/admin/campsite/feature/form.gohtml:27 +msgctxt "title" +msgid "New Campsite Type Feature" +msgstr "Nova característica de tipus d’allotjament" + +#: web/templates/admin/campsite/feature/form.gohtml:34 +#: web/templates/admin/services/form.gohtml:34 +msgctxt "input" +msgid "Icon" +msgstr "Icona" + +#: web/templates/admin/campsite/feature/form.gohtml:52 +#: web/templates/admin/campsite/feature/l10n.gohtml:20 +#: web/templates/admin/campsite/option/form.gohtml:34 +#: web/templates/admin/campsite/option/l10n.gohtml:20 +#: web/templates/admin/campsite/type/form.gohtml:46 +#: web/templates/admin/campsite/type/l10n.gohtml:20 +#: web/templates/admin/season/form.gohtml:46 +#: web/templates/admin/season/l10n.gohtml:20 +#: web/templates/admin/services/form.gohtml:52 +#: web/templates/admin/services/l10n.gohtml:20 +#: web/templates/admin/profile.gohtml:26 +msgctxt "input" +msgid "Name" +msgstr "Nom" + +#: web/templates/admin/campsite/feature/index.gohtml:6 +#: web/templates/admin/campsite/feature/index.gohtml:12 +msgctxt "title" +msgid "Campsite Type Features" +msgstr "Característiques del tipus d’allotjaments" + +#: web/templates/admin/campsite/feature/index.gohtml:11 +msgctxt "action" +msgid "Add Feature" +msgstr "Afegeix característica" + +#: web/templates/admin/campsite/feature/index.gohtml:17 +#: web/templates/admin/campsite/option/index.gohtml:17 +#: web/templates/admin/campsite/type/index.gohtml:17 +#: web/templates/admin/season/index.gohtml:18 +msgctxt "header" +msgid "Name" +msgstr "Nom" + +#: web/templates/admin/campsite/feature/index.gohtml:18 +#: web/templates/admin/campsite/carousel/index.gohtml:19 +#: web/templates/admin/campsite/option/index.gohtml:18 +#: web/templates/admin/campsite/type/index.gohtml:18 +#: web/templates/admin/season/index.gohtml:19 +#: web/templates/admin/services/index.gohtml:19 +#: web/templates/admin/services/index.gohtml:60 +#: web/templates/admin/home/index.gohtml:19 +msgctxt "header" +msgid "Translations" +msgstr "Traduccions" + +#: web/templates/admin/campsite/feature/index.gohtml:39 +msgid "No campsite type features added yet." +msgstr "No s’ha afegit cap característica al tipus d’allotjament encara." + +#: web/templates/admin/campsite/feature/l10n.gohtml:7 +#: web/templates/admin/campsite/feature/l10n.gohtml:14 +msgctxt "title" +msgid "Translate Campsite Type Feature to %s" +msgstr "Traducció de la característica del tipus d’allotjament a %s" + #: web/templates/admin/campsite/carousel/form.gohtml:8 #: web/templates/admin/campsite/carousel/form.gohtml:25 msgctxt "title" @@ -331,17 +415,6 @@ msgctxt "header" msgid "Caption" msgstr "Llegenda" -#: web/templates/admin/campsite/carousel/index.gohtml:19 -#: web/templates/admin/campsite/option/index.gohtml:18 -#: web/templates/admin/campsite/type/index.gohtml:18 -#: web/templates/admin/season/index.gohtml:19 -#: web/templates/admin/services/index.gohtml:19 -#: web/templates/admin/services/index.gohtml:60 -#: web/templates/admin/home/index.gohtml:19 -msgctxt "header" -msgid "Translations" -msgstr "Traduccions" - #: web/templates/admin/campsite/carousel/index.gohtml:20 #: web/templates/admin/services/index.gohtml:20 #: web/templates/admin/services/index.gohtml:61 @@ -420,19 +493,6 @@ msgctxt "title" msgid "New Campsite Type Option" msgstr "Nova opció del tipus d’allotjament" -#: web/templates/admin/campsite/option/form.gohtml:34 -#: web/templates/admin/campsite/option/l10n.gohtml:20 -#: web/templates/admin/campsite/type/form.gohtml:46 -#: web/templates/admin/campsite/type/l10n.gohtml:20 -#: web/templates/admin/season/form.gohtml:46 -#: web/templates/admin/season/l10n.gohtml:20 -#: web/templates/admin/services/form.gohtml:52 -#: web/templates/admin/services/l10n.gohtml:20 -#: web/templates/admin/profile.gohtml:26 -msgctxt "input" -msgid "Name" -msgstr "Nom" - #: web/templates/admin/campsite/option/form.gohtml:42 msgctxt "input" msgid "Minimum" @@ -460,13 +520,6 @@ msgctxt "action" msgid "Add Option" msgstr "Afegeix opció" -#: web/templates/admin/campsite/option/index.gohtml:17 -#: web/templates/admin/campsite/type/index.gohtml:17 -#: web/templates/admin/season/index.gohtml:18 -msgctxt "header" -msgid "Name" -msgstr "Nom" - #: web/templates/admin/campsite/option/index.gohtml:39 msgid "No campsite type options added yet." msgstr "No s’ha afegit cap opció al tipus d’allotjament encara." @@ -493,13 +546,13 @@ msgid "Type" msgstr "Tipus" #: web/templates/admin/campsite/index.gohtml:28 -#: web/templates/admin/campsite/type/index.gohtml:39 +#: web/templates/admin/campsite/type/index.gohtml:47 #: web/templates/admin/season/index.gohtml:39 msgid "Yes" msgstr "Sí" #: web/templates/admin/campsite/index.gohtml:28 -#: web/templates/admin/campsite/type/index.gohtml:39 +#: web/templates/admin/campsite/type/index.gohtml:47 #: web/templates/admin/season/index.gohtml:39 msgid "No" msgstr "No" @@ -521,7 +574,7 @@ msgid "New Campsite Type" msgstr "Nou tipus d’allotjament" #: web/templates/admin/campsite/type/form.gohtml:37 -#: web/templates/admin/campsite/type/index.gohtml:21 +#: web/templates/admin/campsite/type/index.gohtml:22 msgctxt "campsite type" msgid "Active" msgstr "Actiu" @@ -581,25 +634,35 @@ msgstr "Afegeix tipus" #: web/templates/admin/campsite/type/index.gohtml:19 msgctxt "header" +msgid "Features" +msgstr "Característiques" + +#: web/templates/admin/campsite/type/index.gohtml:20 +msgctxt "header" msgid "Options" msgstr "Opcions" -#: web/templates/admin/campsite/type/index.gohtml:20 +#: web/templates/admin/campsite/type/index.gohtml:21 msgctxt "header" msgid "Carousel" msgstr "Carrusel" -#: web/templates/admin/campsite/type/index.gohtml:37 +#: web/templates/admin/campsite/type/index.gohtml:39 +msgctxt "action" +msgid "Edit Features" +msgstr "Edita les característiques" + +#: web/templates/admin/campsite/type/index.gohtml:42 msgctxt "action" msgid "Edit Options" msgstr "Edita les opcions" -#: web/templates/admin/campsite/type/index.gohtml:38 +#: web/templates/admin/campsite/type/index.gohtml:45 msgctxt "action" msgid "Edit Carousel" msgstr "Edita el carrusel" -#: web/templates/admin/campsite/type/index.gohtml:45 +#: web/templates/admin/campsite/type/index.gohtml:53 msgid "No campsite types added yet." msgstr "No s’ha afegit cap tipus d’allotjament encara." @@ -744,11 +807,6 @@ msgctxt "title" msgid "New Service" msgstr "Nou servei" -#: web/templates/admin/services/form.gohtml:34 -msgctxt "input" -msgid "Icon" -msgstr "Icona" - #: web/templates/admin/services/index.gohtml:6 #: web/templates/admin/layout.gohtml:52 msgctxt "title" @@ -1021,8 +1079,9 @@ msgid "Automatic" msgstr "Automàtic" #: pkg/app/user.go:249 pkg/campsite/types/l10n.go:87 -#: pkg/campsite/types/l10n.go:142 pkg/campsite/types/option.go:336 -#: pkg/campsite/types/admin.go:413 pkg/season/l10n.go:69 +#: pkg/campsite/types/l10n.go:144 pkg/campsite/types/l10n.go:268 +#: pkg/campsite/types/option.go:340 pkg/campsite/types/feature.go:243 +#: pkg/campsite/types/admin.go:415 pkg/season/l10n.go:69 #: pkg/season/admin.go:382 pkg/services/l10n.go:73 pkg/services/admin.go:266 msgid "Name can not be empty." msgstr "No podeu deixar el nom en blanc." @@ -1043,85 +1102,90 @@ msgstr "El fitxer has de ser una imatge PNG o JPEG vàlida." msgid "Access forbidden" msgstr "Accés prohibit" -#: pkg/campsite/types/option.go:337 pkg/campsite/types/admin.go:414 +#: pkg/campsite/types/option.go:341 pkg/campsite/types/feature.go:244 +#: pkg/campsite/types/admin.go:416 msgid "Name must have at least one letter." msgstr "El nom ha de tenir com a mínim una lletra." -#: pkg/campsite/types/option.go:340 +#: pkg/campsite/types/option.go:344 msgid "Minimum can not be empty." msgstr "No podeu deixar el mínim en blanc." -#: pkg/campsite/types/option.go:341 +#: pkg/campsite/types/option.go:345 msgid "Minimum must be an integer number." msgstr "El valor del mínim ha de ser un número enter." -#: pkg/campsite/types/option.go:343 +#: pkg/campsite/types/option.go:347 msgid "Minimum must be zero or greater." msgstr "El valor del mínim ha de ser com a mínim zero." -#: pkg/campsite/types/option.go:346 +#: pkg/campsite/types/option.go:350 msgid "Maximum can not be empty." msgstr "No podeu deixar el màxim en blanc." -#: pkg/campsite/types/option.go:347 +#: pkg/campsite/types/option.go:351 msgid "Maximum must be an integer number." msgstr "El valor del màxim ha de ser un número enter." -#: pkg/campsite/types/option.go:349 +#: pkg/campsite/types/option.go:353 msgid "Maximum must be equal or greater than minimum." msgstr "El valor del màxim ha de ser igual o superir al del mínim." -#: pkg/campsite/types/option.go:353 pkg/campsite/types/admin.go:427 +#: pkg/campsite/types/option.go:357 pkg/campsite/types/admin.go:429 msgid "Price per night can not be empty." msgstr "No podeu deixar el preu per nit en blanc." -#: pkg/campsite/types/option.go:354 pkg/campsite/types/admin.go:428 +#: pkg/campsite/types/option.go:358 pkg/campsite/types/admin.go:430 msgid "Price per night must be a decimal number." msgstr "El preu per nit ha de ser un número decimal." -#: pkg/campsite/types/option.go:355 pkg/campsite/types/admin.go:429 +#: pkg/campsite/types/option.go:359 pkg/campsite/types/admin.go:431 msgid "Price per night must be zero or greater." msgstr "El preu per nit ha de ser com a mínim zero." -#: pkg/campsite/types/admin.go:289 +#: pkg/campsite/types/feature.go:242 pkg/services/admin.go:265 +msgid "Selected icon is not valid." +msgstr "La icona escollida no és vàlida." + +#: pkg/campsite/types/admin.go:291 msgctxt "input" msgid "Cover image" msgstr "Imatge de portada" -#: pkg/campsite/types/admin.go:290 +#: pkg/campsite/types/admin.go:292 msgctxt "action" msgid "Set campsite type cover" msgstr "Estableix la portada del tipus d’allotjament" -#: pkg/campsite/types/admin.go:416 +#: pkg/campsite/types/admin.go:418 msgid "Cover image can not be empty." msgstr "No podeu deixar la imatge de portada en blanc." -#: pkg/campsite/types/admin.go:417 +#: pkg/campsite/types/admin.go:419 msgid "Cover image must be an image media type." msgstr "La imatge de portada ha de ser un mèdia de tipus imatge." -#: pkg/campsite/types/admin.go:421 +#: pkg/campsite/types/admin.go:423 msgid "Maximum number of campers can not be empty." msgstr "No podeu deixar el número màxim de persones en blanc." -#: pkg/campsite/types/admin.go:422 +#: pkg/campsite/types/admin.go:424 msgid "Maximum number of campers must be an integer number." msgstr "El número màxim de persones ha de ser enter." -#: pkg/campsite/types/admin.go:423 +#: pkg/campsite/types/admin.go:425 msgid "Maximum number of campers must be one or greater." msgstr "El número màxim de persones no pot ser zero." -#: pkg/campsite/types/admin.go:432 +#: pkg/campsite/types/admin.go:434 msgid "Minimum number of nights can not be empty." msgstr "No podeu deixar el número mínim de nits en blanc." -#: pkg/campsite/types/admin.go:433 +#: pkg/campsite/types/admin.go:435 msgid "Minimum number of nights must be an integer." msgstr "El número mínim de nits ha de ser enter." -#: pkg/campsite/types/admin.go:434 +#: pkg/campsite/types/admin.go:436 msgid "Minimum number of nights must be one or greater." msgstr "El número mínim de nits no pot ser zero." @@ -1222,10 +1286,6 @@ msgstr "No podeu deixar la data de fi en blanc." msgid "End date must be a valid date." msgstr "La data de fi ha de ser una data vàlida." -#: pkg/services/admin.go:265 -msgid "Selected icon is not valid." -msgstr "La icona escollida no és vàlida." - #: pkg/company/admin.go:186 msgid "Selected country is not valid." msgstr "El país escollit no és vàlid." @@ -1306,10 +1366,6 @@ msgstr "No podeu deixar el nom del fitxer en blanc." #~ msgid "Pricing" #~ msgstr "Preus" -#~ msgctxt "title" -#~ msgid "Features" -#~ msgstr "Característiques" - #~ msgctxt "input" #~ msgid "Features" #~ msgstr "Característiques" diff --git a/po/es.po b/po/es.po index 46ddd37..25e0646 100644 --- a/po/es.po +++ b/po/es.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: camper\n" "Report-Msgid-Bugs-To: jordi@tandem.blog\n" -"POT-Creation-Date: 2023-10-13 18:05+0200\n" +"POT-Creation-Date: 2023-10-13 20:28+0200\n" "PO-Revision-Date: 2023-07-22 23:46+0200\n" "Last-Translator: jordi fita mas \n" "Language-Team: Spanish \n" @@ -84,29 +84,34 @@ msgctxt "title" msgid "Prices" msgstr "Precios" -#: web/templates/public/campsite/type.gohtml:47 +#: web/templates/public/campsite/type.gohtml:48 msgid "%s €/night" msgstr "%s €/noche" -#: web/templates/public/campsite/type.gohtml:49 +#: web/templates/public/campsite/type.gohtml:50 msgid "%s: %s €/night" -msgstr ":%s: %s €/noche" +msgstr "%s: %s €/noche" -#: web/templates/public/campsite/type.gohtml:52 +#: web/templates/public/campsite/type.gohtml:53 msgid "*Minimum %d nights per stay" msgstr "*Mínimo %d noches por estancia" -#: web/templates/public/campsite/type.gohtml:61 +#: web/templates/public/campsite/type.gohtml:62 +msgctxt "title" +msgid "Features" +msgstr "Características" + +#: web/templates/public/campsite/type.gohtml:73 msgctxt "title" msgid "Info" msgstr "Información" -#: web/templates/public/campsite/type.gohtml:65 +#: web/templates/public/campsite/type.gohtml:77 msgctxt "title" msgid "Facilities" msgstr "Equipamento" -#: web/templates/public/campsite/type.gohtml:69 +#: web/templates/public/campsite/type.gohtml:81 msgctxt "title" msgid "Description" msgstr "Descripción" @@ -225,6 +230,7 @@ msgid "Caption" msgstr "Leyenda" #: web/templates/admin/carousel/form.gohtml:47 +#: web/templates/admin/campsite/feature/form.gohtml:62 #: web/templates/admin/campsite/carousel/form.gohtml:47 #: web/templates/admin/campsite/form.gohtml:70 #: web/templates/admin/campsite/option/form.gohtml:78 @@ -237,6 +243,7 @@ msgid "Update" msgstr "Actualizar" #: web/templates/admin/carousel/form.gohtml:49 +#: web/templates/admin/campsite/feature/form.gohtml:64 #: web/templates/admin/campsite/carousel/form.gohtml:49 #: web/templates/admin/campsite/form.gohtml:72 #: web/templates/admin/campsite/option/form.gohtml:80 @@ -254,6 +261,7 @@ msgid "Translate Carousel Slide to %s" msgstr "Traducción de la diapositiva de carrusel a %s" #: web/templates/admin/carousel/l10n.gohtml:21 +#: web/templates/admin/campsite/feature/l10n.gohtml:21 #: web/templates/admin/campsite/carousel/l10n.gohtml:21 #: web/templates/admin/campsite/option/l10n.gohtml:21 #: web/templates/admin/campsite/type/l10n.gohtml:21 @@ -268,6 +276,7 @@ msgid "Source:" msgstr "Origen:" #: web/templates/admin/carousel/l10n.gohtml:23 +#: web/templates/admin/campsite/feature/l10n.gohtml:23 #: web/templates/admin/campsite/carousel/l10n.gohtml:23 #: web/templates/admin/campsite/option/l10n.gohtml:23 #: web/templates/admin/campsite/type/l10n.gohtml:23 @@ -283,6 +292,7 @@ msgid "Translation:" msgstr "Traducción" #: web/templates/admin/carousel/l10n.gohtml:32 +#: web/templates/admin/campsite/feature/l10n.gohtml:32 #: web/templates/admin/campsite/carousel/l10n.gohtml:32 #: web/templates/admin/campsite/option/l10n.gohtml:32 #: web/templates/admin/campsite/type/l10n.gohtml:84 @@ -292,6 +302,80 @@ msgctxt "action" msgid "Translate" msgstr "Traducir" +#: web/templates/admin/campsite/feature/form.gohtml:8 +#: web/templates/admin/campsite/feature/form.gohtml:25 +msgctxt "title" +msgid "Edit Campsite Type Feature" +msgstr "Edición de las características del tipo de alojamiento" + +#: web/templates/admin/campsite/feature/form.gohtml:10 +#: web/templates/admin/campsite/feature/form.gohtml:27 +msgctxt "title" +msgid "New Campsite Type Feature" +msgstr "Nueva característica del tipo de alojamiento" + +#: web/templates/admin/campsite/feature/form.gohtml:34 +#: web/templates/admin/services/form.gohtml:34 +msgctxt "input" +msgid "Icon" +msgstr "Icono" + +#: web/templates/admin/campsite/feature/form.gohtml:52 +#: web/templates/admin/campsite/feature/l10n.gohtml:20 +#: web/templates/admin/campsite/option/form.gohtml:34 +#: web/templates/admin/campsite/option/l10n.gohtml:20 +#: web/templates/admin/campsite/type/form.gohtml:46 +#: web/templates/admin/campsite/type/l10n.gohtml:20 +#: web/templates/admin/season/form.gohtml:46 +#: web/templates/admin/season/l10n.gohtml:20 +#: web/templates/admin/services/form.gohtml:52 +#: web/templates/admin/services/l10n.gohtml:20 +#: web/templates/admin/profile.gohtml:26 +msgctxt "input" +msgid "Name" +msgstr "Nombre" + +#: web/templates/admin/campsite/feature/index.gohtml:6 +#: web/templates/admin/campsite/feature/index.gohtml:12 +msgctxt "title" +msgid "Campsite Type Features" +msgstr "Características del tipo de alojamiento" + +#: web/templates/admin/campsite/feature/index.gohtml:11 +msgctxt "action" +msgid "Add Feature" +msgstr "Añadir características" + +#: web/templates/admin/campsite/feature/index.gohtml:17 +#: web/templates/admin/campsite/option/index.gohtml:17 +#: web/templates/admin/campsite/type/index.gohtml:17 +#: web/templates/admin/season/index.gohtml:18 +msgctxt "header" +msgid "Name" +msgstr "Nombre" + +#: web/templates/admin/campsite/feature/index.gohtml:18 +#: web/templates/admin/campsite/carousel/index.gohtml:19 +#: web/templates/admin/campsite/option/index.gohtml:18 +#: web/templates/admin/campsite/type/index.gohtml:18 +#: web/templates/admin/season/index.gohtml:19 +#: web/templates/admin/services/index.gohtml:19 +#: web/templates/admin/services/index.gohtml:60 +#: web/templates/admin/home/index.gohtml:19 +msgctxt "header" +msgid "Translations" +msgstr "Traducciones" + +#: web/templates/admin/campsite/feature/index.gohtml:39 +msgid "No campsite type features added yet." +msgstr "No se ha añadido ninguna característica al tipo de alojamiento todavía." + +#: web/templates/admin/campsite/feature/l10n.gohtml:7 +#: web/templates/admin/campsite/feature/l10n.gohtml:14 +msgctxt "title" +msgid "Translate Campsite Type Feature to %s" +msgstr "Traducción de la característica del tipo de alojamiento a %s" + #: web/templates/admin/campsite/carousel/form.gohtml:8 #: web/templates/admin/campsite/carousel/form.gohtml:25 msgctxt "title" @@ -331,17 +415,6 @@ msgctxt "header" msgid "Caption" msgstr "Leyenda" -#: web/templates/admin/campsite/carousel/index.gohtml:19 -#: web/templates/admin/campsite/option/index.gohtml:18 -#: web/templates/admin/campsite/type/index.gohtml:18 -#: web/templates/admin/season/index.gohtml:19 -#: web/templates/admin/services/index.gohtml:19 -#: web/templates/admin/services/index.gohtml:60 -#: web/templates/admin/home/index.gohtml:19 -msgctxt "header" -msgid "Translations" -msgstr "Traducciones" - #: web/templates/admin/campsite/carousel/index.gohtml:20 #: web/templates/admin/services/index.gohtml:20 #: web/templates/admin/services/index.gohtml:61 @@ -420,19 +493,6 @@ msgctxt "title" msgid "New Campsite Type Option" msgstr "Nueva opción del tipo de alojamiento" -#: web/templates/admin/campsite/option/form.gohtml:34 -#: web/templates/admin/campsite/option/l10n.gohtml:20 -#: web/templates/admin/campsite/type/form.gohtml:46 -#: web/templates/admin/campsite/type/l10n.gohtml:20 -#: web/templates/admin/season/form.gohtml:46 -#: web/templates/admin/season/l10n.gohtml:20 -#: web/templates/admin/services/form.gohtml:52 -#: web/templates/admin/services/l10n.gohtml:20 -#: web/templates/admin/profile.gohtml:26 -msgctxt "input" -msgid "Name" -msgstr "Nombre" - #: web/templates/admin/campsite/option/form.gohtml:42 msgctxt "input" msgid "Minimum" @@ -460,13 +520,6 @@ msgctxt "action" msgid "Add Option" msgstr "Añadir opción" -#: web/templates/admin/campsite/option/index.gohtml:17 -#: web/templates/admin/campsite/type/index.gohtml:17 -#: web/templates/admin/season/index.gohtml:18 -msgctxt "header" -msgid "Name" -msgstr "Nombre" - #: web/templates/admin/campsite/option/index.gohtml:39 msgid "No campsite type options added yet." msgstr "No se ha añadido ninguna opció al tipo de alojamiento todavía." @@ -493,13 +546,13 @@ msgid "Type" msgstr "Tipo" #: web/templates/admin/campsite/index.gohtml:28 -#: web/templates/admin/campsite/type/index.gohtml:39 +#: web/templates/admin/campsite/type/index.gohtml:47 #: web/templates/admin/season/index.gohtml:39 msgid "Yes" msgstr "Sí" #: web/templates/admin/campsite/index.gohtml:28 -#: web/templates/admin/campsite/type/index.gohtml:39 +#: web/templates/admin/campsite/type/index.gohtml:47 #: web/templates/admin/season/index.gohtml:39 msgid "No" msgstr "No" @@ -521,7 +574,7 @@ msgid "New Campsite Type" msgstr "Nuevo tipo de alojamiento" #: web/templates/admin/campsite/type/form.gohtml:37 -#: web/templates/admin/campsite/type/index.gohtml:21 +#: web/templates/admin/campsite/type/index.gohtml:22 msgctxt "campsite type" msgid "Active" msgstr "Activo" @@ -581,25 +634,35 @@ msgstr "Añadir tipo" #: web/templates/admin/campsite/type/index.gohtml:19 msgctxt "header" +msgid "Features" +msgstr "Características" + +#: web/templates/admin/campsite/type/index.gohtml:20 +msgctxt "header" msgid "Options" msgstr "Opciones" -#: web/templates/admin/campsite/type/index.gohtml:20 +#: web/templates/admin/campsite/type/index.gohtml:21 msgctxt "header" msgid "Carousel" msgstr "Carrusel" -#: web/templates/admin/campsite/type/index.gohtml:37 +#: web/templates/admin/campsite/type/index.gohtml:39 +msgctxt "action" +msgid "Edit Features" +msgstr "Editar las características" + +#: web/templates/admin/campsite/type/index.gohtml:42 msgctxt "action" msgid "Edit Options" msgstr "Editar opciones" -#: web/templates/admin/campsite/type/index.gohtml:38 +#: web/templates/admin/campsite/type/index.gohtml:45 msgctxt "action" msgid "Edit Carousel" msgstr "Editar el carrusel" -#: web/templates/admin/campsite/type/index.gohtml:45 +#: web/templates/admin/campsite/type/index.gohtml:53 msgid "No campsite types added yet." msgstr "No se ha añadido ningún tipo de alojamiento todavía." @@ -744,11 +807,6 @@ msgctxt "title" msgid "New Service" msgstr "Nuevo servicio" -#: web/templates/admin/services/form.gohtml:34 -msgctxt "input" -msgid "Icon" -msgstr "Icono" - #: web/templates/admin/services/index.gohtml:6 #: web/templates/admin/layout.gohtml:52 msgctxt "title" @@ -1021,8 +1079,9 @@ msgid "Automatic" msgstr "Automático" #: pkg/app/user.go:249 pkg/campsite/types/l10n.go:87 -#: pkg/campsite/types/l10n.go:142 pkg/campsite/types/option.go:336 -#: pkg/campsite/types/admin.go:413 pkg/season/l10n.go:69 +#: pkg/campsite/types/l10n.go:144 pkg/campsite/types/l10n.go:268 +#: pkg/campsite/types/option.go:340 pkg/campsite/types/feature.go:243 +#: pkg/campsite/types/admin.go:415 pkg/season/l10n.go:69 #: pkg/season/admin.go:382 pkg/services/l10n.go:73 pkg/services/admin.go:266 msgid "Name can not be empty." msgstr "No podéis dejar el nombre en blanco." @@ -1043,85 +1102,90 @@ msgstr "El archivo tiene que ser una imagen PNG o JPEG válida." msgid "Access forbidden" msgstr "Acceso prohibido" -#: pkg/campsite/types/option.go:337 pkg/campsite/types/admin.go:414 +#: pkg/campsite/types/option.go:341 pkg/campsite/types/feature.go:244 +#: pkg/campsite/types/admin.go:416 msgid "Name must have at least one letter." msgstr "El nombre tiene que tener como mínimo una letra." -#: pkg/campsite/types/option.go:340 +#: pkg/campsite/types/option.go:344 msgid "Minimum can not be empty." msgstr "No podéis dejar el mínimo en blanco." -#: pkg/campsite/types/option.go:341 +#: pkg/campsite/types/option.go:345 msgid "Minimum must be an integer number." msgstr "El valor de mínimo tiene que ser un número entero." -#: pkg/campsite/types/option.go:343 +#: pkg/campsite/types/option.go:347 msgid "Minimum must be zero or greater." msgstr "El valor de mínimo tiene que ser como mínimo cero." -#: pkg/campsite/types/option.go:346 +#: pkg/campsite/types/option.go:350 msgid "Maximum can not be empty." msgstr "No podéis dejar el máxmimo en blanco." -#: pkg/campsite/types/option.go:347 +#: pkg/campsite/types/option.go:351 msgid "Maximum must be an integer number." msgstr "El valor del máximo tiene que ser un número entero." -#: pkg/campsite/types/option.go:349 +#: pkg/campsite/types/option.go:353 msgid "Maximum must be equal or greater than minimum." msgstr "El valor del máximo tiene que ser igual o mayor al del mínimo." -#: pkg/campsite/types/option.go:353 pkg/campsite/types/admin.go:427 +#: pkg/campsite/types/option.go:357 pkg/campsite/types/admin.go:429 msgid "Price per night can not be empty." msgstr "No podéis dejar el precio por noche en blanco." -#: pkg/campsite/types/option.go:354 pkg/campsite/types/admin.go:428 +#: pkg/campsite/types/option.go:358 pkg/campsite/types/admin.go:430 msgid "Price per night must be a decimal number." msgstr "El precio por noche tien que ser un número decimal." -#: pkg/campsite/types/option.go:355 pkg/campsite/types/admin.go:429 +#: pkg/campsite/types/option.go:359 pkg/campsite/types/admin.go:431 msgid "Price per night must be zero or greater." msgstr "El precio por noche tiene que ser como mínimo cero." -#: pkg/campsite/types/admin.go:289 +#: pkg/campsite/types/feature.go:242 pkg/services/admin.go:265 +msgid "Selected icon is not valid." +msgstr "El icono escogido no es válido." + +#: pkg/campsite/types/admin.go:291 msgctxt "input" msgid "Cover image" msgstr "Imagen de portada" -#: pkg/campsite/types/admin.go:290 +#: pkg/campsite/types/admin.go:292 msgctxt "action" msgid "Set campsite type cover" msgstr "Establecer la portada del tipo de alojamiento" -#: pkg/campsite/types/admin.go:416 +#: pkg/campsite/types/admin.go:418 msgid "Cover image can not be empty." msgstr "No podéis dejar la imagen de portada en blanco." -#: pkg/campsite/types/admin.go:417 +#: pkg/campsite/types/admin.go:419 msgid "Cover image must be an image media type." msgstr "La imagen de portada tiene que ser un medio de tipo imagen." -#: pkg/campsite/types/admin.go:421 +#: pkg/campsite/types/admin.go:423 msgid "Maximum number of campers can not be empty." msgstr "No podéis dejar el número máximo de personas en blanco." -#: pkg/campsite/types/admin.go:422 +#: pkg/campsite/types/admin.go:424 msgid "Maximum number of campers must be an integer number." msgstr "El número máximo de personas tiene que ser entero." -#: pkg/campsite/types/admin.go:423 +#: pkg/campsite/types/admin.go:425 msgid "Maximum number of campers must be one or greater." msgstr "El número máximo de personas no puede ser cero." -#: pkg/campsite/types/admin.go:432 +#: pkg/campsite/types/admin.go:434 msgid "Minimum number of nights can not be empty." msgstr "No podéis dejar el número mínimo de noches en blanco." -#: pkg/campsite/types/admin.go:433 +#: pkg/campsite/types/admin.go:435 msgid "Minimum number of nights must be an integer." msgstr "El número mínimo de noches tiene que ser entero." -#: pkg/campsite/types/admin.go:434 +#: pkg/campsite/types/admin.go:436 msgid "Minimum number of nights must be one or greater." msgstr "El número mínimo de noches no puede ser cero." @@ -1222,10 +1286,6 @@ msgstr "No podéis dejar la fecha final en blanco." msgid "End date must be a valid date." msgstr "La fecha final tiene que ser una fecha válida." -#: pkg/services/admin.go:265 -msgid "Selected icon is not valid." -msgstr "El icono escogido no es válido." - #: pkg/company/admin.go:186 msgid "Selected country is not valid." msgstr "El país escogido no es válido." @@ -1306,10 +1366,6 @@ msgstr "No podéis dejar el nombre del archivo en blanco." #~ msgid "Pricing" #~ msgstr "Precios" -#~ msgctxt "title" -#~ msgid "Features" -#~ msgstr "Características" - #~ msgctxt "input" #~ msgid "Features" #~ msgstr "Características" diff --git a/revert/add_campsite_type_feature.sql b/revert/add_campsite_type_feature.sql new file mode 100644 index 0000000..cc12b98 --- /dev/null +++ b/revert/add_campsite_type_feature.sql @@ -0,0 +1,7 @@ +-- Revert camper:add_campsite_type_feature from pg + +begin; + +drop function if exists camper.add_campsite_type_feature(uuid, text, text); + +commit; diff --git a/revert/campsite_type_feature.sql b/revert/campsite_type_feature.sql new file mode 100644 index 0000000..48fd827 --- /dev/null +++ b/revert/campsite_type_feature.sql @@ -0,0 +1,7 @@ +-- Revert camper:campsite_type_feature from pg + +begin; + +drop table if exists camper.campsite_type_feature; + +commit; diff --git a/revert/campsite_type_feature_i18n.sql b/revert/campsite_type_feature_i18n.sql new file mode 100644 index 0000000..26cb0c4 --- /dev/null +++ b/revert/campsite_type_feature_i18n.sql @@ -0,0 +1,7 @@ +-- Revert camper:campsite_type_feature_i18n from pg + +begin; + +drop table if exists camper.campsite_type_feature_i18n; + +commit; diff --git a/revert/edit_campsite_type_feature.sql b/revert/edit_campsite_type_feature.sql new file mode 100644 index 0000000..30cc056 --- /dev/null +++ b/revert/edit_campsite_type_feature.sql @@ -0,0 +1,7 @@ +-- Revert camper:edit_campsite_type_feature from pg + +begin; + +drop function if exists camper.edit_campsite_type_feature(integer, text, text); + +commit; diff --git a/revert/translate_campsite_type_feature.sql b/revert/translate_campsite_type_feature.sql new file mode 100644 index 0000000..b7e1dc8 --- /dev/null +++ b/revert/translate_campsite_type_feature.sql @@ -0,0 +1,7 @@ +-- Revert camper:translate_campsite_type_feature from pg + +begin; + +drop function if exists camper.translate_campsite_type_feature(integer, text, text); + +commit; diff --git a/sqitch.plan b/sqitch.plan index 1dea491..808bc9c 100644 --- a/sqitch.plan +++ b/sqitch.plan @@ -104,3 +104,8 @@ campsite_type_carousel_i18n [roles schema_camper campsite_type_carousel language add_campsite_type_carousel_slide [roles schema_camper campsite_type_carousel campsite_type] 2023-10-09T17:59:49Z jordi fita mas # Add function to create slides for the campsite type carousel translate_campsite_type_carousel_slide [roles schema_camper campsite_type campsite_type_carousel_i18n] 2023-10-09T18:17:13Z jordi fita mas # Add function to translate a campsite type slide remove_campsite_type_carousel_slide [roles schema_camper campsite_type_carousel campsite_type_carousel_i18n] 2023-10-09T18:26:49Z jordi fita mas # Add function to remove campsite type slides +campsite_type_feature [roles schema_camper campsite_type icon user_profile] 2023-10-13T16:14:27Z jordi fita mas # Add the relation of campsite type feature +campsite_type_feature_i18n [roles schema_camper campsite_type_feature language] 2023-10-13T16:29:07Z jordi fita mas # Add relation for campsite_type_feature internationalization +add_campsite_type_feature [roles schema_camper campsite_type_feature campsite_type] 2023-10-13T16:26:04Z jordi fita mas # Add function to create new campsite type features +edit_campsite_type_feature [roles schema_camper campsite_type_feature] 2023-10-13T16:37:43Z jordi fita mas # Add function to update campsite type features +translate_campsite_type_feature [roles schema_camper campsite_type_feature_i18n] 2023-10-13T16:43:55Z jordi fita mas # Add function to translate campsite type features diff --git a/test/add_campsite_type_feature.sql b/test/add_campsite_type_feature.sql new file mode 100644 index 0000000..5e2388c --- /dev/null +++ b/test/add_campsite_type_feature.sql @@ -0,0 +1,77 @@ +-- Test add_campsite_type_feature +set client_min_messages to warning; +create extension if not exists pgtap; +reset client_min_messages; + +begin; + +select plan(13); + +set search_path to camper, public; + +select has_function('camper', 'add_campsite_type_feature', array['uuid', 'text', 'text']); +select function_lang_is('camper', 'add_campsite_type_feature', array['uuid', 'text', 'text'], 'sql'); +select function_returns('camper', 'add_campsite_type_feature', array['uuid', 'text', 'text'], 'integer'); +select isnt_definer('camper', 'add_campsite_type_feature', array['uuid', 'text', 'text']); +select volatility_is('camper', 'add_campsite_type_feature', array['uuid', 'text', 'text'], 'volatile'); +select function_privs_are('camper', 'add_campsite_type_feature', array ['uuid', 'text', 'text'], 'guest', array[]::text[]); +select function_privs_are('camper', 'add_campsite_type_feature', array ['uuid', 'text', 'text'], 'employee', array[]::text[]); +select function_privs_are('camper', 'add_campsite_type_feature', array ['uuid', 'text', 'text'], 'admin', array['EXECUTE']); +select function_privs_are('camper', 'add_campsite_type_feature', array ['uuid', 'text', 'text'], 'authenticator', array[]::text[]); + + +set client_min_messages to warning; +truncate campsite_type_feature_i18n cascade; +truncate campsite_type_feature cascade; +truncate campsite_type cascade; +truncate media cascade; +truncate media_content cascade; +truncate company cascade; +reset client_min_messages; + + +insert into company (company_id, business_name, vatin, trade_name, phone, email, web, address, city, province, postal_code, country_code, currency_code, default_lang_tag) +values (1, 'Company 2', 'XX123', '', '555-555-555', 'a@a', '', '', '', '', '', 'ES', 'EUR', 'ca') +; + +insert into media_content (media_type, bytes) +values ('image/x-xpixmap', 'static char *s[]={"1 1 1 1","a c #ffffff","a"};') +; + +insert into media (media_id, company_id, original_filename, content_hash) +values (2, 1, 'cover2.xpm', sha256('static char *s[]={"1 1 1 1","a c #ffffff","a"};')) +; + +insert into campsite_type (campsite_type_id, company_id, slug, media_id, name, description, active, dogs_allowed, max_campers) +values (3, 1, '87452b88-b48f-48d3-bb6c-0296de64164e', 2, 'Type A', '

A

', true, false, 4) + , (4, 1, '9ae5cf87-cd69-4541-b5a5-75f937cc9e58', 2, 'Type B', '

B

', true, false, 5) +; + +select lives_ok( + $$ select add_campsite_type_feature('87452b88-b48f-48d3-bb6c-0296de64164e', 'wifi', 'Feature 1') $$, + 'Should be able to add an feature to the first campsite type' +); + +select lives_ok( + $$ select add_campsite_type_feature('9ae5cf87-cd69-4541-b5a5-75f937cc9e58', 'information', 'Feature 2') $$, + 'Should be able to add an feature to the second campsite type' +); + +select bag_eq( + $$ select campsite_type_id, icon_name, name from campsite_type_feature $$, + $$ values (3, 'wifi', 'Feature 1') + , (4, 'information', 'Feature 2') + $$, + 'Should have added all two campsite type features' +); + +select is_empty( + $$ select * from campsite_type_feature_i18n $$, + 'Should not have added any translation for campsite type features.' +); + + +select * +from finish(); + +rollback; diff --git a/test/campsite_type_feature.sql b/test/campsite_type_feature.sql new file mode 100644 index 0000000..e1e7a67 --- /dev/null +++ b/test/campsite_type_feature.sql @@ -0,0 +1,204 @@ +-- Test campsite_type_feature +set client_min_messages to warning; +create extension if not exists pgtap; +reset client_min_messages; + +begin; + +select plan(41); + +set search_path to camper, public; + +select has_table('campsite_type_feature'); +select has_pk('campsite_type_feature'); +select table_privs_are('campsite_type_feature', 'guest', array['SELECT']); +select table_privs_are('campsite_type_feature', 'employee', array['SELECT']); +select table_privs_are('campsite_type_feature', 'admin', array['SELECT', 'INSERT', 'UPDATE', 'DELETE']); +select table_privs_are('campsite_type_feature', 'authenticator', array[]::text[]); + +select has_column('campsite_type_feature', 'campsite_type_feature_id'); +select col_is_pk('campsite_type_feature', 'campsite_type_feature_id'); +select col_type_is('campsite_type_feature', 'campsite_type_feature_id', 'integer'); +select col_not_null('campsite_type_feature', 'campsite_type_feature_id'); +select col_hasnt_default('campsite_type_feature', 'campsite_type_feature_id'); + +select has_column('campsite_type_feature', 'campsite_type_id'); +select col_is_fk('campsite_type_feature', 'campsite_type_id'); +select fk_ok('campsite_type_feature', 'campsite_type_id', 'campsite_type', 'campsite_type_id'); +select col_type_is('campsite_type_feature', 'campsite_type_id', 'integer'); +select col_not_null('campsite_type_feature', 'campsite_type_id'); +select col_hasnt_default('campsite_type_feature', 'campsite_type_id'); + +select has_column('campsite_type_feature', 'icon_name'); +select col_is_fk('campsite_type_feature', 'icon_name'); +select fk_ok('campsite_type_feature', 'icon_name', 'icon', 'icon_name'); +select col_type_is('campsite_type_feature', 'icon_name', 'text'); +select col_not_null('campsite_type_feature', 'icon_name'); +select col_hasnt_default('campsite_type_feature', 'icon_name'); + +select has_column('campsite_type_feature', 'name'); +select col_type_is('campsite_type_feature', 'name', 'text'); +select col_not_null('campsite_type_feature', 'name'); +select col_hasnt_default('campsite_type_feature', 'name'); + + +set client_min_messages to warning; +truncate campsite_type_feature cascade; +truncate campsite_type cascade; +truncate media cascade; +truncate media_content cascade; +truncate company_host cascade; +truncate company_user cascade; +truncate company cascade; +truncate auth."user" cascade; +reset client_min_messages; + +insert into auth."user" (user_id, email, name, password, cookie, cookie_expires_at) +values (1, 'demo@tandem.blog', 'Demo', 'test', '44facbb30d8a419dfd4bfbc44a4b5539d4970148dfc84bed0e', current_timestamp + interval '1 month') + , (5, 'admin@tandem.blog', 'Demo', 'test', '12af4c88b528c2ad4222e3740496ecbc58e76e26f087657524', current_timestamp + interval '1 month') +; + +insert into company (company_id, business_name, vatin, trade_name, phone, email, web, address, city, province, postal_code, country_code, currency_code, default_lang_tag) +values (2, 'Company 2', 'XX123', '', '555-555-555', 'a@a', '', '', '', '', '', 'ES', 'EUR', 'ca') + , (4, 'Company 4', 'XX234', '', '666-666-666', 'b@b', '', '', '', '', '', 'FR', 'USD', 'ca') +; + +insert into company_user (company_id, user_id, role) +values (2, 1, 'admin') + , (4, 5, 'admin') +; + +insert into company_host (company_id, host) +values (2, 'co2') + , (4, 'co4') +; + +insert into media_content (media_type, bytes) +values ('image/x-xpixmap', 'static char *s[]={"1 1 1 1","a c #ffffff","a"};') +; + +insert into media (media_id, company_id, original_filename, content_hash) +values (6, 2, 'cover2.xpm', sha256('static char *s[]={"1 1 1 1","a c #ffffff","a"};')) + , (8, 4, 'cover4.xpm', sha256('static char *s[]={"1 1 1 1","a c #ffffff","a"};')) +; + +insert into campsite_type (campsite_type_id, company_id, name, media_id, dogs_allowed, max_campers) +values (16, 2, 'Wooden lodge', 6, false, 7) + , (18, 4, 'Bungalow', 8, false, 6) +; + +insert into campsite_type_feature (campsite_type_id, icon_name, name) +values (16, 'information', 'Feature 16.1') + , (18, 'wifi', 'Feature 18.1') +; + +prepare campsite_feature_data as +select campsite_type_id, name +from campsite_type_feature +; + +set role guest; +select bag_eq( + 'campsite_feature_data', + $$ values (16, 'Feature 16.1') + , (18, 'Feature 18.1') + $$, + 'Everyone should be able to list all campsite type features across all companies' +); +reset role; + +select set_cookie('44facbb30d8a419dfd4bfbc44a4b5539d4970148dfc84bed0e/demo@tandem.blog', 'co2'); + +select lives_ok( + $$ insert into campsite_type_feature(campsite_type_id, icon_name, name) values (16, 'castle', 'Feature 16.2') $$, + 'Admin from company 2 should be able to insert a new campsite type feature to that company.' +); + +select bag_eq( + 'campsite_feature_data', + $$ values (16, 'Feature 16.1') + , (16, 'Feature 16.2') + , (18, 'Feature 18.1') + $$, + 'The new row should have been added' +); + +select lives_ok( + $$ update campsite_type_feature set name = 'Feature 16-2' where campsite_type_id = 16 and name = 'Feature 16.2' $$, + 'Admin from company 2 should be able to update campsite type feature of that company.' +); + +select bag_eq( + 'campsite_feature_data', + $$ values (16, 'Feature 16.1') + , (16, 'Feature 16-2') + , (18, 'Feature 18.1') + $$, + 'The row should have been updated.' +); + +select lives_ok( + $$ delete from campsite_type_feature where campsite_type_id = 16 and name = 'Feature 16-2' $$, + 'Admin from company 2 should be able to delete campsite type feature from that company.' +); + +select bag_eq( + 'campsite_feature_data', + $$ values (16, 'Feature 16.1') + , (18, 'Feature 18.1') + $$, + 'The row should have been deleted.' +); + +select throws_ok( + $$ insert into campsite_type_feature (campsite_type_id, icon_name, name) values (18, 'toilet', 'Feature 18.2') $$, + '42501', 'new row violates row-level security policy for table "campsite_type_feature"', + 'Admin from company 2 should NOT be able to insert new campsite type features to company 4.' +); + +select lives_ok( + $$ update campsite_type_feature set name = 'Feature 18-1' where campsite_type_id = 18 $$, + 'Admin from company 2 should not be able to update campsite types of company 4, but no error if campsite_type_id is not changed.' +); + +select bag_eq( + 'campsite_feature_data', + $$ values (16, 'Feature 16.1') + , (18, 'Feature 18.1') + $$, + 'No row should have been changed.' +); + +select throws_ok( + $$ update campsite_type_feature set campsite_type_id = 18 where campsite_type_id = 16 $$, + '42501', 'new row violates row-level security policy for table "campsite_type_feature"', + 'Admin from company 2 should NOT be able to move campsite type feature to one of company 4' +); + +select lives_ok( + $$ delete from campsite_type_feature where campsite_type_id = 18 $$, + 'Admin from company 2 should NOT be able to delete campsite type from company 4, but not error is thrown' +); + +select bag_eq( + 'campsite_feature_data', + $$ values (16, 'Feature 16.1') + , (18, 'Feature 18.1') + $$, + 'No row should have been changed' +); + +select throws_ok( + $$ insert into campsite_type_feature (campsite_type_id, icon_name, name) values (16, 'baby', ' ') $$, + '23514', 'new row for relation "campsite_type_feature" violates check constraint "name_not_empty"', + 'Should not be able to insert campsite type features with a blank name.' +); + +reset role; + + +select * +from finish(); + +rollback; + diff --git a/test/campsite_type_feature_i18n.sql b/test/campsite_type_feature_i18n.sql new file mode 100644 index 0000000..02ecb99 --- /dev/null +++ b/test/campsite_type_feature_i18n.sql @@ -0,0 +1,44 @@ +-- Test campsite_type_feature_i18n +set client_min_messages to warning; +create extension if not exists pgtap; +reset client_min_messages; + +begin; + +select plan(23); + +set search_path to camper, public; + +select has_table('campsite_type_feature_i18n'); +select has_pk('campsite_type_feature_i18n'); +select col_is_pk('campsite_type_feature_i18n', array['campsite_type_feature_id', 'lang_tag']); +select table_privs_are('campsite_type_feature_i18n', 'guest', array['SELECT']); +select table_privs_are('campsite_type_feature_i18n', 'employee', array['SELECT']); +select table_privs_are('campsite_type_feature_i18n', 'admin', array['SELECT', 'INSERT', 'UPDATE', 'DELETE']); +select table_privs_are('campsite_type_feature_i18n', 'authenticator', array[]::text[]); + +select has_column('campsite_type_feature_i18n', 'campsite_type_feature_id'); +select col_is_fk('campsite_type_feature_i18n', 'campsite_type_feature_id'); +select fk_ok('campsite_type_feature_i18n', 'campsite_type_feature_id', 'campsite_type_feature', 'campsite_type_feature_id'); +select col_type_is('campsite_type_feature_i18n', 'campsite_type_feature_id', 'integer'); +select col_not_null('campsite_type_feature_i18n', 'campsite_type_feature_id'); +select col_hasnt_default('campsite_type_feature_i18n', 'campsite_type_feature_id'); + +select has_column('campsite_type_feature_i18n', 'lang_tag'); +select col_is_fk('campsite_type_feature_i18n', 'lang_tag'); +select fk_ok('campsite_type_feature_i18n', 'lang_tag', 'language', 'lang_tag'); +select col_type_is('campsite_type_feature_i18n', 'lang_tag', 'text'); +select col_not_null('campsite_type_feature_i18n', 'lang_tag'); +select col_hasnt_default('campsite_type_feature_i18n', 'lang_tag'); + +select has_column('campsite_type_feature_i18n', 'name'); +select col_type_is('campsite_type_feature_i18n', 'name', 'text'); +select col_not_null('campsite_type_feature_i18n', 'name'); +select col_hasnt_default('campsite_type_feature_i18n', 'name'); + + +select * +from finish(); + +rollback; + diff --git a/test/edit_campsite_type_feature.sql b/test/edit_campsite_type_feature.sql new file mode 100644 index 0000000..b12d224 --- /dev/null +++ b/test/edit_campsite_type_feature.sql @@ -0,0 +1,81 @@ +-- Test edit_campsite_type_feature +set client_min_messages to warning; +create extension if not exists pgtap; +reset client_min_messages; + +begin; + +select plan(13); + +set search_path to camper, public; + +select has_function('camper', 'edit_campsite_type_feature', array['integer', 'text', 'text']); +select function_lang_is('camper', 'edit_campsite_type_feature', array['integer', 'text', 'text'], 'sql'); +select function_returns('camper', 'edit_campsite_type_feature', array['integer', 'text', 'text'], 'integer'); +select isnt_definer('camper', 'edit_campsite_type_feature', array['integer', 'text', 'text']); +select volatility_is('camper', 'edit_campsite_type_feature', array['integer', 'text', 'text'], 'volatile'); +select function_privs_are('camper', 'edit_campsite_type_feature', array ['integer', 'text', 'text'], 'guest', array[]::text[]); +select function_privs_are('camper', 'edit_campsite_type_feature', array ['integer', 'text', 'text'], 'employee', array[]::text[]); +select function_privs_are('camper', 'edit_campsite_type_feature', array ['integer', 'text', 'text'], 'admin', array['EXECUTE']); +select function_privs_are('camper', 'edit_campsite_type_feature', array ['integer', 'text', 'text'], 'authenticator', array[]::text[]); + + +set client_min_messages to warning; +truncate campsite_type_feature_i18n cascade; +truncate campsite_type_feature cascade; +truncate campsite_type cascade; +truncate media cascade; +truncate media_content cascade; +truncate company cascade; +reset client_min_messages; + + +insert into company (company_id, business_name, vatin, trade_name, phone, email, web, address, city, province, postal_code, country_code, currency_code, default_lang_tag) +values (1, 'Company 2', 'XX123', '', '555-555-555', 'a@a', '', '', '', '', '', 'ES', 'EUR', 'ca') +; + +insert into media_content (media_type, bytes) +values ('image/x-xpixmap', 'static char *s[]={"1 1 1 1","a c #ffffff","a"};') +; + +insert into media (media_id, company_id, original_filename, content_hash) +values (2, 1, 'cover2.xpm', sha256('static char *s[]={"1 1 1 1","a c #ffffff","a"};')) +; + +insert into campsite_type (campsite_type_id, company_id, media_id, name, description, active, dogs_allowed, max_campers) +values (3, 1, 2, 'Type A', '

A

', true, false, 4) +; + +insert into campsite_type_feature (campsite_type_feature_id, campsite_type_id, icon_name, name) +values (4, 3, 'information', 'Feature 1') + , (5, 3, 'wifi', 'Feature 2') +; + +select lives_ok( + $$ select edit_campsite_type_feature(4, 'toilet', 'Feature A') $$, + 'Should be able to edit the first feature' +); + +select lives_ok( + $$ select edit_campsite_type_feature(5, 'baby', 'Feature B') $$, + 'Should be able to edit the second feature' +); + +select bag_eq( + $$ select campsite_type_feature_id, campsite_type_id, icon_name, name from campsite_type_feature $$, + $$ values (4, 3, 'toilet', 'Feature A') + , (5, 3, 'baby', 'Feature B') + $$, + 'Should have updated all campsite type features.' +); + +select is_empty( + $$ select * from campsite_type_feature_i18n $$, + 'Should not have added any translation for campsite type features.' +); + + +select * +from finish(); + +rollback; diff --git a/test/translate_campsite_type_feature.sql b/test/translate_campsite_type_feature.sql new file mode 100644 index 0000000..4044b9a --- /dev/null +++ b/test/translate_campsite_type_feature.sql @@ -0,0 +1,85 @@ +-- Test translate_campsite_type_feature +set client_min_messages to warning; +create extension if not exists pgtap; +reset client_min_messages; + +begin; + +select plan(13); + +set search_path to camper, public; + +select has_function('camper', 'translate_campsite_type_feature', array['integer', 'text', 'text']); +select function_lang_is('camper', 'translate_campsite_type_feature', array['integer', 'text', 'text'], 'sql'); +select function_returns('camper', 'translate_campsite_type_feature', array['integer', 'text', 'text'], 'void'); +select isnt_definer('camper', 'translate_campsite_type_feature', array['integer', 'text', 'text']); +select volatility_is('camper', 'translate_campsite_type_feature', array['integer', 'text', 'text'], 'volatile'); +select function_privs_are('camper', 'translate_campsite_type_feature', array['integer', 'text', 'text'], 'guest', array[]::text[]); +select function_privs_are('camper', 'translate_campsite_type_feature', array['integer', 'text', 'text'], 'employee', array[]::text[]); +select function_privs_are('camper', 'translate_campsite_type_feature', array['integer', 'text', 'text'], 'admin', array['EXECUTE']); +select function_privs_are('camper', 'translate_campsite_type_feature', array['integer', 'text', 'text'], 'authenticator', array[]::text[]); + + +set client_min_messages to warning; +truncate campsite_type_feature_i18n cascade; +truncate campsite_type_feature cascade; +truncate campsite_type cascade; +truncate media cascade; +truncate media_content cascade; +truncate company cascade; +reset client_min_messages; + +insert into company (company_id, business_name, vatin, trade_name, phone, email, web, address, city, province, postal_code, country_code, currency_code, default_lang_tag) +values (1, 'Company 2', 'XX123', '', '555-555-555', 'a@a', '', '', '', '', '', 'ES', 'EUR', 'ca') +; + +insert into media_content (media_type, bytes) +values ('image/x-xpixmap', 'static char *s[]={"1 1 1 1","a c #ffffff","a"};') +; + +insert into media (media_id, company_id, original_filename, content_hash) +values (2, 1, 'cover2.xpm', sha256('static char *s[]={"1 1 1 1","a c #ffffff","a"};')) +; + +insert into campsite_type (campsite_type_id, company_id, slug, media_id, name, description, active, dogs_allowed, max_campers) +values (3, 1, '87452b88-b48f-48d3-bb6c-0296de64164e', 2, 'Type A', '

A

', true, false, 4) +; + +insert into campsite_type_feature (campsite_type_feature_id, campsite_type_id, icon_name, name) +values (4, 3, 'toilet', 'Feature 1') + , (5, 3, 'toilet', 'Feature 2') +; + +insert into campsite_type_feature_i18n (campsite_type_feature_id, lang_tag, name) +values (5, 'ca', 'carácter2') +; + +select lives_ok( + $$ select translate_campsite_type_feature(4, 'ca', 'Carácter 1') $$, + 'Should be able to translate the first feature' +); + +select lives_ok( + $$ select translate_campsite_type_feature(5, 'es', 'Característica 2') $$, + 'Should be able to translate the second feature' +); + +select lives_ok( + $$ select translate_campsite_type_feature(5, 'ca', 'Carácter 2') $$, + 'Should be able to overwrite the catalan translation of the second feature' +); + +select bag_eq( + $$ select campsite_type_feature_id, lang_tag, name from campsite_type_feature_i18n $$, + $$ values (4, 'ca', 'Carácter 1') + , (5, 'ca', 'Carácter 2') + , (5, 'es', 'Característica 2') + $$, + 'Should have added and updated all translations.' +); + + +select * +from finish(); + +rollback; diff --git a/verify/add_campsite_type_feature.sql b/verify/add_campsite_type_feature.sql new file mode 100644 index 0000000..e8794ff --- /dev/null +++ b/verify/add_campsite_type_feature.sql @@ -0,0 +1,7 @@ +-- Verify camper:add_campsite_type_feature on pg + +begin; + +select has_function_privilege('camper.add_campsite_type_feature(uuid, text, text)', 'execute'); + +rollback; diff --git a/verify/available_icons.sql b/verify/available_icons.sql index 3b3b5bc..fe3fa4b 100644 --- a/verify/available_icons.sql +++ b/verify/available_icons.sql @@ -4,15 +4,20 @@ begin; set search_path to camper; +select 1 / count(*) from icon where icon_name = 'area'; select 1 / count(*) from icon where icon_name = 'baby'; select 1 / count(*) from icon where icon_name = 'ball'; select 1 / count(*) from icon where icon_name = 'bicycle'; select 1 / count(*) from icon where icon_name = 'campfire'; select 1 / count(*) from icon where icon_name = 'castle'; +select 1 / count(*) from icon where icon_name = 'ecofriendly'; select 1 / count(*) from icon where icon_name = 'fridge'; +select 1 / count(*) from icon where icon_name = 'hvac'; select 1 / count(*) from icon where icon_name = 'information'; select 1 / count(*) from icon where icon_name = 'kayak'; +select 1 / count(*) from icon where icon_name = 'nopet'; select 1 / count(*) from icon where icon_name = 'outing'; +select 1 / count(*) from icon where icon_name = 'person'; select 1 / count(*) from icon where icon_name = 'pool'; select 1 / count(*) from icon where icon_name = 'puzzle'; select 1 / count(*) from icon where icon_name = 'restaurant'; diff --git a/verify/campsite_type_feature.sql b/verify/campsite_type_feature.sql new file mode 100644 index 0000000..5f4418d --- /dev/null +++ b/verify/campsite_type_feature.sql @@ -0,0 +1,18 @@ +-- Verify camper:campsite_type_feature on pg + +begin; + +select campsite_type_feature_id + , campsite_type_id + , icon_name + , name +from camper.campsite_type_feature +where false; + +select 1 / count(*) from pg_class where oid = 'camper.campsite_type_feature'::regclass and relrowsecurity; +select 1 / count(*) from pg_policy where polname = 'guest_ok' and polrelid = 'camper.campsite_type_feature'::regclass; +select 1 / count(*) from pg_policy where polname = 'insert_to_company' and polrelid = 'camper.campsite_type_feature'::regclass; +select 1 / count(*) from pg_policy where polname = 'update_company' and polrelid = 'camper.campsite_type_feature'::regclass; +select 1 / count(*) from pg_policy where polname = 'delete_from_company' and polrelid = 'camper.campsite_type_feature'::regclass; + +rollback; diff --git a/verify/campsite_type_feature_i18n.sql b/verify/campsite_type_feature_i18n.sql new file mode 100644 index 0000000..52d8e4d --- /dev/null +++ b/verify/campsite_type_feature_i18n.sql @@ -0,0 +1,11 @@ +-- Verify camper:campsite_type_feature_i18n on pg + +begin; + +select campsite_type_feature_id + , lang_tag + , name +from camper.campsite_type_feature_i18n +where false; + +rollback; diff --git a/verify/edit_campsite_type_feature.sql b/verify/edit_campsite_type_feature.sql new file mode 100644 index 0000000..765b2b8 --- /dev/null +++ b/verify/edit_campsite_type_feature.sql @@ -0,0 +1,7 @@ +-- Verify camper:edit_campsite_type_feature on pg + +begin; + +select has_function_privilege('camper.edit_campsite_type_feature(integer, text, text)', 'execute'); + +rollback; diff --git a/verify/translate_campsite_type_feature.sql b/verify/translate_campsite_type_feature.sql new file mode 100644 index 0000000..11939fc --- /dev/null +++ b/verify/translate_campsite_type_feature.sql @@ -0,0 +1,7 @@ +-- Verify camper:translate_campsite_type_feature on pg + +begin; + +select has_function_privilege('camper.translate_campsite_type_feature(integer, text, text)', 'execute'); + +rollback; diff --git a/web/static/icons.css b/web/static/icons.css index 9245611..3107a58 100644 --- a/web/static/icons.css +++ b/web/static/icons.css @@ -3,6 +3,9 @@ * SPDX-License-Identifier: AGPL-3.0-only */ +.icon_area { + background-image: url('data:image/svg+xml,%3Csvg viewBox="0 0 29.52905 28.08545" xmlns="http://www.w3.org/2000/svg"%3E%3Cpolyline stroke-linejoin="round" stroke-linecap="round" stroke="%23303334" fill="none" points="24.65173 25.48701 24.65173 26.48701 23.65173 26.48701"/%3E%%3Cline stroke-linejoin="round" stroke-linecap="round" stroke-dasharray="0 0 1.87099 4.67747" stroke="%23303334" fill="none" y2="26.48701" x2="8.21606" y1="26.48701" x1="18.97425"/%3E%3Cpolyline stroke-linejoin="round" stroke-linecap="round" stroke="%23303334" fill="none" points="5.87732 26.48701 4.87732 26.48701 4.87732 25.48701"/%3E%3Cline stroke-linejoin="round" stroke-linecap="round" stroke-dasharray="0 0 1.76066 4.40165" stroke="%23303334" fill="none" y2="4.79927" x2="4.87732" y1="21.08536" x1="4.87732"/%3E%3Cpolyline stroke-linejoin="round" stroke-linecap="round" stroke="%23303334" fill="none" points="4.87732 2.59844 4.87732 1.59844 5.87732 1.59844"/%3E%3Cline stroke-linejoin="round" stroke-linecap="round" stroke-dasharray="0 0 1.87099 4.67747" stroke="%23303334" fill="none" y2="1.59844" x2="21.31299" y1="1.59844" x1="10.5548"/%3E%3Cpolyline stroke-linejoin="round" stroke-linecap="round" stroke="%23303334" fill="none" points="23.65173 1.59844 24.65173 1.59844 24.65173 2.59844"/%3E%3Cline stroke-linejoin="round" stroke-linecap="round" stroke-dasharray="0 0 1.76066 4.40165" stroke="%23303334" fill="none" y2="23.28618" x2="24.65173" y1="7.00009" x1="24.65173"/%3E%3Cline stroke-linejoin="round" stroke-linecap="round" stroke-dasharray="0 0 2 5" stroke="%23303334" fill="none" y2="20.16764" x2="22.97111" y1="20.16764" x1="6.55794"/%3E%3C/svg%3E'); +} .icon_baby { background-image: url('data:image/svg+xml,%3Csvg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256" fill="%23000000" height="32" width="32"%3E%3Cpath d="M92,136a8,8,0,1,1,8-8A8,8,0,0,1,92,136Zm72-16a8,8,0,1,0,8,8A8,8,0,0,0,164,120Zm-10.13,44.62a49,49,0,0,1-51.74,0,4,4,0,0,0-4.26,6.76,57,57,0,0,0,60.26,0,4,4,0,1,0-4.26-6.76ZM228,128A100,100,0,1,1,128,28,100.11,100.11,0,0,1,228,128Zm-8,0a92.11,92.11,0,0,0-90.06-92C116.26,54.07,116,71.83,116,72a12,12,0,0,0,24,0,4,4,0,0,1,8,0,20,20,0,0,1-40,0c0-.78.16-17.31,12-35.64A92,92,0,1,0,220,128Z"/%3E%3C/svg%3E'); } @@ -23,10 +26,18 @@ background-image: url('data:image/svg+xml,%3Csvg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256" fill="%23000000" height="32" width="32"%3E%3Cpath d="M200,28H184a12,12,0,0,0-12,12V56a4,4,0,0,1-4,4H152a4,4,0,0,1-4-4V40a12,12,0,0,0-12-12H120a12,12,0,0,0-12,12V56a4,4,0,0,1-4,4H88a4,4,0,0,1-4-4V40A12,12,0,0,0,72,28H56A12,12,0,0,0,44,40V84.69a11.93,11.93,0,0,0,3.51,8.48l11.32,11.32A4,4,0,0,1,60,107.31V216a12,12,0,0,0,12,12H184a12,12,0,0,0,12-12V107.31a4,4,0,0,1,1.17-2.82l11.32-11.32A11.93,11.93,0,0,0,212,84.69V40A12,12,0,0,0,200,28ZM148,220H108V152a20,20,0,0,1,40,0ZM204,84.69a4,4,0,0,1-1.17,2.82L191.51,98.83a11.93,11.93,0,0,0-3.51,8.48V216a4,4,0,0,1-4,4H156V152a28,28,0,0,0-56,0v68H72a4,4,0,0,1-4-4V107.31a11.93,11.93,0,0,0-3.51-8.48L53.17,87.51A4,4,0,0,1,52,84.69V40a4,4,0,0,1,4-4H72a4,4,0,0,1,4,4V56A12,12,0,0,0,88,68h16a12,12,0,0,0,12-12V40a4,4,0,0,1,4-4h16a4,4,0,0,1,4,4V56a12,12,0,0,0,12,12h16a12,12,0,0,0,12-12V40a4,4,0,0,1,4-4h16a4,4,0,0,1,4,4Z"/%3E%3C/svg%3E'); } +.icon_ecofriendly { + background-image: url('data:image/svg+xml,%3Csvg viewBox="0 0 29.52905 28.08545" xmlns="http://www.w3.org/2000/svg"%3E%3Cpolygon stroke-linejoin="round" stroke-linecap="round" stroke="%23303334" fill="none" points="20.03209 6.02239 14.30328 13.46984 17.16768 13.46984 13.15752 18.62577 26.90666 18.62577 22.89649 13.46984 25.7609 13.46984 20.03209 6.02239"/%3E%3Cline stroke-linejoin="round" stroke-linecap="round" stroke="%23303334" fill="none" y2="22.06306" x2="20.03209" y1="18.62577" x1="20.03209"/%3E%3Cpath stroke-linejoin="round" stroke-linecap="round" stroke="%23303334" fill="none" d="m12.38607,21.36565v-4.18444c0-.38517-.31224-.69741-.69741-.69741h-2.78962c-.38517,0-.69741.31224-.69741.69741v4.18444c0,.38517-.31224.69741-.69741.69741H3.31979c-.38517,0-.69741-.31224-.69741-.69741v-8.06027c.00002-.19652.08295-.38392.2284-.51608l6.97406-6.58526c.2661-.24221.67278-.24221.93888,0l6.97406,6.58526c.14545.13216.22838.31956.2284.51608v8.06027c0,.38517-.31224.69741-.69741.69741h-4.18531c-.38517,0-.69741-.31224-.69741-.69741Z"/%3E%3C/svg%3E'); +} + .icon_fridge { background-image: url('data:image/svg+xml,%3Csvg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 17.57617 28.04395"%3E%3Cpath stroke-width="0" d="m15.62988,0H1.94629C.87305,0,0,.87305,0,1.94629v25.59766c0,.27637.22363.5.5.5h16.57617c.27637,0,.5-.22363.5-.5V1.94629c0-1.07324-.87305-1.94629-1.94629-1.94629ZM1.94629,1h13.68359c.52148,0,.94629.42432.94629.94629v7.41357H1V1.94629c0-.52197.42432-.94629.94629-.94629Zm-.94629,26.04395V10.35986h15.57617v16.68408H1Z"/%3E%3Cpath stroke-width="0" d="m3.64453,5.36426h2.04248c.27637,0,.5-.22363.5-.5s-.22363-.5-.5-.5h-2.04248c-.27637,0-.5.22363-.5.5s.22363.5.5.5Z"/%3E%3Cpath stroke-width="0" d="m5.68701,13.271h-2.04248c-.27637,0-.5.22363-.5.5s.22363.5.5.5h2.04248c.27637,0,.5-.22363.5-.5s-.22363-.5-.5-.5Z"/%3E%3C/svg%3E'); } +.icon_hvac { + background-image: url('data:image/svg+xml,%3Csvg viewBox="0 0 29.52905 28.08545" xmlns="http://www.w3.org/2000/svg"%3E%3Cline stroke-linejoin="round" stroke-linecap="round" stroke="%23303334" fill="none" y2="22.04885" x2="12.97909" y1="12.87674" x1="12.97909"/%3E%3Cline stroke-linejoin="round" stroke-linecap="round" stroke="%23303334" fill="none" y2="12.87674" x2="12.97909" y1="11.15696" x1="11.25932"/%3E%3Cline stroke-linejoin="round" stroke-linecap="round" stroke="%23303334" fill="none" y2="22.04885" x2="12.97909" y1="23.76863" x1="11.25932"/%3E%3Cline stroke-linejoin="round" stroke-linecap="round" stroke="%23303334" fill="none" y2="17.4628" x2="12.97909" y1="15.16977" x1="9.00713"/%3E%3Cpolyline stroke-linejoin="round" stroke-linecap="round" stroke="%23303334" fill="none" points="6.67326 15.74302 9.00713 15.16977 8.39303 12.87674"/%3E%3Cline stroke-linejoin="round" stroke-linecap="round" stroke="%23303334" fill="none" y2="17.4628" x2="12.97909" y1="19.75582" x1="9.00713"/%3E%3Cpolyline stroke-linejoin="round" stroke-linecap="round" stroke="%23303334" fill="none" points="8.39303 22.04885 9.00713 19.75582 6.67326 19.18257"/%3E%3Cline stroke-linejoin="round" stroke-linecap="round" stroke="%23303334" fill="none" y2="9.63923" x2="15.03222" y1="11.3157" x1="15.03222"/%3E%3Cpath stroke-linejoin="round" stroke-linecap="round" stroke="%23303334" fill="none" d="m15.03222,13.55101c2.16042,0,3.91179,1.75137,3.91179,3.91179s-1.75137,3.91179-3.91179,3.91179"/%3E%3Cline stroke-linejoin="round" stroke-linecap="round" stroke="%23303334" fill="none" y2="11.87453" x2="20.62049" y1="12.99218" x1="19.50284"/%3E%3Cline stroke-linejoin="round" stroke-linecap="round" stroke="%23303334" fill="none" y2="23.05106" x2="20.62049" y1="21.93341" x1="19.50284"/%3E%3Cline stroke-linejoin="round" stroke-linecap="round" stroke="%23303334" fill="none" y2="25.28637" x2="15.03222" y1="23.60989" x1="15.03222"/%3E%3Cline stroke-linejoin="round" stroke-linecap="round" stroke="%23303334" fill="none" y2="17.4628" x2="22.85579" y1="17.4628" x1="21.17931"/%3E%3Cpolyline stroke-linejoin="round" stroke-linecap="round" stroke="%23303334" fill="none" points="27.00983 19.18257 27.00983 11.50591 14.76453 2.79908 2.51922 11.50591 2.51922 19.18257 2.51922 11.50591 14.76453 2.79908"/%3E%3C/svg%3E'); +} + .icon_information { background-image: url('data:image/svg+xml,%3Csvg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256" fill="%23000000" height="32" width="32"%3E%3Cpath d="M140,176a4,4,0,0,1-4,4,12,12,0,0,1-12-12V128a4,4,0,0,0-4-4,4,4,0,0,1,0-8,12,12,0,0,1,12,12v40a4,4,0,0,0,4,4A4,4,0,0,1,140,176ZM124,92a8,8,0,1,0-8-8A8,8,0,0,0,124,92Zm104,36A100,100,0,1,1,128,28,100.11,100.11,0,0,1,228,128Zm-8,0a92,92,0,1,0-92,92A92.1,92.1,0,0,0,220,128Z"/%3E%3C/svg%3E'); } @@ -35,10 +46,18 @@ background-image: url('data:image/svg+xml,%3Csvg viewBox="0 0 29.52905 28.08545" xmlns="http://www.w3.org/2000/svg" id="uuid-87ec619a-4896-40b8-8478-aa076dee3f7c"%3E%3Cpath stroke-width="0" d="m29.46728,11.26074l-.57617-2.15186c-.08398-.31299-.24609-.59229-.45801-.78613-.26758-.24658-.59863-.3418-.90625-.25879l-3.4834.93213c-.26953.07227-.48535.26807-.6084.54932l-.5752,1.30713c-.0719.16376-.10333.34686-.10742.53485l-3.05627.81787c-.23956-3.37067-1.03204-8.79126-2.35193-10.66425l-.22363-.31836C16.58057.45752,15.69971.00049,14.76318,0h-.00049c-.94873,0-1.80566.44434-2.35156,1.21875-1.71973,2.44043-2.65771,9.75146-2.65771,12.82422,0,.24457.00812.52399.0199.81769l-3.25604.87134c-.09741-.16071-.21613-.30347-.36005-.40924l-1.15088-.84424c-.24854-.18213-.53125-.24463-.80127-.17236l-3.48389.93164c-.56689.15234-.85693.84961-.65918,1.58691l.57617,2.15137c.0835.31348.24561.59277.45654.78613.20508.18848.4458.28809.68506.28809.07471,0,.14893-.00879.22168-.02832l3.48486-.93359c.26807-.07227.48389-.26758.60693-.54883l.57568-1.30664c.07172-.16357.10309-.3465.10718-.5343l3.05591-.81793c.25531,3.52673,1.14368,8.94983,2.5791,10.98602.5459.77441,1.40332,1.21875,2.35205,1.21875.93652,0,1.81738-.45703,2.35645-1.22266l.22461-.31738c1.60352-2.27637,2.43066-9.79492,2.43066-12.50244,0-.23499-.00854-.51709-.021-.81805l3.25732-.87183c.09747.16071.21625.30347.36035.40912l1.15039.84473c.18066.13281.38086.20166.5791.20166.07422,0,.14941-.00977.22168-.0293l3.48535-.93262c.56641-.15234.85645-.84863.65918-1.58594Zm-24.24072,6.86182l-3.41602.93262c-.03516-.01465-.14844-.12109-.20654-.33691l-.57617-2.15137c-.05713-.21484-.01318-.3623-.04688-.3623h-.00098l3.4165-.93164s.00586.00293.01709.01074c.00049,0,.00098.00098.00098.00098l1.00842.74017-1.90881.5108c-.2666.07227-.4248.3457-.35352.6123.05957.22363.26172.37109.48242.37109.04297,0,.08643-.00586.12988-.0166l1.91559-.5127-.46198,1.13281Zm5.52686-4.07959c0-3.07959.93994-10.06934,2.4751-12.24805.35596-.50488.91504-.79492,1.53418-.79492.62109.00049,1.18213.2915,1.54053.79932l.22266.31689c1.17902,1.67261,1.96533,7.08936,2.18457,10.35278l-1.50305.40228c-.19397-2.55505-1.02423-4.83514-2.44324-4.83514-1.63379,0-2.48828,3.02197-2.48828,6.00684,0,.04913.00214.09814.00262.14728l-1.51538.40552c-.00574-.19403-.0097-.38147-.0097-.5528Zm2.52692-.12079c.02441-2.93951.89984-4.88605,1.48383-4.88605.53003,0,1.29828,1.6062,1.45496,4.09961l-2.93878.78644Zm2.96765.24115c-.02441,2.93933-.89978,4.88599-1.48383,4.88599-.53003,0-1.29834-1.60632-1.45496-4.09937l2.93878-.78662Zm2.52692-.12036c0,2.66846-.84961,9.94092-2.24902,11.92725l-.22363.31641c-.71387,1.01367-2.36084,1.01562-3.07373.00391-1.31152-1.86127-2.18622-7.22681-2.414-10.67285l1.50641-.4032c.19397,2.55505,1.02417,4.83484,2.44324,4.83484,1.63379,0,2.48828-3.02148,2.48828-6.00635,0-.04926-.00214-.09845-.00262-.14777l1.51514-.40558c.00659.20087.00995.38617.00995.55334Zm6.35742-1.23047s-.00684-.00293-.01855-.01123l-1.00897-.74091,1.90936-.51105c.26758-.07129.42578-.34521.35449-.6123-.07227-.26562-.3418-.42725-.6123-.35352l-1.91577.5127.46069-1.13379,3.41699-.93164c.03516.01416.14844.12012.20605.33691l.57617,2.15186c.05957.21973.01953.36816.04883.36133l-3.41699.93164Z"/%3E%3C/svg%3E'); } +.icon_nopet { + background-image: url('data:image/svg+xml,%3Csvg viewBox="0 0 29.52905 28.08545" xmlns="http://www.w3.org/2000/svg"%3E%3Cpath stroke-linejoin="round" stroke-linecap="round" stroke="%23303334" fill="none" d="m17.21239,7.49301s-4.87977.84632-8.52421,5.99327c-2.16998,3.06461-2.2348,5.76054-2.16343,6.96413-.20971.12828-.41646.23744-.60915.31138-1.52884.5874-3.2956.47276-3.31213.47143-.4288-.03178-.80523.29289-.83649.72349-.03114.43058.29272.80523.72347.83649.02465.00178.21278.01443.51249.01443.7371,0,2.14964-.07768,3.47368-.58659.40917-.15748.83392-.40365,1.23448-.67824-.00552.04298-.00973.08677-.00973.1312,0,.54621.44274.98878.98878.98878h6.45055l1.19972-.00985-.13038-.52151c-.09972-.39887-.3556-.74091-.71009-.94918h0c-.23523-.1382-.5031-.21107-.77593-.21107h-2.41299c1.63184-.71439,2.2461-2.1492,1.45294-3.47113l1.43136-.57254,2.78807,4.33263c.01879.03918.04916.06965.06582.10978.29509.71048,1.15391,1.28302,2.02047,1.28302h1.70989v-.33504c0-.49573-.28234-.94819-.72763-1.16605l-.35082-.11881c-.36735-.12441-.66131-.40423-.80365-.76502l-1.90756-4.83493,1.54591-4.46211,4.3585-1.15763.43585-1.29053c.16876-.49968-.12132-1.03771-.63151-1.17133l-1.93096-.54479c-.13345-1.31408-1.37293-1.72864-2.77284-1.4432-1.14081.23261-1.87678.73329-1.81146,1.89575.00439.07821.01285.15499.02897.23377h0Z"/%3E%3Cpath stroke-linejoin="round" stroke-linecap="round" stroke="%23303334" fill="none" d="m19.69654,6.92365l-.56872,1.6591c-.13062.38105-.45388.66399-.84887.74299h0c-.45328.09066-.90313-.1704-1.04931-.60894l-.0304-.09119"/%3E%3Cline stroke-linejoin="round" stroke-linecap="round" stroke="%23303334" fill="none" y2="25.06162" x2="24.09052" y1="3.02383" x1="2.05273"/%3E%3C/svg%3E'); +} + .icon_outing { background-image: url('data:image/svg+xml,%3Csvg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256" fill="%23000000" height="32" width="32"%3E%3Cpath d="M164,44.17V32a20,20,0,0,0-20-20H112A20,20,0,0,0,92,32V44.17A52.05,52.05,0,0,0,44,96V216a12,12,0,0,0,12,12H200a12,12,0,0,0,12-12V96A52.05,52.05,0,0,0,164,44.17ZM112,20h32a12,12,0,0,1,12,12V44H100V32A12,12,0,0,1,112,20Zm60,144H84V152a12,12,0,0,1,12-12h64a12,12,0,0,1,12,12Zm-88,8h56v12a4,4,0,0,0,8,0V172h24v48H84Zm120,44a4,4,0,0,1-4,4H180V152a20,20,0,0,0-20-20H96a20,20,0,0,0-20,20v68H56a4,4,0,0,1-4-4V96A44.05,44.05,0,0,1,96,52h64a44.05,44.05,0,0,1,44,44ZM148,88a4,4,0,0,1-4,4H112a4,4,0,0,1,0-8h32A4,4,0,0,1,148,88Z"/%3E%3C/svg%3E'); } +.icon_person { + background-image: url('data:image/svg+xml,%3Csvg viewBox="0 0 29.52905 28.08545" xmlns="http://www.w3.org/2000/svg"%3E%3Ccircle stroke-linejoin="round" stroke-linecap="round" stroke="%23000" fill="none" r="2.55477" cy="5.10916" cx="14.76453"/%3E%3Cpath stroke-linejoin="round" stroke-linecap="round" stroke="%23303334" fill="none" d="m17.01911,9.36711c.7333.00012,1.43119.31534,1.91608.86543l4.82426,5.46934c.49972.49972.49972,1.30991,0,1.80963s-1.30991.49972-1.80963,0l-3.77787-3.03485,2.43448,9.25359c.28892.64361.00138,1.39957-.64222,1.68848-.63062.28309-1.37202.01317-1.67304-.60909l-3.52558-6.07503-3.52558,6.07503c-.30721.63508-1.07109.90086-1.70617.59365-.62226-.30102-.89218-1.04242-.60909-1.67304l2.43448-9.25359-3.78,3.03273c-.49972.49972-1.30991.49972-1.80963,0s-.49972-1.30991,0-1.80963l4.82639-5.46721c.48489-.55009,1.18278-.86531,1.91608-.86543h4.50704Z"/%3E%3C/svg%3E'); +} + .icon_pool { background-image: url('data:image/svg+xml,%3Csvg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256" fill="%23000000" height="32" width="32"%3E%3Cpath d="M88,145.39a4,4,0,0,0,4-4V124h72v19.29a4,4,0,0,0,8,0V32a4,4,0,0,0-8,0V52H92V32a4,4,0,0,0-8,0V141.39A4,4,0,0,0,88,145.39ZM92,116V92h72v24Zm72-56V84H92V60ZM28,168a4,4,0,0,1,4-4c13.21,0,20.12,4.61,26.22,8.67,5.9,3.93,11,7.33,21.78,7.33s15.88-3.4,21.78-7.33c6.09-4.06,13-8.67,26.21-8.67s20.13,4.61,26.22,8.67c5.9,3.93,11,7.33,21.79,7.33s15.88-3.4,21.78-7.33c6.1-4.06,13-8.67,26.22-8.67a4,4,0,0,1,0,8c-10.79,0-15.88,3.4-21.78,7.33-6.1,4.06-13,8.67-26.22,8.67s-20.13-4.61-26.22-8.67c-5.9-3.93-11-7.33-21.79-7.33s-15.88,3.4-21.78,7.33c-6.09,4.06-13,8.67-26.21,8.67s-20.12-4.61-26.22-8.67C47.88,175.4,42.79,172,32,172A4,4,0,0,1,28,168Zm200,40a4,4,0,0,1-4,4c-10.79,0-15.88,3.4-21.78,7.33-6.1,4.06-13,8.67-26.22,8.67s-20.13-4.61-26.22-8.67c-5.9-3.93-11-7.33-21.79-7.33s-15.88,3.4-21.78,7.33c-6.09,4.06-13,8.67-26.21,8.67s-20.12-4.61-26.22-8.67C47.88,215.4,42.79,212,32,212a4,4,0,0,1,0-8c13.21,0,20.12,4.61,26.22,8.67,5.9,3.93,11,7.33,21.78,7.33s15.88-3.4,21.78-7.33c6.09-4.06,13-8.67,26.21-8.67s20.13,4.61,26.22,8.67c5.9,3.93,11,7.33,21.79,7.33s15.88-3.4,21.78-7.33c6.1-4.06,13-8.67,26.22-8.67A4,4,0,0,1,228,208Z"/%3E%3C/svg%3E'); } diff --git a/web/static/public.css b/web/static/public.css index e7c832a..d8745dd 100644 --- a/web/static/public.css +++ b/web/static/public.css @@ -235,10 +235,13 @@ h1 a .name { left: -9999em; } -nav ul { +nav ul, .campsite_type_features ul { list-style: none; padding-left: 0; display: flex; +} + +nav ul { flex-wrap: wrap; align-items: center; } @@ -666,18 +669,32 @@ dt { align-items: center; } +.campsite_type_features li { + flex: 1; + font-size: 2.4rem; + text-align: center; + justify-content: space-between; + background-repeat: no-repeat; + background-position: top center; + background-size: 7.2rem 7.2rem; + padding-top: 7.2rem; +} + footer { display: flex; flex-direction: column; gap: 1rem; } +footer div, .campsite_type_features, .campsite_type_detail { + padding: 5rem 0; + border-top: 3px solid black; +} + footer div { display: flex; justify-content: space-between; - padding: 5rem 0; margin: 0 2.5rem; - border-top: 3px solid black; border-bottom: 3px solid black; } diff --git a/web/templates/admin/campsite/feature/form.gohtml b/web/templates/admin/campsite/feature/form.gohtml new file mode 100644 index 0000000..a3f3729 --- /dev/null +++ b/web/templates/admin/campsite/feature/form.gohtml @@ -0,0 +1,75 @@ + +{{ define "title" -}} + {{- /*gotype: dev.tandem.ws/tandem/camper/pkg/campsite/types.featureForm*/ -}} + {{ if .ID}} + {{( pgettext "Edit Campsite Type Feature" "title" )}} + {{ else }} + {{( pgettext "New Campsite Type Feature" "title" )}} + {{ end }} +{{- end }} + +{{ define "content" -}} + {{- /*gotype: dev.tandem.ws/tandem/camper/pkg/campsite/types.featureForm*/ -}} +
+

+ {{ if .ID }} + {{( pgettext "Edit Campsite Type Feature" "title" )}} + {{ else }} + {{( pgettext "New Campsite Type Feature" "title" )}} + {{ end }} +

+ {{ CSRFInput }} +
+ {{ with $field := .Icon -}} +
+ {{( pgettext "Icon" "input")}} + +
    + {{- range .Options }} +
  • + +
  • + {{- end }} +
+ {{ template "error-message" . }} +
+ {{- end }} + {{ with .Name -}} + + {{ template "error-message" . }} + {{- end }} +
+
+ +
+
+ + +{{- end }} diff --git a/web/templates/admin/campsite/feature/index.gohtml b/web/templates/admin/campsite/feature/index.gohtml new file mode 100644 index 0000000..ab61948 --- /dev/null +++ b/web/templates/admin/campsite/feature/index.gohtml @@ -0,0 +1,41 @@ + +{{ define "title" -}} + {{( pgettext "Campsite Type Features" "title" )}} +{{- end }} + +{{ define "content" -}} + {{- /*gotype: dev.tandem.ws/tandem/camper/pkg/campsite/types.featureIndex*/ -}} + {{( pgettext "Add Feature" "action" )}} +

{{( pgettext "Campsite Type Features" "title" )}}

+ {{ if .Features -}} + + + + + + + + + {{ range .Features -}} + + + + + {{- end }} + +
{{( pgettext "Name" "header" )}}{{( pgettext "Translations" "header" )}}
{{ .Name }} + {{ range .Translations }} + {{ .Endonym }} + {{ end }} +
+ {{ else -}} +

{{( gettext "No campsite type features added yet." )}}

+ {{- end }} +{{- end }} diff --git a/web/templates/admin/campsite/feature/l10n.gohtml b/web/templates/admin/campsite/feature/l10n.gohtml new file mode 100644 index 0000000..dd2ff9b --- /dev/null +++ b/web/templates/admin/campsite/feature/l10n.gohtml @@ -0,0 +1,35 @@ + +{{ define "title" -}} + {{- /*gotype: dev.tandem.ws/tandem/camper/pkg/campsite/types.featureL10nForm*/ -}} + {{printf (pgettext "Translate Campsite Type Feature to %s" "title") .Locale.Endonym }} +{{- end }} + +{{ define "content" -}} + {{- /*gotype: dev.tandem.ws/tandem/camper/pkg/campsite/types.featureL10nForm*/ -}} +
+

+ {{printf (pgettext "Translate Campsite Type Feature to %s" "title") .Locale.Endonym }} +

+ {{ CSRFInput }} +
+ {{ with .Name -}} +
+ {{( pgettext "Name" "input")}} + {{( gettext "Source:" )}} {{ .Source }}
+ + {{ template "error-message" . }} +
+ {{- end }} +
+
+ +
+
+{{- end }} diff --git a/web/templates/admin/campsite/type/index.gohtml b/web/templates/admin/campsite/type/index.gohtml index 55c388a..96f676d 100644 --- a/web/templates/admin/campsite/type/index.gohtml +++ b/web/templates/admin/campsite/type/index.gohtml @@ -16,6 +16,7 @@ {{( pgettext "Name" "header" )}} {{( pgettext "Translations" "header" )}} + {{( pgettext "Features" "header" )}} {{( pgettext "Options" "header" )}} {{( pgettext "Carousel" "header" )}} {{( pgettext "Active" "campsite type" )}} @@ -34,8 +35,15 @@ href="/admin/campsites/types/{{ $type.Slug }}/{{ .Language }}">{{ .Endonym }} {{ end }} - {{( pgettext "Edit Options" "action" )}} - {{( pgettext "Edit Carousel" "action" )}} + + {{( pgettext "Edit Features" "action" )}} + + + {{( pgettext "Edit Options" "action" )}} + + + {{( pgettext "Edit Carousel" "action" )}} + {{ if .Active }}{{( gettext "Yes" )}}{{ else }}{{( gettext "No" )}}{{ end }} {{- end }} diff --git a/web/templates/public/campsite/type.gohtml b/web/templates/public/campsite/type.gohtml index 2194127..0a243f4 100644 --- a/web/templates/public/campsite/type.gohtml +++ b/web/templates/public/campsite/type.gohtml @@ -56,7 +56,18 @@ {{- end }} -
+ {{ with .Features -}} +
+

{{( pgettext "Features" "title" )}}

+
    + {{ range . -}} +
  • {{ .Name }}
  • + {{- end }} +
+
+ {{- end }} + +

{{( pgettext "Info" "title" )}}

{{ .Info }}