initial commit
This commit is contained in:
commit
085b645265
8
.idea/.gitignore
generated
vendored
Normal file
8
.idea/.gitignore
generated
vendored
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
# Default ignored files
|
||||||
|
/shelf/
|
||||||
|
/workspace.xml
|
||||||
|
# Editor-based HTTP Client requests
|
||||||
|
/httpRequests/
|
||||||
|
# Datasource local storage ignored files
|
||||||
|
/dataSources/
|
||||||
|
/dataSources.local.xml
|
||||||
8
.idea/modules.xml
generated
Normal file
8
.idea/modules.xml
generated
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="ProjectModuleManager">
|
||||||
|
<modules>
|
||||||
|
<module fileurl="file://$PROJECT_DIR$/.idea/templating.iml" filepath="$PROJECT_DIR$/.idea/templating.iml" />
|
||||||
|
</modules>
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
9
.idea/templating.iml
generated
Normal file
9
.idea/templating.iml
generated
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<module type="WEB_MODULE" version="4">
|
||||||
|
<component name="Go" enabled="true" />
|
||||||
|
<component name="NewModuleRootManager">
|
||||||
|
<content url="file://$MODULE_DIR$" />
|
||||||
|
<orderEntry type="inheritedJdk" />
|
||||||
|
<orderEntry type="sourceFolder" forTests="false" />
|
||||||
|
</component>
|
||||||
|
</module>
|
||||||
32
go.mod
Normal file
32
go.mod
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
module templating
|
||||||
|
|
||||||
|
go 1.25.0
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/go-playground/validator/v10 v10.27.0
|
||||||
|
github.com/gofiber/fiber/v2 v2.52.9
|
||||||
|
github.com/gofiber/template/html/v2 v2.1.3
|
||||||
|
)
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/andybalholm/brotli v1.1.0 // indirect
|
||||||
|
github.com/gabriel-vasile/mimetype v1.4.8 // indirect
|
||||||
|
github.com/go-playground/locales v0.14.1 // indirect
|
||||||
|
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||||
|
github.com/gofiber/template v1.8.3 // indirect
|
||||||
|
github.com/gofiber/utils v1.1.0 // indirect
|
||||||
|
github.com/google/uuid v1.6.0 // indirect
|
||||||
|
github.com/klauspost/compress v1.17.9 // indirect
|
||||||
|
github.com/leodido/go-urn v1.4.0 // indirect
|
||||||
|
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||||
|
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||||
|
github.com/mattn/go-runewidth v0.0.16 // indirect
|
||||||
|
github.com/rivo/uniseg v0.2.0 // indirect
|
||||||
|
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
||||||
|
github.com/valyala/fasthttp v1.51.0 // indirect
|
||||||
|
github.com/valyala/tcplisten v1.0.0 // indirect
|
||||||
|
golang.org/x/crypto v0.33.0 // indirect
|
||||||
|
golang.org/x/net v0.34.0 // indirect
|
||||||
|
golang.org/x/sys v0.30.0 // indirect
|
||||||
|
golang.org/x/text v0.22.0 // indirect
|
||||||
|
)
|
||||||
59
go.sum
Normal file
59
go.sum
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M=
|
||||||
|
github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY=
|
||||||
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM=
|
||||||
|
github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8=
|
||||||
|
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
|
||||||
|
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
||||||
|
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
|
||||||
|
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
|
||||||
|
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
|
||||||
|
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
||||||
|
github.com/go-playground/validator/v10 v10.27.0 h1:w8+XrWVMhGkxOaaowyKH35gFydVHOvC0/uWoy2Fzwn4=
|
||||||
|
github.com/go-playground/validator/v10 v10.27.0/go.mod h1:I5QpIEbmr8On7W0TktmJAumgzX4CA1XNl4ZmDuVHKKo=
|
||||||
|
github.com/gofiber/fiber/v2 v2.52.9 h1:YjKl5DOiyP3j0mO61u3NTmK7or8GzzWzCFzkboyP5cw=
|
||||||
|
github.com/gofiber/fiber/v2 v2.52.9/go.mod h1:YEcBbO/FB+5M1IZNBP9FO3J9281zgPAreiI1oqg8nDw=
|
||||||
|
github.com/gofiber/template v1.8.3 h1:hzHdvMwMo/T2kouz2pPCA0zGiLCeMnoGsQZBTSYgZxc=
|
||||||
|
github.com/gofiber/template v1.8.3/go.mod h1:bs/2n0pSNPOkRa5VJ8zTIvedcI/lEYxzV3+YPXdBvq8=
|
||||||
|
github.com/gofiber/template/html/v2 v2.1.3 h1:n1LYBtmr9C0V/k/3qBblXyMxV5B0o/gpb6dFLp8ea+o=
|
||||||
|
github.com/gofiber/template/html/v2 v2.1.3/go.mod h1:U5Fxgc5KpyujU9OqKzy6Kn6Qup6Tm7zdsISR+VpnHRE=
|
||||||
|
github.com/gofiber/utils v1.1.0 h1:vdEBpn7AzIUJRhe+CiTOJdUcTg4Q9RK+pEa0KPbLdrM=
|
||||||
|
github.com/gofiber/utils v1.1.0/go.mod h1:poZpsnhBykfnY1Mc0KeEa6mSHrS3dV0+oBWyeQmb2e0=
|
||||||
|
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||||
|
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
|
github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA=
|
||||||
|
github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
|
||||||
|
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
|
||||||
|
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
|
||||||
|
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||||
|
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||||
|
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||||
|
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||||
|
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||||
|
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
|
||||||
|
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
|
||||||
|
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||||
|
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||||
|
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||||
|
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
|
||||||
|
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
||||||
|
github.com/valyala/fasthttp v1.51.0 h1:8b30A5JlZ6C7AS81RsWjYMQmrZG6feChmgAolCl1SqA=
|
||||||
|
github.com/valyala/fasthttp v1.51.0/go.mod h1:oI2XroL+lI7vdXyYoQk03bXBThfFl2cVdIA3Xl7cH8g=
|
||||||
|
github.com/valyala/tcplisten v1.0.0 h1:rBHj/Xf+E1tRGZyWIWwJDiRY0zc1Js+CV5DqwacVSA8=
|
||||||
|
github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc=
|
||||||
|
golang.org/x/crypto v0.33.0 h1:IOBPskki6Lysi0lo9qQvbxiQ+FvsCC/YWOecCHAixus=
|
||||||
|
golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M=
|
||||||
|
golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0=
|
||||||
|
golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k=
|
||||||
|
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
|
||||||
|
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
|
golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM=
|
||||||
|
golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
109
main.go
Normal file
109
main.go
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log/slog"
|
||||||
|
"templating/ui"
|
||||||
|
|
||||||
|
"github.com/gofiber/fiber/v2"
|
||||||
|
"github.com/gofiber/template/html/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
engine := html.New("./templates", ".html")
|
||||||
|
engine.Reload(true)
|
||||||
|
|
||||||
|
app := fiber.New(
|
||||||
|
fiber.Config{
|
||||||
|
Views: engine,
|
||||||
|
})
|
||||||
|
|
||||||
|
app.Get("/", func(c *fiber.Ctx) error {
|
||||||
|
return c.Render("pages/index", fiber.Map{
|
||||||
|
"Message": "Hello World",
|
||||||
|
"UsernameField": ui.UsernameField().ToMap(),
|
||||||
|
"EmailField": ui.EmailField().ToMap(),
|
||||||
|
"PasswordField": ui.PasswordField().ToMap(),
|
||||||
|
}, "layouts/base")
|
||||||
|
})
|
||||||
|
|
||||||
|
type SomeForm struct {
|
||||||
|
Username string `json:"username" validate:"required,min=3,max=30,username"`
|
||||||
|
Email string `json:"email" validate:"required,email"`
|
||||||
|
Password string `json:"password" validate:"required,min=8,max=20"`
|
||||||
|
}
|
||||||
|
|
||||||
|
app.Post("/validate/username", func(c *fiber.Ctx) error {
|
||||||
|
slog.Info(`validate username triggered`)
|
||||||
|
|
||||||
|
username := c.FormValue("username")
|
||||||
|
input := SomeForm{
|
||||||
|
Username: username,
|
||||||
|
}
|
||||||
|
|
||||||
|
validate := ui.GetValidator()
|
||||||
|
err := validate.StructPartial(input, "Username")
|
||||||
|
if err != nil {
|
||||||
|
field := ui.UsernameField().
|
||||||
|
WithValue(username).
|
||||||
|
WithError(ui.GetFirstError(err))
|
||||||
|
return c.Render("components/input", field.ToMap())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Emular que el nombre de usuario existe
|
||||||
|
if username == "juan" {
|
||||||
|
slog.Info("username already exists")
|
||||||
|
field := ui.UsernameField().
|
||||||
|
WithValue(username).
|
||||||
|
WithError("El nombre de usuario ya existe")
|
||||||
|
return c.Render("components/input", field.ToMap())
|
||||||
|
}
|
||||||
|
|
||||||
|
field := ui.UsernameField().WithValue(username)
|
||||||
|
return c.Render("components/input", field.ToMap())
|
||||||
|
})
|
||||||
|
|
||||||
|
app.Post("/validate/email", func(c *fiber.Ctx) error {
|
||||||
|
slog.Info(`validate email triggered`)
|
||||||
|
|
||||||
|
email := c.FormValue("email")
|
||||||
|
input := SomeForm{
|
||||||
|
Email: email,
|
||||||
|
}
|
||||||
|
|
||||||
|
validate := ui.GetValidator()
|
||||||
|
err := validate.StructPartial(input, "Email")
|
||||||
|
if err != nil {
|
||||||
|
field := ui.EmailField().
|
||||||
|
WithValue(email).
|
||||||
|
WithError(ui.GetFirstError(err))
|
||||||
|
return c.Render("components/input", field.ToMap())
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
field := ui.EmailField().WithValue(email)
|
||||||
|
return c.Render("components/input", field.ToMap())
|
||||||
|
})
|
||||||
|
|
||||||
|
app.Post("/validate/password", func(c *fiber.Ctx) error {
|
||||||
|
slog.Info(`validate password triggered`)
|
||||||
|
|
||||||
|
password := c.FormValue("password")
|
||||||
|
input := SomeForm{
|
||||||
|
Password: password,
|
||||||
|
}
|
||||||
|
|
||||||
|
validate := ui.GetValidator()
|
||||||
|
err := validate.StructPartial(input, "Password")
|
||||||
|
if err != nil {
|
||||||
|
field := ui.PasswordField().
|
||||||
|
WithValue(password).
|
||||||
|
WithError(ui.GetFirstError(err))
|
||||||
|
return c.Render("components/input", field.ToMap())
|
||||||
|
}
|
||||||
|
|
||||||
|
field := ui.PasswordField().WithValue(password)
|
||||||
|
return c.Render("components/input", field.ToMap())
|
||||||
|
})
|
||||||
|
|
||||||
|
app.Listen(":3000")
|
||||||
|
}
|
||||||
28
templates/components/head.html
Normal file
28
templates/components/head.html
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
|
||||||
|
|
||||||
|
<title>{{ .Title }}</title>
|
||||||
|
|
||||||
|
<link href="https://cdn.jsdelivr.net/npm/daisyui@5.0" rel="stylesheet" type="text/css" />
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>
|
||||||
|
<link href="https://cdn.jsdelivr.net/npm/daisyui@5/themes.css" rel="stylesheet" type="text/css" />
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/htmx.org@2/dist/htmx.min.js"></script>
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/alpinejs@3/dist/cdn.min.js" defer></script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.spinner {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.htmx-request .spinner {
|
||||||
|
display: inline;
|
||||||
|
}
|
||||||
|
|
||||||
|
.htmx-request.spinner {
|
||||||
|
display: inline;
|
||||||
|
}
|
||||||
|
|
||||||
|
.htmx-request .button-text {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
20
templates/components/input.html
Normal file
20
templates/components/input.html
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
<fieldset class="fieldset" id="{{ .ID }}">
|
||||||
|
<legend class="fieldset-legend">{{ .Legend }}</legend>
|
||||||
|
<input
|
||||||
|
type="{{ .Type }}"
|
||||||
|
class="input {{ .InputClass }}"
|
||||||
|
placeholder="{{ .Placeholder }}"
|
||||||
|
name="{{ .Name }}"
|
||||||
|
value="{{ .Value }}"
|
||||||
|
hx-post="{{ .Endpoint }}"
|
||||||
|
hx-trigger="{{ .Trigger }}"
|
||||||
|
hx-target="#{{ .ID }}"
|
||||||
|
hx-swap="outerHTML"
|
||||||
|
x-data
|
||||||
|
@input="$el.classList.remove('input-error', 'text-error'); $el.nextElementSibling.textContent = ''"
|
||||||
|
|
||||||
|
/>
|
||||||
|
<p class="label min-h-[1.2rem] {{ .InputClass }}">
|
||||||
|
{{ .Hint }}
|
||||||
|
</p>
|
||||||
|
</fieldset>
|
||||||
14
templates/layouts/base.html
Normal file
14
templates/layouts/base.html
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="es" data-theme="bumblebee" class="bg-base-200">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
{{ template "components/head" . }}
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
|
||||||
|
{{ embed }}
|
||||||
|
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
12
templates/pages/index.html
Normal file
12
templates/pages/index.html
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
<div class="container p-2">
|
||||||
|
|
||||||
|
|
||||||
|
<h1 class="text-2xl">Inicio de sesión</h1>
|
||||||
|
|
||||||
|
|
||||||
|
{{ template "components/input" .EmailField }}
|
||||||
|
|
||||||
|
{{ template "components/input" .PasswordField }}
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
67
ui/components.go
Normal file
67
ui/components.go
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
package ui
|
||||||
|
|
||||||
|
import "github.com/gofiber/fiber/v2"
|
||||||
|
|
||||||
|
type InputField struct {
|
||||||
|
ID string
|
||||||
|
Legend string
|
||||||
|
Type string
|
||||||
|
Placeholder string
|
||||||
|
Name string
|
||||||
|
Value string
|
||||||
|
Endpoint string
|
||||||
|
Trigger string
|
||||||
|
Hint string
|
||||||
|
InputClass string
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewInputField(id, name, legend, placeholder, endpoint string) InputField {
|
||||||
|
return InputField{
|
||||||
|
ID: id,
|
||||||
|
Legend: legend,
|
||||||
|
Type: "text",
|
||||||
|
Placeholder: placeholder,
|
||||||
|
Name: name,
|
||||||
|
Value: "",
|
||||||
|
Endpoint: endpoint,
|
||||||
|
Trigger: "blur",
|
||||||
|
Hint: "",
|
||||||
|
InputClass: "",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f InputField) ToMap() fiber.Map {
|
||||||
|
return fiber.Map{
|
||||||
|
"ID": f.ID,
|
||||||
|
"Legend": f.Legend,
|
||||||
|
"Type": f.Type,
|
||||||
|
"Placeholder": f.Placeholder,
|
||||||
|
"Name": f.Name,
|
||||||
|
"Value": f.Value,
|
||||||
|
"Endpoint": f.Endpoint,
|
||||||
|
"Trigger": f.Trigger,
|
||||||
|
"Hint": f.Hint,
|
||||||
|
"InputClass": f.InputClass,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f InputField) WithValue(value string) InputField {
|
||||||
|
f.Value = value
|
||||||
|
return f
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f InputField) WithError(hint string) InputField {
|
||||||
|
f.Hint = hint
|
||||||
|
f.InputClass = "input-error text-error"
|
||||||
|
return f
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f InputField) WithType(inputType string) InputField {
|
||||||
|
f.Type = inputType
|
||||||
|
return f
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f InputField) WithTrigger(trigger string) InputField {
|
||||||
|
f.Trigger = trigger
|
||||||
|
return f
|
||||||
|
}
|
||||||
35
ui/helpers.go
Normal file
35
ui/helpers.go
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
package ui
|
||||||
|
|
||||||
|
// Aquí puedes añadir helpers predefinidos para campos comunes
|
||||||
|
|
||||||
|
// UsernameField crea un campo de username preconfigurado
|
||||||
|
func UsernameField() InputField {
|
||||||
|
return NewInputField(
|
||||||
|
"username-container",
|
||||||
|
"username",
|
||||||
|
"Nombre de usuario",
|
||||||
|
"juan.01",
|
||||||
|
"/validate/username",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// EmailField crea un campo de email preconfigurado
|
||||||
|
func EmailField() InputField {
|
||||||
|
return NewInputField(
|
||||||
|
"email-container",
|
||||||
|
"email",
|
||||||
|
"Correo electrónico",
|
||||||
|
"juan.01@email.com",
|
||||||
|
"/validate/email",
|
||||||
|
).WithType("email")
|
||||||
|
}
|
||||||
|
|
||||||
|
func PasswordField() InputField {
|
||||||
|
return NewInputField(
|
||||||
|
"password-container",
|
||||||
|
"password",
|
||||||
|
"Contraseña",
|
||||||
|
"••••••••",
|
||||||
|
"/validate/password",
|
||||||
|
).WithType("password")
|
||||||
|
}
|
||||||
75
ui/validation.go
Normal file
75
ui/validation.go
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
package ui
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"github.com/go-playground/validator/v10"
|
||||||
|
"regexp"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Instancia global del validador
|
||||||
|
var validate *validator.Validate
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
validate = validator.New()
|
||||||
|
_ = validate.RegisterValidation("username", validateUsername)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetValidator devuelve la instancia del validador
|
||||||
|
func GetValidator() *validator.Validate {
|
||||||
|
return validate
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validación personalizada de username (ejemplo)
|
||||||
|
func validateUsername(fl validator.FieldLevel) bool {
|
||||||
|
value := fl.Field().String()
|
||||||
|
re := regexp.MustCompile(`^[a-zA-Z0-9][a-zA-Z0-9._-]*[a-zA-Z0-9_]$`)
|
||||||
|
if !re.MatchString(value) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if regexp.MustCompile(`[.-]{2}`).MatchString(value) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
var ErrorMessages = map[string]string{
|
||||||
|
"required": "Este campo es obligatorio",
|
||||||
|
"min": "Debe tener al menos %s caracteres",
|
||||||
|
"max": "No puede tener más de %s caracteres",
|
||||||
|
"email": "Correo electrónico no válido",
|
||||||
|
"alphanum": "Solo se permiten letras y números",
|
||||||
|
"alpha": "Solo se permiten letras",
|
||||||
|
"numeric": "Solo se permiten números",
|
||||||
|
"len": "Debe tener exactamente %s caracteres",
|
||||||
|
"url": "Formato de URL no válido",
|
||||||
|
"containsany": "Debe contener al menos uno de estos caracteres: %s",
|
||||||
|
"username": "Formato de usuario no válido",
|
||||||
|
}
|
||||||
|
|
||||||
|
// translateError convierte un error de validación a mensaje legible
|
||||||
|
func translateError(err validator.FieldError) string {
|
||||||
|
template, exists := ErrorMessages[err.Tag()]
|
||||||
|
if !exists {
|
||||||
|
return "Valor inválido"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Si el mensaje tiene placeholder, reemplazar con el parámetro
|
||||||
|
if err.Param() != "" {
|
||||||
|
return fmt.Sprintf(template, err.Param())
|
||||||
|
}
|
||||||
|
|
||||||
|
return template
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetFirstError extrae el primer error de validación
|
||||||
|
func GetFirstError(err error) string {
|
||||||
|
var validationErrors validator.ValidationErrors
|
||||||
|
if errors.As(err, &validationErrors) {
|
||||||
|
if len(validationErrors) > 0 {
|
||||||
|
return translateError(validationErrors[0])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "Error de validación"
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user