mirror of
https://github.com/acaloiaro/frm
synced 2026-07-21 10:12:23 +00:00
feat: give users submission feedback
This commit is contained in:
parent
f7a5935229
commit
a44d713a99
8 changed files with 126 additions and 51 deletions
49
flake.nix
49
flake.nix
|
|
@ -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"
|
||||
'';
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
52
static/img/bars.svg
Normal 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 |
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
Loading…
Reference in a new issue