Split templates and handlers into admin and public

I need to check that the user is an employee (or admin) in
administration handlers, but i do not want to do it for each handler,
because i am bound to forget it.  Thus, i added the /admin sub-path for
these resources.

The public-facing web is the rest of the resources outside /admin, but
for now there is only home, to test whether it works as expected or not.

The public-facing web can not relay on the user’s language settings, as
the guest user has no way to set that.  I would be happy to just use the
Accept-Language header for that, but apparently Google does not use that
header[0], and they give four alternatives: a country-specific domain,
a subdomain with a generic top-level domain (gTLD), subdirectories with
a gTLD, or URL parameters (e.g., site.com?loc=de).

Of the four, Google does not recommend URL parameters, and the customer
is already using subdirectories with the current site, therefor that’s
what i have chosen.

Google also tells me that it is a very good idea to have links between
localized version of the same resources, either with <link> elements,
Link HTTP response headers, or a sitemap file[1]; they are all
equivalent in the eyes of Google.

I have choosen the Link response headers way, because for that i can
simply “augment” ResponseHeader to automatically add these headers when
the response status is 2xx, otherwise i would need to pass down the
original URL path until it reaches the template.

Even though Camper is supposed to be a “generic”, multi-company
application, i think i will stick to the easiest route and write the
templates for just the “first” customer.

[0]: https://developers.google.com/search/docs/specialty/international/managing-multi-regional-sites
[1]: https://developers.google.com/search/docs/specialty/international/localized-versions
This commit is contained in:
jordi fita mas 2023-08-05 03:42:37 +02:00
parent 9349cda5f6
commit e128680e9a
23 changed files with 379 additions and 117 deletions

59
pkg/app/admin.go Normal file
View File

@ -0,0 +1,59 @@
/*
* SPDX-FileCopyrightText: 2023 jordi fita mas <jfita@peritasoft.com>
* SPDX-License-Identifier: AGPL-3.0-only
*/
package app
import (
"net/http"
"dev.tandem.ws/tandem/camper/pkg/auth"
"dev.tandem.ws/tandem/camper/pkg/campsite"
"dev.tandem.ws/tandem/camper/pkg/database"
httplib "dev.tandem.ws/tandem/camper/pkg/http"
"dev.tandem.ws/tandem/camper/pkg/template"
)
type adminHandler struct {
campsite *campsite.Handler
}
func newAdminHandler() *adminHandler {
return &adminHandler{
campsite: campsite.NewHandler(),
}
}
func (h *adminHandler) Handle(user *auth.User, company *auth.Company, conn *database.Conn, requestPath string) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if !user.LoggedIn {
w.WriteHeader(http.StatusUnauthorized)
serveLoginForm(w, r, user, company, requestPath)
return
}
if !user.IsEmployee() {
http.Error(w, user.Locale.Gettext("Access forbidden"), http.StatusForbidden)
return
}
var head string
head, r.URL.Path = httplib.ShiftPath(r.URL.Path)
switch head {
case "campsites":
h.campsite.Handler(user, company, conn).ServeHTTP(w, r)
case "":
switch r.Method {
case http.MethodGet:
serveDashboard(w, r, user, company)
default:
httplib.MethodNotAllowed(w, r, http.MethodGet)
}
}
})
}
func serveDashboard(w http.ResponseWriter, r *http.Request, user *auth.User, company *auth.Company) {
template.MustRenderAdmin(w, r, user, company, "dashboard.gohtml", nil)
}

View File

@ -11,18 +11,17 @@ import (
"golang.org/x/text/language"
"dev.tandem.ws/tandem/camper/pkg/auth"
"dev.tandem.ws/tandem/camper/pkg/campsite"
"dev.tandem.ws/tandem/camper/pkg/database"
httplib "dev.tandem.ws/tandem/camper/pkg/http"
"dev.tandem.ws/tandem/camper/pkg/locale"
"dev.tandem.ws/tandem/camper/pkg/template"
)
type App struct {
db *database.DB
fileHandler http.Handler
profile *profileHandler
campsite *campsite.Handler
admin *adminHandler
public *publicHandler
locales locale.Locales
defaultLocale *locale.Locale
languageMatcher language.Matcher
@ -39,7 +38,8 @@ func New(db *database.DB, avatarsDir string) (http.Handler, error) {
db: db,
fileHandler: static,
profile: profile,
campsite: campsite.NewHandler(),
admin: newAdminHandler(),
public: newPublicHandler(),
locales: locales,
defaultLocale: locales[language.Catalan],
languageMatcher: language.NewMatcher(locales.Tags()),
@ -81,41 +81,38 @@ func (h *App) ServeHTTP(w http.ResponseWriter, r *http.Request) {
panic(err)
}
if head == "login" {
switch head {
case "":
http.Redirect(w, r, "/"+user.Locale.Language.String()+"/", http.StatusFound)
case "admin":
h.admin.Handle(user, company, conn, requestPath).ServeHTTP(w, r)
case "login":
switch r.Method {
case http.MethodGet:
serveLoginForm(w, r, user, company, "/")
serveLoginForm(w, r, user, company, "/admin")
case http.MethodPost:
handleLogin(w, r, user, company, conn)
default:
httplib.MethodNotAllowed(w, r, http.MethodPost, http.MethodGet)
}
} else {
if !user.LoggedIn {
w.WriteHeader(http.StatusUnauthorized)
serveLoginForm(w, r, user, company, requestPath)
case "me":
h.profile.Handler(user, company, conn, requestPath).ServeHTTP(w, r)
default:
langTag, err := language.Parse(head)
if err != nil {
http.NotFound(w, r)
return
}
switch head {
case "me":
h.profile.Handler(user, company, conn).ServeHTTP(w, r)
case "campsites":
h.campsite.Handler(user, company, conn).ServeHTTP(w, r)
case "":
switch r.Method {
case http.MethodGet:
serveDashboard(w, r, user, company)
default:
httplib.MethodNotAllowed(w, r, http.MethodGet)
}
default:
urlLocale := h.locales[langTag]
if urlLocale == nil {
http.NotFound(w, r)
return
}
user.Locale = urlLocale
if r.Method == http.MethodGet || r.Method == http.MethodHead {
w = httplib.LanguageLinks(w, false, r.Host, r.URL.Path, h.locales)
}
h.public.Handler(user, company, conn).ServeHTTP(w, r)
}
}
}
}
func serveDashboard(w http.ResponseWriter, r *http.Request, user *auth.User, company *auth.Company) {
template.MustRender(w, r, user, company, "dashboard.gohtml", nil)
}

View File

@ -46,7 +46,7 @@ func (f *loginForm) Parse(r *http.Request) error {
f.Password.FillValue(r)
f.Redirect.FillValue(r)
if f.Redirect.Val == "" {
f.Redirect.Val = "/"
f.Redirect.Val = "/admin/"
}
return nil
}
@ -61,7 +61,7 @@ func (f *loginForm) Valid(l *locale.Locale) bool {
}
func (f *loginForm) MustRender(w http.ResponseWriter, r *http.Request, user *auth.User, company *auth.Company) {
template.MustRender(w, r, user, company, "login.gohtml", f)
template.MustRenderAdmin(w, r, user, company, "login.gohtml", f)
}
func serveLoginForm(w http.ResponseWriter, r *http.Request, user *auth.User, company *auth.Company, redirectPath string) {

34
pkg/app/public.go Normal file
View File

@ -0,0 +1,34 @@
/*
* SPDX-FileCopyrightText: 2023 jordi fita mas <jfita@peritasoft.com>
* SPDX-License-Identifier: AGPL-3.0-only
*/
package app
import (
"net/http"
"dev.tandem.ws/tandem/camper/pkg/auth"
"dev.tandem.ws/tandem/camper/pkg/database"
httplib "dev.tandem.ws/tandem/camper/pkg/http"
"dev.tandem.ws/tandem/camper/pkg/template"
)
type publicHandler struct{}
func newPublicHandler() *publicHandler {
return &publicHandler{}
}
func (h *publicHandler) Handler(user *auth.User, company *auth.Company, conn *database.Conn) 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 "":
template.MustRenderPublic(w, r, user, company, "home.gohtml", nil)
default:
http.NotFound(w, r)
}
})
}

View File

@ -80,8 +80,14 @@ func newProfileHandler(static http.Handler, avatarsDir string) (*profileHandler,
return handler, nil
}
func (h *profileHandler) Handler(user *auth.User, company *auth.Company, conn *database.Conn) http.HandlerFunc {
func (h *profileHandler) Handler(user *auth.User, company *auth.Company, conn *database.Conn, requestPath string) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
if !user.LoggedIn {
w.WriteHeader(http.StatusUnauthorized)
serveLoginForm(w, r, user, company, requestPath)
return
}
var head string
head, r.URL.Path = httplib.ShiftPath(r.URL.Path)
@ -249,7 +255,7 @@ func (f *profileForm) Valid(l *locale.Locale) bool {
}
func (f *profileForm) MustRender(w http.ResponseWriter, r *http.Request, user *auth.User, company *auth.Company) {
template.MustRender(w, r, user, company, "profile.gohtml", f)
template.MustRenderAdmin(w, r, user, company, "profile.gohtml", f)
}
func (f *profileForm) HasAvatarFile() bool {

View File

@ -39,3 +39,8 @@ func (user *User) VerifyCSRFToken(r *http.Request) error {
}
return errors.New(user.Locale.Gettext("Cross-site request forgery detected."))
}
func (user *User) IsEmployee() bool {
role := user.Role[0]
return role == 'e' || role == 'a'
}

View File

@ -89,7 +89,7 @@ type typeIndex struct {
}
func (page *typeIndex) MustRender(w http.ResponseWriter, r *http.Request, user *auth.User, company *auth.Company) {
template.MustRender(w, r, user, company, "campsite/type/index.gohtml", page)
template.MustRenderAdmin(w, r, user, company, "campsite/type/index.gohtml", page)
}
func addType(w http.ResponseWriter, r *http.Request, user *auth.User, company *auth.Company, conn *database.Conn) {
@ -145,5 +145,5 @@ func (f *typeForm) Valid(l *locale.Locale) bool {
}
func (f *typeForm) MustRender(w http.ResponseWriter, r *http.Request, user *auth.User, company *auth.Company) {
template.MustRender(w, r, user, company, "campsite/type/new.gohtml", f)
template.MustRenderAdmin(w, r, user, company, "campsite/type/new.gohtml", f)
}

49
pkg/http/links.go Normal file
View File

@ -0,0 +1,49 @@
/*
* SPDX-FileCopyrightText: 2023 jordi fita mas <jfita@peritasoft.com>
* SPDX-License-Identifier: AGPL-3.0-only
*/
package http
import (
"fmt"
"net/http"
"dev.tandem.ws/tandem/camper/pkg/locale"
)
type languageLinks struct {
http.ResponseWriter
schemaAuthority string
path string
locales locale.Locales
wroteHeader bool
}
func (w *languageLinks) WriteHeader(statusCode int) {
if statusCode >= 200 && statusCode < 300 {
for k := range w.locales {
tag := k.String()
w.Header().Add("Link", fmt.Sprintf(`<%[1]s/%[2]s%[3]s>; rel="alternate"; hreflang="%[2]s"`, w.schemaAuthority, tag, w.path))
}
}
w.wroteHeader = true
w.ResponseWriter.WriteHeader(statusCode)
}
func (w *languageLinks) Write(data []byte) (int, error) {
if !w.wroteHeader {
w.WriteHeader(http.StatusOK)
}
return w.ResponseWriter.Write(data)
}
func LanguageLinks(w http.ResponseWriter, https bool, authority string, path string, locales locale.Locales) http.ResponseWriter {
var schema string
if https {
schema = "https"
} else {
schema = "http"
}
return &languageLinks{w, schema + "://" + authority, path, locales, false}
}

View File

@ -15,19 +15,28 @@ import (
httplib "dev.tandem.ws/tandem/camper/pkg/http"
)
func templateFile(name string) string {
return "web/templates/" + name
func adminTemplateFile(name string) string {
return "web/templates/admin/" + name
}
func MustRender(w io.Writer, r *http.Request, user *auth.User, company *auth.Company, filename string, data interface{}) {
func publicTemplateFile(name string) string {
return "web/templates/public/" + name
}
func MustRenderAdmin(w io.Writer, r *http.Request, user *auth.User, company *auth.Company, filename string, data interface{}) {
layout := "layout.gohtml"
if httplib.IsHTMxRequest(r) {
layout = "htmx.gohtml"
}
mustRenderLayout(w, user, company, layout, filename, data)
mustRenderLayout(w, user, company, adminTemplateFile, layout, filename, data)
}
func mustRenderLayout(w io.Writer, user *auth.User, company *auth.Company, layout string, filename string, data interface{}) {
func MustRenderPublic(w io.Writer, r *http.Request, user *auth.User, company *auth.Company, filename string, data interface{}) {
layout := "layout.gohtml"
mustRenderLayout(w, user, company, publicTemplateFile, layout, filename, data)
}
func mustRenderLayout(w io.Writer, user *auth.User, company *auth.Company, templateFile func(string) string, layout string, filename string, data interface{}) {
t := template.New(filename)
t.Funcs(template.FuncMap{
"gettext": user.Locale.Get,

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-08-04 19:51+0200\n"
"POT-Creation-Date: 2023-08-05 03:23+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"
@ -18,123 +18,133 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: web/templates/campsite/type/new.gohtml:14
#: web/templates/campsite/type/new.gohtml:20
#: web/templates/public/home.gohtml:6
msgctxt "title"
msgid "Home"
msgstr "Inici"
#: web/templates/public/layout.gohtml:10 web/templates/public/layout.gohtml:17
msgid "Campsite Montagut"
msgstr "Càmping Montagut"
#: web/templates/public/layout.gohtml:16 web/templates/admin/layout.gohtml:18
msgid "Skip to main content"
msgstr "Salta al contingut principal"
#: web/templates/admin/campsite/type/new.gohtml:14
#: web/templates/admin/campsite/type/new.gohtml:20
msgctxt "title"
msgid "New Campsite Type"
msgstr "Nou tipus dallotjament"
#: web/templates/campsite/type/new.gohtml:25 web/templates/profile.gohtml:26
#: web/templates/admin/campsite/type/new.gohtml:25
#: web/templates/admin/profile.gohtml:26
msgctxt "input"
msgid "Name"
msgstr "Nom"
#: web/templates/campsite/type/new.gohtml:33
#: web/templates/admin/campsite/type/new.gohtml:33
msgctxt "input"
msgid "Description"
msgstr "Descripció"
#: web/templates/campsite/type/new.gohtml:40
#: web/templates/admin/campsite/type/new.gohtml:40
msgctxt "action"
msgid "Add"
msgstr "Afegeix"
#: web/templates/campsite/type/index.gohtml:6
#: web/templates/campsite/type/index.gohtml:12
#: web/templates/admin/campsite/type/index.gohtml:6
#: web/templates/admin/campsite/type/index.gohtml:12
msgctxt "title"
msgid "Campsite Types"
msgstr "Tipus dallotjaments"
#: web/templates/campsite/type/index.gohtml:11
#: web/templates/admin/campsite/type/index.gohtml:11
msgctxt "action"
msgid "Add Type"
msgstr "Afegeix tipus"
#: web/templates/campsite/type/index.gohtml:17
#: web/templates/admin/campsite/type/index.gohtml:17
msgctxt "header"
msgid "Name"
msgstr "Nom"
#: web/templates/campsite/type/index.gohtml:29
#: web/templates/admin/campsite/type/index.gohtml:29
msgid "No campsite types added yet."
msgstr "No sha afegit cap tipus dallotjament encara."
#: web/templates/dashboard.gohtml:6 web/templates/dashboard.gohtml:10
#: web/templates/layout.gohtml:44
#: web/templates/admin/dashboard.gohtml:6
#: web/templates/admin/dashboard.gohtml:10 web/templates/admin/layout.gohtml:44
msgctxt "title"
msgid "Dashboard"
msgstr "Tauler"
#: web/templates/login.gohtml:6 web/templates/login.gohtml:13
#: web/templates/admin/login.gohtml:6 web/templates/admin/login.gohtml:13
msgctxt "title"
msgid "Login"
msgstr "Entrada"
#: web/templates/login.gohtml:22 web/templates/profile.gohtml:35
#: web/templates/admin/login.gohtml:22 web/templates/admin/profile.gohtml:35
msgctxt "input"
msgid "Email"
msgstr "Correu-e"
#: web/templates/login.gohtml:31 web/templates/profile.gohtml:46
#: web/templates/admin/login.gohtml:31 web/templates/admin/profile.gohtml:46
msgctxt "input"
msgid "Password"
msgstr "Contrasenya"
#: web/templates/login.gohtml:40
#: web/templates/admin/login.gohtml:40
msgctxt "action"
msgid "Login"
msgstr "Entra"
#: web/templates/profile.gohtml:6 web/templates/profile.gohtml:12
#: web/templates/layout.gohtml:29
#: web/templates/admin/profile.gohtml:6 web/templates/admin/profile.gohtml:12
#: web/templates/admin/layout.gohtml:29
msgctxt "title"
msgid "Profile"
msgstr "Perfil"
#: web/templates/profile.gohtml:17
#: web/templates/admin/profile.gohtml:17
msgctxt "inut"
msgid "Profile Image"
msgstr "Imatge del perfil"
#: web/templates/profile.gohtml:43
#: web/templates/admin/profile.gohtml:43
msgctxt "legend"
msgid "Change password"
msgstr "Canvi de contrasenya"
#: web/templates/profile.gohtml:55
#: web/templates/admin/profile.gohtml:55
msgctxt "input"
msgid "Password Confirmation"
msgstr "Confirmació de la contrasenya"
#: web/templates/profile.gohtml:65
#: web/templates/admin/profile.gohtml:65
msgctxt "input"
msgid "Language"
msgstr "Idioma"
#: web/templates/profile.gohtml:75
#: web/templates/admin/profile.gohtml:75
msgctxt "action"
msgid "Save changes"
msgstr "Desa els canvis"
#: web/templates/layout.gohtml:18
msgid "Skip to main content"
msgstr "Salta al contingut principal"
#: web/templates/layout.gohtml:25
#: web/templates/admin/layout.gohtml:25
msgctxt "title"
msgid "User Menu"
msgstr "Menú dusuari"
#: web/templates/layout.gohtml:33
#: web/templates/admin/layout.gohtml:33
msgctxt "action"
msgid "Logout"
msgstr "Surt"
#: pkg/app/login.go:56 pkg/app/user.go:239
#: pkg/app/login.go:56 pkg/app/user.go:245
msgid "Email can not be empty."
msgstr "No podeu deixar el correu en blanc."
#: pkg/app/login.go:57 pkg/app/user.go:240
#: pkg/app/login.go:57 pkg/app/user.go:246
msgid "This email is not valid. It should be like name@domain.com."
msgstr "Aquest correu-e no és vàlid. Hauria de ser similar a nom@domini.com."
@ -146,27 +156,31 @@ msgstr "No podeu deixar la contrasenya en blanc."
msgid "Invalid user or password."
msgstr "Nom dusuari o contrasenya incorrectes."
#: pkg/app/user.go:190
#: pkg/app/user.go:196
msgctxt "language option"
msgid "Automatic"
msgstr "Automàtic"
#: pkg/app/user.go:242 pkg/campsite/type.go:143
#: pkg/app/user.go:248 pkg/campsite/type.go:143
msgid "Name can not be empty."
msgstr "No podeu deixar el nom en blanc."
#: pkg/app/user.go:243
#: pkg/app/user.go:249
msgid "Confirmation does not match password."
msgstr "La confirmació no es correspon amb la contrasenya."
#: pkg/app/user.go:244
#: pkg/app/user.go:250
msgid "Selected language is not valid."
msgstr "Lidioma escollit no és vàlid."
#: pkg/app/user.go:246
#: pkg/app/user.go:252
msgid "File must be a valid PNG or JPEG image."
msgstr "El fitxer has de ser una imatge PNG o JPEG vàlida."
#: pkg/app/admin.go:37
msgid "Access forbidden"
msgstr "Accés prohibit"
#: pkg/auth/user.go:40
msgid "Cross-site request forgery detected."
msgstr "Sha detectat un intent de falsificació de petició a llocs creuats."

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-08-04 19:51+0200\n"
"POT-Creation-Date: 2023-08-05 03:23+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"
@ -18,123 +18,133 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: web/templates/campsite/type/new.gohtml:14
#: web/templates/campsite/type/new.gohtml:20
#: web/templates/public/home.gohtml:6
msgctxt "title"
msgid "Home"
msgstr "Inicio"
#: web/templates/public/layout.gohtml:10 web/templates/public/layout.gohtml:17
msgid "Campsite Montagut"
msgstr "Camping Montagut"
#: web/templates/public/layout.gohtml:16 web/templates/admin/layout.gohtml:18
msgid "Skip to main content"
msgstr "Saltar al contenido principal"
#: web/templates/admin/campsite/type/new.gohtml:14
#: web/templates/admin/campsite/type/new.gohtml:20
msgctxt "title"
msgid "New Campsite Type"
msgstr "Nuevo tipo de alojamiento"
#: web/templates/campsite/type/new.gohtml:25 web/templates/profile.gohtml:26
#: web/templates/admin/campsite/type/new.gohtml:25
#: web/templates/admin/profile.gohtml:26
msgctxt "input"
msgid "Name"
msgstr "Nombre"
#: web/templates/campsite/type/new.gohtml:33
#: web/templates/admin/campsite/type/new.gohtml:33
msgctxt "input"
msgid "Description"
msgstr "Descripción"
#: web/templates/campsite/type/new.gohtml:40
#: web/templates/admin/campsite/type/new.gohtml:40
msgctxt "action"
msgid "Add"
msgstr "Añadir"
#: web/templates/campsite/type/index.gohtml:6
#: web/templates/campsite/type/index.gohtml:12
#: web/templates/admin/campsite/type/index.gohtml:6
#: web/templates/admin/campsite/type/index.gohtml:12
msgctxt "title"
msgid "Campsite Types"
msgstr "Tipos de alojamientos"
#: web/templates/campsite/type/index.gohtml:11
#: web/templates/admin/campsite/type/index.gohtml:11
msgctxt "action"
msgid "Add Type"
msgstr "Añadir tipo"
#: web/templates/campsite/type/index.gohtml:17
#: web/templates/admin/campsite/type/index.gohtml:17
msgctxt "header"
msgid "Name"
msgstr "Nombre"
#: web/templates/campsite/type/index.gohtml:29
#: web/templates/admin/campsite/type/index.gohtml:29
msgid "No campsite types added yet."
msgstr "No se ha añadido ningún tipo de alojamiento todavía."
#: web/templates/dashboard.gohtml:6 web/templates/dashboard.gohtml:10
#: web/templates/layout.gohtml:44
#: web/templates/admin/dashboard.gohtml:6
#: web/templates/admin/dashboard.gohtml:10 web/templates/admin/layout.gohtml:44
msgctxt "title"
msgid "Dashboard"
msgstr "Panel"
#: web/templates/login.gohtml:6 web/templates/login.gohtml:13
#: web/templates/admin/login.gohtml:6 web/templates/admin/login.gohtml:13
msgctxt "title"
msgid "Login"
msgstr "Entrada"
#: web/templates/login.gohtml:22 web/templates/profile.gohtml:35
#: web/templates/admin/login.gohtml:22 web/templates/admin/profile.gohtml:35
msgctxt "input"
msgid "Email"
msgstr "Correo-e"
#: web/templates/login.gohtml:31 web/templates/profile.gohtml:46
#: web/templates/admin/login.gohtml:31 web/templates/admin/profile.gohtml:46
msgctxt "input"
msgid "Password"
msgstr "Contraseña"
#: web/templates/login.gohtml:40
#: web/templates/admin/login.gohtml:40
msgctxt "action"
msgid "Login"
msgstr "Entrar"
#: web/templates/profile.gohtml:6 web/templates/profile.gohtml:12
#: web/templates/layout.gohtml:29
#: web/templates/admin/profile.gohtml:6 web/templates/admin/profile.gohtml:12
#: web/templates/admin/layout.gohtml:29
msgctxt "title"
msgid "Profile"
msgstr "Perfil"
#: web/templates/profile.gohtml:17
#: web/templates/admin/profile.gohtml:17
msgctxt "inut"
msgid "Profile Image"
msgstr "Imagen del perfil"
#: web/templates/profile.gohtml:43
#: web/templates/admin/profile.gohtml:43
msgctxt "legend"
msgid "Change password"
msgstr "Cambio de contraseña"
#: web/templates/profile.gohtml:55
#: web/templates/admin/profile.gohtml:55
msgctxt "input"
msgid "Password Confirmation"
msgstr "Confirmación de la contraseña"
#: web/templates/profile.gohtml:65
#: web/templates/admin/profile.gohtml:65
msgctxt "input"
msgid "Language"
msgstr "Idioma"
#: web/templates/profile.gohtml:75
#: web/templates/admin/profile.gohtml:75
msgctxt "action"
msgid "Save changes"
msgstr "Guardar los cambios"
#: web/templates/layout.gohtml:18
msgid "Skip to main content"
msgstr "Saltar al contenido principal"
#: web/templates/layout.gohtml:25
#: web/templates/admin/layout.gohtml:25
msgctxt "title"
msgid "User Menu"
msgstr "Menú de usuario"
#: web/templates/layout.gohtml:33
#: web/templates/admin/layout.gohtml:33
msgctxt "action"
msgid "Logout"
msgstr "Salir"
#: pkg/app/login.go:56 pkg/app/user.go:239
#: pkg/app/login.go:56 pkg/app/user.go:245
msgid "Email can not be empty."
msgstr "No podéis dejar el correo-e en blanco."
#: pkg/app/login.go:57 pkg/app/user.go:240
#: pkg/app/login.go:57 pkg/app/user.go:246
msgid "This email is not valid. It should be like name@domain.com."
msgstr "Este correo-e no es válido. Tiene que ser parecido a nombre@dominio.com."
@ -146,27 +156,31 @@ msgstr "No podéis dejar la contraseña en blanco."
msgid "Invalid user or password."
msgstr "Usuario o contraseña incorrectos."
#: pkg/app/user.go:190
#: pkg/app/user.go:196
msgctxt "language option"
msgid "Automatic"
msgstr "Automático"
#: pkg/app/user.go:242 pkg/campsite/type.go:143
#: pkg/app/user.go:248 pkg/campsite/type.go:143
msgid "Name can not be empty."
msgstr "No podéis dejar el nombre en blanco."
#: pkg/app/user.go:243
#: pkg/app/user.go:249
msgid "Confirmation does not match password."
msgstr "La confirmación no se corresponde con la contraseña."
#: pkg/app/user.go:244
#: pkg/app/user.go:250
msgid "Selected language is not valid."
msgstr "El idioma escogido no es válido."
#: pkg/app/user.go:246
#: pkg/app/user.go:252
msgid "File must be a valid PNG or JPEG image."
msgstr "El archivo tiene que ser una imagen PNG o JPEG válida."
#: pkg/app/admin.go:37
msgid "Access forbidden"
msgstr "Acceso prohibido"
#: pkg/auth/user.go:40
msgid "Cross-site request forgery detected."
msgstr "Se ha detectado un intento de falsificación de petición en sitios cruzados."

41
web/static/public.css Normal file
View File

@ -0,0 +1,41 @@
/**
* SPDX-FileCopyrightText: 2023 jordi fita mas <jordi@tandem.blog>
* SPDX-License-Identifier: AGPL-3.0-only
*/
*, *::before, *::after {
box-sizing: border-box;
}
* {
margin: 0;
}
html, body {
height: 100%;
}
html {
font-size: 62.5%;
}
body {
font-size: 1.6rem;
line-height: 1.5;
-webkit-font-smoothing: antialiased;
background-color: white;
color: #3f3b37;
}
img, picture, video, canvas, svg {
display: block;
max-width: 100%;
}
input, button, textarea, select {
font: inherit;
}
p, h1, h2, h3, h4, h5, h6 {
overflow-wrap: break-word;
}

View File

@ -8,7 +8,7 @@
{{ define "content" -}}
{{- /*gotype: dev.tandem.ws/tandem/camper/pkg/campsite.typeIndex*/ -}}
<a href="/campsites/types/new">{{( pgettext "Add Type" "action" )}}</a>
<a href="/admin/campsites/types/new">{{( pgettext "Add Type" "action" )}}</a>
<h2>{{( pgettext "Campsite Types" "title" )}}</h2>
{{ if .Types -}}
<table>
@ -20,7 +20,7 @@
<tbody>
{{ range .Types -}}
<tr>
<td><a href="/campsites/type/{{ .Slug }}">{{ .Name }}</a></td>
<td><a href="/admin/campsites/type/{{ .Slug }}">{{ .Name }}</a></td>
</tr>
{{- end }}
</tbody>

View File

@ -16,7 +16,7 @@
{{ define "content" -}}
{{- /*gotype: dev.tandem.ws/tandem/camper/pkg/campsite.typeForm*/ -}}
<form action="/campsites/types" method="post">
<form action="/admin/campsites/types" method="post">
<h2>{{( pgettext "New Campsite Type" "title" )}}</h2>
{{ CSRFInput }}
<fieldset>

View File

@ -41,7 +41,7 @@
<nav>
<ul role="menu">
<li role="presentation">
<a role="menuitem" href="/">{{( pgettext "Dashboard" "title" )}}</a>
<a role="menuitem" href="/admin/">{{( pgettext "Dashboard" "title" )}}</a>
</li>
</ul>
</nav>

View File

@ -0,0 +1 @@
../admin/form.gohtml

View File

@ -0,0 +1,10 @@
<!--
SPDX-FileCopyrightText: 2023 jordi fita mas <jordi@tandem.blog>
SPDX-License-Identifier: AGPL-3.0-only
-->
{{ define "title" -}}
{{( pgettext "Home" "title" )}}
{{- end }}
{{ define "content" -}}
{{- end }}

View File

@ -0,0 +1,23 @@
<!--
SPDX-FileCopyrightText: 2023 jordi fita mas <jordi@tandem.blog>
SPDX-License-Identifier: AGPL-3.0-only
-->
<!doctype html>
<html lang="{{ currentLocale }}">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>{{ template "title" . }} — {{( gettext "Campsite Montagut" )}}</title>
<link rel="stylesheet" media="screen" href="/static/public.css">
{{ block "head" . }}{{ end }}
</head>
<body>
<header>
<a href="#content">{{( gettext "Skip to main content" )}}</a>
<h1>{{( gettext "Campsite Montagut" )}}</h1>
</header>
<main id="content">
{{- template "content" . }}
</main>
</body>
</html>