commit c4b3d0f95285211fad7a0d72c2ef4992c1817f65
parent 01ae524ead2264774d877b135a581681651a74f5
Author: Sebastian <sebasjm@taler-systems.com>
Date: Mon, 1 Jun 2026 11:14:20 -0300
several fixes and improvements
better error reporting (user can copy the content)
normalize safeHandler and MFA
simplify button
only one notificaton handler
prevent printing sensitive info
Diffstat:
7 files changed, 151 insertions(+), 168 deletions(-)
diff --git a/packages/kyc-ui/src/Routing.tsx b/packages/kyc-ui/src/Routing.tsx
@@ -23,7 +23,6 @@ import {
import { Fragment, VNode, h } from "preact";
import { assertUnreachable } from "@gnu-taler/taler-util";
-import { useErrorBoundary } from "preact/hooks";
import { useSessionState } from "./hooks/session.js";
import { ChallengeCompleted } from "./pages/ChallengeCompleted.js";
import { Frame } from "./pages/Frame.js";
@@ -72,9 +71,6 @@ function PublicRounting(): VNode {
const location = useCurrentLocation(publicPages);
const { state, start } = useSessionState();
const { navigateTo } = useNavigationContext();
- useErrorBoundary((e) => {
- console.log("error", e);
- });
const currentToken = state?.accessToken;
switch (location.name) {
case undefined: {
diff --git a/packages/kyc-ui/src/app.tsx b/packages/kyc-ui/src/app.tsx
@@ -26,6 +26,7 @@ import {
BrowserHashNavigationProvider,
ExchangeApiProvider,
Loading,
+ NotificationProvider,
TalerWalletIntegrationBrowserProvider,
TranslationProvider,
UiForms,
@@ -59,51 +60,53 @@ export function App(): VNode {
return (
<SettingsProvider value={settings}>
<TranslationProvider source={strings}>
- <ExchangeApiProvider
- baseUrl={new URL("/", baseUrl)}
- frameOnError={Frame}
- evictors={{
- exchange: evictExchangeSwrCache,
- }}
- >
- <SWRConfig
- value={{
- provider: WITH_LOCAL_STORAGE_CACHE
- ? localStorageProvider
- : undefined,
- // normally, do not revalidate
- revalidateOnFocus: false,
- revalidateOnReconnect: false,
- revalidateIfStale: false,
- revalidateOnMount: undefined,
- focusThrottleInterval: undefined,
-
- // normally, do not refresh
- refreshInterval: undefined,
- dedupingInterval: 2000,
- refreshWhenHidden: false,
- refreshWhenOffline: false,
-
- // ignore errors
- shouldRetryOnError: false,
- errorRetryCount: 0,
- errorRetryInterval: undefined,
-
- // do not go to loading again if already has data
- keepPreviousData: true,
+ <NotificationProvider>
+ <ExchangeApiProvider
+ baseUrl={new URL("/", baseUrl)}
+ frameOnError={Frame}
+ evictors={{
+ exchange: evictExchangeSwrCache,
}}
>
- <TalerWalletIntegrationBrowserProvider>
- <BrowserHashNavigationProvider>
- <UiFormsProvider value={forms}>
- <NotifierProvider>
- <Routing />
- </NotifierProvider>
- </UiFormsProvider>
- </BrowserHashNavigationProvider>
- </TalerWalletIntegrationBrowserProvider>
- </SWRConfig>
- </ExchangeApiProvider>
+ <SWRConfig
+ value={{
+ provider: WITH_LOCAL_STORAGE_CACHE
+ ? localStorageProvider
+ : undefined,
+ // normally, do not revalidate
+ revalidateOnFocus: false,
+ revalidateOnReconnect: false,
+ revalidateIfStale: false,
+ revalidateOnMount: undefined,
+ focusThrottleInterval: undefined,
+
+ // normally, do not refresh
+ refreshInterval: undefined,
+ dedupingInterval: 2000,
+ refreshWhenHidden: false,
+ refreshWhenOffline: false,
+
+ // ignore errors
+ shouldRetryOnError: false,
+ errorRetryCount: 0,
+ errorRetryInterval: undefined,
+
+ // do not go to loading again if already has data
+ keepPreviousData: true,
+ }}
+ >
+ <TalerWalletIntegrationBrowserProvider>
+ <BrowserHashNavigationProvider>
+ <UiFormsProvider value={forms}>
+ <NotifierProvider>
+ <Routing />
+ </NotifierProvider>
+ </UiFormsProvider>
+ </BrowserHashNavigationProvider>
+ </TalerWalletIntegrationBrowserProvider>
+ </SWRConfig>
+ </ExchangeApiProvider>
+ </NotificationProvider>
</TranslationProvider>
</SettingsProvider>
);
diff --git a/packages/kyc-ui/src/pages/FillForm.tsx b/packages/kyc-ui/src/pages/FillForm.tsx
@@ -24,17 +24,16 @@ import {
import {
AcceptTermOfServiceContext,
Attention,
- ButtonBetter,
+ Button,
ErrorsSummary,
FormMetadata,
FormUI,
InternationalizationAPI,
Loading,
- LocalNotificationBanner,
preloadedForms,
useAsyncAsHook,
useExchangeApiContext,
- useLocalNotificationBetter,
+ useNotificationContext,
useTranslationContext,
} from "@gnu-taler/web-util/browser";
import { Fragment, VNode, h } from "preact";
@@ -115,7 +114,7 @@ function ShowForm({
onComplete: () => void;
}): VNode {
const { lib } = useExchangeApiContext();
- const [notification, safeFunctionHandler] = useLocalNotificationBetter();
+ const { actionHandler, showError } = useNotificationContext();
const [preferences] = usePreferences();
const { i18n } = useTranslationContext();
@@ -131,30 +130,31 @@ function ShowForm({
validatedForm[TalerFormAttributes.FORM_CONTEXT] = formContext;
}
- const submit = safeFunctionHandler(
- i18n.str`upload kyc form`,
- lib.exchange.uploadKycForm.bind(lib.exchange),
- !validatedForm ? undefined : [reqId, validatedForm],
+ const submit = actionHandler(
+ (ct, id, f) => lib.exchange.uploadKycForm(id, f),
+ !validatedForm ? undefined : ([reqId, validatedForm] as const),
);
submit.onSuccess = onComplete;
- submit.onFail = (fail) => {
- switch (fail.case) {
- case HttpStatusCode.PayloadTooLarge:
- return i18n.str`The form is too big for the server, try uploading smaller files.`;
- case HttpStatusCode.InternalServerError:
- return i18n.str`There was a problem processing your request. Please try again later.`;
- case HttpStatusCode.NotFound:
- return i18n.str`The account was not found`;
- case HttpStatusCode.Conflict:
- return i18n.str`Officer disabled or more recent decision was already submitted.`;
- default:
- assertUnreachable(fail);
- }
- };
+ submit.onFail = showError(
+ i18n.str`Failed to upload the KYC information.`,
+ (fail) => {
+ switch (fail.case) {
+ case HttpStatusCode.PayloadTooLarge:
+ return i18n.str`The form is too big for the server, try uploading smaller files.`;
+ case HttpStatusCode.InternalServerError:
+ return i18n.str`There was a problem processing your request. Please try again later.`;
+ case HttpStatusCode.NotFound:
+ return i18n.str`The account was not found`;
+ case HttpStatusCode.Conflict:
+ return i18n.str`Officer disabled or more recent decision was already submitted.`;
+ default:
+ assertUnreachable(fail);
+ }
+ },
+ );
return (
<div class="rounded-lg bg-white px-5 py-6 shadow m-4">
- <LocalNotificationBanner notification={notification} />
<div class="space-y-10 divide-y -mt-5 divide-gray-900/10">
<FormUI model={handler} design={design} />
</div>
@@ -181,13 +181,13 @@ function ShowForm({
>
<i18n.Translate>Cancel</i18n.Translate>
</button>
- <ButtonBetter
+ <Button
submit
class="disabled:opacity-50 disabled:cursor-default rounded-md bg-indigo-600 px-3 py-2 text-sm font-semibold text-white shadow-sm hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600"
onClick={submit}
>
<i18n.Translate>Submit</i18n.Translate>
- </ButtonBetter>
+ </Button>
</div>
{!status.errors ? undefined : <ErrorsSummary errors={status.errors} />}
</div>
diff --git a/packages/kyc-ui/src/pages/Frame.tsx b/packages/kyc-ui/src/pages/Frame.tsx
@@ -14,25 +14,22 @@
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/
-import { TranslatedString } from "@gnu-taler/taler-util";
import {
Footer,
Header,
RouteDefinition,
ToastBanner,
- notifyError,
- notifyException,
+ useRenderErrorReport,
useTranslationContext,
} from "@gnu-taler/web-util/browser";
import { ComponentChildren, Fragment, VNode, h } from "preact";
-import { useEffect, useErrorBoundary, useState } from "preact/hooks";
+import { useEffect, useState } from "preact/hooks";
import { useNotifierContext } from "../context/notifier.js";
import {
getAllBooleanPreferences,
getLabelForPreferences,
usePreferences,
} from "../context/preferences.js";
-import { useSettingsContext } from "../context/settings.js";
const GIT_HASH = typeof __GIT_HASH__ !== "undefined" ? __GIT_HASH__ : undefined;
const VERSION = typeof __VERSION__ !== "undefined" ? __VERSION__ : undefined;
@@ -46,27 +43,15 @@ export function Frame({
routeTestForms?: RouteDefinition;
children: ComponentChildren;
}): VNode {
- const settings = useSettingsContext();
const [preferences, updatePreferences] = usePreferences();
const [title, setTitle] = useState<string>();
const notifier = useNotifierContext();
- const [error, resetError] = useErrorBoundary();
const { i18n } = useTranslationContext();
- useEffect(() => {
- if (error) {
- if (error instanceof Error) {
- console.log("Internal error, please report", error);
- notifyException(i18n.str`Internal error, please report.`, error);
- } else {
- console.log("Internal error, please report", error);
- notifyError(
- i18n.str`Internal error, please report.`,
- String(error) as TranslatedString,
- );
- }
- resetError();
- }
- }, [error]);
+
+ useRenderErrorReport({
+ hash: __GIT_HASH__,
+ version: __VERSION__,
+ });
useEffect(() => {
return notifier.subscribe((event) => {
diff --git a/packages/kyc-ui/src/pages/Start.tsx b/packages/kyc-ui/src/pages/Start.tsx
@@ -22,13 +22,12 @@ import {
} from "@gnu-taler/taler-util";
import {
Attention,
- ButtonBetter,
+ Button,
ErrorLoading,
Loading,
- LocalNotificationBanner,
useExchangeApiContext,
- useLocalNotificationBetter,
- useTranslationContext,
+ useNotificationContext,
+ useTranslationContext
} from "@gnu-taler/web-util/browser";
import { Fragment, VNode, h } from "preact";
import { useEffect, useState } from "preact/hooks";
@@ -53,7 +52,12 @@ function ShowReqList({
return <Loading />;
}
if (result instanceof TalerError) {
- return <ErrorLoading error={result} />;
+ return (
+ <ErrorLoading
+ error={result}
+ title={i18n.str`Failed to load KYC information.`}
+ />
+ );
}
if (result.type === "fail") {
@@ -176,29 +180,31 @@ function LinkGenerator({ req }: { req: KycRequirementInformation }): VNode {
state: LinkGenerationState.WAIT,
});
- const [notification, safeFunctionHandler] = useLocalNotificationBetter();
+ const { actionHandler, showError } = useNotificationContext();
const { lib } = useExchangeApiContext();
- const start = safeFunctionHandler(
- i18n.str`start external kyc`,
- async (id: string) => {
+ const start = actionHandler(
+ async (ct, id: string) => {
return lib.exchange.startExternalKycProcess(id);
},
[req.id!],
);
- start.onFail = (fail) => {
- setLoading({ state: LinkGenerationState.ERROR });
- switch (fail.case) {
- case HttpStatusCode.NotFound:
- return i18n.str`not found`;
- case HttpStatusCode.Conflict:
- return i18n.str`conflict`;
- case HttpStatusCode.PayloadTooLarge:
- return i18n.str`payload is too large`;
- default:
- assertUnreachable(fail.case);
- }
- };
+ start.onFail = showError(
+ i18n.str`Failed to start the KYC process.`,
+ (fail) => {
+ setLoading({ state: LinkGenerationState.ERROR });
+ switch (fail.case) {
+ case HttpStatusCode.NotFound:
+ return i18n.str`not found`;
+ case HttpStatusCode.Conflict:
+ return i18n.str`conflict`;
+ case HttpStatusCode.PayloadTooLarge:
+ return i18n.str`payload is too large`;
+ default:
+ assertUnreachable(fail.case);
+ }
+ },
+ );
start.onSuccess = (success) => {
setLoading({
state: LinkGenerationState.DONE,
@@ -215,7 +221,6 @@ function LinkGenerator({ req }: { req: KycRequirementInformation }): VNode {
const row = (
<Fragment>
<div class="flex min-w-0 gap-x-4">
- <LocalNotificationBanner notification={notification} />
<div class="inline-block h-10 w-10 rounded-full">
{!redirectUrl ? (
<svg
@@ -257,12 +262,12 @@ function LinkGenerator({ req }: { req: KycRequirementInformation }): VNode {
</p>
) : (
<p class="text-sm font-semibold leading-6 text-gray-900">
- <ButtonBetter onClick={start} >
+ <Button onClick={start}>
<span class="absolute inset-x-0 -top-px bottom-0"></span>
<i18n.Translate context="KYC_REQUIREMENT_LINK_DESCRIPTION">
{req.description}
</i18n.Translate>
- </ButtonBetter>
+ </Button>
</p>
)}
</div>
diff --git a/packages/kyc-ui/src/pages/TriggerForms.tsx b/packages/kyc-ui/src/pages/TriggerForms.tsx
@@ -16,12 +16,10 @@
import {
FormMetadata,
FormUI,
- LocalNotificationBanner,
preloadedForms,
UIHandlerId,
useFormMeta,
- useLocalNotificationBetter,
- useTranslationContext,
+ useTranslationContext
} from "@gnu-taler/web-util/browser";
import { Fragment, h, VNode } from "preact";
@@ -35,7 +33,6 @@ type Props = {
export function TriggerForms({ formId }: Props): VNode {
const { i18n } = useTranslationContext();
- const [notification, safeFunctionHandler] = useLocalNotificationBetter();
const pf = preloadedForms(i18n);
@@ -80,7 +77,6 @@ export function TriggerForms({ formId }: Props): VNode {
: pf.find((f) => f.id === status.result.form);
return (
<div class="rounded-lg bg-white px-5 py-6 shadow m-4">
- <LocalNotificationBanner notification={notification} />
<div class="space-y-10 divide-y -mt-5 divide-gray-900/10">
<FormUI model={handler} design={design} />
</div>
diff --git a/packages/kyc-ui/src/pages/TriggerKyc.tsx b/packages/kyc-ui/src/pages/TriggerKyc.tsx
@@ -30,13 +30,12 @@ import {
WalletKycRequest,
} from "@gnu-taler/taler-util";
import {
- ButtonBetter,
+ Button,
FormMetadata,
FormUI,
- LocalNotificationBanner,
UIHandlerId,
useExchangeApiContext,
- useLocalNotificationBetter,
+ useNotificationContext,
useTranslationContext,
} from "@gnu-taler/web-util/browser";
import { Fragment, h, VNode } from "preact";
@@ -54,7 +53,7 @@ type Props = {
export function TriggerKyc({ onKycStarted }: Props): VNode {
const { i18n } = useTranslationContext();
- const [notification, safeFunctionHandler] = useLocalNotificationBetter();
+ const { actionHandler, showError } = useNotificationContext();
const { config, lib } = useExchangeApiContext();
const theForm: FormMetadata = {
@@ -99,15 +98,15 @@ export function TriggerKyc({ onKycStarted }: Props): VNode {
return createNewWalletKycAccount(extraEntropy);
}, [1]);
- const send = safeFunctionHandler(
- i18n.str`trigger kyc process`,
- async (balance: AmountString) => {
+ // i18n.str`trigger kyc process`,
+ const send = actionHandler(
+ async (ct, balance: AmountString) => {
const account = await accountPromise;
const limit: WalletKycRequest = {
balance,
reserve_pub: account.id,
reserve_sig: encodeCrock(
- signWalletAccountSetup(account.signingKey, balance),
+ signWalletAccountSetup(account.__signingKey, balance),
),
};
const resp = await lib.exchange.notifyKycBalanceLimit(limit);
@@ -116,9 +115,9 @@ export function TriggerKyc({ onKycStarted }: Props): VNode {
}
if (resp.case === HttpStatusCode.UnavailableForLegalReasons) {
const paytoHash = resp.body.h_payto;
- const { signingKey } = await accountPromise;
- const merchantPub = eddsaGetPublic(signingKey);
- const accountOwnerSig = encodeCrock(signKycAuth(signingKey));
+ const { __signingKey } = await accountPromise;
+ const merchantPub = eddsaGetPublic(__signingKey);
+ const accountOwnerSig = encodeCrock(signKycAuth(__signingKey));
const statusRes = await lib.exchange.checkKycStatus({
accountPub: encodeCrock(merchantPub),
accountSig: accountOwnerSig,
@@ -140,7 +139,7 @@ export function TriggerKyc({ onKycStarted }: Props): VNode {
send.onSuccess = (success) => {
onKycStarted(success.access_token);
};
- send.onFail = (fail) => {
+ send.onFail = showError(i18n.str`Failed to trigger a KYC event.`, (fail) => {
switch (fail.case) {
case HttpStatusCode.NoContent:
return i18n.str`No kyc configured.`;
@@ -153,11 +152,10 @@ export function TriggerKyc({ onKycStarted }: Props): VNode {
default:
assertUnreachable(fail);
}
- };
+ });
return (
<div class="rounded-lg bg-white px-5 py-6 shadow m-4">
- <LocalNotificationBanner notification={notification} />
<div class="space-y-10 divide-y -mt-5 divide-gray-900/10">
<FormUI model={handler} design={design} />
</div>
@@ -170,12 +168,12 @@ export function TriggerKyc({ onKycStarted }: Props): VNode {
>
<i18n.Translate>Cancel</i18n.Translate>
</button>
- <ButtonBetter
+ <Button
onClick={send}
class="disabled:opacity-50 disabled:cursor-default rounded-md bg-indigo-600 px-3 py-2 text-sm font-semibold text-white shadow-sm hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600"
>
<i18n.Translate>Submit</i18n.Translate>
- </ButtonBetter>
+ </Button>
</div>
<div class="grid grid-cols-1 gap-x-8 gap-y-4 ">
@@ -187,130 +185,130 @@ export function TriggerKyc({ onKycStarted }: Props): VNode {
</i18n.Translate>
</p>
<div>
- <ButtonBetter
+ <Button
onClick={send.withArgs(`${config.config.currency}:1000000`)}
// disabled={!submitHandler}
class="disabled:opacity-50 disabled:cursor-default rounded-md bg-indigo-600 px-3 py-2 text-sm font-semibold text-white shadow-sm hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600"
>
<i18n.Translate>Trigger TOPS Terms of service</i18n.Translate>
- </ButtonBetter>
+ </Button>
</div>
<div>
- <ButtonBetter
+ <Button
onClick={send.withArgs(`${config.config.currency}:1000010`)}
// disabled={!submitHandler}
class="disabled:opacity-50 disabled:cursor-default rounded-md bg-indigo-600 px-3 py-2 text-sm font-semibold text-white shadow-sm hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600"
>
<i18n.Translate>Trigger GLS onboarding</i18n.Translate>
- </ButtonBetter>
+ </Button>
</div>
<div>
- <ButtonBetter
+ <Button
onClick={send.withArgs(`${config.config.currency}:1000020`)}
// disabled={!submitHandler}
class="disabled:opacity-50 disabled:cursor-default rounded-md bg-indigo-600 px-3 py-2 text-sm font-semibold text-white shadow-sm hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600"
>
<i18n.Translate>Trigger VQF 902.1</i18n.Translate>
- </ButtonBetter>
+ </Button>
</div>
<div>
- <ButtonBetter
+ <Button
onClick={send.withArgs(`${config.config.currency}:1000030`)}
// disabled={!submitHandler}
class="disabled:opacity-50 disabled:cursor-default rounded-md bg-indigo-600 px-3 py-2 text-sm font-semibold text-white shadow-sm hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600"
>
<i18n.Translate>Trigger VQF 902.4</i18n.Translate>
- </ButtonBetter>
+ </Button>
</div>
<div>
- <ButtonBetter
+ <Button
onClick={send.withArgs(`${config.config.currency}:1000040`)}
// disabled={!submitHandler}
class="disabled:opacity-50 disabled:cursor-default rounded-md bg-indigo-600 px-3 py-2 text-sm font-semibold text-white shadow-sm hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600"
>
<i18n.Translate>Trigger VQF 902.5</i18n.Translate>
- </ButtonBetter>
+ </Button>
</div>
<div>
- <ButtonBetter
+ <Button
onClick={send.withArgs(`${config.config.currency}:1000050`)}
// disabled={!submitHandler}
class="disabled:opacity-50 disabled:cursor-default rounded-md bg-indigo-600 px-3 py-2 text-sm font-semibold text-white shadow-sm hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600"
>
<i18n.Translate>Trigger VQF 902.9</i18n.Translate>
- </ButtonBetter>
+ </Button>
</div>
<div>
- <ButtonBetter
+ <Button
onClick={send.withArgs(`${config.config.currency}:1000060`)}
// disabled={!submitHandler}
class="disabled:opacity-50 disabled:cursor-default rounded-md bg-indigo-600 px-3 py-2 text-sm font-semibold text-white shadow-sm hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600"
>
<i18n.Translate>Trigger VQF 902.11</i18n.Translate>
- </ButtonBetter>
+ </Button>
</div>
<div>
- <ButtonBetter
+ <Button
onClick={send.withArgs(`${config.config.currency}:1000070`)}
// disabled={!submitHandler}
class="disabled:opacity-50 disabled:cursor-default rounded-md bg-indigo-600 px-3 py-2 text-sm font-semibold text-white shadow-sm hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600"
>
<i18n.Translate>Trigger VQF 902.12</i18n.Translate>
- </ButtonBetter>
+ </Button>
</div>
<div>
- <ButtonBetter
+ <Button
onClick={send.withArgs(`${config.config.currency}:1000080`)}
// disabled={!submitHandler}
class="disabled:opacity-50 disabled:cursor-default rounded-md bg-indigo-600 px-3 py-2 text-sm font-semibold text-white shadow-sm hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600"
>
<i18n.Translate>Trigger VQF 902.13</i18n.Translate>
- </ButtonBetter>
+ </Button>
</div>
<div>
- <ButtonBetter
+ <Button
onClick={send.withArgs(`${config.config.currency}:1000090`)}
// disabled={!submitHandler}
class="disabled:opacity-50 disabled:cursor-default rounded-md bg-indigo-600 px-3 py-2 text-sm font-semibold text-white shadow-sm hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600"
>
<i18n.Translate>Trigger VQF 902.14</i18n.Translate>
- </ButtonBetter>
+ </Button>
</div>
<div>
- <ButtonBetter
+ <Button
onClick={send.withArgs(`${config.config.currency}:1000100`)}
// disabled={!submitHandler}
class="disabled:opacity-50 disabled:cursor-default rounded-md bg-indigo-600 px-3 py-2 text-sm font-semibold text-white shadow-sm hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600"
>
<i18n.Translate>Trigger VQF 902.15</i18n.Translate>
- </ButtonBetter>
+ </Button>
</div>
<div>
- <ButtonBetter
+ <Button
onClick={send.withArgs(`${config.config.currency}:1000110`)}
// disabled={!submitHandler}
class="disabled:opacity-50 disabled:cursor-default rounded-md bg-indigo-600 px-3 py-2 text-sm font-semibold text-white shadow-sm hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600"
>
<i18n.Translate>Challenger test</i18n.Translate>
- </ButtonBetter>
+ </Button>
</div>
<div>
- <ButtonBetter
+ <Button
onClick={send.withArgs(`${config.config.currency}:1000120`)}
// disabled={!submitHandler}
class="disabled:opacity-50 disabled:cursor-default rounded-md bg-indigo-600 px-3 py-2 text-sm font-semibold text-white shadow-sm hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600"
>
<i18n.Translate>Trigger VQF 902.9 customer</i18n.Translate>
- </ButtonBetter>
+ </Button>
</div>
<div>
- <ButtonBetter
+ <Button
onClick={send.withArgs(`${config.config.currency}:1000130`)}
// disabled={!submitHandler}
class="disabled:opacity-50 disabled:cursor-default rounded-md bg-indigo-600 px-3 py-2 text-sm font-semibold text-white shadow-sm hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600"
>
<i18n.Translate>Trigger VQF 902.9 officer</i18n.Translate>
- </ButtonBetter>
+ </Button>
</div>
</div>
</div>