aboutsummaryrefslogtreecommitdiff
path: root/packages/demobank-ui/src/pages/BusinessAccount.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'packages/demobank-ui/src/pages/BusinessAccount.tsx')
-rw-r--r--packages/demobank-ui/src/pages/BusinessAccount.tsx311
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),
});
}
}