Compare commits

...

3 Commits

Author SHA1 Message Date
ekzyis 6e40ef6f54 Fix error order 2024-09-11 12:28:46 +02:00
ekzyis e868f91f84 Show form errors 2024-09-11 12:26:15 +02:00
ekzyis 5788d5b509 Allow users to edit their name 2024-09-11 11:30:25 +02:00
5 changed files with 109 additions and 5 deletions

View File

@ -23,6 +23,10 @@
transition: background-color 150ms ease-in;
}
* {
transition: background-color 150ms ease-in, color 150ms ease-in;
}
#content {
min-height: 85svh;
}
@ -97,9 +101,11 @@
}
input {
padding: 0 0.2em;
color: var(--black);
}
.text-muted {
color: var(--muted);
}

View File

@ -1,6 +1,10 @@
package handler
import (
"fmt"
"net/http"
"regexp"
"git.ekzyis.com/ekzyis/delphi.market/server/router/context"
"git.ekzyis.com/ekzyis/delphi.market/server/router/pages"
"git.ekzyis.com/ekzyis/delphi.market/types"
@ -10,6 +14,42 @@ import (
func HandleUser(sc context.Context) echo.HandlerFunc {
return func(c echo.Context) error {
u := c.Get("session").(types.User)
return pages.User(&u).Render(context.RenderContext(sc, c), c.Response().Writer)
return pages.User(&u, nil).Render(context.RenderContext(sc, c), c.Response().Writer)
}
}
func HandleUserEdit(sc context.Context) echo.HandlerFunc {
return func(c echo.Context) error {
var (
db = sc.Db
ctx = c.Request().Context()
u = c.Get("session").(types.User)
name = c.FormValue("name")
maxLength = 16
errors types.UserEditError
err error
)
if name == "" {
errors.Name = "required"
} else if len(name) > maxLength {
errors.Name = fmt.Sprintf("%d characters too long", len(name)-maxLength)
} else if !regexp.MustCompile(`^[a-zA-Z0-9_-]+$`).MatchString(name) {
errors.Name = "only letters, numbers, _ and - allowed"
}
if errors.Name != "" {
c.Response().WriteHeader(http.StatusBadRequest)
return pages.User(&u, &errors).Render(context.RenderContext(sc, c), c.Response().Writer)
}
if err = db.QueryRowContext(ctx,
"UPDATE users SET name = $1 WHERE id = $2 RETURNING name",
name, u.Id).Scan(&u.Name); err != nil {
return err
}
return pages.User(&u, &errors).Render(context.RenderContext(sc, c), c.Response().Writer)
}
}

View File

@ -1,13 +1,14 @@
package pages
import (
"fmt"
"git.ekzyis.com/ekzyis/delphi.market/server/router/pages/components"
"git.ekzyis.com/ekzyis/delphi.market/types"
"strconv"
"time"
)
templ User(user *types.User) {
templ User(user *types.User, errors *types.UserEditError) {
<html>
@components.Head()
<body class="container">
@ -15,7 +16,7 @@ templ User(user *types.User) {
<div id="content" class="flex flex-col">
@components.Figlet("random", "user")
<div
class="grid grid-cols-2 gap-4 my-3 mx-auto"
class="grid grid-cols-[1fr_3fr] gap-4 my-3 mx-auto"
hx-target="#content"
hx-swap="outerHTML"
hx-select="#content"
@ -25,17 +26,69 @@ templ User(user *types.User) {
<div class="font-bold">id</div>
<div>{ strconv.Itoa(user.Id) }</div>
<div class="font-bold">name</div>
<div>{ user.Name }</div>
<div>
<div class="flex">
<span id="name" class={ maybeShow("", errors == nil || errors.Name == "") }>{ user.Name }</span>
<form
class="hidden flex mb-0"
hx-put="/user"
hx-push-url="false"
hx-target="#name"
hx-select="#name"
hx-select-oob="#error"
>
<input
name="name"
type="text"
value={ user.Name }
class="font-mono w-[160px]"
/>
<button class="ms-1 px-1" type="submit">save</button>
</form>
<button id="cancel" class="hidden flex text-muted hover:text-reset text-xs items-center ms-1">cancel</button>
<button id="edit" class="flex text-muted hover:text-reset text-xs items-center ms-1">edit</button>
</div>
if errors != nil && errors.Name != "" {
<div id="error" class="text-xs text-error">{ errors.Name }</div>
} else {
<div id="error" class="hidden"></div>
}
</div>
<div class="font-bold">joined</div>
<div>{ user.CreatedAt.Format(time.DateOnly) }</div>
<div class="font-bold">sats</div>
<!-- TODO: implement withdrawal of sats -->
<div>{ strconv.Itoa(int(user.Msats) / 1000) }</div>
<div class="flex">
<span>{ strconv.Itoa(int(user.Msats) / 1000) }</span>
<button class="flex text-muted hover:text-reset text-xs items-center ms-3">withdraw</button>
</div>
<!-- TODO: add WebLN and NWC for send+recv -->
<button hx-post="/logout" class="col-span-2">logout</button>
</div>
<script>
function toggleForm () {
htmx.toggleClass("#name", "hidden")
htmx.toggleClass("#name+form", "hidden")
htmx.toggleClass("#cancel", "hidden")
htmx.toggleClass("#edit", "hidden")
$("#name+form>input").focus()
}
htmx.on("#edit", "click", toggleForm)
htmx.on("#cancel", "click", toggleForm)
htmx.on("form", "htmx:afterRequest", ({ detail }) => {
if (detail.successful) toggleForm()
})
</script>
</div>
@components.Footer()
</body>
</html>
}
func maybeShow(class string, show bool) string {
if show {
return class
}
return fmt.Sprintf("%s %s", class, "hidden")
}

View File

@ -28,6 +28,7 @@ func Init(e *echo.Echo, sc Context) {
e.GET("/session", handler.HandleSessionCheck(sc))
e.GET("/user", handler.HandleUser(sc), middleware.SessionGuard(sc))
e.PUT("/user", handler.HandleUserEdit(sc), middleware.SessionGuard(sc))
e.POST("/logout", handler.HandleLogout(sc), middleware.SessionGuard(sc))
e.GET("/invoice/:hash", handler.HandleInvoice(sc), middleware.SessionGuard(sc))

View File

@ -15,6 +15,10 @@ type User struct {
Msats int64
}
type UserEditError struct {
Name string
}
type Invoice struct {
Id int
UserId int