Files
JoshHeaps.Net/JoshHeaps.Net/wwwroot/js/casino/holdem/feedback.js
T
jheapsandClaude Sonnet 5 d01c67fc45 Replace the browser confirm boxes with undo and in-page dialogs
Three system confirm() dialogs sat in the middle of dealing. The worst was on
Next Hand — the most-pressed button in a session, asking "are you sure?" every
single time, stealing focus and, on a phone, taking over the screen.

Next Hand now just happens, and a toast says where the button landed with an
Undo beside it for the few seconds anyone would notice a misclick. The same
undo covers the pot wizard, which is the more valuable case: paying the wrong
player is a real mistake, and rolling it back restores the hand history entry
and the session standings along with the seats. The snapshot is in-memory only
— an undo is for the mistake you just made, not one from last night.

The two genuinely destructive actions keep a confirmation, but as an in-page
dialog that matches the table instead of the browser's own: Escape or the
backdrop cancels, Enter confirms, and on a phone the buttons go full width with
Cancel nearest the thumb.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_015bnp7xKUPFFSpwkYNFaPgb
2026-08-31 18:22:50 -06:00

116 lines
4.3 KiB
JavaScript

// Toasts and confirmation dialogs, so the table never has to fall back on the
// browser's own alert boxes. A system confirm() steals focus, looks nothing like
// the rest of the page, and on a phone it's a full-screen interruption — all of
// which is a bad trade for a dealer trying to keep a game moving.
// A short-lived message at the bottom of the screen, optionally with one action
// (in practice: Undo). Only one is ever on screen; a second replaces the first.
const Toast = {
DEFAULT_DURATION_MS: 8000,
timer: null,
show(message, options = {}) {
const host = document.getElementById("toastHost");
if (!host) return;
this.dismiss();
const toast = document.createElement("div");
toast.className = "toast";
toast.setAttribute("role", "status");
const text = document.createElement("span");
text.className = "toastText";
text.textContent = message;
toast.appendChild(text);
if (options.actionLabel && options.onAction) {
const action = document.createElement("button");
action.type = "button";
action.className = "toastAction";
action.textContent = options.actionLabel;
action.addEventListener("click", () => {
this.dismiss();
options.onAction();
});
toast.appendChild(action);
}
const close = document.createElement("button");
close.type = "button";
close.className = "toastClose";
close.setAttribute("aria-label", "Dismiss");
close.textContent = "✕";
close.addEventListener("click", () => this.dismiss());
toast.appendChild(close);
host.appendChild(toast);
// Next frame, so the entrance transition actually runs.
requestAnimationFrame(() => toast.classList.add("toastVisible"));
this.timer = setTimeout(
() => this.dismiss(),
options.duration || this.DEFAULT_DURATION_MS
);
},
dismiss() {
if (this.timer) {
clearTimeout(this.timer);
this.timer = null;
}
const host = document.getElementById("toastHost");
if (host) host.innerHTML = "";
},
};
// Styled stand-in for confirm(). Resolves true if the dealer goes through with it.
const Dialog = {
confirm({ title, message, confirmLabel = "Confirm", cancelLabel = "Cancel", danger = false }) {
return new Promise(resolve => {
const overlay = document.getElementById("confirmOverlay");
const modal = overlay.querySelector("#confirmModal");
overlay.querySelector("#confirmTitle").textContent = title;
overlay.querySelector("#confirmMessage").textContent = message;
const confirmBtn = overlay.querySelector("#confirmAcceptBtn");
const cancelBtn = overlay.querySelector("#confirmCancelBtn");
confirmBtn.textContent = confirmLabel;
cancelBtn.textContent = cancelLabel;
confirmBtn.classList.toggle("holdemBtnDanger", danger);
confirmBtn.classList.toggle("holdemBtnAccent", !danger);
const finish = result => {
overlay.hidden = true;
confirmBtn.removeEventListener("click", onAccept);
cancelBtn.removeEventListener("click", onCancel);
overlay.removeEventListener("click", onBackdrop);
document.removeEventListener("keydown", onKey);
resolve(result);
};
const onAccept = () => finish(true);
const onCancel = () => finish(false);
const onBackdrop = e => {
if (e.target === overlay) finish(false);
};
const onKey = e => {
if (e.key === "Escape") finish(false);
if (e.key === "Enter") finish(true);
};
confirmBtn.addEventListener("click", onAccept);
cancelBtn.addEventListener("click", onCancel);
overlay.addEventListener("click", onBackdrop);
document.addEventListener("keydown", onKey);
overlay.hidden = false;
modal.focus();
confirmBtn.focus();
});
},
};
if (typeof module !== "undefined" && module.exports) {
module.exports = { Toast, Dialog };
}