feat: give users submission feedback

This commit is contained in:
Adriano Caloiaro 2025-02-14 11:39:22 -07:00
parent f7a5935229
commit a44d713a99
8 changed files with 126 additions and 51 deletions

View file

@ -143,17 +143,10 @@
description = "Execute 'ess' with default parameters";
};
frm-server = {
frm-dev = {
description = "Run the development server";
exec = ''
go generate ./... && go run ./cmd/dev_server
'';
};
templ-server = {
description = "Run templ and watch for changes";
exec = ''
templ generate -cmd frm-server -watch -watch-pattern='(^(?:[^e]|e[^n]|en[^u]|enu[^m]|enum[^e]|enume[^r])*\.go$)|(.+\.templ$)|(.+_templ\.txt$)|(.+styles\.css$)' -proxy 'http://localhost:3000'
go generate ./... && go run cmd/dev_server/main.go
'';
};
@ -168,19 +161,21 @@
${sqlc}/bin/sqlc generate && echo sqlc generate done
'';
};
rebuild-tailwind = {
description = "rebuild tailwind's css";
exec = "tailwindcss -c ui/tailwind.config.js -i ./ui/css/tailwind.css -o ./static/css/styles.css && echo -e \"package frm\nimport \\x22fmt\\x22\nfunc main() { fmt.Println($(head -c2 /dev/urandom | od -An -vtu4)) }\" > ./trigger_restart.go";
};
};
processes."server" = {
exec = "templ-server";
processes.frm-server = {
exec = ''
reflex \
--start-service \
--inverse-regex=testdata \
--inverse-regex='_test.go$' \
--inverse-regex='^\.devenv' \
--inverse-regex='^\.direnv' \
--inverse-regex='^vendor' \
--inverse-regex='.*_enumer\.go|.+\.templ|.+frm-dev$' -v \
frm-dev
'';
process-compose = {
availability = {
restart = "on_failure";
};
readiness_probe = {
http_get = {
host = "127.0.0.1";
@ -206,11 +201,17 @@
processes.tailwindcss = {
exec = ''
reflex \
-v \
--inverse-regex='^\.devenv' \
--inverse-regex='^\.direnv' \
-r '.+tailwind\.css$|.+\.templ$' \
-- rebuild-tailwind
--start-service \
-r '.*tailwind\.css$|.*\.templ$' \
--inverse-regex='\.devenv' \
--inverse-regex='\.direnv' \
-- tailwindcss -i ./css/tailwind.css -o ./static/css/styles.css
'';
};
processes.templ = {
exec = ''
templ generate --watch --proxy="http://localhost:3000"
'';
};

View file

@ -7,6 +7,7 @@ import (
"net/http"
"net/url"
"slices"
"time"
"github.com/acaloiaro/frm"
"github.com/acaloiaro/frm/internal"
@ -163,6 +164,7 @@ func Collect(w http.ResponseWriter, r *http.Request) {
slog.Error("[collector] unable to execute submission receiver", "error", err)
}
}
time.Sleep(3 * time.Second)
err = collector.ThankYou().Render(ctx, w)
if err != nil {
slog.Error("[collector] unable to render thank you page", "error", err)

View file

@ -5,6 +5,7 @@ import (
"encoding/json"
"errors"
"fmt"
"log/slog"
"math/rand"
"strings"
@ -118,6 +119,7 @@ func getPool(ctx context.Context, args DBArgs) (p *pgxpool.Pool, err error) {
func Q(ctx context.Context, args DBArgs) *Queries {
p, err := getPool(ctx, args)
if err != nil {
slog.Error("unable to get database connect", "error", err)
return New(&NoopDBTX{})
}
return New(p)

View file

@ -2,5 +2,5 @@ package static
import "embed"
//go:embed js/* css/*
//go:embed js/* css/* img/*
var Assets embed.FS

52
static/img/bars.svg Normal file
View file

@ -0,0 +1,52 @@
<svg width="16" height="16" viewBox="0 0 135 140" xmlns="http://www.w3.org/2000/svg" fill="#494949">
<rect y="10" width="15" height="120" rx="6">
<animate attributeName="height"
begin="0.5s" dur="1s"
values="120;110;100;90;80;70;60;50;40;140;120" calcMode="linear"
repeatCount="indefinite" />
<animate attributeName="y"
begin="0.5s" dur="1s"
values="10;15;20;25;30;35;40;45;50;0;10" calcMode="linear"
repeatCount="indefinite" />
</rect>
<rect x="30" y="10" width="15" height="120" rx="6">
<animate attributeName="height"
begin="0.25s" dur="1s"
values="120;110;100;90;80;70;60;50;40;140;120" calcMode="linear"
repeatCount="indefinite" />
<animate attributeName="y"
begin="0.25s" dur="1s"
values="10;15;20;25;30;35;40;45;50;0;10" calcMode="linear"
repeatCount="indefinite" />
</rect>
<rect x="60" width="15" height="140" rx="6">
<animate attributeName="height"
begin="0s" dur="1s"
values="120;110;100;90;80;70;60;50;40;140;120" calcMode="linear"
repeatCount="indefinite" />
<animate attributeName="y"
begin="0s" dur="1s"
values="10;15;20;25;30;35;40;45;50;0;10" calcMode="linear"
repeatCount="indefinite" />
</rect>
<rect x="90" y="10" width="15" height="120" rx="6">
<animate attributeName="height"
begin="0.25s" dur="1s"
values="120;110;100;90;80;70;60;50;40;140;120" calcMode="linear"
repeatCount="indefinite" />
<animate attributeName="y"
begin="0.25s" dur="1s"
values="10;15;20;25;30;35;40;45;50;0;10" calcMode="linear"
repeatCount="indefinite" />
</rect>
<rect x="120" y="10" width="15" height="120" rx="6">
<animate attributeName="height"
begin="0.5s" dur="1s"
values="120;110;100;90;80;70;60;50;40;140;120" calcMode="linear"
repeatCount="indefinite" />
<animate attributeName="y"
begin="0.5s" dur="1s"
values="10;15;20;25;30;35;40;45;50;0;10" calcMode="linear"
repeatCount="indefinite" />
</rect>
</svg>

After

Width:  |  Height:  |  Size: 2.3 KiB

View file

@ -70,6 +70,8 @@ templ FormView(args ViewerArgs) {
data-hx-post={ formCollectorUrl[string](ctx, args.ShortCode) }
data-hx-target-400="#errors"
}
data-hx-disabled-elt="find #submit_button"
data-hx-indicator="#spinner"
>
if args.ShortCode != "" {
<input name="short_code" type="hidden" value={ args.ShortCode }/>
@ -87,14 +89,16 @@ templ FormView(args ViewerArgs) {
</div>
}
<div class="py-3"></div>
<input
type="submit"
value="submit"
class="btn bg-primary-500 hover:bg-primary-400 cursor-pointer justify-center uppercase"
<button
id="submit_button"
class="btn bg-primary-500 hover:bg-primary-400 cursor-pointer justify-center uppercase disabled:bg-gray-500"
if args.Preview {
disabled
}
/>
>
Submit
<img id="spinner" class="htmx-indicator" src={ frm.CollectorPath(ctx, "/static/img/bars.svg") }/>
</button>
</form>
</div>
if f, err := frm.Instance(ctx); err == nil {

View file

@ -206,7 +206,7 @@ func FormView(args ViewerArgs) templ.Component {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, ">")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, " data-hx-disabled-elt=\"find #submit_button\" data-hx-indicator=\"#spinner\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@ -218,7 +218,7 @@ func FormView(args ViewerArgs) templ.Component {
var templ_7745c5c3_Var8 string
templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinStringErrs(args.ShortCode)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/collector/collector.templ`, Line: 75, Col: 66}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/collector/collector.templ`, Line: 77, Col: 66}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var8))
if templ_7745c5c3_Err != nil {
@ -237,7 +237,7 @@ func FormView(args ViewerArgs) templ.Component {
var templ_7745c5c3_Var9 string
templ_7745c5c3_Var9, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("field-container-%s", field.ID.String()))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/collector/collector.templ`, Line: 79, Col: 63}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/collector/collector.templ`, Line: 81, Col: 63}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var9))
if templ_7745c5c3_Err != nil {
@ -271,7 +271,7 @@ func FormView(args ViewerArgs) templ.Component {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 22, "<div class=\"py-3\"></div><input type=\"submit\" value=\"submit\" class=\"btn bg-primary-500 hover:bg-primary-400 cursor-pointer justify-center uppercase\"")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 22, "<div class=\"py-3\"></div><button id=\"submit_button\" class=\"btn bg-primary-500 hover:bg-primary-400 cursor-pointer justify-center uppercase disabled:bg-gray-500\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@ -281,12 +281,25 @@ func FormView(args ViewerArgs) templ.Component {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 24, "></form></div>")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 24, ">Submit <img id=\"spinner\" class=\"htmx-indicator\" src=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var10 string
templ_7745c5c3_Var10, templ_7745c5c3_Err = templ.JoinStringErrs(frm.CollectorPath(ctx, "/static/img/bars.svg"))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/collector/collector.templ`, Line: 100, Col: 98}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var10))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 25, "\"></button></form></div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if f, err := frm.Instance(ctx); err == nil {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 25, "<div id=\"collector_footer\" class=\"pt-6 text-center\">")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 26, "<div id=\"collector_footer\" class=\"pt-6 text-center\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@ -294,12 +307,12 @@ func FormView(args ViewerArgs) templ.Component {
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 26, "</div>")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 27, "</div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 27, "</div>")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 28, "</div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@ -332,12 +345,12 @@ func ThankYou() templ.Component {
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var10 := templ.GetChildren(ctx)
if templ_7745c5c3_Var10 == nil {
templ_7745c5c3_Var10 = templ.NopComponent
templ_7745c5c3_Var11 := templ.GetChildren(ctx)
if templ_7745c5c3_Var11 == nil {
templ_7745c5c3_Var11 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
templ_7745c5c3_Var11 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_Var12 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
@ -349,13 +362,13 @@ func ThankYou() templ.Component {
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 28, "<div class=\"bg-sky-100 h-screen\"><section id=\"app-container\" class=\"container mx-auto\"><p class=\"text-4xl pt-9\">Thank you!</p></section></div>")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 29, "<div class=\"bg-sky-100 h-screen\"><section id=\"app-container\" class=\"container mx-auto\"><p class=\"text-4xl pt-9\">Thank you!</p></section></div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
templ_7745c5c3_Err = ui.App("Thank you").Render(templ.WithChildren(ctx, templ_7745c5c3_Var11), templ_7745c5c3_Buffer)
templ_7745c5c3_Err = ui.App("Thank you").Render(templ.WithChildren(ctx, templ_7745c5c3_Var12), templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@ -398,12 +411,12 @@ func externalStyles() templ.Component {
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var12 := templ.GetChildren(ctx)
if templ_7745c5c3_Var12 == nil {
templ_7745c5c3_Var12 = templ.NopComponent
templ_7745c5c3_Var13 := templ.GetChildren(ctx)
if templ_7745c5c3_Var13 == nil {
templ_7745c5c3_Var13 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 29, "<p class=\"underline\"></p>")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 30, "<p class=\"underline\"></p>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}

View file

@ -10,7 +10,7 @@
</h1><div id=\"form-input\" hx-ext=\"response-targets\"><div id=\"errors\"></div><form _=\"on field_change(field_id, value) formValueChanged(field_id, value)\" class=\"flex flex-col gap-3\"
data-hx-post=\"
\" data-hx-target-400=\"#errors\"
>
data-hx-disabled-elt=\"find #submit_button\" data-hx-indicator=\"#spinner\">
<input name=\"short_code\" type=\"hidden\" value=\"
\">
<div id=\"
@ -19,9 +19,10 @@
class=\"flex flex-col bg-sky-200 rounded-xl p-6\"
>
</div>
<div class=\"py-3\"></div><input type=\"submit\" value=\"submit\" class=\"btn bg-primary-500 hover:bg-primary-400 cursor-pointer justify-center uppercase\"
<div class=\"py-3\"></div><button id=\"submit_button\" class=\"btn bg-primary-500 hover:bg-primary-400 cursor-pointer justify-center uppercase disabled:bg-gray-500\"
disabled
></form></div>
>Submit <img id=\"spinner\" class=\"htmx-indicator\" src=\"
\"></button></form></div>
<div id=\"collector_footer\" class=\"pt-6 text-center\">
</div>
</div>