Add the carousel for campsite types

I can not reuse the carousel package because these carousels need the
campsite site’s slug as a first parameters: i can not have a relation
per campsite type, as i do in home and services pages, because the
campsite types are added by administration types; even if i had a
single relation for slides of home and services pages, these would go
in a different relation due to the foreign key to campsite type.

What i could reuse, however, is the Slide and SlideEntry types from
that package, although i had to export carousel.Translation to be usable
from the types package.  I should change that to use locale.Translation,
but this was the easier option, or i would need to change the queries
and templates for carousel package too.

Besides that, they work exactly like the slides in home and services
pages.
This commit is contained in:
jordi fita mas 2023-10-12 16:33:45 +02:00
parent ad7126cc92
commit 471ed9e870
59 changed files with 1765 additions and 167 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

View File

@ -46,6 +46,30 @@ select add_media(52, 'services_carousel0.avif', 'image/avif', decode('m4_esyscmd
select add_media(52, 'services_carousel1.avif', 'image/avif', decode('m4_esyscmd([[base64 -w0 demo/services_carousel1.avif]])', 'base64'));
select add_media(52, 'services_carousel2.avif', 'image/avif', decode('m4_esyscmd([[base64 -w0 demo/services_carousel2.avif]])', 'base64'));
select add_media(52, 'services_carousel3.avif', 'image/avif', decode('m4_esyscmd([[base64 -w0 demo/services_carousel3.avif]])', 'base64'));
select add_media(52, 'safari_tents_carousel1.avif', 'image/avif', decode('m4_esyscmd([[base64 -w0 demo/safari_tents_carousel1.avif]])', 'base64'));
select add_media(52, 'safari_tents_carousel2.avif', 'image/avif', decode('m4_esyscmd([[base64 -w0 demo/safari_tents_carousel2.avif]])', 'base64'));
select add_media(52, 'safari_tents_carousel3.avif', 'image/avif', decode('m4_esyscmd([[base64 -w0 demo/safari_tents_carousel3.avif]])', 'base64'));
select add_media(52, 'safari_tents_carousel4.avif', 'image/avif', decode('m4_esyscmd([[base64 -w0 demo/safari_tents_carousel4.avif]])', 'base64'));
select add_media(52, 'safari_tents_carousel5.avif', 'image/avif', decode('m4_esyscmd([[base64 -w0 demo/safari_tents_carousel5.avif]])', 'base64'));
select add_media(52, 'safari_tents_carousel6.avif', 'image/avif', decode('m4_esyscmd([[base64 -w0 demo/safari_tents_carousel6.avif]])', 'base64'));
select add_media(52, 'bungalows_carousel2.avif', 'image/avif', decode('m4_esyscmd([[base64 -w0 demo/bungalows_carousel2.avif]])', 'base64'));
select add_media(52, 'bungalows_carousel3.avif', 'image/avif', decode('m4_esyscmd([[base64 -w0 demo/bungalows_carousel3.avif]])', 'base64'));
select add_media(52, 'bungalows_carousel4.avif', 'image/avif', decode('m4_esyscmd([[base64 -w0 demo/bungalows_carousel4.avif]])', 'base64'));
select add_media(52, 'bungalows_carousel5.avif', 'image/avif', decode('m4_esyscmd([[base64 -w0 demo/bungalows_carousel5.avif]])', 'base64'));
select add_media(52, 'bungalows_carousel6.avif', 'image/avif', decode('m4_esyscmd([[base64 -w0 demo/bungalows_carousel6.avif]])', 'base64'));
select add_media(52, 'wooden_lodges_carousel0.avif', 'image/avif', decode('m4_esyscmd([[base64 -w0 demo/wooden_lodges_carousel0.avif]])', 'base64'));
select add_media(52, 'wooden_lodges_carousel1.avif', 'image/avif', decode('m4_esyscmd([[base64 -w0 demo/wooden_lodges_carousel1.avif]])', 'base64'));
select add_media(52, 'wooden_lodges_carousel2.avif', 'image/avif', decode('m4_esyscmd([[base64 -w0 demo/wooden_lodges_carousel2.avif]])', 'base64'));
select add_media(52, 'wooden_lodges_carousel3.avif', 'image/avif', decode('m4_esyscmd([[base64 -w0 demo/wooden_lodges_carousel3.avif]])', 'base64'));
select add_media(52, 'wooden_lodges_carousel4.avif', 'image/avif', decode('m4_esyscmd([[base64 -w0 demo/wooden_lodges_carousel4.avif]])', 'base64'));
select add_media(52, 'wooden_lodges_carousel5.avif', 'image/avif', decode('m4_esyscmd([[base64 -w0 demo/wooden_lodges_carousel5.avif]])', 'base64'));
select add_media(52, 'wooden_lodges_carousel6.avif', 'image/avif', decode('m4_esyscmd([[base64 -w0 demo/wooden_lodges_carousel6.avif]])', 'base64'));
select add_media(52, 'wooden_lodges_carousel7.avif', 'image/avif', decode('m4_esyscmd([[base64 -w0 demo/wooden_lodges_carousel7.avif]])', 'base64'));
select add_media(52, 'wooden_lodges_carousel8.avif', 'image/avif', decode('m4_esyscmd([[base64 -w0 demo/wooden_lodges_carousel8.avif]])', 'base64'));
select add_media(52, 'wooden_lodges_carousel9.avif', 'image/avif', decode('m4_esyscmd([[base64 -w0 demo/wooden_lodges_carousel9.avif]])', 'base64'));
select add_media(52, 'wooden_lodges_carouselA.avif', 'image/avif', decode('m4_esyscmd([[base64 -w0 demo/wooden_lodges_carouselA.avif]])', 'base64'));
select add_media(52, 'wooden_lodges_carouselB.avif', 'image/avif', decode('m4_esyscmd([[base64 -w0 demo/wooden_lodges_carouselB.avif]])', 'base64'));
select add_media(52, 'wooden_lodges_carouselC.avif', 'image/avif', decode('m4_esyscmd([[base64 -w0 demo/wooden_lodges_carouselC.avif]])', 'base64'));
;
insert into home_carousel (media_id, caption)
@ -370,4 +394,115 @@ values (102, 92, 795)
, (111, 94, 590)
;
insert into campsite_type_carousel (campsite_type_id, media_id, caption)
values (72, 77, 'Llegenda')
, (72, 78, 'Llegenda')
, (72, 79, 'Llegenda')
, (73, 63, 'Llegenda')
, (73, 80, 'Llegenda')
, (73, 81, 'Llegenda')
, (73, 82, 'Llegenda')
, (73, 83, 'Llegenda')
, (73, 84, 'Llegenda')
, (73, 85, 'Llegenda')
, (74, 64, 'Llegenda')
, (74, 65, 'Llegenda')
, (74, 86, 'Llegenda')
, (74, 87, 'Llegenda')
, (74, 88, 'Llegenda')
, (74, 89, 'Llegenda')
, (74, 90, 'Llegenda')
, (75, 65, 'Llegenda')
, (75, 64, 'Llegenda')
, (75, 88, 'Llegenda')
, (75, 86, 'Llegenda')
, (75, 87, 'Llegenda')
, (76, 91, 'Llegenda')
, (76, 92, 'Llegenda')
, (76, 93, 'Llegenda')
, (76, 94, 'Llegenda')
, (76, 95, 'Llegenda')
, (76, 96, 'Llegenda')
, (76, 97, 'Llegenda')
, (76, 98, 'Llegenda')
, (76, 99, 'Llegenda')
, (76, 100, 'Llegenda')
, (76, 101, 'Llegenda')
, (76, 102, 'Llegenda')
, (76, 103, 'Llegenda')
;
insert into campsite_type_carousel_i18n (campsite_type_id, media_id, lang_tag, caption)
values (72, 77, 'en', 'Legend')
, (72, 77, 'es', 'Leyenda')
, (72, 78, 'en', 'Legend')
, (72, 78, 'es', 'Leyenda')
, (72, 79, 'en', 'Legend')
, (72, 79, 'es', 'Leyenda')
, (73, 63, 'en', 'Legend')
, (73, 63, 'es', 'Leyenda')
, (73, 80, 'en', 'Legend')
, (73, 80, 'es', 'Leyenda')
, (73, 81, 'en', 'Legend')
, (73, 81, 'es', 'Leyenda')
, (73, 82, 'en', 'Legend')
, (73, 82, 'es', 'Leyenda')
, (73, 83, 'en', 'Legend')
, (73, 83, 'es', 'Leyenda')
, (73, 84, 'en', 'Legend')
, (73, 84, 'es', 'Leyenda')
, (73, 85, 'en', 'Legend')
, (73, 85, 'es', 'Leyenda')
, (74, 64, 'en', 'Legend')
, (74, 64, 'es', 'Leyenda')
, (74, 65, 'en', 'Legend')
, (74, 65, 'es', 'Leyenda')
, (74, 86, 'en', 'Legend')
, (74, 86, 'es', 'Leyenda')
, (74, 87, 'en', 'Legend')
, (74, 87, 'es', 'Leyenda')
, (74, 88, 'en', 'Legend')
, (74, 88, 'es', 'Leyenda')
, (74, 89, 'en', 'Legend')
, (74, 89, 'es', 'Leyenda')
, (74, 90, 'en', 'Legend')
, (74, 90, 'es', 'Leyenda')
, (75, 65, 'en', 'Legend')
, (75, 65, 'es', 'Leyenda')
, (75, 64, 'en', 'Legend')
, (75, 64, 'es', 'Leyenda')
, (75, 88, 'en', 'Legend')
, (75, 88, 'es', 'Leyenda')
, (75, 86, 'en', 'Legend')
, (75, 86, 'es', 'Leyenda')
, (75, 87, 'en', 'Legend')
, (75, 87, 'es', 'Leyenda')
, (76, 91, 'en', 'Legend')
, (76, 91, 'es', 'Leyenda')
, (76, 92, 'en', 'Legend')
, (76, 92, 'es', 'Leyenda')
, (76, 93, 'en', 'Legend')
, (76, 93, 'es', 'Leyenda')
, (76, 94, 'en', 'Legend')
, (76, 94, 'es', 'Leyenda')
, (76, 95, 'en', 'Legend')
, (76, 95, 'es', 'Leyenda')
, (76, 96, 'en', 'Legend')
, (76, 96, 'es', 'Leyenda')
, (76, 97, 'en', 'Legend')
, (76, 97, 'es', 'Leyenda')
, (76, 98, 'en', 'Legend')
, (76, 98, 'es', 'Leyenda')
, (76, 99, 'en', 'Legend')
, (76, 99, 'es', 'Leyenda')
, (76, 100, 'en', 'Legend')
, (76, 100, 'es', 'Leyenda')
, (76, 101, 'en', 'Legend')
, (76, 101, 'es', 'Leyenda')
, (76, 102, 'en', 'Legend')
, (76, 102, 'es', 'Leyenda')
, (76, 103, 'en', 'Legend')
, (76, 103, 'es', 'Leyenda')
;
commit;

Binary file not shown.

After

Width:  |  Height:  |  Size: 141 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 117 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 120 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 71 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.5 KiB

View File

@ -0,0 +1,28 @@
-- Deploy camper:add_campsite_type_carousel_slide to pg
-- requires: roles
-- requires: schema_camper
-- requires: campsite_type_carousel
-- requires: campsite_type
begin;
set search_path to camper, public;
create or replace function add_campsite_type_carousel_slide(slug uuid, media_id integer, caption text) returns integer as
$$
insert into campsite_type_carousel (campsite_type_id, media_id, caption)
select campsite_type_id, add_campsite_type_carousel_slide.media_id, coalesce(caption, '')
from campsite_type
where slug = add_campsite_type_carousel_slide.slug
on conflict (campsite_type_id, media_id) do update
set caption = excluded.caption
returning campsite_type_id
;
$$
language sql
;
revoke execute on function add_campsite_type_carousel_slide(uuid, integer, text) from public;
grant execute on function add_campsite_type_carousel_slide(uuid, integer, text) to admin;
commit;

View File

@ -0,0 +1,55 @@
-- Deploy camper:campsite_type_carousel to pg
-- requires: roles
-- requires: schema_camper
-- requires: campsite_type
-- requires: media
-- requires: user_profile
begin;
set search_path to camper, public;
create table campsite_type_carousel (
campsite_type_id integer not null references campsite_type,
media_id integer not null references media,
caption text not null,
primary key (campsite_type_id, media_id)
);
grant select on table campsite_type_carousel to guest;
grant select on table campsite_type_carousel to employee;
grant select, insert, update, delete on table campsite_type_carousel to admin;
alter table campsite_type_carousel enable row level security;
create policy guest_ok
on campsite_type_carousel
for select
using (true)
;
create policy insert_to_company
on campsite_type_carousel
for insert
with check (
exists (select 1 from campsite_type join media using (company_id) join user_profile using (company_id) where campsite_type.campsite_type_id = campsite_type_carousel.campsite_type_id and media.media_id = campsite_type_carousel.media_id)
)
;
create policy update_company
on campsite_type_carousel
for update
using (
exists (select 1 from campsite_type join media using (company_id) join user_profile using (company_id) where campsite_type.campsite_type_id = campsite_type_carousel.campsite_type_id and media.media_id = campsite_type_carousel.media_id)
)
;
create policy delete_from_company
on campsite_type_carousel
for delete
using (
exists (select 1 from campsite_type join media using (company_id) join user_profile using (company_id) where campsite_type.campsite_type_id = campsite_type_carousel.campsite_type_id and media.media_id = campsite_type_carousel.media_id)
)
;
commit;

View File

@ -0,0 +1,24 @@
-- Deploy camper:campsite_type_carousel_i18n to pg
-- requires: roles
-- requires: schema_camper
-- requires: campsite_type_carousel
-- requires: language
begin;
set search_path to camper, public;
create table campsite_type_carousel_i18n (
campsite_type_id integer not null,
media_id integer not null,
lang_tag text not null references language,
caption text not null,
primary key (campsite_type_id, media_id, lang_tag),
foreign key (campsite_type_id, media_id) references campsite_type_carousel
);
grant select on table campsite_type_carousel_i18n to guest;
grant select on table campsite_type_carousel_i18n to employee;
grant select, insert, update, delete on table campsite_type_carousel_i18n to admin;
commit;

View File

@ -0,0 +1,37 @@
-- Deploy camper:remove_campsite_type_carousel_slide to pg
-- requires: roles
-- requires: schema_camper
-- requires: campsite_type_carousel
-- requires: campsite_type_carousel_i18n
begin;
set search_path to camper, public;
create or replace function remove_campsite_type_carousel_slide(slug uuid, media_id integer) returns void as
$$
declare
csid integer;
begin
select campsite_type_id
into csid
from campsite_type
where campsite_type.slug = $1
;
delete from campsite_type_carousel_i18n
where campsite_type_id = csid
and campsite_type_carousel_i18n.media_id = $2
;
delete from campsite_type_carousel
where campsite_type_id = csid
and campsite_type_carousel.media_id = $2
;
end
$$
language plpgsql
;
revoke execute on function remove_campsite_type_carousel_slide(uuid, integer) from public;
grant execute on function remove_campsite_type_carousel_slide(uuid, integer) to admin;
commit;

View File

@ -0,0 +1,27 @@
-- Deploy camper:translate_campsite_type_carousel_slide to pg
-- requires: roles
-- requires: schema_camper
-- requires: campsite_type
-- requires: campsite_type_carousel_i18n
begin;
set search_path to camper, public;
create or replace function translate_campsite_type_carousel_slide(slug uuid, media_id integer, lang_tag text, caption text) returns void as
$$
insert into campsite_type_carousel_i18n (campsite_type_id, media_id, lang_tag, caption)
select campsite_type_id, translate_campsite_type_carousel_slide.media_id, lang_tag, coalesce(caption, '')
from campsite_type
where slug = translate_campsite_type_carousel_slide.slug
on conflict (campsite_type_id, media_id, lang_tag) do update
set caption = excluded.caption
;
$$
language sql
;
revoke execute on function translate_campsite_type_carousel_slide(uuid, integer, text, text) from public;
grant execute on function translate_campsite_type_carousel_slide(uuid, integer, text, text) to admin;
commit;

View File

@ -93,6 +93,8 @@ func (h *AdminHandler) typeHandler(user *auth.User, company *auth.Company, conn
}
case "options":
h.optionsHandler(user, company, conn, f.Slug).ServeHTTP(w, r)
case "slides":
h.carouselHandler(user, company, conn, f.Slug).ServeHTTP(w, r)
default:
loc, ok := h.locales.Get(head)
if !ok {

View File

@ -0,0 +1,297 @@
/*
* SPDX-FileCopyrightText: 2023 jordi fita mas <jfita@peritasoft.com>
* 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/carousel"
"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) carouselHandler(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:
serveCarouselIndex(w, r, user, company, conn, typeSlug)
case http.MethodPost:
addSlide(w, r, user, company, conn, typeSlug)
default:
httplib.MethodNotAllowed(w, r, http.MethodGet, http.MethodGet)
}
case "new":
switch r.Method {
case http.MethodGet:
f := newSlideForm(typeSlug)
f.MustRender(w, r, user, company)
default:
httplib.MethodNotAllowed(w, r, http.MethodGet)
}
default:
mediaID, err := strconv.Atoi(head)
if err != nil {
http.NotFound(w, r)
}
f := newSlideForm(typeSlug)
if err := f.FillFromDatabase(r.Context(), conn, mediaID); err != nil {
if database.ErrorIsNotFound(err) {
http.NotFound(w, r)
return
}
panic(err)
}
var langTag string
langTag, r.URL.Path = httplib.ShiftPath(r.URL.Path)
switch langTag {
case "":
switch r.Method {
case http.MethodGet:
f.MustRender(w, r, user, company)
case http.MethodPut:
editSlide(w, r, user, company, conn, f)
case http.MethodDelete:
h.deleteSlide(w, r, user, conn, typeSlug, mediaID)
default:
httplib.MethodNotAllowed(w, r, http.MethodGet, http.MethodPut, http.MethodDelete)
}
default:
loc, ok := h.locales.Get(langTag)
if !ok {
http.NotFound(w, r)
return
}
l10n := newSlideL10nForm(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:
editSlideL10n(w, r, user, company, conn, l10n)
default:
httplib.MethodNotAllowed(w, r, http.MethodGet, http.MethodPut)
}
}
}
})
}
func serveCarouselIndex(w http.ResponseWriter, r *http.Request, user *auth.User, company *auth.Company, conn *database.Conn, typeSlug string) {
slides, err := collectSlideEntries(r.Context(), conn, typeSlug)
if err != nil {
panic(err)
}
page := &carouselIndex{
TypeSlug: typeSlug,
Slides: slides,
}
page.MustRender(w, r, user, company)
}
type carouselIndex struct {
TypeSlug string
Slides []*carousel.SlideEntry
}
func (page *carouselIndex) MustRender(w http.ResponseWriter, r *http.Request, user *auth.User, company *auth.Company) {
template.MustRenderAdmin(w, r, user, company, "campsite/carousel/index.gohtml", page)
}
func mustCollectSlides(ctx context.Context, conn *database.Conn, loc *locale.Locale, typeSlug string) []*carousel.Slide {
rows, err := conn.Query(ctx, `
select coalesce(i18n.caption, slide.caption) as l10_caption
, media.path
from campsite_type_carousel as slide
join campsite_type using (campsite_type_id)
join media on media.media_id = slide.media_id
left join campsite_type_carousel_i18n as i18n
on i18n.campsite_type_id = slide.campsite_type_id
and i18n.media_id = slide.media_id
and lang_tag = $1
where campsite_type.slug = $2
`, loc.Language, typeSlug)
if err != nil {
panic(err)
}
defer rows.Close()
var slides []*carousel.Slide
for rows.Next() {
slide := &carousel.Slide{}
err = rows.Scan(&slide.Caption, &slide.Media)
if err != nil {
panic(err)
}
slides = append(slides, slide)
}
if rows.Err() != nil {
panic(rows.Err())
}
return slides
}
func collectSlideEntries(ctx context.Context, conn *database.Conn, typeSlug string) ([]*carousel.SlideEntry, error) {
rows, err := conn.Query(ctx, `
select carousel.media_id
, media.path
, caption
, array_agg((lang_tag, endonym, not exists (select 1 from campsite_type_carousel_i18n as i18n where i18n.media_id = carousel.media_id and i18n.lang_tag = language.lang_tag)) order by endonym)
from campsite_type_carousel as carousel
join campsite_type using (campsite_type_id)
join media on media.media_id = carousel.media_id
join company on company.company_id = campsite_type.company_id
, language
where lang_tag <> default_lang_tag
and language.selectable
and campsite_type.slug = $1
group by carousel.media_id
, media.path
, caption
order by caption
`, pgx.QueryResultFormats{pgx.BinaryFormatCode}, typeSlug)
if err != nil {
return nil, err
}
defer rows.Close()
var slides []*carousel.SlideEntry
for rows.Next() {
slide := &carousel.SlideEntry{}
var translations database.RecordArray
if err = rows.Scan(&slide.ID, &slide.Media, &slide.Caption, &translations); err != nil {
return nil, err
}
for _, el := range translations.Elements {
slide.Translations = append(slide.Translations, &carousel.Translation{
Language: el.Fields[0].Get().(string),
Endonym: el.Fields[1].Get().(string),
Missing: el.Fields[2].Get().(bool),
})
}
slides = append(slides, slide)
}
return slides, nil
}
func addSlide(w http.ResponseWriter, r *http.Request, user *auth.User, company *auth.Company, conn *database.Conn, typeSlug string) {
f := newSlideForm(typeSlug)
editSlide(w, r, user, company, conn, f)
}
func editSlide(w http.ResponseWriter, r *http.Request, user *auth.User, company *auth.Company, conn *database.Conn, f *slideForm) {
f.process(w, r, user, company, conn, func(ctx context.Context) {
conn.MustExec(ctx, "select add_campsite_type_carousel_slide($1, $2, $3)", f.TypeSlug, f.Media, f.Caption)
})
}
func (h *AdminHandler) deleteSlide(w http.ResponseWriter, r *http.Request, user *auth.User, conn *database.Conn, typeSlug string, mediaId int) {
if err := user.VerifyCSRFToken(r); err != nil {
http.Error(w, err.Error(), http.StatusForbidden)
return
}
conn.MustExec(r.Context(), "select remove_campsite_type_carousel_slide($1, $2)", typeSlug, mediaId)
httplib.Redirect(w, r, "/admin/campsites/types/"+typeSlug+"/slides", http.StatusSeeOther)
}
type slideForm struct {
TypeSlug string
MediaID int
Media *form.Media
Caption *form.Input
}
func newSlideForm(typeSlug string) *slideForm {
return &slideForm{
TypeSlug: typeSlug,
Media: &form.Media{
Input: &form.Input{
Name: "media",
},
Label: locale.PgettextNoop("Slide image", "input"),
Prompt: locale.PgettextNoop("Set slide image", "action"),
},
Caption: &form.Input{
Name: "caption",
},
}
}
func (f *slideForm) FillFromDatabase(ctx context.Context, conn *database.Conn, mediaID int) error {
f.MediaID = mediaID
row := conn.QueryRow(ctx, `
select caption
, carousel.media_id::text
from campsite_type_carousel as carousel
join campsite_type using (campsite_type_id)
where campsite_type.slug = $1
and carousel.media_id = $2
`, f.TypeSlug, mediaID)
return row.Scan(&f.Caption.Val, &f.Media.Val)
}
func (f *slideForm) process(w http.ResponseWriter, r *http.Request, user *auth.User, company *auth.Company, conn *database.Conn, act func(ctx context.Context)) {
if err := f.Parse(r); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
if err := user.VerifyCSRFToken(r); err != nil {
http.Error(w, err.Error(), http.StatusForbidden)
return
}
if ok, err := f.Valid(r.Context(), conn, user.Locale); err != nil {
panic(err)
} else if !ok {
if !httplib.IsHTMxRequest(r) {
w.WriteHeader(http.StatusUnprocessableEntity)
}
f.MustRender(w, r, user, company)
return
}
act(r.Context())
httplib.Redirect(w, r, "/admin/campsites/types/"+f.TypeSlug+"/slides", http.StatusSeeOther)
}
func (f *slideForm) Parse(r *http.Request) error {
if err := r.ParseForm(); err != nil {
return err
}
f.Caption.FillValue(r)
f.Media.FillValue(r)
return nil
}
func (f *slideForm) Valid(ctx context.Context, conn *database.Conn, l *locale.Locale) (bool, error) {
v := form.NewValidator(l)
if v.CheckRequired(f.Media.Input, l.GettextNoop("Slide image can not be empty.")) {
if _, err := v.CheckImageMedia(ctx, conn, f.Media.Input, l.GettextNoop("Slide image must be an image media type.")); err != nil {
return false, err
}
}
return v.AllOK, nil
}
func (f *slideForm) MustRender(w http.ResponseWriter, r *http.Request, user *auth.User, company *auth.Company) {
template.MustRenderAdmin(w, r, user, company, "campsite/carousel/form.gohtml", f)
}

View File

@ -128,3 +128,70 @@ func (l10n *optionL10nForm) Valid(l *locale.Locale) bool {
v.CheckRequired(&l10n.Name.Input, l.GettextNoop("Name can not be empty."))
return v.AllOK
}
type slideL10nForm struct {
Locale *locale.Locale
TypeSlug string
MediaID int
Caption *form.L10nInput
}
func newSlideL10nForm(f *slideForm, loc *locale.Locale) *slideL10nForm {
return &slideL10nForm{
Locale: loc,
TypeSlug: f.TypeSlug,
MediaID: f.MediaID,
Caption: f.Caption.L10nInput(),
}
}
func (l10n *slideL10nForm) FillFromDatabase(ctx context.Context, conn *database.Conn) error {
row := conn.QueryRow(ctx, `
select coalesce(i18n.caption, '') as l10n_caption
from campsite_type_carousel as carousel
join campsite_type using (campsite_type_id)
left join campsite_type_carousel_i18n as i18n
on carousel.campsite_type_id = i18n.campsite_type_id
and carousel.media_id = i18n.media_id
and i18n.lang_tag = $1
where campsite_type.slug = $2 and carousel.media_id = $3
`, l10n.Locale.Language, l10n.TypeSlug, l10n.MediaID)
return row.Scan(&l10n.Caption.Val)
}
func (l10n *slideL10nForm) MustRender(w http.ResponseWriter, r *http.Request, user *auth.User, company *auth.Company) {
template.MustRenderAdmin(w, r, user, company, "campsite/carousel/l10n.gohtml", l10n)
}
func editSlideL10n(w http.ResponseWriter, r *http.Request, user *auth.User, company *auth.Company, conn *database.Conn, l10n *slideL10nForm) {
if err := l10n.Parse(r); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
if err := user.VerifyCSRFToken(r); err != nil {
http.Error(w, err.Error(), http.StatusForbidden)
return
}
if !l10n.Valid(user.Locale) {
if !httplib.IsHTMxRequest(r) {
w.WriteHeader(http.StatusUnprocessableEntity)
}
l10n.MustRender(w, r, user, company)
return
}
conn.MustExec(r.Context(), "select translate_campsite_type_carousel_slide($1, $2, $3, $4)", l10n.TypeSlug, l10n.MediaID, l10n.Locale.Language, l10n.Caption)
httplib.Redirect(w, r, "/admin/campsites/types/"+l10n.TypeSlug+"/slides", http.StatusSeeOther)
}
func (l10n *slideL10nForm) Parse(r *http.Request) error {
if err := r.ParseForm(); err != nil {
return err
}
l10n.Caption.FillValue(r)
return nil
}
func (l10n *slideL10nForm) Valid(l *locale.Locale) bool {
v := form.NewValidator(l)
return v.AllOK
}

View File

@ -13,6 +13,7 @@ import (
"github.com/jackc/pgx/v4"
"dev.tandem.ws/tandem/camper/pkg/auth"
"dev.tandem.ws/tandem/camper/pkg/carousel"
"dev.tandem.ws/tandem/camper/pkg/database"
httplib "dev.tandem.ws/tandem/camper/pkg/http"
"dev.tandem.ws/tandem/camper/pkg/locale"
@ -51,6 +52,7 @@ func (h *PublicHandler) Handler(user *auth.User, company *auth.Company, conn *da
type publicPage struct {
*template.PublicPage
Name string
Carousel []*carousel.Slide
Prices []*typePrice
Description gotemplate.HTML
}
@ -71,6 +73,7 @@ type optionPrice struct {
func newPublicPage(ctx context.Context, conn *database.Conn, loc *locale.Locale, slug string) (*publicPage, error) {
page := &publicPage{
PublicPage: template.NewPublicPage(),
Carousel: mustCollectSlides(ctx, conn, loc, slug),
}
row := conn.QueryRow(ctx, `
select coalesce(i18n.name, campsite_type.name) as l10n_name

View File

@ -144,10 +144,10 @@ func MustCollectSlides(ctx context.Context, company *auth.Company, conn *databas
type SlideEntry struct {
Slide
ID int
Translations []*translation
Translations []*Translation
}
type translation struct {
type Translation struct {
Language string
Endonym string
Missing bool
@ -184,7 +184,7 @@ func CollectSlideEntries(ctx context.Context, company *auth.Company, conn *datab
return nil, err
}
for _, el := range translations.Elements {
slide.Translations = append(slide.Translations, &translation{
slide.Translations = append(slide.Translations, &Translation{
el.Fields[0].Get().(string),
el.Fields[1].Get().(string),
el.Fields[2].Get().(bool),

211
po/ca.po
View File

@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: camper\n"
"Report-Msgid-Bugs-To: jordi@tandem.blog\n"
"POT-Creation-Date: 2023-10-06 22:10+0200\n"
"POT-Creation-Date: 2023-10-12 16:26+0200\n"
"PO-Revision-Date: 2023-07-22 23:45+0200\n"
"Last-Translator: jordi fita mas <jordi@tandem.blog>\n"
"Language-Team: Catalan <ca@dodds.net>\n"
@ -77,22 +77,22 @@ msgstr "Descobreix lentorn"
msgid "Come and enjoy!"
msgstr "Vine a gaudir!"
#: web/templates/public/campsite/type.gohtml:17
#: web/templates/public/campsite/type.gohtml:40
#: web/templates/admin/campsite/option/form.gohtml:58
#: web/templates/admin/campsite/type/form.gohtml:73
msgctxt "title"
msgid "Prices"
msgstr "Preus"
#: web/templates/public/campsite/type.gohtml:26
#: web/templates/public/campsite/type.gohtml:50
msgid "%s €/night"
msgstr "%s €/nit"
#: web/templates/public/campsite/type.gohtml:28
#: web/templates/public/campsite/type.gohtml:52
msgid "%s: %s €/night"
msgstr "%s: %s €/nit"
#: web/templates/public/campsite/type.gohtml:31
#: web/templates/public/campsite/type.gohtml:55
msgid "*Minimum %d nights per stay"
msgstr "*Mínim %d nits per estada"
@ -203,11 +203,14 @@ msgstr "Nova diapositiva del carrusel"
#: web/templates/admin/carousel/form.gohtml:37
#: web/templates/admin/carousel/l10n.gohtml:20
#: web/templates/admin/campsite/carousel/form.gohtml:37
#: web/templates/admin/campsite/carousel/l10n.gohtml:20
msgctxt "input"
msgid "Caption"
msgstr "Llegenda"
#: web/templates/admin/carousel/form.gohtml:47
#: web/templates/admin/campsite/carousel/form.gohtml:47
#: web/templates/admin/campsite/form.gohtml:70
#: web/templates/admin/campsite/option/form.gohtml:78
#: web/templates/admin/campsite/type/form.gohtml:108
@ -219,6 +222,7 @@ msgid "Update"
msgstr "Actualitza"
#: web/templates/admin/carousel/form.gohtml:49
#: web/templates/admin/campsite/carousel/form.gohtml:49
#: web/templates/admin/campsite/form.gohtml:72
#: web/templates/admin/campsite/option/form.gohtml:80
#: web/templates/admin/campsite/type/form.gohtml:110
@ -235,6 +239,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/carousel/l10n.gohtml:21
#: web/templates/admin/campsite/option/l10n.gohtml:21
#: web/templates/admin/campsite/type/l10n.gohtml:21
#: web/templates/admin/campsite/type/l10n.gohtml:33
@ -245,6 +250,7 @@ msgid "Source:"
msgstr "Origen:"
#: web/templates/admin/carousel/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
#: web/templates/admin/campsite/type/l10n.gohtml:36
@ -256,6 +262,7 @@ msgid "Translation:"
msgstr "Traducció:"
#: web/templates/admin/carousel/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:45
#: web/templates/admin/season/l10n.gohtml:32
@ -264,6 +271,90 @@ msgctxt "action"
msgid "Translate"
msgstr "Tradueix"
#: web/templates/admin/campsite/carousel/form.gohtml:8
#: web/templates/admin/campsite/carousel/form.gohtml:25
msgctxt "title"
msgid "Edit Campsite Type Carousel Slide"
msgstr "Edició de la diapositiva del carrusel del tipus dallotjament"
#: web/templates/admin/campsite/carousel/form.gohtml:10
#: web/templates/admin/campsite/carousel/form.gohtml:27
msgctxt "title"
msgid "New Campsite Type Carousel Slide"
msgstr "Nova diapositiva del carrusel del tipus dallotjament"
#: web/templates/admin/campsite/carousel/index.gohtml:6
#: web/templates/admin/campsite/carousel/index.gohtml:11
msgctxt "title"
msgid "Campsite Type Carousel"
msgstr "Carrusel del tipus dallotjament"
#: web/templates/admin/campsite/carousel/index.gohtml:12
#: web/templates/admin/services/index.gohtml:12
#: web/templates/admin/home/index.gohtml:12
msgctxt "action"
msgid "Add slide"
msgstr "Afegeix diapositiva"
#: web/templates/admin/campsite/carousel/index.gohtml:17
#: web/templates/admin/services/index.gohtml:17
#: web/templates/admin/home/index.gohtml:17
msgctxt "header"
msgid "Image"
msgstr "Imatge"
#: web/templates/admin/campsite/carousel/index.gohtml:18
#: web/templates/admin/services/index.gohtml:18
#: web/templates/admin/home/index.gohtml:18
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
#: web/templates/admin/home/index.gohtml:20
msgctxt "header"
msgid "Actions"
msgstr "Accions"
#: web/templates/admin/campsite/carousel/index.gohtml:24
#: web/templates/admin/services/index.gohtml:24
#: web/templates/admin/home/index.gohtml:24
msgid "Are you sure you wish to delete this slide?"
msgstr "Esteu segur de voler esborrar aquesta diapositiva?"
#: web/templates/admin/campsite/carousel/index.gohtml:42
#: web/templates/admin/services/index.gohtml:42
#: web/templates/admin/services/index.gohtml:80
#: web/templates/admin/home/index.gohtml:42
msgctxt "action"
msgid "Delete"
msgstr "Esborra"
#: web/templates/admin/campsite/carousel/index.gohtml:50
#: web/templates/admin/services/index.gohtml:50
#: web/templates/admin/home/index.gohtml:50
msgid "No slides added yet."
msgstr "No sha afegit cap diapositiva encara."
#: web/templates/admin/campsite/carousel/l10n.gohtml:7
#: web/templates/admin/campsite/carousel/l10n.gohtml:14
msgctxt "title"
msgid "Translate Campsite Type Carousel Slide to %s"
msgstr "Traducció de la diapositiva del carrusel del tipus dallotjament a %s"
#: web/templates/admin/campsite/form.gohtml:8
#: web/templates/admin/campsite/form.gohtml:25
msgctxt "title"
@ -355,16 +446,6 @@ msgctxt "header"
msgid "Name"
msgstr "Nom"
#: 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/option/index.gohtml:39
msgid "No campsite type options added yet."
msgstr "No sha afegit cap opció al tipus dallotjament encara."
@ -391,13 +472,13 @@ msgid "Type"
msgstr "Tipus"
#: web/templates/admin/campsite/index.gohtml:28
#: web/templates/admin/campsite/type/index.gohtml:37
#: web/templates/admin/campsite/type/index.gohtml:39
#: 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:37
#: web/templates/admin/campsite/type/index.gohtml:39
#: web/templates/admin/season/index.gohtml:39
msgid "No"
msgstr "No"
@ -419,7 +500,7 @@ msgid "New Campsite Type"
msgstr "Nou tipus dallotjament"
#: web/templates/admin/campsite/type/form.gohtml:37
#: web/templates/admin/campsite/type/index.gohtml:20
#: web/templates/admin/campsite/type/index.gohtml:21
msgctxt "campsite type"
msgid "Active"
msgstr "Actiu"
@ -464,12 +545,22 @@ msgctxt "header"
msgid "Options"
msgstr "Opcions"
#: web/templates/admin/campsite/type/index.gohtml:36
#: web/templates/admin/campsite/type/index.gohtml:20
msgctxt "header"
msgid "Carousel"
msgstr "Carrusel"
#: web/templates/admin/campsite/type/index.gohtml:37
msgctxt "action"
msgid "Edit Options"
msgstr "Edita les opcions"
#: web/templates/admin/campsite/type/index.gohtml:43
#: web/templates/admin/campsite/type/index.gohtml:38
msgctxt "action"
msgid "Edit Carousel"
msgstr "Edita el carrusel"
#: web/templates/admin/campsite/type/index.gohtml:45
msgid "No campsite types added yet."
msgstr "No sha afegit cap tipus dallotjament encara."
@ -631,48 +722,6 @@ msgctxt "title"
msgid "Carousel"
msgstr "Carrusel"
#: web/templates/admin/services/index.gohtml:12
#: web/templates/admin/home/index.gohtml:12
msgctxt "action"
msgid "Add slide"
msgstr "Afegeix diapositiva"
#: web/templates/admin/services/index.gohtml:17
#: web/templates/admin/home/index.gohtml:17
msgctxt "header"
msgid "Image"
msgstr "Imatge"
#: web/templates/admin/services/index.gohtml:18
#: web/templates/admin/home/index.gohtml:18
msgctxt "header"
msgid "Caption"
msgstr "Llegenda"
#: web/templates/admin/services/index.gohtml:20
#: web/templates/admin/services/index.gohtml:61
#: web/templates/admin/home/index.gohtml:20
msgctxt "header"
msgid "Actions"
msgstr "Accions"
#: web/templates/admin/services/index.gohtml:24
#: web/templates/admin/home/index.gohtml:24
msgid "Are you sure you wish to delete this slide?"
msgstr "Esteu segur de voler esborrar aquesta diapositiva?"
#: web/templates/admin/services/index.gohtml:42
#: web/templates/admin/services/index.gohtml:80
#: web/templates/admin/home/index.gohtml:42
msgctxt "action"
msgid "Delete"
msgstr "Esborra"
#: web/templates/admin/services/index.gohtml:50
#: web/templates/admin/home/index.gohtml:50
msgid "No slides added yet."
msgstr "No sha afegit cap diapositiva encara."
#: web/templates/admin/services/index.gohtml:54
msgctxt "action"
msgid "Add service"
@ -893,21 +942,21 @@ msgctxt "title"
msgid "Upload Media"
msgstr "Pujada de mèdia"
#: pkg/carousel/admin.go:233
#: pkg/carousel/admin.go:233 pkg/campsite/types/carousel.go:232
msgctxt "input"
msgid "Slide image"
msgstr "Imatge de la diapositiva"
#: pkg/carousel/admin.go:234
#: pkg/carousel/admin.go:234 pkg/campsite/types/carousel.go:233
msgctxt "action"
msgid "Set slide image"
msgstr "Estableix la imatge de la diapositiva"
#: pkg/carousel/admin.go:286
#: pkg/carousel/admin.go:286 pkg/campsite/types/carousel.go:287
msgid "Slide image can not be empty."
msgstr "No podeu deixar la imatge de la diapositiva en blanc."
#: pkg/carousel/admin.go:287
#: pkg/carousel/admin.go:287 pkg/campsite/types/carousel.go:288
msgid "Slide image must be an image media type."
msgstr "La imatge de la diapositiva ha de ser un mèdia de tipus imatge."
@ -934,7 +983,7 @@ msgstr "Automàtic"
#: pkg/app/user.go:249 pkg/campsite/types/l10n.go:73
#: pkg/campsite/types/l10n.go:128 pkg/campsite/types/option.go:336
#: pkg/campsite/types/admin.go:389 pkg/season/l10n.go:69
#: pkg/campsite/types/admin.go:391 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."
@ -955,7 +1004,7 @@ 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:390
#: pkg/campsite/types/option.go:337 pkg/campsite/types/admin.go:392
msgid "Name must have at least one letter."
msgstr "El nom ha de tenir com a mínim una lletra."
@ -983,57 +1032,57 @@ msgstr "El valor del màxim ha de ser un número enter."
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:403
#: pkg/campsite/types/option.go:353 pkg/campsite/types/admin.go:405
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:404
#: pkg/campsite/types/option.go:354 pkg/campsite/types/admin.go:406
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:405
#: pkg/campsite/types/option.go:355 pkg/campsite/types/admin.go:407
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:280
#: pkg/campsite/types/admin.go:282
msgctxt "input"
msgid "Cover image"
msgstr "Imatge de portada"
#: pkg/campsite/types/admin.go:281
#: pkg/campsite/types/admin.go:283
msgctxt "action"
msgid "Set campsite type cover"
msgstr "Estableix la portada del tipus dallotjament"
#: pkg/campsite/types/admin.go:392
#: pkg/campsite/types/admin.go:394
msgid "Cover image can not be empty."
msgstr "No podeu deixar la imatge de portada en blanc."
#: pkg/campsite/types/admin.go:393
#: pkg/campsite/types/admin.go:395
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:397
#: pkg/campsite/types/admin.go:399
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:398
#: pkg/campsite/types/admin.go:400
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:399
#: pkg/campsite/types/admin.go:401
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:408
#: pkg/campsite/types/admin.go:410
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:409
#: pkg/campsite/types/admin.go:411
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:410
#: pkg/campsite/types/admin.go:412
msgid "Minimum number of nights must be one or greater."
msgstr "El número mínim de nits no pot ser zero."

211
po/es.po
View File

@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: camper\n"
"Report-Msgid-Bugs-To: jordi@tandem.blog\n"
"POT-Creation-Date: 2023-10-06 22:10+0200\n"
"POT-Creation-Date: 2023-10-12 16:26+0200\n"
"PO-Revision-Date: 2023-07-22 23:46+0200\n"
"Last-Translator: jordi fita mas <jordi@tandem.blog>\n"
"Language-Team: Spanish <es@tp.org.es>\n"
@ -77,22 +77,22 @@ msgstr "Descubre el entorno"
msgid "Come and enjoy!"
msgstr "¡Ven a disfrutar!"
#: web/templates/public/campsite/type.gohtml:17
#: web/templates/public/campsite/type.gohtml:40
#: web/templates/admin/campsite/option/form.gohtml:58
#: web/templates/admin/campsite/type/form.gohtml:73
msgctxt "title"
msgid "Prices"
msgstr "Precios"
#: web/templates/public/campsite/type.gohtml:26
#: web/templates/public/campsite/type.gohtml:50
msgid "%s €/night"
msgstr "%s €/noche"
#: web/templates/public/campsite/type.gohtml:28
#: web/templates/public/campsite/type.gohtml:52
msgid "%s: %s €/night"
msgstr ":%s: %s €/noche"
#: web/templates/public/campsite/type.gohtml:31
#: web/templates/public/campsite/type.gohtml:55
msgid "*Minimum %d nights per stay"
msgstr "*Mínimo %d noches por estancia"
@ -203,11 +203,14 @@ msgstr "Nueva diapositiva del carrusel"
#: web/templates/admin/carousel/form.gohtml:37
#: web/templates/admin/carousel/l10n.gohtml:20
#: web/templates/admin/campsite/carousel/form.gohtml:37
#: web/templates/admin/campsite/carousel/l10n.gohtml:20
msgctxt "input"
msgid "Caption"
msgstr "Leyenda"
#: web/templates/admin/carousel/form.gohtml:47
#: web/templates/admin/campsite/carousel/form.gohtml:47
#: web/templates/admin/campsite/form.gohtml:70
#: web/templates/admin/campsite/option/form.gohtml:78
#: web/templates/admin/campsite/type/form.gohtml:108
@ -219,6 +222,7 @@ msgid "Update"
msgstr "Actualizar"
#: web/templates/admin/carousel/form.gohtml:49
#: web/templates/admin/campsite/carousel/form.gohtml:49
#: web/templates/admin/campsite/form.gohtml:72
#: web/templates/admin/campsite/option/form.gohtml:80
#: web/templates/admin/campsite/type/form.gohtml:110
@ -235,6 +239,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/carousel/l10n.gohtml:21
#: web/templates/admin/campsite/option/l10n.gohtml:21
#: web/templates/admin/campsite/type/l10n.gohtml:21
#: web/templates/admin/campsite/type/l10n.gohtml:33
@ -245,6 +250,7 @@ msgid "Source:"
msgstr "Origen:"
#: web/templates/admin/carousel/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
#: web/templates/admin/campsite/type/l10n.gohtml:36
@ -256,6 +262,7 @@ msgid "Translation:"
msgstr "Traducción"
#: web/templates/admin/carousel/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:45
#: web/templates/admin/season/l10n.gohtml:32
@ -264,6 +271,90 @@ msgctxt "action"
msgid "Translate"
msgstr "Traducir"
#: web/templates/admin/campsite/carousel/form.gohtml:8
#: web/templates/admin/campsite/carousel/form.gohtml:25
msgctxt "title"
msgid "Edit Campsite Type Carousel Slide"
msgstr "Edición de la diapositiva del carrusel del tipo de alojamiento"
#: web/templates/admin/campsite/carousel/form.gohtml:10
#: web/templates/admin/campsite/carousel/form.gohtml:27
msgctxt "title"
msgid "New Campsite Type Carousel Slide"
msgstr "Nueva diapositiva del carrusel del tipo de alojamiento"
#: web/templates/admin/campsite/carousel/index.gohtml:6
#: web/templates/admin/campsite/carousel/index.gohtml:11
msgctxt "title"
msgid "Campsite Type Carousel"
msgstr "Carrusel del tipo de alojamiento"
#: web/templates/admin/campsite/carousel/index.gohtml:12
#: web/templates/admin/services/index.gohtml:12
#: web/templates/admin/home/index.gohtml:12
msgctxt "action"
msgid "Add slide"
msgstr "Añadir diapositiva"
#: web/templates/admin/campsite/carousel/index.gohtml:17
#: web/templates/admin/services/index.gohtml:17
#: web/templates/admin/home/index.gohtml:17
msgctxt "header"
msgid "Image"
msgstr "Imagen"
#: web/templates/admin/campsite/carousel/index.gohtml:18
#: web/templates/admin/services/index.gohtml:18
#: web/templates/admin/home/index.gohtml:18
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
#: web/templates/admin/home/index.gohtml:20
msgctxt "header"
msgid "Actions"
msgstr "Acciones"
#: web/templates/admin/campsite/carousel/index.gohtml:24
#: web/templates/admin/services/index.gohtml:24
#: web/templates/admin/home/index.gohtml:24
msgid "Are you sure you wish to delete this slide?"
msgstr "¿Estáis seguro de querer borrar esta diapositiva?"
#: web/templates/admin/campsite/carousel/index.gohtml:42
#: web/templates/admin/services/index.gohtml:42
#: web/templates/admin/services/index.gohtml:80
#: web/templates/admin/home/index.gohtml:42
msgctxt "action"
msgid "Delete"
msgstr "Borrar"
#: web/templates/admin/campsite/carousel/index.gohtml:50
#: web/templates/admin/services/index.gohtml:50
#: web/templates/admin/home/index.gohtml:50
msgid "No slides added yet."
msgstr "No se ha añadido ninguna diapositiva todavía."
#: web/templates/admin/campsite/carousel/l10n.gohtml:7
#: web/templates/admin/campsite/carousel/l10n.gohtml:14
msgctxt "title"
msgid "Translate Campsite Type Carousel Slide to %s"
msgstr "Traducción de la diapositiva de carrusel del tipo de alojamiento a %s"
#: web/templates/admin/campsite/form.gohtml:8
#: web/templates/admin/campsite/form.gohtml:25
msgctxt "title"
@ -355,16 +446,6 @@ msgctxt "header"
msgid "Name"
msgstr "Nombre"
#: 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/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."
@ -391,13 +472,13 @@ msgid "Type"
msgstr "Tipo"
#: web/templates/admin/campsite/index.gohtml:28
#: web/templates/admin/campsite/type/index.gohtml:37
#: web/templates/admin/campsite/type/index.gohtml:39
#: 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:37
#: web/templates/admin/campsite/type/index.gohtml:39
#: web/templates/admin/season/index.gohtml:39
msgid "No"
msgstr "No"
@ -419,7 +500,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:20
#: web/templates/admin/campsite/type/index.gohtml:21
msgctxt "campsite type"
msgid "Active"
msgstr "Activo"
@ -464,12 +545,22 @@ msgctxt "header"
msgid "Options"
msgstr "Opciones"
#: web/templates/admin/campsite/type/index.gohtml:36
#: web/templates/admin/campsite/type/index.gohtml:20
msgctxt "header"
msgid "Carousel"
msgstr "Carrusel"
#: web/templates/admin/campsite/type/index.gohtml:37
msgctxt "action"
msgid "Edit Options"
msgstr "Editar opciones"
#: web/templates/admin/campsite/type/index.gohtml:43
#: web/templates/admin/campsite/type/index.gohtml:38
msgctxt "action"
msgid "Edit Carousel"
msgstr "Editar el carrusel"
#: web/templates/admin/campsite/type/index.gohtml:45
msgid "No campsite types added yet."
msgstr "No se ha añadido ningún tipo de alojamiento todavía."
@ -631,48 +722,6 @@ msgctxt "title"
msgid "Carousel"
msgstr "Carrusel"
#: web/templates/admin/services/index.gohtml:12
#: web/templates/admin/home/index.gohtml:12
msgctxt "action"
msgid "Add slide"
msgstr "Añadir diapositiva"
#: web/templates/admin/services/index.gohtml:17
#: web/templates/admin/home/index.gohtml:17
msgctxt "header"
msgid "Image"
msgstr "Imagen"
#: web/templates/admin/services/index.gohtml:18
#: web/templates/admin/home/index.gohtml:18
msgctxt "header"
msgid "Caption"
msgstr "Leyenda"
#: web/templates/admin/services/index.gohtml:20
#: web/templates/admin/services/index.gohtml:61
#: web/templates/admin/home/index.gohtml:20
msgctxt "header"
msgid "Actions"
msgstr "Acciones"
#: web/templates/admin/services/index.gohtml:24
#: web/templates/admin/home/index.gohtml:24
msgid "Are you sure you wish to delete this slide?"
msgstr "¿Estáis seguro de querer borrar esta diapositiva?"
#: web/templates/admin/services/index.gohtml:42
#: web/templates/admin/services/index.gohtml:80
#: web/templates/admin/home/index.gohtml:42
msgctxt "action"
msgid "Delete"
msgstr "Borrar"
#: web/templates/admin/services/index.gohtml:50
#: web/templates/admin/home/index.gohtml:50
msgid "No slides added yet."
msgstr "No se ha añadido ninguna diapositiva todavía."
#: web/templates/admin/services/index.gohtml:54
msgctxt "action"
msgid "Add service"
@ -893,21 +942,21 @@ msgctxt "title"
msgid "Upload Media"
msgstr "Subida de medio"
#: pkg/carousel/admin.go:233
#: pkg/carousel/admin.go:233 pkg/campsite/types/carousel.go:232
msgctxt "input"
msgid "Slide image"
msgstr "Imagen de la diapositiva"
#: pkg/carousel/admin.go:234
#: pkg/carousel/admin.go:234 pkg/campsite/types/carousel.go:233
msgctxt "action"
msgid "Set slide image"
msgstr "Establecer la imagen de la diapositiva"
#: pkg/carousel/admin.go:286
#: pkg/carousel/admin.go:286 pkg/campsite/types/carousel.go:287
msgid "Slide image can not be empty."
msgstr "No podéis dejar la imagen de la diapositiva en blanco."
#: pkg/carousel/admin.go:287
#: pkg/carousel/admin.go:287 pkg/campsite/types/carousel.go:288
msgid "Slide image must be an image media type."
msgstr "La imagen de la diapositiva tiene que ser un medio de tipo imagen."
@ -934,7 +983,7 @@ msgstr "Automático"
#: pkg/app/user.go:249 pkg/campsite/types/l10n.go:73
#: pkg/campsite/types/l10n.go:128 pkg/campsite/types/option.go:336
#: pkg/campsite/types/admin.go:389 pkg/season/l10n.go:69
#: pkg/campsite/types/admin.go:391 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."
@ -955,7 +1004,7 @@ 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:390
#: pkg/campsite/types/option.go:337 pkg/campsite/types/admin.go:392
msgid "Name must have at least one letter."
msgstr "El nombre tiene que tener como mínimo una letra."
@ -983,57 +1032,57 @@ msgstr "El valor del máximo tiene que ser un número entero."
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:403
#: pkg/campsite/types/option.go:353 pkg/campsite/types/admin.go:405
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:404
#: pkg/campsite/types/option.go:354 pkg/campsite/types/admin.go:406
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:405
#: pkg/campsite/types/option.go:355 pkg/campsite/types/admin.go:407
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:280
#: pkg/campsite/types/admin.go:282
msgctxt "input"
msgid "Cover image"
msgstr "Imagen de portada"
#: pkg/campsite/types/admin.go:281
#: pkg/campsite/types/admin.go:283
msgctxt "action"
msgid "Set campsite type cover"
msgstr "Establecer la portada del tipo de alojamiento"
#: pkg/campsite/types/admin.go:392
#: pkg/campsite/types/admin.go:394
msgid "Cover image can not be empty."
msgstr "No podéis dejar la imagen de portada en blanco."
#: pkg/campsite/types/admin.go:393
#: pkg/campsite/types/admin.go:395
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:397
#: pkg/campsite/types/admin.go:399
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:398
#: pkg/campsite/types/admin.go:400
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:399
#: pkg/campsite/types/admin.go:401
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:408
#: pkg/campsite/types/admin.go:410
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:409
#: pkg/campsite/types/admin.go:411
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:410
#: pkg/campsite/types/admin.go:412
msgid "Minimum number of nights must be one or greater."
msgstr "El número mínimo de noches no puede ser cero."

View File

@ -0,0 +1,7 @@
-- Revert camper:add_campsite_type_carousel_slide from pg
begin;
drop function if exists camper.add_campsite_type_carousel_slide(uuid, integer, text);
commit;

View File

@ -0,0 +1,7 @@
-- Revert camper:campsite_type_carousel from pg
begin;
drop table if exists camper.campsite_type_carousel;
commit;

View File

@ -0,0 +1,7 @@
-- Revert camper:campsite_type_carousel_i18n from pg
begin;
drop table if exists camper.campsite_type_carousel_i18n;
commit;

View File

@ -0,0 +1,7 @@
-- Revert camper:remove_campsite_type_carousel_slide from pg
begin;
drop function if exists camper.remove_campsite_type_carousel_slide(uuid, integer);
commit;

View File

@ -0,0 +1,7 @@
-- Revert camper:translate_campsite_type_carousel_slide from pg
begin;
drop function if exists camper.translate_campsite_type_carousel_slide(uuid, integer, text, text);
commit;

View File

@ -99,3 +99,8 @@ add_campsite_type_option [roles schema_camper campsite_type_option campsite_type
edit_campsite_type_option [roles schema_camper campsite_type_option] 2023-10-06T09:51:02Z jordi fita mas <jordi@tandem.blog> # Add function to edit campsite type options
extension_postgis [schema_public] 2023-10-06T17:18:52Z jordi fita mas <jordi@tandem.blog> # Add PostGIS extension
company_geography [roles schema_camper company user_profile extension_postgis] 2023-10-06T17:53:34Z jordi fita mas <jordi@tandem.blog> # Add the relation for the GPS coordinates of companies
campsite_type_carousel [roles schema_camper campsite_type media user_profile] 2023-10-09T17:18:38Z jordi fita mas <jordi@tandem.blog> # Add the carousel for campsite types
campsite_type_carousel_i18n [roles schema_camper campsite_type_carousel language] 2023-10-09T17:45:09Z jordi fita mas <jordi@tandem.blog> # Add relation for campsite type carousel translations
add_campsite_type_carousel_slide [roles schema_camper campsite_type_carousel campsite_type] 2023-10-09T17:59:49Z jordi fita mas <jordi@tandem.blog> # 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 <jordi@tandem.blog> # 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 <jordi@tandem.blog> # Add function to remove campsite type slides

View File

@ -0,0 +1,94 @@
-- Test add_campsite_type_carousel_slide
set client_min_messages to warning;
create extension if not exists pgtap;
reset client_min_messages;
begin;
select plan(14);
set search_path to camper, public;
select has_function('camper', 'add_campsite_type_carousel_slide', array['uuid', 'integer', 'text']);
select function_lang_is('camper', 'add_campsite_type_carousel_slide', array['uuid', 'integer', 'text'], 'sql');
select function_returns('camper', 'add_campsite_type_carousel_slide', array['uuid', 'integer', 'text'], 'integer');
select isnt_definer('camper', 'add_campsite_type_carousel_slide', array['uuid', 'integer', 'text']);
select volatility_is('camper', 'add_campsite_type_carousel_slide', array['uuid', 'integer', 'text'], 'volatile');
select function_privs_are('camper', 'add_campsite_type_carousel_slide', array['uuid', 'integer', 'text'], 'guest', array[]::text[]);
select function_privs_are('camper', 'add_campsite_type_carousel_slide', array['uuid', 'integer', 'text'], 'employee', array[]::text[]);
select function_privs_are('camper', 'add_campsite_type_carousel_slide', array['uuid', 'integer', 'text'], 'admin', array['EXECUTE']);
select function_privs_are('camper', 'add_campsite_type_carousel_slide', array['uuid', 'integer', 'text'], 'authenticator', array[]::text[]);
set client_min_messages to warning;
truncate campsite_type_carousel_i18n cascade;
truncate campsite_type_carousel 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"};')
, ('image/x-xpixmap', 'static char *s[]={"1 1 1 1","a c #ff00ff","a"};')
, ('image/x-xpixmap', 'static char *s[]={"1 1 1 1","a c #ffff00","a"};')
, ('text/plain', 'hello, world!')
, ('image/svg+xml', '<svg xmlns="http://www.w3.org/2000/svg" width="1" height="1"/>')
;
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"};'))
, (3, 1, 'cover3.xpm', sha256('static char *s[]={"1 1 1 1","a c #ff00ff","a"};'))
, (4, 1, 'cover4.xpm', sha256('static char *s[]={"1 1 1 1","a c #ffff00","a"};'))
, (5, 1, 'text.txt', sha256('hello, world!'))
, (6, 1, 'image.svg', sha256('<svg xmlns="http://www.w3.org/2000/svg" width="1" height="1"/>'))
;
insert into campsite_type (campsite_type_id, company_id, slug, media_id, name, description, max_campers, dogs_allowed, active)
values (10, 1, '87452b88-b48f-48d3-bb6c-0296de64164e', 2, 'Type A', '<p>A</p>', 5, false, true)
, (11, 1, '9b6370f7-f941-46f2-bc6e-de455675bd0a', 3, 'Type B', '<p>B</p>', 4, true, false)
;
insert into campsite_type_carousel (campsite_type_id, media_id, caption)
values (10, 4, 'A caption')
;
select lives_ok(
$$ select add_campsite_type_carousel_slide('87452b88-b48f-48d3-bb6c-0296de64164e', 5, 'A caption') $$,
'Should be able to add a carousel slide with a caption'
);
select lives_ok(
$$ select add_campsite_type_carousel_slide('9b6370f7-f941-46f2-bc6e-de455675bd0a', 6, null) $$,
'Should be able to add a carousel slide without caption'
);
select lives_ok(
$$ select add_campsite_type_carousel_slide('87452b88-b48f-48d3-bb6c-0296de64164e', 4, 'New caption') $$,
'Should be able to overwrite a slide with a new caption'
);
select bag_eq(
$$ select campsite_type_id, media_id, caption from campsite_type_carousel $$,
$$ values (10, 4, 'New caption')
, (10, 5, 'A caption')
, (11, 6, '')
$$,
'Should have all three slides'
);
select is_empty(
$$ select * from campsite_type_carousel_i18n $$,
'Should not have added any translation'
);
select *
from finish();
rollback;

View File

@ -0,0 +1,223 @@
-- Test campsite_type_carousel
set client_min_messages to warning;
create extension if not exists pgtap;
reset client_min_messages;
begin;
select plan(40);
set search_path to camper, public;
select has_table('campsite_type_carousel');
select has_pk('campsite_type_carousel');
select col_is_pk('campsite_type_carousel', array['campsite_type_id', 'media_id']);
select table_privs_are('campsite_type_carousel', 'guest', array['SELECT']);
select table_privs_are('campsite_type_carousel', 'employee', array['SELECT']);
select table_privs_are('campsite_type_carousel', 'admin', array['SELECT', 'INSERT', 'UPDATE', 'DELETE']);
select table_privs_are('campsite_type_carousel', 'authenticator', array[]::text[]);
select has_column('campsite_type_carousel', 'campsite_type_id');
select col_is_fk('campsite_type_carousel', 'campsite_type_id');
select fk_ok('campsite_type_carousel', 'campsite_type_id', 'campsite_type', 'campsite_type_id');
select col_type_is('campsite_type_carousel', 'campsite_type_id', 'integer');
select col_not_null('campsite_type_carousel', 'campsite_type_id');
select col_hasnt_default('campsite_type_carousel', 'campsite_type_id');
select has_column('campsite_type_carousel', 'media_id');
select col_is_fk('campsite_type_carousel', 'media_id');
select fk_ok('campsite_type_carousel', 'media_id', 'media', 'media_id');
select col_type_is('campsite_type_carousel', 'media_id', 'integer');
select col_not_null('campsite_type_carousel', 'media_id');
select col_hasnt_default('campsite_type_carousel', 'media_id');
select has_column('campsite_type_carousel', 'caption');
select col_type_is('campsite_type_carousel', 'caption', 'text');
select col_not_null('campsite_type_carousel', 'caption');
select col_hasnt_default('campsite_type_carousel', 'caption');
set client_min_messages to warning;
truncate campsite_type_carousel 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"};')
, ('text/plain', 'content2')
, ('text/plain', 'content3')
, ('text/plain', 'content4')
, ('text/plain', 'content5')
;
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"};'))
, ( 7, 2, 'text2.txt', sha256('content2'))
, ( 8, 2, 'text3.txt', sha256('content3'))
, ( 9, 4, 'cover4.xpm', sha256('static char *s[]={"1 1 1 1","a c #ffffff","a"};'))
, (10, 4, 'text4.txt', sha256('content4'))
, (11, 4, 'text5.txt', sha256('content5'))
;
insert into campsite_type (campsite_type_id, company_id, name, media_id, dogs_allowed, max_campers)
values (21, 2, 'Wooden lodge', 6, false, 7)
, (22, 4, 'Bungalow', 9, false, 6)
;
insert into campsite_type_carousel (campsite_type_id, media_id, caption)
values (21, 7, 'Caption 7')
, (22, 10, 'Caption 10')
;
prepare carousel_data as
select campsite_type_id, media_id, caption
from campsite_type_carousel
;
set role guest;
select bag_eq(
'carousel_data',
$$ values (21, 7, 'Caption 7')
, (22, 10, 'Caption 10')
$$,
'Everyone should be able to list all campsite slides across all companies'
);
reset role;
select set_cookie('44facbb30d8a419dfd4bfbc44a4b5539d4970148dfc84bed0e/demo@tandem.blog', 'co2');
select lives_ok(
$$ insert into campsite_type_carousel(campsite_type_id, media_id, caption) values (21, 8, 'Caption 8') $$,
'Admin from company 2 should be able to insert a new slide to campsite types of that company.'
);
select bag_eq(
'carousel_data',
$$ values (21, 7, 'Caption 7')
, (21, 8, 'Caption 8')
, (22, 10, 'Caption 10')
$$,
'The new row should have been added'
);
select lives_ok(
$$ update campsite_type_carousel set caption = 'Caption 8.8' where media_id = 8 $$,
'Admin from company 2 should be able to update campsite type slides of that company.'
);
select bag_eq(
'carousel_data',
$$ values (21, 7, 'Caption 7')
, (21, 8, 'Caption 8.8')
, (22, 10, 'Caption 10')
$$,
'The row should have been updated.'
);
select lives_ok(
$$ delete from campsite_type_carousel where media_id = 8 $$,
'Admin from company 2 should be able to delete campsite type slides from that company.'
);
select bag_eq(
'carousel_data',
$$ values (21, 7, 'Caption 7')
, (22, 10, 'Caption 10')
$$,
'The row should have been deleted.'
);
select throws_ok(
$$ insert into campsite_type_carousel (campsite_type_id, media_id, caption) values (21, 10, 'Nope') $$,
'42501', 'new row violates row-level security policy for table "campsite_type_carousel"',
'Admin from company 2 should NOT be able to insert a new campsite types slide from a media of company 4.'
);
select throws_ok(
$$ insert into campsite_type_carousel (campsite_type_id, media_id, caption) values (22, 8, 'Nope') $$,
'42501', 'new row violates row-level security policy for table "campsite_type_carousel"',
'Admin from company 2 should NOT be able to insert a slide to a campsite type of company 4.'
);
select lives_ok(
$$ update campsite_type_carousel set caption = 'Nope' where campsite_type_id = 22 $$,
'Admin from company 2 should not be able to update slides of campsite types from company 4, but no error if campsite_type_id or media_id is not changed.'
);
select lives_ok(
$$ update campsite_type_carousel set caption = 'Nope' where media_id = 10 $$,
'Admin from company 2 should not be able to update slides of campsite types from company 4, but no error if campsite_type_id or media_id is not changed.'
);
select bag_eq(
'carousel_data',
$$ values (21, 7, 'Caption 7')
, (22, 10, 'Caption 10')
$$,
'No row should have been changed.'
);
select throws_ok(
$$ update campsite_type_carousel set campsite_type_id = 22 where campsite_type_id = 21 $$,
'42501', 'new row violates row-level security policy for table "campsite_type_carousel"',
'Admin from company 2 should NOT be able to move slides to campsite types of company 4'
);
select throws_ok(
$$ update campsite_type_carousel set media_id = 11 where media_id = 7 $$,
'42501', 'new row violates row-level security policy for table "campsite_type_carousel"',
'Admin from company 2 should NOT be able to use media from company 4'
);
select lives_ok(
$$ delete from campsite_type_carousel where campsite_type_id = 22 $$,
'Admin from company 2 should NOT be able to delete slides of campsite types from company 4, but not error is thrown'
);
select lives_ok(
$$ delete from campsite_type_carousel where media_id = 10 $$,
'Admin from company 2 should NOT be able to delete slides with media from company 4, but not error is thrown'
);
select bag_eq(
'carousel_data',
$$ values (21, 7, 'Caption 7')
, (22, 10, 'Caption 10')
$$,
'No row should have been changed'
);
reset role;
select *
from finish();
rollback;

View File

@ -0,0 +1,49 @@
-- Test campsite_type_carousel_i18n
set client_min_messages to warning;
create extension if not exists pgtap;
reset client_min_messages;
begin;
select plan(27);
set search_path to camper, public;
select has_table('campsite_type_carousel_i18n');
select has_pk('campsite_type_carousel_i18n');
select col_is_pk('campsite_type_carousel_i18n', array['campsite_type_id', 'media_id', 'lang_tag']);
select col_is_fk('campsite_type_carousel_i18n', array['campsite_type_id', 'media_id']);
select fk_ok('campsite_type_carousel_i18n', array['campsite_type_id', 'media_id'], 'campsite_type_carousel', array['campsite_type_id', 'media_id']);
select table_privs_are('campsite_type_carousel_i18n', 'guest', array['SELECT']);
select table_privs_are('campsite_type_carousel_i18n', 'employee', array['SELECT']);
select table_privs_are('campsite_type_carousel_i18n', 'admin', array['SELECT', 'INSERT', 'UPDATE', 'DELETE']);
select table_privs_are('campsite_type_carousel_i18n', 'authenticator', array[]::text[]);
select has_column('campsite_type_carousel_i18n', 'campsite_type_id');
select col_type_is('campsite_type_carousel_i18n', 'campsite_type_id', 'integer');
select col_not_null('campsite_type_carousel_i18n', 'campsite_type_id');
select col_hasnt_default('campsite_type_carousel_i18n', 'campsite_type_id');
select has_column('campsite_type_carousel_i18n', 'media_id');
select col_type_is('campsite_type_carousel_i18n', 'media_id', 'integer');
select col_not_null('campsite_type_carousel_i18n', 'media_id');
select col_hasnt_default('campsite_type_carousel_i18n', 'media_id');
select has_column('campsite_type_carousel_i18n', 'lang_tag');
select col_is_fk('campsite_type_carousel_i18n', 'lang_tag');
select fk_ok('campsite_type_carousel_i18n', 'lang_tag', 'language', 'lang_tag');
select col_type_is('campsite_type_carousel_i18n', 'lang_tag', 'text');
select col_not_null('campsite_type_carousel_i18n', 'lang_tag');
select col_hasnt_default('campsite_type_carousel_i18n', 'lang_tag');
select has_column('campsite_type_carousel_i18n', 'caption');
select col_type_is('campsite_type_carousel_i18n', 'caption', 'text');
select col_not_null('campsite_type_carousel_i18n', 'caption');
select col_hasnt_default('campsite_type_carousel_i18n', 'caption');
select *
from finish();
rollback;

View File

@ -0,0 +1,106 @@
-- Test remove_campsite_type_carousel_slide
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', 'remove_campsite_type_carousel_slide', array['uuid', 'integer']);
select function_lang_is('camper', 'remove_campsite_type_carousel_slide', array['uuid', 'integer'], 'plpgsql');
select function_returns('camper', 'remove_campsite_type_carousel_slide', array['uuid', 'integer'], 'void');
select isnt_definer('camper', 'remove_campsite_type_carousel_slide', array['uuid', 'integer']);
select volatility_is('camper', 'remove_campsite_type_carousel_slide', array['uuid', 'integer'], 'volatile');
select function_privs_are('camper', 'remove_campsite_type_carousel_slide', array['uuid', 'integer'], 'guest', array[]::text[]);
select function_privs_are('camper', 'remove_campsite_type_carousel_slide', array['uuid', 'integer'], 'employee', array[]::text[]);
select function_privs_are('camper', 'remove_campsite_type_carousel_slide', array['uuid', 'integer'], 'admin', array['EXECUTE']);
select function_privs_are('camper', 'remove_campsite_type_carousel_slide', array['uuid', 'integer'], 'authenticator', array[]::text[]);
set client_min_messages to warning;
truncate campsite_type_carousel_i18n cascade;
truncate campsite_type_carousel 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"};')
, ('image/x-xpixmap', 'static char *s[]={"1 1 1 1","a c #ff00ff","a"};')
, ('image/x-xpixmap', 'static char *s[]={"1 1 1 1","a c #ffff00","a"};')
, ('text/plain', 'hello, world!')
, ('image/svg+xml', '<svg xmlns="http://www.w3.org/2000/svg" width="1" height="1"/>')
;
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"};'))
, (3, 1, 'cover3.xpm', sha256('static char *s[]={"1 1 1 1","a c #ff00ff","a"};'))
, (4, 1, 'cover4.xpm', sha256('static char *s[]={"1 1 1 1","a c #ffff00","a"};'))
, (5, 1, 'text.txt', sha256('hello, world!'))
, (6, 1, 'image.svg', sha256('<svg xmlns="http://www.w3.org/2000/svg" width="1" height="1"/>'))
;
insert into campsite_type (campsite_type_id, company_id, slug, media_id, name, description, max_campers, dogs_allowed, active)
values (10, 1, '87452b88-b48f-48d3-bb6c-0296de64164e', 2, 'Type A', '<p>A</p>', 5, false, true)
, (11, 1, '9b6370f7-f941-46f2-bc6e-de455675bd0a', 3, 'Type B', '<p>B</p>', 4, true, false)
;
insert into campsite_type_carousel (campsite_type_id, media_id, caption)
values (10, 3, 'Source caption')
, (10, 4, 'Source caption')
, (11, 5, 'Another caption')
, (11, 6, 'N/A')
;
insert into campsite_type_carousel_i18n (campsite_type_id, media_id, lang_tag, caption)
values (10, 3, 'en', 'Target caption')
, (10, 3, 'es', 'Target caption (spanish)')
, (10, 4, 'en', 'Target caption')
, (10, 4, 'es', 'Target caption (spanish)')
, (11, 5, 'en', 'Target caption')
, (11, 5, 'es', 'Target caption (spanish)')
, (11, 6, 'en', 'Target caption')
, (11, 6, 'es', 'Target caption (spanish)')
;
select lives_ok(
$$ select remove_campsite_type_carousel_slide('87452b88-b48f-48d3-bb6c-0296de64164e', 3) $$,
'Should be able to delete a slide from the first campsite_type'
);
select lives_ok(
$$ select remove_campsite_type_carousel_slide('9b6370f7-f941-46f2-bc6e-de455675bd0a', 6) $$,
'Should be able to delete a slide from the first campsite_type'
);
select bag_eq(
$$ select campsite_type_id, media_id, caption from campsite_type_carousel $$,
$$ values (10, 4, 'Source caption')
, (11, 5, 'Another caption')
$$,
'Should have removed the slides'
);
select bag_eq(
$$ select campsite_type_id, media_id, lang_tag, caption from campsite_type_carousel_i18n $$,
$$ values (10, 4, 'en', 'Target caption')
, (10, 4, 'es', 'Target caption (spanish)')
, (11, 5, 'en', 'Target caption')
, (11, 5, 'es', 'Target caption (spanish)')
$$,
'Should have removed the slides translations'
);
select *
from finish();
rollback;

View File

@ -0,0 +1,94 @@
-- Test translate_campsite_type_carousel_slide
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_carousel_slide', array['uuid', 'integer', 'text', 'text']);
select function_lang_is('camper', 'translate_campsite_type_carousel_slide', array['uuid', 'integer', 'text', 'text'], 'sql');
select function_returns('camper', 'translate_campsite_type_carousel_slide', array['uuid', 'integer', 'text', 'text'], 'void');
select isnt_definer('camper', 'translate_campsite_type_carousel_slide', array['uuid', 'integer', 'text', 'text']);
select volatility_is('camper', 'translate_campsite_type_carousel_slide', array['uuid', 'integer', 'text', 'text'], 'volatile');
select function_privs_are('camper', 'translate_campsite_type_carousel_slide', array['uuid', 'integer', 'text', 'text'], 'guest', array[]::text[]);
select function_privs_are('camper', 'translate_campsite_type_carousel_slide', array['uuid', 'integer', 'text', 'text'], 'employee', array[]::text[]);
select function_privs_are('camper', 'translate_campsite_type_carousel_slide', array['uuid', 'integer', 'text', 'text'], 'admin', array['EXECUTE']);
select function_privs_are('camper', 'translate_campsite_type_carousel_slide', array['uuid', 'integer', 'text', 'text'], 'authenticator', array[]::text[]);
set client_min_messages to warning;
truncate campsite_type_carousel_i18n cascade;
truncate campsite_type_carousel 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"};')
, ('image/x-xpixmap', 'static char *s[]={"1 1 1 1","a c #ff00ff","a"};')
, ('image/x-xpixmap', 'static char *s[]={"1 1 1 1","a c #ffff00","a"};')
, ('text/plain', 'hello, world!')
, ('image/svg+xml', '<svg xmlns="http://www.w3.org/2000/svg" width="1" height="1"/>')
;
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"};'))
, (3, 1, 'cover3.xpm', sha256('static char *s[]={"1 1 1 1","a c #ff00ff","a"};'))
, (4, 1, 'cover4.xpm', sha256('static char *s[]={"1 1 1 1","a c #ffff00","a"};'))
, (5, 1, 'text.txt', sha256('hello, world!'))
, (6, 1, 'image.svg', sha256('<svg xmlns="http://www.w3.org/2000/svg" width="1" height="1"/>'))
;
insert into campsite_type (campsite_type_id, company_id, slug, media_id, name, description, max_campers, dogs_allowed, active)
values (10, 1, '87452b88-b48f-48d3-bb6c-0296de64164e', 2, 'Type A', '<p>A</p>', 5, false, true)
, (11, 1, '9b6370f7-f941-46f2-bc6e-de455675bd0a', 3, 'Type B', '<p>B</p>', 4, true, false)
;
insert into campsite_type_carousel (campsite_type_id, media_id, caption)
values (10, 4, 'Source caption')
, (11, 5, 'Another caption')
, (11, 6, 'N/A')
;
insert into campsite_type_carousel_i18n (campsite_type_id, media_id, lang_tag, caption)
values (10, 4, 'en', 'Target caption')
;
select lives_ok(
$$ select translate_campsite_type_carousel_slide('87452b88-b48f-48d3-bb6c-0296de64164e', 4, 'ca', 'Traducció') $$,
'Should be able to translate a carousel slide'
);
select lives_ok(
$$ select translate_campsite_type_carousel_slide('9b6370f7-f941-46f2-bc6e-de455675bd0a', 5, 'es', null) $$,
'Should be able to “translate” a carousel slide to the empty string'
);
select lives_ok(
$$ select translate_campsite_type_carousel_slide('87452b88-b48f-48d3-bb6c-0296de64164e', 4, 'en', 'Not anymore') $$,
'Should be able to overwrite a slides translation'
);
select bag_eq(
$$ select campsite_type_id, media_id, lang_tag, caption from campsite_type_carousel_i18n $$,
$$ values (10, 4, 'ca', 'Traducció')
, (10, 4, 'en', 'Not anymore')
, (11, 5, 'es', '')
$$,
'Should have all three slides'
);
select *
from finish();
rollback;

View File

@ -0,0 +1,7 @@
-- Verify camper:add_campsite_type_carousel_slide on pg
begin;
select has_function_privilege('camper.add_campsite_type_carousel_slide(uuid, integer, text)', 'execute');
rollback;

View File

@ -0,0 +1,17 @@
-- Verify camper:campsite_type_carousel on pg
begin;
select campsite_type_id
, media_id
, caption
from camper.campsite_type_carousel
where false;
select 1 / count(*) from pg_class where oid = 'camper.campsite_type_carousel'::regclass and relrowsecurity;
select 1 / count(*) from pg_policy where polname = 'guest_ok' and polrelid = 'camper.campsite_type_carousel'::regclass;
select 1 / count(*) from pg_policy where polname = 'insert_to_company' and polrelid = 'camper.campsite_type_carousel'::regclass;
select 1 / count(*) from pg_policy where polname = 'update_company' and polrelid = 'camper.campsite_type_carousel'::regclass;
select 1 / count(*) from pg_policy where polname = 'delete_from_company' and polrelid = 'camper.campsite_type_carousel'::regclass;
rollback;

View File

@ -0,0 +1,12 @@
-- Verify camper:campsite_type_carousel_i18n on pg
begin;
select campsite_type_id
, media_id
, lang_tag
, caption
from camper.campsite_type_carousel_i18n
where false;
rollback;

View File

@ -0,0 +1,7 @@
-- Verify camper:remove_campsite_type_carousel_slide on pg
begin;
select has_function_privilege('camper.remove_campsite_type_carousel_slide(uuid, integer)', 'execute');
rollback;

View File

@ -0,0 +1,7 @@
-- Verify camper:translate_campsite_type_carousel_slide on pg
begin;
select has_function_privilege('camper.translate_campsite_type_carousel_slide(uuid, integer, text, text)', 'execute');
rollback;

View File

@ -584,7 +584,7 @@ dt {
margin-top: 2rem;
}
.outside_activities h3, .campsite_services .spiel {
.outside_activities h3, .campsite_services .spiel, .campsite_type .spiel {
font-size: calc(2.2rem + 4vw);
font-weight: 600;
line-height: .9em;
@ -662,7 +662,7 @@ dt {
margin-bottom: 5rem;
}
.campsite_services.carousel .slick-track {
.campsite_services.carousel .slick-track, .campsite_type.carousel .slick-track {
align-items: center;
}

View File

@ -0,0 +1,54 @@
<!--
SPDX-FileCopyrightText: 2023 jordi fita mas <jordi@tandem.blog>
SPDX-License-Identifier: AGPL-3.0-only
-->
{{ define "title" -}}
{{- /*gotype: dev.tandem.ws/tandem/camper/pkg/campsite/types.slideForm*/ -}}
{{ if .MediaID }}
{{( pgettext "Edit Campsite Type Carousel Slide" "title" )}}
{{ else }}
{{( pgettext "New Campsite Type Carousel Slide" "title" )}}
{{ end }}
{{- end }}
{{ define "content" -}}
{{- /*gotype: dev.tandem.ws/tandem/camper/pkg/campsite/types.slideForm*/ -}}
<form
{{ if .MediaID }}
data-hx-put="/admin/campsites/types/{{ .TypeSlug }}/slides/{{ .MediaID }}"
{{ else }}
action="/admin/campsites/types/{{ .TypeSlug }}/slides" method="post"
{{ end }}
>
<h2>
{{ if .MediaID }}
{{( pgettext "Edit Campsite Type Carousel Slide" "title" )}}
{{ else }}
{{( pgettext "New Campsite Type Carousel Slide" "title" )}}
{{ end }}
</h2>
{{ CSRFInput }}
<fieldset>
{{ with .Media -}}
{{ template "media-picker" . }}
{{- end }}
{{ with .Caption -}}
<label>
{{( pgettext "Caption" "input")}}<br>
<input type="text" name="{{ .Name }}" value="{{ .Val }}"
{{ template "error-attrs" . }}><br>
</label>
{{ template "error-message" . }}
{{- end }}
</fieldset>
<footer>
<button type="submit">
{{ if .MediaID }}
{{( pgettext "Update" "action" )}}
{{ else }}
{{( pgettext "Add" "action" )}}
{{ end }}
</button>
</footer>
</form>
{{- end }}

View File

@ -0,0 +1,52 @@
<!--
SPDX-FileCopyrightText: 2023 jordi fita mas <jordi@tandem.blog>
SPDX-License-Identifier: AGPL-3.0-only
-->
{{ define "title" -}}
{{( pgettext "Campsite Type Carousel" "title" )}}
{{- end }}
{{ define "content" -}}
{{- /*gotype: dev.tandem.ws/tandem/camper/pkg/campsite/types.carouselIndex*/ -}}
<h2>{{( pgettext "Campsite Type Carousel" "title" )}}</h2>
<a href="/admin/campsites/types/{{ .TypeSlug }}/slides/new">{{( pgettext "Add slide" "action" )}}</a>
{{ if .Slides -}}
<table>
<thead>
<tr>
<th scope="col">{{( pgettext "Image" "header" )}}</th>
<th scope="col">{{( pgettext "Caption" "header" )}}</th>
<th scope="col">{{( pgettext "Translations" "header" )}}</th>
<th scope="col">{{( pgettext "Actions" "header" )}}</th>
</tr>
</thead>
<tbody>
{{ $confirm := (gettext "Are you sure you wish to delete this slide?")}}
{{ range $slide := .Slides -}}
<tr>
<td><a href="/admin/campsites/types/{{ $.TypeSlug }}/slides/{{ .ID }}"><img src="{{ .Media }}" alt=""></a></td>
<td><a href="/admin/campsites/types/{{ $.TypeSlug }}/slides/{{ .ID }}">{{ .Caption }}</a></td>
<td>
{{ range .Translations }}
<a
{{ if .Missing }}
class="missing-translation"
{{ end }}
href="/admin/campsites/types/{{ $.TypeSlug }}/slides/{{ $slide.ID }}/{{ .Language }}">{{ .Endonym }}</a>
{{ end }}
</td>
<td>
<form data-hx-delete="/admin/campsites/types/{{ $.TypeSlug }}/slides/{{ .ID }}"
data-hx-confirm="{{ $confirm }}"
data-hx-headers='{ {{ CSRFHeader }} }'>
<button type="submit">{{( pgettext "Delete" "action" )}}</button>
</form>
</td>
</tr>
{{- end }}
</tbody>
</table>
{{ else -}}
<p>{{( gettext "No slides added yet." )}}</p>
{{- end }}
{{- end }}

View File

@ -0,0 +1,35 @@
<!--
SPDX-FileCopyrightText: 2023 jordi fita mas <jordi@tandem.blog>
SPDX-License-Identifier: AGPL-3.0-only
-->
{{ define "title" -}}
{{- /*gotype: dev.tandem.ws/tandem/camper/pkg/campsite/types.slideL10nForm*/ -}}
{{printf (pgettext "Translate Campsite Type Carousel Slide to %s" "title") .Locale.Endonym }}
{{- end }}
{{ define "content" -}}
{{- /*gotype: dev.tandem.ws/tandem/camper/pkg/campsite/types.slideL10nForm*/ -}}
<form data-hx-put="/admin/campsites/types/{{ .TypeSlug }}/slides/{{ .MediaID }}/{{ .Locale.Language }}">
<h2>
{{printf (pgettext "Translate Campsite Type Carousel Slide to %s" "title") .Locale.Endonym }}
</h2>
{{ CSRFInput }}
<fieldset>
{{ with .Caption -}}
<fieldset>
<legend>{{( pgettext "Caption" "input")}}</legend>
{{( gettext "Source:" )}} {{ .Source }}<br>
<label>
{{( pgettext "Translation:" "input" )}}
<input type="text" name="{{ .Name }}" value="{{ .Val }}"
{{ template "error-attrs" . }}><br>
</label>
{{ template "error-message" . }}
</fieldset>
{{- end }}
</fieldset>
<footer>
<button type="submit">{{( pgettext "Translate" "action" )}}</button>
</footer>
</form>
{{- end }}

View File

@ -17,6 +17,7 @@
<th scope="col">{{( pgettext "Name" "header" )}}</th>
<th scope="col">{{( pgettext "Translations" "header" )}}</th>
<th scope="col">{{( pgettext "Options" "header" )}}</th>
<th scope="col">{{( pgettext "Carousel" "header" )}}</th>
<th scope="col">{{( pgettext "Active" "campsite type" )}}</th>
</tr>
</thead>
@ -34,6 +35,7 @@
{{ end }}
</td>
<td><a href="/admin/campsites/types/{{ .Slug }}/options">{{( pgettext "Edit Options" "action" )}}</a></td>
<td><a href="/admin/campsites/types/{{ .Slug }}/slides">{{( pgettext "Edit Carousel" "action" )}}</a></td>
<td>{{ if .Active }}{{( gettext "Yes" )}}{{ else }}{{( gettext "No" )}}{{ end }}</td>
</tr>
{{- end }}

View File

@ -7,9 +7,32 @@
{{ .Name }}
{{- end }}
{{ define "head" -}}
{{ template "carouselStyle" }}
{{- end }}
{{ define "content" -}}
{{- /*gotype: dev.tandem.ws/tandem/camper/pkg/campsite/types.publicPage*/ -}}
<h2>{{ .Name }}</h2>
{{ with .Carousel -}}
<div class="campsite_type carousel">
<div class="spiel">
<p>TODO</p>
</div>
{{ range . -}}
{{ if .Caption -}}
<figure>
<img src="{{ .Media }}" alt=""/>
<figcaption>{{ .Caption }}</figcaption>
</figure>
{{- else -}}
<img src="{{ .Media }}" alt=""/>
{{- end }}
{{- end }}
</div>
{{- end }}
{{ .Description }}
{{ with .Prices -}}
@ -34,4 +57,7 @@
</dl>
</article>
{{- end }}
{{ template "carouselInit" }}
{{- end }}