View Issue Details
| ID | Project | Category | View Status | Date Submitted | Last Update |
|---|---|---|---|---|---|
| 0012022 | Taler | merchant portal webui | public | 2026-11-10 23:41 | 2026-11-10 23:43 |
| Reporter | bot-cg [agent] | Assigned To | Resolution deadline | ||
| Implementer | Reviewer | ||||
| Release signed by | Deployed by | ||||
| Verifier | Communicator | CM_7 | |||
| Data protection authority notification required | No | ||||
| CSIRT notification required | No | ||||
| Estimated effort (hours) | Actual effort (hours) | 0 | |||
| Fast-track | No | ||||
| Work package | none | ||||
| Priority | normal | Severity | minor | Reproducibility | have not tried |
| Status | new | Resolution | open | ||
| Summary | 0012022: Merchant PoS: ad-hoc item and quick-refund dialogs, and the paid screen, do not focus what they ask for | ||||
| Description | What happens: Web PoS: (1) '+ Ad-hoc Item' opens 'Add Ad-hoc Custom Item' - focus stays on the '+ Ad-hoc Item' button behind the overlay. (2) Till History -> '↩ Issue Refund' opens the refund dialog - focus stays on the row button behind it, also after the remaining amount has loaded; when nothing is left to refund the amount stays disabled and focus is still behind. (3) When the customer pays, the till switches to 'Payment Successful!' - focus is wherever it was (BODY in the real flow) instead of the only action. Expected: (1) Item Description input. (2) Refund amount once enabled (after the order's remainder loads), otherwise Cancel. (3) 'Start New Sale' button, so the cashier can press Enter. Where: - packages/taler-merchant-webui/src/screens/PosScreen.tsx:290 - packages/taler-merchant-webui/src/screens/PosScreen.tsx:1291 - packages/taler-merchant-webui/src/screens/PosScreen.tsx:1415 - packages/taler-merchant-webui/src/screens/PosScreen.tsx:1485 - packages/taler-merchant-webui/src/screens/PosScreen.tsx:1541 Measured in headless Chromium (document.activeElement): before: ad-hoc: BUTTON '+ Ad-hoc Item' (behind overlay); refund: BUTTON '↩ Issue Refund' (behind overlay), also when fully refunded; success: unchanged previous focus after: ad-hoc: INPUT[text] description; refund: INPUT[number] amount; fully refunded: BUTTON 'Cancel'; success: BUTTON#pos_new_sale_btn (other shots: merchant-b--pos-custom-item-dialog--*.png, merchant-b--pos-success-step--*.png) Proposed fix: Refs plus three effects: on showCustomModal -> description input; on refundOrderId/isRefundDetailsLoading settling -> amount input if enabled, else Cancel; on activeTab === 'success' -> Start New Sale button. Patch against taler-typescript-core master attached; the same change is committed on branch dev/bots/autofocus (durian). Screenshots attached (focused element outlined in red): 'before' is master, 'after' is with the patch. Verified: tsc ok for file; screens.test.js passes; screenshots confirm | ||||
| Tags | No tags attached. | ||||
| Attached Files | merchant-b--pos-dialogs-and-success.diff (2,797 bytes)
diff --git a/packages/taler-merchant-webui/src/screens/PosScreen.tsx b/packages/taler-merchant-webui/src/screens/PosScreen.tsx
index d324f83f3..bfe0cc4a8 100644
--- a/packages/taler-merchant-webui/src/screens/PosScreen.tsx
+++ b/packages/taler-merchant-webui/src/screens/PosScreen.tsx
@@ -285,6 +285,26 @@ export function PosScreen({
const [isRefundDetailsLoading, setIsRefundDetailsLoading] =
useState<boolean>(false);
+ // Each overlay and the paid screen asks for one thing first; put focus there
+ // when it appears instead of leaving it on the control behind the overlay.
+ const customDescInput = useRef<HTMLInputElement>(null);
+ const refundAmountInput = useRef<HTMLInputElement>(null);
+ const refundCancelButton = useRef<HTMLButtonElement>(null);
+ const newSaleButton = useRef<HTMLButtonElement>(null);
+ useEffect(() => {
+ if (showCustomModal) customDescInput.current?.focus();
+ }, [showCustomModal]);
+ // The refund amount stays disabled until the order's refundable remainder
+ // has loaded; if nothing is left to refund, only Cancel is useful.
+ useEffect(() => {
+ if (!refundOrderId || isRefundDetailsLoading) return;
+ const amount = refundAmountInput.current;
+ (amount && !amount.disabled ? amount : refundCancelButton.current)?.focus();
+ }, [refundOrderId, isRefundDetailsLoading]);
+ useEffect(() => {
+ if (activeTab === "success") newSaleButton.current?.focus();
+ }, [activeTab]);
+
const { copied: copiedPayUrl, error: copyPayUrlError, copy } = useClipboard();
// Which order the caller last asked for. Seeded with the mount value so the
@@ -1268,6 +1288,7 @@ export function PosScreen({
)}
<button
+ ref={newSaleButton}
type="button"
id="pos_new_sale_btn"
onClick={() => {
@@ -1391,6 +1412,7 @@ export function PosScreen({
{t`Item Description *`}
</label>
<input
+ ref={customDescInput}
type="text"
required
placeholder={t`e.g. Custom Bakery Gift Set`}
@@ -1460,6 +1482,7 @@ export function PosScreen({
{t`Refund Amount (${Amounts.amountCurrency(refundDetails?.remaining) || "—"}) *`}
</label>
<input
+ ref={refundAmountInput}
type="number"
step="any"
required
@@ -1515,6 +1538,7 @@ export function PosScreen({
title={t`Failed to issue refund.`}
>
<button
+ ref={refundCancelButton}
type="button"
onClick={() => {
setRefundOrderId(null);
| ||||
| child of | 0012002 | new | Merchant portal: dialogs, PIN/code prompts and single-purpose forms open without keyboard focus on the obvious field |
| Date Modified | Username | Field | Change |
|---|---|---|---|
| 2026-11-10 23:41 | bot-cg | New Issue | |
| 2026-11-10 23:41 | bot-cg | Relationship added | child of 0012002 |
| 2026-11-10 23:43 | bot-cg | File Added: merchant-b--pos-dialogs-and-success.diff | |
| 2026-11-10 23:43 | bot-cg | File Added: merchant-b--pos-refund-dialog--before.png | |
| 2026-11-10 23:43 | bot-cg | File Added: merchant-b--pos-refund-dialog--after.png |