form locking

This commit is contained in:
bronku 2026-11-09 22:40:27 +01:00
parent c4b06eb6c4
commit b2f10bb408
5 changed files with 64 additions and 10 deletions

View file

@ -255,6 +255,24 @@
}
}
#order-form form.submitting button[type=submit] {
pointer-events: none;
opacity: .7;
}
#order-form form.submitting button[type=submit]::after {
content: "";
display: inline-block;
width: 1em;
height: 1em;
margin-left: .5rem;
vertical-align: -.15em;
border: 2px solid currentColor;
border-right-color: transparent;
border-radius: 50%;
animation: spin .7s linear infinite;
}
@keyframes spin { to { transform: rotate(360deg); } }
#confirmation {
text-align: center;
min-height: calc(100vh - var(--nav-height));

View file

@ -29,12 +29,57 @@ function decrease(id) {
input.value= min(parseFloat(input.value)-step, 0)
calculate_total()
}
const busyText = 'Przechodzę do płatności…';
function lockForm(form) {
console.log("locking form")
console.log(form.querySelector('button'))
console.log(form.querySelector('button[type=submit]'))
const btn = form.querySelector('button[type=submit]');
form.classList.add('submitting');
form.setAttribute('aria-busy', 'true');
btn.dataset.label = btn.dataset.label || btn.textContent;
btn.textContent = busyText;
}
function unlockForms() {
document.querySelectorAll('form.submitting').forEach(form => {
const btn = form.querySelector('button[type=submit]');
form.classList.remove('submitting');
form.removeAttribute('aria-busy');
if (btn.dataset.label) btn.textContent = btn.dataset.label;
});
}
document.body.addEventListener('htmx:beforeRequest', e => {
console.log("form clicked")
const form = e.target;
if (!form.matches('#order-form form')) return;
if (form.classList.contains('submitting')) { e.preventDefault(); return; } // blocks double submit, incl. Enter key
lockForm(form);
});
// Replace your existing beforeSwap handler with this one: validation errors and 5xx unlock the form
document.body.addEventListener('htmx:beforeSwap', e => {
if (e.detail.xhr.status >= 400) {
e.detail.shouldSwap = true;
e.detail.isError = false;
unlockForms();
}
});
// network failure or timeout: nothing gets swapped, so say something
['htmx:sendError', 'htmx:timeout'].forEach(ev =>
document.body.addEventListener(ev, () => {
unlockForms();
const box = document.getElementById('form-errors');
if (box) box.textContent = 'Nie udało się połączyć z serwerem. Spróbuj ponownie lub zadzwoń: 690 214 515.';
})
);
// Back button from Stripe can restore the page from cache with the button still locked
window.addEventListener('pageshow', e => { if (e.persisted) unlockForms(); });
htmx.config.timeout = 30000; // default is no timeout, so a hung request would spin forever
calculate_total()

View file

@ -22,7 +22,6 @@ import (
"github.com/tdewolff/minify/v2"
mcss "github.com/tdewolff/minify/v2/css"
mhtml "github.com/tdewolff/minify/v2/html"
mjs "github.com/tdewolff/minify/v2/js"
)
@ -32,10 +31,6 @@ type ComponentRenderer interface {
var minifier = sync.OnceValue(func() *minify.M {
m := minify.New()
m.Add("text/html", &mhtml.Minifier{
KeepDocumentTags: true,
KeepEndTags: true,
})
m.Add("text/css", &mcss.Minifier{})
m.Add("application/javascript", &mjs.Minifier{})
return m
@ -66,7 +61,7 @@ func saveFile(name string, comp ComponentRenderer) {
if err != nil {
log.Fatal(err)
}
minified := minifyData("text/html", buf.Bytes())
minified := buf.Bytes()
if name == "index" {
err = os.WriteFile("www/index.html", minified, 0666)

View file

@ -31,10 +31,6 @@ func init() {
func startCheckout(w http.ResponseWriter, r *http.Request, o order) {
log.Println("ORDER_PENDING", o)
url := r.URL.RequestURI()
log.Println(url)
s, err := session.New(&stripe.CheckoutSessionParams{
Mode: stripe.String("payment"),
ClientReferenceID: stripe.String(o.id),

BIN
piekarnikgosipl Executable file

Binary file not shown.