commit efb8269f95afd8ab9e93faa0d3f127385a3c4017 parent 6e20f6bf65fb5190d04e701293c56692bb8fdbb1 Author: Florian Dold <dold@taler.net> Date: Mon, 24 Aug 2026 02:31:43 +0200 bank web UI: fix accessibility and hook lint warnings Diffstat:
16 files changed, 46 insertions(+), 75 deletions(-)
diff --git a/packages/libeufin-bank-webui/src/app.tsx b/packages/libeufin-bank-webui/src/app.tsx @@ -31,7 +31,7 @@ import { TalerWalletIntegrationBrowserProvider, TranslationProvider, } from "@gnu-taler/web-util/browser"; -import { Fragment, h } from "preact"; +import { h } from "preact"; import { useEffect, useState } from "preact/hooks"; import { SWRConfig } from "swr"; import { Routing } from "./Routing.js"; @@ -115,7 +115,7 @@ function getInitialBackendBaseURL( } try { return canonicalizeBaseUrl(result); - } catch (e) { + } catch { // fall back return canonicalizeBaseUrl(window.origin); } diff --git a/packages/libeufin-bank-webui/src/hooks/preferences.ts b/packages/libeufin-bank-webui/src/hooks/preferences.ts @@ -28,8 +28,6 @@ import { import { UiSettings } from "../settings.js"; import { codecOptionalDefault } from "@gnu-taler/taler-util"; -const TALER_SCREEN_ID = 102; - interface Preferences { showWithdrawalSuccess: boolean; hideDemo: boolean; diff --git a/packages/libeufin-bank-webui/src/pages/LoginForm.tsx b/packages/libeufin-bank-webui/src/pages/LoginForm.tsx @@ -179,7 +179,7 @@ export function LoginForm({ > <div> <label - for="username" + htmlFor="username" class="block text-sm font-medium leading-6 text-gray-900" > <i18n.Translate>Username</i18n.Translate> @@ -212,7 +212,7 @@ export function LoginForm({ <div> <div class="flex items-center justify-between"> <label - for="password" + htmlFor="password" class="block text-sm font-medium leading-6 text-gray-900" > <i18n.Translate>Password</i18n.Translate> diff --git a/packages/libeufin-bank-webui/src/pages/NewConversionRateClass.tsx b/packages/libeufin-bank-webui/src/pages/NewConversionRateClass.tsx @@ -18,7 +18,6 @@ import { useState } from "preact/hooks"; import { useSessionState } from "../hooks/session.js"; import { ConversionRateClassForm } from "./admin/ConversionRateClassForm.js"; -const TALER_SCREEN_ID = 13; interface Props { routeCancel: RouteDefinition; onCreated: (id: number) => void; diff --git a/packages/libeufin-bank-webui/src/pages/PaymentOptions.tsx b/packages/libeufin-bank-webui/src/pages/PaymentOptions.tsx @@ -19,13 +19,11 @@ import { RouteDefinition, useTranslationContext, } from "@gnu-taler/web-util/browser"; -import { Fragment, VNode, h } from "preact"; +import { VNode, h } from "preact"; import { PaytoWireTransferForm } from "./PaytoWireTransferForm.js"; import { WalletWithdrawForm } from "./WalletWithdrawForm.js"; import { IntAmountJson } from "./regional/CreateCashout.js"; -const TALER_SCREEN_ID = 105; - // function ShowOperationPendingTag({ // woid, // onOperationAlreadyCompleted, @@ -112,7 +110,7 @@ export function PaymentOptions({ <div class="px-4 mt-4 grid grid-cols-1 gap-y-6 sm:grid-cols-2 sm:gap-x-4"> {/* <!-- Active: "border-indigo-600 ring-2 ring-indigo-600", Not Active: "border-gray-300" --> */} <a name="charge wallet" href={routeChargeWallet.url({})}> - <label + <div class={ "relative flex cursor-pointer rounded-lg border bg-white p-4 shadow-sm focus:outline-none " + (tab === "charge-wallet" @@ -158,11 +156,11 @@ export function PaymentOptions({ /> )} */} </div> - </label> + </div> </a> <a name="wire transfer" href={routeWireTransfer.url({})}> - <label + <div class={ "relative flex cursor-pointer rounded-lg border bg-white p-4 shadow-sm focus:outline-none " + (tab === "wire-transfer" @@ -197,7 +195,7 @@ export function PaymentOptions({ </i18n.Translate> </div> </div> - </label> + </div> </a> </div> {tab === "charge-wallet" && ( diff --git a/packages/libeufin-bank-webui/src/pages/PaytoWireTransferForm.tsx b/packages/libeufin-bank-webui/src/pages/PaytoWireTransferForm.tsx @@ -46,8 +46,6 @@ import { LoggedIn, useSessionState } from "../hooks/session.js"; import { undefinedIfEmpty, validateIBAN, validateTalerBank } from "../utils.js"; import { IntAmountJson, IntAmounts } from "./regional/CreateCashout.js"; -const TALER_SCREEN_ID = 106; - export interface Props { focus?: boolean; withAccount?: string; @@ -259,6 +257,7 @@ export function PaytoWireTransferForm({ </legend> <div class="-space-y-px rounded-md "> <label + aria-label={i18n.str`Using a form`} data-checked={inputType === "form"} class="group rounded-tl-md rounded-tr-md relative flex cursor-pointer border p-4 focus:outline-none bg-white data-[checked=true]:z-10 data-[checked=true]:border-indigo-200 data-[checked=true]:bg-indigo-50" > @@ -329,6 +328,7 @@ export function PaytoWireTransferForm({ {sendingToFixedAccount ? undefined : ( <Fragment> <label + aria-label="payto:// URI" data-checked={inputType === "payto"} class="relative flex cursor-pointer border p-4 focus:outline-none bg-white data-[checked=true]:z-10 data-[checked=true]:border-indigo-200 data-[checked=true]:bg-indigo-50" > @@ -401,6 +401,7 @@ export function PaytoWireTransferForm({ // eslint-disable-next-line no-constant-binary-expression false && ( <label + aria-label={i18n.str`Scan a QR code`} data-checked={inputType === "qr"} class="rounded-bl-md rounded-br-md relative flex cursor-pointer border p-4 focus:outline-none bg-white data-[checked=true]:z-10 data-[checked=true]:border-indigo-200 data-[checked=true]:bg-indigo-50" > diff --git a/packages/libeufin-bank-webui/src/pages/ProfileNavigation.tsx b/packages/libeufin-bank-webui/src/pages/ProfileNavigation.tsx @@ -23,8 +23,6 @@ import { useBankCoreApiContext } from "@gnu-taler/web-util/browser"; import { useSessionState } from "../hooks/session.js"; import { RouteDefinition } from "@gnu-taler/web-util/browser"; -const TALER_SCREEN_ID = 107; - export function ProfileNavigation({ current, routeMyAccountCashout, @@ -51,7 +49,10 @@ export function ProfileNavigation({ return ( <div> <div class="sm:hidden"> - <label for="tabs" class="sr-only"> + <label + htmlFor="tabs" + class="sr-only" + > <i18n.Translate>Select a section</i18n.Translate> </label> <select @@ -116,7 +117,7 @@ export function ProfileNavigation({ <div class="hidden sm:block"> <nav class="isolate flex divide-x divide-gray-200 rounded-lg shadow" - aria-label="Tabs" + aria-label={i18n.str`Tabs`} > <a name="my account details" diff --git a/packages/libeufin-bank-webui/src/pages/RegistrationPage.tsx b/packages/libeufin-bank-webui/src/pages/RegistrationPage.tsx @@ -31,9 +31,9 @@ import { import { Fragment, h, VNode } from "preact"; import { useState } from "preact/hooks"; import { useSettingsContext } from "../context/settings.js"; -import { usePreferences } from "../hooks/preferences.js"; import { undefinedIfEmpty } from "../utils.js"; import { getRandomUsername } from "./rnd.js"; +import { doAutoFocus } from "./PaytoWireTransferForm.js"; const TALER_SCREEN_ID = 110; @@ -84,8 +84,6 @@ function RegistrationForm({ const [repeatPassword, setRepeatPassword] = useState<string | undefined>(); const { showError } = useNotificationContext(); const settings = useSettingsContext(); - const [pref] = usePreferences(); - const { lib: { bank: api }, } = useBankCoreApiContext(); @@ -202,7 +200,7 @@ function RegistrationForm({ > <div> <label - for="username" + htmlFor="username" class="block text-sm font-medium leading-6 text-gray-900" > <i18n.Translate>Login username</i18n.Translate> @@ -210,7 +208,7 @@ function RegistrationForm({ </label> <div class="mt-2"> <input - autoFocus + ref={doAutoFocus} type="text" name="username" id="username" @@ -234,7 +232,7 @@ function RegistrationForm({ <div> <div class="flex items-center justify-between"> <label - for="password" + htmlFor="password" class="block text-sm font-medium leading-6 text-gray-900" > <i18n.Translate>Password</i18n.Translate> @@ -274,7 +272,7 @@ function RegistrationForm({ <div> <div class="flex items-center justify-between"> <label - for="register-repeat" + htmlFor="register-repeat" class="block text-sm font-medium leading-6 text-gray-900" > <i18n.Translate>Repeat password</i18n.Translate> @@ -306,7 +304,7 @@ function RegistrationForm({ <div> <div class="flex items-center justify-between"> <label - for="name" + htmlFor="name" class="block text-sm font-medium leading-6 text-gray-900" > <i18n.Translate>Full name</i18n.Translate> @@ -315,7 +313,6 @@ function RegistrationForm({ </div> <div class="mt-2"> <input - autoFocus type="text" name="name" id="name" diff --git a/packages/libeufin-bank-webui/src/pages/SolveMFA.tsx b/packages/libeufin-bank-webui/src/pages/SolveMFA.tsx @@ -22,11 +22,10 @@ import { useTranslationContext, } from "@gnu-taler/web-util/browser"; import { ComponentChildren, Fragment, h, VNode } from "preact"; -import { useEffect, useRef, useState } from "preact/hooks"; +import { useCallback, useEffect, useRef, useState } from "preact/hooks"; import { useBankChallengeHandlerContext } from "../context/challenge.js"; import { doAutoFocus } from "./PaytoWireTransferForm.js"; -const TALER_SCREEN_ID = 9; export interface Props { onCompleted: AsyncAction<[challenges: string[]]>; username: string; @@ -64,12 +63,15 @@ function SolveChallenge({ const [manualRestart, setManualRestart] = useState(false); const terminalHandled = useRef(false); - async function handleTerminal(manual = false): Promise<void> { - if (!manual && terminalHandled.current) return; - if (!manual) terminalHandled.current = true; - const restarted = await onTerminal(manual); - if (!restarted) setManualRestart(true); - } + const handleTerminal = useCallback( + async (manual = false): Promise<void> => { + if (!manual && terminalHandled.current) return; + if (!manual) terminalHandled.current = true; + const restarted = await onTerminal(manual); + if (!restarted) setManualRestart(true); + }, + [onTerminal], + ); const errors = undefinedIfEmpty({ code: !tanCode ? i18n.str`Required` : undefined, @@ -89,7 +91,7 @@ function SolveChallenge({ return () => { clearTimeout(handler); }; - }, [expiration.t_ms]); + }, [expiration, handleTerminal, showExpired]); // i18n.str`confirm MFA challenge`, const doVerification = useNotifiedOperation< @@ -170,7 +172,7 @@ function SolveChallenge({ > <div> <label - for={`tan-${challenge.challenge_id}`} + htmlFor={`tan-${challenge.challenge_id}`} class="block text-sm font-medium leading-6 text-gray-900" > <i18n.Translate>Code</i18n.Translate> @@ -263,16 +265,14 @@ export function SolveChallengeDialog({ <Fragment> <dialog id="dialog" + tabIndex={-1} // open open={mfa.pending !== undefined} aria-labelledby="dialog-title" class="z-30 fixed inset-0 size-auto max-h-none max-w-none overflow-y-auto bg-transparent backdrop:bg-transparent" > <div class="fixed inset-0 bg-gray-500/75 transition-opacity data-closed:opacity-0 data-enter:duration-300 data-enter:ease-out data-leave:duration-200 data-leave:ease-in dark:bg-gray-900/50"></div> - <div - tabIndex={0} - class="flex min-h-full items-end justify-center p-4 text-center focus:outline-none sm:items-center sm:p-0 w-800" - > + <div class="flex min-h-full items-end justify-center p-4 text-center focus:outline-none sm:items-center sm:p-0 w-800"> <div class="z-40 max-w-7xl text-left"> {!mfa.pending ? undefined : ( <SolveMFAChallenges @@ -361,6 +361,8 @@ function SolveMFAChallenges({ } }), }); + const sendMessageRunning = sendMessage.running; + const sendMessageRun = sendMessage.run; // i18n.str`select challenge`, const selectChallenge = useNotifiedOperation(async (ct, ch: Challenge) => { @@ -381,19 +383,14 @@ function SolveMFAChallenges({ if ( !nextAndChallenge || selected || - sendMessage.running || + sendMessageRunning || autoSent.current.has(nextAndChallenge.challenge_id) ) { return; } autoSent.current.add(nextAndChallenge.challenge_id); - void sendMessage.run(nextAndChallenge); - }, [ - currentChallenge.combi_and, - nextAndChallenge?.challenge_id, - selected?.ch.challenge_id, - sendMessage.running, - ]); + void sendMessageRun(nextAndChallenge); + }, [nextAndChallenge, selected, sendMessageRun, sendMessageRunning]); useEffect(() => { const waits = Object.values(retransmission) diff --git a/packages/libeufin-bank-webui/src/pages/WalletWithdrawForm.tsx b/packages/libeufin-bank-webui/src/pages/WalletWithdrawForm.tsx @@ -47,8 +47,6 @@ import { OperationState } from "./OperationState/index.js"; import { InputAmount, doAutoFocus } from "./PaytoWireTransferForm.js"; import { IntAmountJson } from "./regional/CreateCashout.js"; -const TALER_SCREEN_ID = 112; - const RefAmount = forwardRef(InputAmount); export function isWithdrawalWithinLimit( diff --git a/packages/libeufin-bank-webui/src/pages/account/CashoutListForAccount.tsx b/packages/libeufin-bank-webui/src/pages/account/CashoutListForAccount.tsx @@ -21,8 +21,6 @@ import { ProfileNavigation } from "../ProfileNavigation.js"; import { CreateCashout } from "../regional/CreateCashout.js"; import { RouteDefinition } from "@gnu-taler/web-util/browser"; -const TALER_SCREEN_ID = 117; - interface Props { account: string; routeClose: RouteDefinition; diff --git a/packages/libeufin-bank-webui/src/pages/account/UpdateAccountPassword.tsx b/packages/libeufin-bank-webui/src/pages/account/UpdateAccountPassword.tsx @@ -37,8 +37,6 @@ import { undefinedIfEmpty } from "../../utils.js"; import { doAutoFocus } from "../PaytoWireTransferForm.js"; import { ProfileNavigation } from "../ProfileNavigation.js"; -const TALER_SCREEN_ID = 119; - export function UpdateAccountPassword({ account: accountName, routeClose, diff --git a/packages/libeufin-bank-webui/src/pages/admin/AccountForm.tsx b/packages/libeufin-bank-webui/src/pages/admin/AccountForm.tsx @@ -47,8 +47,6 @@ import { } from "../PaytoWireTransferForm.js"; import { getRandomPassword } from "../rnd.js"; -const TALER_SCREEN_ID = 120; - const EMAIL_REGEX = /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/; const REGEX_JUST_NUMBERS_REGEX = /^\+[0-9 ]*$/; @@ -600,11 +598,7 @@ export function AccountForm<PurposeType extends keyof ChangeByPurposeType>({ {config.supported_tan_channels.indexOf(TanChannel.EMAIL) === -1 ? undefined : ( <label - onClick={(e) => { - if (purpose === "show" || !hasEmail) return; - toggleTanChannel(TanChannel.EMAIL); - e.preventDefault(); - }} + aria-label={i18n.str`Using email`} data-disabled={purpose === "show" || !hasEmail} data-selected={effectiveForm.tan_channels?.includes( TanChannel.EMAIL, @@ -620,6 +614,7 @@ export function AccountForm<PurposeType extends keyof ChangeByPurposeType>({ checked={effectiveForm.tan_channels?.includes( TanChannel.EMAIL, )} + onChange={() => toggleTanChannel(TanChannel.EMAIL)} class="sr-only" /> <span class="flex flex-1"> @@ -656,11 +651,7 @@ export function AccountForm<PurposeType extends keyof ChangeByPurposeType>({ {config.supported_tan_channels.indexOf(TanChannel.SMS) === -1 ? undefined : ( <label - onClick={(e) => { - if (purpose === "show" || !hasPhone) return; - toggleTanChannel(TanChannel.SMS); - e.preventDefault(); - }} + aria-label={i18n.str`Using SMS`} data-disabled={purpose === "show" || !hasPhone} data-selected={effectiveForm.tan_channels?.includes( TanChannel.SMS, @@ -676,6 +667,7 @@ export function AccountForm<PurposeType extends keyof ChangeByPurposeType>({ checked={effectiveForm.tan_channels?.includes( TanChannel.SMS, )} + onChange={() => toggleTanChannel(TanChannel.SMS)} class="sr-only" /> <span class="flex flex-1"> diff --git a/packages/libeufin-bank-webui/src/pages/admin/ConversionRateClassForm.tsx b/packages/libeufin-bank-webui/src/pages/admin/ConversionRateClassForm.tsx @@ -28,8 +28,6 @@ import { useSessionState } from "../../hooks/session.js"; import { ErrorMessageMappingFor, undefinedIfEmpty } from "../../utils.js"; import { doAutoFocus } from "../PaytoWireTransferForm.js"; -const TALER_SCREEN_ID = 129; - export type ConversionRateClassFormData = { name?: string; description?: string; diff --git a/packages/libeufin-bank-webui/src/pages/admin/CreateNewAccount.tsx b/packages/libeufin-bank-webui/src/pages/admin/CreateNewAccount.tsx @@ -33,8 +33,6 @@ import { useState } from "preact/hooks"; import { useSessionState } from "../../hooks/session.js"; import { AccountForm } from "./AccountForm.js"; -const TALER_SCREEN_ID = 123; - export function CreateNewAccount({ routeCancel, onCreateSuccess, diff --git a/packages/libeufin-bank-webui/src/pages/admin/DownloadStats.tsx b/packages/libeufin-bank-webui/src/pages/admin/DownloadStats.tsx @@ -38,8 +38,6 @@ import { useState } from "preact/hooks"; import { useSessionState } from "../../hooks/session.js"; import { getTimeframesForDate } from "./AdminHome.js"; -const TALER_SCREEN_ID = 124; - interface Props { routeCancel: RouteDefinition; }