feat: edit shopping list name

This commit is contained in:
juancwu 2026-02-07 15:20:43 +00:00
commit e8d34fde3f
4 changed files with 72 additions and 1 deletions

View file

@ -146,6 +146,31 @@ func (h *SpaceHandler) CreateList(w http.ResponseWriter, r *http.Request) {
ui.Render(w, r, shoppinglist.ListItem(newList)) ui.Render(w, r, shoppinglist.ListItem(newList))
} }
func (h *SpaceHandler) UpdateList(w http.ResponseWriter, r *http.Request) {
spaceID := r.PathValue("spaceID")
listID := r.PathValue("listID")
if err := r.ParseForm(); err != nil {
http.Error(w, "Bad Request", http.StatusBadRequest)
return
}
name := r.FormValue("name")
if name == "" {
http.Error(w, "List name is required", http.StatusBadRequest)
return
}
updatedList, err := h.listService.UpdateList(listID, name)
if err != nil {
slog.Error("failed to update list", "error", err, "list_id", listID)
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
return
}
ui.Render(w, r, shoppinglist.ListNameHeader(spaceID, updatedList))
}
func (h *SpaceHandler) ListPage(w http.ResponseWriter, r *http.Request) { func (h *SpaceHandler) ListPage(w http.ResponseWriter, r *http.Request) {
spaceID := r.PathValue("spaceID") spaceID := r.PathValue("spaceID")
listID := r.PathValue("listID") listID := r.PathValue("listID")

View file

@ -76,6 +76,10 @@ func SetupRoutes(a *app.App) http.Handler {
listPageWithAccess := middleware.RequireSpaceAccess(a.SpaceService)(listPageHandler) listPageWithAccess := middleware.RequireSpaceAccess(a.SpaceService)(listPageHandler)
mux.Handle("GET /app/spaces/{spaceID}/lists/{listID}", listPageWithAccess) mux.Handle("GET /app/spaces/{spaceID}/lists/{listID}", listPageWithAccess)
updateListHandler := middleware.RequireAuth(space.UpdateList)
updateListWithAccess := middleware.RequireSpaceAccess(a.SpaceService)(updateListHandler)
mux.Handle("PATCH /app/spaces/{spaceID}/lists/{listID}", updateListWithAccess)
addItemHandler := middleware.RequireAuth(space.AddItemToList) addItemHandler := middleware.RequireAuth(space.AddItemToList)
addItemWithAccess := middleware.RequireSpaceAccess(a.SpaceService)(addItemHandler) addItemWithAccess := middleware.RequireSpaceAccess(a.SpaceService)(addItemHandler)
mux.Handle("POST /app/spaces/{spaceID}/lists/{listID}/items", addItemWithAccess) mux.Handle("POST /app/spaces/{spaceID}/lists/{listID}/items", addItemWithAccess)

View file

@ -4,8 +4,50 @@ import (
"fmt" "fmt"
"git.juancwu.dev/juancwu/budgit/internal/model" "git.juancwu.dev/juancwu/budgit/internal/model"
"git.juancwu.dev/juancwu/budgit/internal/ui/components/checkbox" "git.juancwu.dev/juancwu/budgit/internal/ui/components/checkbox"
"git.juancwu.dev/juancwu/budgit/internal/ui/components/csrf"
"git.juancwu.dev/juancwu/budgit/internal/ui/components/input"
) )
templ ListNameHeader(spaceID string, list *model.ShoppingList) {
<div id="list-name-header" class="flex items-center gap-2 group">
<h1 class="text-2xl font-bold">{ list.Name }</h1>
<button
class="text-muted-foreground hover:text-foreground opacity-0 group-hover:opacity-100 transition-opacity"
_="on click toggle .hidden on #list-name-header then toggle .hidden on #list-name-edit then focus() the first <input/> in #list-name-edit"
>
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21.174 6.812a1 1 0 0 0-3.986-3.987L3.842 16.174a2 2 0 0 0-.5.83l-1.321 4.352a.5.5 0 0 0 .623.622l4.353-1.32a2 2 0 0 0 .83-.497z"></path><path d="m15 5 4 4"></path></svg>
</button>
</div>
<form
id="list-name-edit"
class="hidden flex items-center gap-2"
hx-patch={ fmt.Sprintf("/app/spaces/%s/lists/%s", spaceID, list.ID) }
hx-target="#list-name-header"
hx-swap="outerHTML"
_="on htmx:afterRequest toggle .hidden on me then toggle .hidden on #list-name-header"
>
@csrf.Token()
@input.Input(input.Props{
Name: "name",
Value: list.Name,
Class: "max-w-xs",
Attributes: templ.Attributes{
"required": "true",
},
})
<button type="submit" class="inline-flex items-center justify-center rounded-md text-sm font-medium h-9 px-3 bg-primary text-primary-foreground hover:bg-primary/90">
Save
</button>
<button
type="button"
class="inline-flex items-center justify-center rounded-md text-sm font-medium h-9 px-3 border hover:bg-muted"
_="on click toggle .hidden on #list-name-edit then toggle .hidden on #list-name-header"
>
Cancel
</button>
</form>
}
templ ListItem(list *model.ShoppingList) { templ ListItem(list *model.ShoppingList) {
<a href={ templ.URL(fmt.Sprintf("/app/spaces/%s/lists/%s", list.SpaceID, list.ID)) } class="block p-4 border rounded-lg hover:bg-muted transition-colors"> <a href={ templ.URL(fmt.Sprintf("/app/spaces/%s/lists/%s", list.SpaceID, list.ID)) } class="block p-4 border rounded-lg hover:bg-muted transition-colors">
<div class="flex justify-between items-center"> <div class="flex justify-between items-center">

View file

@ -12,7 +12,7 @@ import (
templ SpaceListDetailPage(space *model.Space, list *model.ShoppingList, items []*model.ListItem) { templ SpaceListDetailPage(space *model.Space, list *model.ShoppingList, items []*model.ListItem) {
@layouts.Space(list.Name, space) { @layouts.Space(list.Name, space) {
<div class="space-y-4"> <div class="space-y-4">
<h1 class="text-2xl font-bold">{ list.Name }</h1> @shoppinglist.ListNameHeader(space.ID, list)
<form <form
hx-post={ "/app/spaces/" + space.ID + "/lists/" + list.ID + "/items" } hx-post={ "/app/spaces/" + space.ID + "/lists/" + list.ID + "/items" }
hx-target="#items-container" hx-target="#items-container"