diff options
Diffstat (limited to 'packages/demobank-ui/src/pages/BusinessAccount.tsx')
-rw-r--r-- | packages/demobank-ui/src/pages/BusinessAccount.tsx | 311 |
1 files changed, 130 insertions, 181 deletions
diff --git a/packages/demobank-ui/src/pages/BusinessAccount.tsx b/packages/demobank-ui/src/pages/BusinessAccount.tsx index 6278fe08b..9bd799746 100644 --- a/packages/demobank-ui/src/pages/BusinessAccount.tsx +++ b/packages/demobank-ui/src/pages/BusinessAccount.tsx @@ -20,13 +20,13 @@ import { TranslatedString, } from "@gnu-taler/taler-util"; import { - ErrorType, + HttpResponse, HttpResponsePaginated, RequestError, useTranslationContext, } from "@gnu-taler/web-util/lib/index.browser"; import { Fragment, h, VNode } from "preact"; -import { useState } from "preact/hooks"; +import { useEffect, useState } from "preact/hooks"; import { Cashouts } from "../components/Cashouts/index.js"; import { useBackendContext } from "../context/backend.js"; import { ErrorMessage, usePageContext } from "../context/pageState.js"; @@ -36,9 +36,13 @@ import { useCircuitAccountAPI, useRatiosAndFeeConfig, } from "../hooks/circuit.js"; -import { TanChannel, undefinedIfEmpty } from "../utils.js"; +import { + buildRequestErrorMessage, + TanChannel, + undefinedIfEmpty, +} from "../utils.js"; import { ShowAccountDetails, UpdateAccountPassword } from "./AdminPage.js"; -import { ErrorBanner } from "./BankFrame.js"; +import { ErrorBannerFloat } from "./BankFrame.js"; import { LoginForm } from "./LoginForm.js"; import { ShowInputErrorLabel } from "./ShowInputErrorLabel.js"; @@ -177,6 +181,46 @@ type ErrorFrom<T> = { [P in keyof T]+?: string; }; +// check #7719 +function useRatiosAndFeeConfigWithChangeDetection(): HttpResponse< + SandboxBackend.Circuit.Config & { hasChanged?: boolean }, + SandboxBackend.SandboxError +> { + const result = useRatiosAndFeeConfig(); + const [oldResult, setOldResult] = useState< + SandboxBackend.Circuit.Config | undefined + >(undefined); + const dataFromBackend = result.ok ? result.data : undefined; + useEffect(() => { + // save only the first result of /config to the backend + if (!dataFromBackend || oldResult !== undefined) return; + setOldResult(dataFromBackend); + }, [dataFromBackend]); + + if (!result.ok) return result; + + const data = !oldResult ? result.data : oldResult; + const hasChanged = + oldResult && + (result.data.name !== oldResult.name || + result.data.version !== oldResult.version || + result.data.ratios_and_fees.buy_at_ratio !== + oldResult.ratios_and_fees.buy_at_ratio || + result.data.ratios_and_fees.buy_in_fee !== + oldResult.ratios_and_fees.buy_in_fee || + result.data.ratios_and_fees.sell_at_ratio !== + oldResult.ratios_and_fees.sell_at_ratio || + result.data.ratios_and_fees.sell_out_fee !== + oldResult.ratios_and_fees.sell_out_fee || + result.data.ratios_and_fees.fiat_currency !== + oldResult.ratios_and_fees.fiat_currency); + + return { + ...result, + data: { ...data, hasChanged }, + }; +} + function CreateCashout({ account, onComplete, @@ -207,15 +251,6 @@ function CreateCashout({ if (!sellRate || sellRate < 0) return <div>error rate</div>; - function truncate(a: AmountJson): AmountJson { - const str = Amounts.stringify(a); - const idx = str.indexOf("."); - if (idx === -1) return a; - const truncated = str.substring(0, idx + 3); - console.log(str, truncated); - return Amounts.parseOrThrow(truncated); - } - const amount = Amounts.parse(`${balance.currency}:${form.amount}`); const amount_debit = !amount ? zero @@ -256,7 +291,7 @@ function CreateCashout({ return ( <div> {error && ( - <ErrorBanner error={error} onClear={() => saveError(undefined)} /> + <ErrorBannerFloat error={error} onClear={() => saveError(undefined)} /> )} <h1>New cashout</h1> <form class="pure-form"> @@ -555,74 +590,31 @@ function CreateCashout({ onComplete(res.data.uuid); } catch (error) { if (error instanceof RequestError) { - const e = error as RequestError<SandboxBackend.SandboxError>; - switch (e.cause.type) { - case ErrorType.TIMEOUT: { - saveError({ - title: i18n.str`Request timeout, try again later.`, - }); - break; - } - case ErrorType.CLIENT: { - const errorData = e.cause.error; - - if ( - e.cause.status === HttpStatusCode.PreconditionFailed - ) { - saveError({ - title: i18n.str`The account does not have sufficient funds`, - description: errorData.error.description, - debug: JSON.stringify(error.info), - }); - } else if (e.cause.status === HttpStatusCode.Conflict) { - saveError({ - title: i18n.str`No contact information for this channel`, - description: errorData.error.description, - debug: JSON.stringify(error.info), - }); - } else { - saveError({ - title: i18n.str`New cashout gave response error`, - description: errorData.error.description, - debug: JSON.stringify(error.info), - }); - } - break; - } - case ErrorType.SERVER: { - const errorData = e.cause.error; - if ( - e.cause.status === HttpStatusCode.ServiceUnavailable - ) { - saveError({ - title: i18n.str`The bank does not support the TAN channel for this operation`, - description: errorData.error.description, - debug: JSON.stringify(error.info), - }); - } else { - saveError({ - title: i18n.str`Creating cashout returned with a server error`, - description: errorData.error.description, - debug: JSON.stringify(error.cause), - }); - } - break; - } - case ErrorType.UNEXPECTED: { - saveError({ - title: i18n.str`Unexpected error trying to create cashout.`, - debug: JSON.stringify(error.cause), - }); - break; - } - default: { - assertUnreachable(e.cause); - } - } - } else if (error instanceof Error) { + saveError( + buildRequestErrorMessage(i18n, error.cause, { + onClientError: (status) => + status === HttpStatusCode.BadRequest + ? i18n.str`The exchange rate was incorrectly applied` + : status === HttpStatusCode.Forbidden + ? i18n.str`A institutional user tried the operation` + : status === HttpStatusCode.Conflict + ? i18n.str`Need a contact data where to send the TAN` + : status === HttpStatusCode.PreconditionFailed + ? i18n.str`The account does not have sufficient funds` + : undefined, + onServerError: (status) => + status === HttpStatusCode.ServiceUnavailable + ? i18n.str`The bank does not support the TAN channel for this operation` + : undefined, + }), + ); + } else { saveError({ - title: i18n.str`Cashout failed, please report`, - description: error.message, + title: i18n.str`Operation failed, please report`, + description: + error instanceof Error + ? error.message + : JSON.stringify(error), }); } } @@ -636,6 +628,25 @@ function CreateCashout({ ); } +const MAX_AMOUNT_DIGIT = 2; +/** + * Truncate the amount of digits to display + * in the form based on the fee calculations + * + * Backend must have the same truncation + * @param a + * @returns + */ +function truncate(a: AmountJson): AmountJson { + const str = Amounts.stringify(a); + const idx = str.indexOf("."); + if (idx === -1) { + return a; + } + const truncated = str.substring(0, idx + 1 + MAX_AMOUNT_DIGIT); + return Amounts.parseOrThrow(truncated); +} + interface ShowCashoutProps { id: string; onCancel: () => void; @@ -662,7 +673,7 @@ export function ShowCashoutDetails({ <div> <h1>Cashout details {id}</h1> {error && ( - <ErrorBanner error={error} onClear={() => saveError(undefined)} /> + <ErrorBannerFloat error={error} onClear={() => saveError(undefined)} /> )} <form class="pure-form"> <fieldset> @@ -744,68 +755,27 @@ export function ShowCashoutDetails({ onClick={async (e) => { e.preventDefault(); try { - const rest = await abortCashout(id); + await abortCashout(id); onCancel(); } catch (error) { if (error instanceof RequestError) { - const e = - error as RequestError<SandboxBackend.SandboxError>; - switch (e.cause.type) { - case ErrorType.TIMEOUT: { - saveError({ - title: i18n.str`Request timeout, try again later.`, - }); - break; - } - case ErrorType.CLIENT: { - const errorData = e.cause.error; - if ( - e.cause.status === HttpStatusCode.PreconditionFailed - ) { - saveError({ - title: i18n.str`Cashout was already aborted`, - description: errorData.error.description, - debug: JSON.stringify(error.info), - }); - } else { - saveError({ - title: i18n.str`Aborting cashout gave response error`, - description: errorData.error.description, - debug: JSON.stringify(error.info), - }); - } - - saveError({ - title: i18n.str`Aborting cashout gave response error`, - description: errorData.error.description, - debug: JSON.stringify(error.cause), - }); - break; - } - case ErrorType.SERVER: { - const errorData = e.cause.error; - saveError({ - title: i18n.str`Aborting cashout returned with a server error`, - description: errorData.error.description, - debug: JSON.stringify(error.cause), - }); - break; - } - case ErrorType.UNEXPECTED: { - saveError({ - title: i18n.str`Unexpected error trying to abort cashout.`, - debug: JSON.stringify(error.cause), - }); - break; - } - default: { - assertUnreachable(e.cause); - } - } - } else if (error instanceof Error) { + saveError( + buildRequestErrorMessage(i18n, error.cause, { + onClientError: (status) => + status === HttpStatusCode.NotFound + ? i18n.str`Cashout not found. It may be also mean that it was already aborted.` + : status === HttpStatusCode.PreconditionFailed + ? i18n.str`Cashout was already confimed` + : undefined, + }), + ); + } else { saveError({ - title: i18n.str`Aborting failed, please report`, - description: error.message, + title: i18n.str`Operation failed, please report`, + description: + error instanceof Error + ? error.message + : JSON.stringify(error), }); } } @@ -827,48 +797,27 @@ export function ShowCashoutDetails({ }); } catch (error) { if (error instanceof RequestError) { - const e = - error as RequestError<SandboxBackend.SandboxError>; - switch (e.cause.type) { - case ErrorType.TIMEOUT: { - saveError({ - title: i18n.str`Request timeout, try again later.`, - }); - break; - } - case ErrorType.CLIENT: { - const errorData = e.cause.error; - saveError({ - title: i18n.str`Confirmation of cashout gave response error`, - description: errorData.error.description, - debug: JSON.stringify(error.cause), - }); - break; - } - case ErrorType.SERVER: { - const errorData = e.cause.error; - saveError({ - title: i18n.str`Confirmation of cashout gave response error`, - description: errorData.error.description, - debug: JSON.stringify(error.cause), - }); - break; - } - case ErrorType.UNEXPECTED: { - saveError({ - title: i18n.str`Unexpected error trying to cashout.`, - debug: JSON.stringify(error.cause), - }); - break; - } - default: { - assertUnreachable(e.cause); - } - } - } else if (error instanceof Error) { + saveError( + buildRequestErrorMessage(i18n, error.cause, { + onClientError: (status) => + status === HttpStatusCode.NotFound + ? i18n.str`Cashout not found. It may be also mean that it was already aborted.` + : status === HttpStatusCode.PreconditionFailed + ? i18n.str`Cashout was already confimed` + : status === HttpStatusCode.Conflict + ? i18n.str`Confirmation failed. Maybe the user changed their cash-out address between the creation and the confirmation` + : status === HttpStatusCode.Forbidden + ? i18n.str`Invalid code` + : undefined, + }), + ); + } else { saveError({ - title: i18n.str`Confirmation failed, please report`, - description: error.message, + title: i18n.str`Operation failed, please report`, + description: + error instanceof Error + ? error.message + : JSON.stringify(error), }); } } |