feat(settings): save feedback — success/error toasts, disabled buttons; fix scan-form raw-JSON swap
Release / build-and-push (push) Successful in 4m35s
Release / build-and-push (push) Successful in 4m35s
None of the three save surfaces on the admin settings page gave usable
feedback: the Base URL form re-rendered into an identical-looking form
(its only indicator was the Go-source twin's banner — markup that
diverged from the templ original), the scan form was WORSE — its handler
responds JSON, so htmx's outerHTML swap replaced the whole scan card
with literal JSON text — and the tunable rows showed only a tiny
per-row label.
Client side (toasts are the sanctioned client-local island; no new
inline scripts, CSP unchanged):
- web/src/settings.ts: one delegated htmx:afterRequest/sendError
listener keyed on a data-settings-save attribute stamped on each
form. Success → green toast per surface ("Settings saved" / "Scan
settings saved" / "Setting saved"); failure → error toast carrying
the server's JSON error field, degrading to a status line for the
tunable endpoint's HTML error body (markup must never reach a toast).
Network-level failures (htmx:sendError) toast instead of silently
re-enabling the button.
- htmx 2.x quirk handled: for FORM submissions, afterRequest's
detail.elt is the event-dispatch ancestor, not the form — the
requesting element lives on detail.requestConfig.elt (verified
empirically; saveLabelFor reads the right one).
- hx-disabled-elt="this" on all save buttons: the click is
acknowledged and double-clicks cannot double-save.
Server side:
- Scan form: hx-swap="none" (the inputs already hold the saved values;
nothing needs re-rendering) — kills the JSON-over-the-card swap.
- The Base URL form's save response is a Go-source copy of the templ
form (handlers cannot import templates: templates/utils.go imports
handlers types). The twin now carries the functional attributes
(data-settings-save, hx-disabled-elt) so feedback survives the swap,
with a drift-warning comment; the double-save e2e pins this.
Dev-DB safety: every success save in the spec re-submits the value
already on the page (zero drift), and the spec restores
system_config.updated_by after itself — the save handler stamps the
requesting admin's id there, and that FK (no cascade) otherwise blocks
the integration harness's transient-user sweep (it broke
TestUpdateProfileAdminMode/TestDeleteUserConsolidated on this run's
first pass; pollution cleaned up by hand and now self-restoring).
Tests: web/src/settings.test.ts pins the form→label map and the
error-extraction contract (including the no-markup-in-toast rule);
e2e/settings-feedback.spec.ts drives all three surfaces through the
real admin UI including the error path and the twin-drift double-save.
This commit is contained in:
1 parent
7b9e8e4fc2
commit
9f39e2efd8
7 files changed
+357
-24
No files matched your search
@@ -489,12 +489,18 @@ func (h *SidecarHandler) UpdateSystemConfiguration(c *echo.Context) error {
|
||||
defaultTimezone = tz
|
||||
}
|
||||
|
||||
// Render success message with updated form
|
||||
// Render success message with updated form. NOTE: this Go-source
|
||||
// copy is the htmx-swapped twin of the templ form in
|
||||
// templates/admin_settings.templ (handlers cannot import templates:
|
||||
// templates/utils.go imports handlers types — cycle). It MUST keep
|
||||
// the twin's functional attributes — id, hx-*, data-settings-save,
|
||||
// hx-disabled-elt — or save feedback silently dies after the first
|
||||
// swap. e2e/settings-feedback.spec.ts saves twice to pin this.
|
||||
return c.HTML(http.StatusOK, fmt.Sprintf(`
|
||||
<div class="mb-4 p-4 rounded-lg" style="background-color: var(--bg-secondary); border: 1px solid var(--accent);">
|
||||
<p style="color: var(--text-success);">✅ Settings saved successfully!</p>
|
||||
</div>
|
||||
<form id="settings-form" hx-put="/api/system/config" hx-target="#settings-form" hx-swap="outerHTML">
|
||||
<form id="settings-form" data-settings-save="settings" hx-put="/api/system/config" hx-target="#settings-form" hx-swap="outerHTML">
|
||||
<div class="card p-6 rounded-lg border" style="background-color: var(--bg-secondary); border-color: var(--border);">
|
||||
<h3 class="text-xl font-semibold mb-6" style="color: var(--text-primary)">Base URL</h3>
|
||||
|
||||
@@ -513,7 +519,7 @@ func (h *SidecarHandler) UpdateSystemConfiguration(c *echo.Context) error {
|
||||
</div>
|
||||
|
||||
<div class="mt-6 flex justify-end">
|
||||
<button type="submit" class="btn-primary px-6 py-2 rounded-lg font-medium">
|
||||
<button type="submit" class="btn-primary px-6 py-2 rounded-lg font-medium" hx-disabled-elt="this">
|
||||
Save Settings
|
||||
</button>
|
||||
</div>
|
||||
@@ -551,7 +557,7 @@ func (h *SidecarHandler) UpdateSystemConfiguration(c *echo.Context) error {
|
||||
<p class="text-sm mt-1" style="color: var(--text-secondary)">Default timezone for users who haven't set their own.</p>
|
||||
</div>
|
||||
<div class="mt-6 flex justify-end">
|
||||
<button type="submit" class="btn-primary px-6 py-2 rounded-lg font-medium">
|
||||
<button type="submit" class="btn-primary px-6 py-2 rounded-lg font-medium" hx-disabled-elt="this">
|
||||
Save Settings
|
||||
</button>
|
||||
</div>
|
||||
|
||||
Reference in new issue
Block a user