aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSebastian <sebasjm@gmail.com>2023-03-15 09:25:23 -0300
committerSebastian <sebasjm@gmail.com>2023-03-15 09:25:23 -0300
commit0bf92a44df14f1946df2b1cd58a6b0b92b5befa2 (patch)
tree6d3094d35556e94f681f588088e0c55cf04e0e21
parent0700bbe9d14c7e0c6c2e998285a40d56e8b1d5cc (diff)
test login with an endpoint and cleaner calculation
-rw-r--r--packages/demobank-ui/src/hooks/backend.ts28
-rw-r--r--packages/demobank-ui/src/hooks/circuit.ts3
-rw-r--r--packages/demobank-ui/src/pages/AdminPage.tsx5
-rw-r--r--packages/demobank-ui/src/pages/BusinessAccount.tsx111
-rw-r--r--packages/demobank-ui/src/pages/LoginForm.tsx48
5 files changed, 146 insertions, 49 deletions
diff --git a/packages/demobank-ui/src/hooks/backend.ts b/packages/demobank-ui/src/hooks/backend.ts
index 3eaf1f186..851a3fb5b 100644
--- a/packages/demobank-ui/src/hooks/backend.ts
+++ b/packages/demobank-ui/src/hooks/backend.ts
@@ -16,6 +16,7 @@
import { canonicalizeBaseUrl } from "@gnu-taler/taler-util";
import {
+ ErrorType,
RequestError,
useLocalStorage,
} from "@gnu-taler/web-util/lib/index.browser";
@@ -192,6 +193,33 @@ export function usePublicBackend(): useBackendType {
};
}
+export function useCredentialsChecker() {
+ const { request } = useApiContext();
+ const baseUrl = getInitialBackendBaseURL();
+ //check against account details endpoint
+ //while sandbox backend doesn't have a login endpoint
+ return async function testLogin(
+ username: string,
+ password: string,
+ ): Promise<{
+ valid: boolean;
+ cause?: ErrorType;
+ }> {
+ try {
+ await request(baseUrl, `access-api/accounts/${username}/`, {
+ basicAuth: { username, password },
+ preventCache: true,
+ });
+ return { valid: true };
+ } catch (error) {
+ if (error instanceof RequestError) {
+ return { valid: false, cause: error.cause.type };
+ }
+ return { valid: false, cause: ErrorType.UNEXPECTED };
+ }
+ };
+}
+
export function useAuthenticatedBackend(): useBackendType {
const { state } = useBackendContext();
const { request: requestHandler } = useApiContext();
diff --git a/packages/demobank-ui/src/hooks/circuit.ts b/packages/demobank-ui/src/hooks/circuit.ts
index 548862d85..137a7aee2 100644
--- a/packages/demobank-ui/src/hooks/circuit.ts
+++ b/packages/demobank-ui/src/hooks/circuit.ts
@@ -288,9 +288,10 @@ export function useRatiosAndFeeConfig(): HttpResponse<
HttpResponseOk<SandboxBackend.Circuit.Config>,
RequestError<SandboxBackend.SandboxError>
>([`circuit-api/config`], fetcher, {
- refreshInterval: 1000,
+ refreshInterval: 60 * 1000,
refreshWhenHidden: false,
revalidateOnFocus: false,
+ revalidateIfStale: false,
revalidateOnReconnect: false,
refreshWhenOffline: false,
errorRetryCount: 0,
diff --git a/packages/demobank-ui/src/pages/AdminPage.tsx b/packages/demobank-ui/src/pages/AdminPage.tsx
index f565455bb..ac0457e9b 100644
--- a/packages/demobank-ui/src/pages/AdminPage.tsx
+++ b/packages/demobank-ui/src/pages/AdminPage.tsx
@@ -36,9 +36,9 @@ import {
} from "../context/pageState.js";
import { useAccountDetails } from "../hooks/access.js";
import {
+ useAdminAccountAPI,
useBusinessAccountDetails,
useBusinessAccounts,
- useAdminAccountAPI,
} from "../hooks/circuit.js";
import {
buildRequestErrorMessage,
@@ -50,7 +50,6 @@ import {
} from "../utils.js";
import { ErrorBannerFloat } from "./BankFrame.js";
import { ShowCashoutDetails } from "./BusinessAccount.js";
-import { PaymentOptions } from "./PaymentOptions.js";
import { PaytoWireTransferForm } from "./PaytoWireTransferForm.js";
import { ShowInputErrorLabel } from "./ShowInputErrorLabel.js";
@@ -581,7 +580,6 @@ function CreateNewAccount({
template={undefined}
purpose="create"
onChange={(a) => {
- console.log(a);
setSubmitAccount(a);
}}
/>
@@ -831,6 +829,7 @@ function RemoveAccount({
title: i18n.str`Can't delete the account`,
description: i18n.str`Balance is not empty`,
}}
+ onClear={() => saveError(undefined)}
/>
)}
{error && (
diff --git a/packages/demobank-ui/src/pages/BusinessAccount.tsx b/packages/demobank-ui/src/pages/BusinessAccount.tsx
index 128b47114..258802e58 100644
--- a/packages/demobank-ui/src/pages/BusinessAccount.tsx
+++ b/packages/demobank-ui/src/pages/BusinessAccount.tsx
@@ -237,6 +237,7 @@ function CreateCashout({
if (!result.ok) return onLoadNotOk(result);
if (!ratiosResult.ok) return onLoadNotOk(ratiosResult);
const config = ratiosResult.data;
+
const balance = Amounts.parseOrThrow(result.data.balance.amount);
const debitThreshold = Amounts.parseOrThrow(result.data.debitThreshold);
const zero = Amounts.zeroOfCurrency(balance.currency);
@@ -254,23 +255,14 @@ function CreateCashout({
if (!sellRate || sellRate < 0) return <div>error rate</div>;
const amount = Amounts.parse(`${balance.currency}:${form.amount}`);
- const amount_debit = !amount
- ? zero
- : form.isDebit
- ? amount
- : truncate(Amounts.divide(Amounts.add(amount, sellFee).amount, sellRate));
- const credit_before_fee = !amount
- ? zero
- : form.isDebit
- ? truncate(Amounts.divide(amount, 1 / sellRate))
- : Amounts.add(amount, sellFee).amount;
- const __amount_credit = Amounts.sub(credit_before_fee, sellFee).amount;
- const amount_credit = Amounts.parseOrThrow(
- `${fiatCurrency}:${Amounts.stringifyValue(__amount_credit)}`,
- );
+ const calc = !amount
+ ? { debit: zero, credit: zero, beforeFee: zero }
+ : !form.isDebit
+ ? calculateFromCredit(amount, sellFee, sellRate)
+ : calculateFromDebit(amount, sellFee, sellRate);
- const balanceAfter = Amounts.sub(balance, amount_debit).amount;
+ const balanceAfter = Amounts.sub(balance, calc.debit).amount;
function updateForm(newForm: typeof form): void {
setForm(newForm);
@@ -280,11 +272,11 @@ function CreateCashout({
? i18n.str`required`
: !amount
? i18n.str`could not be parsed`
- : Amounts.cmp(limit, amount_debit) === -1
+ : Amounts.cmp(limit, calc.debit) === -1
? i18n.str`balance is not enough`
- : Amounts.cmp(credit_before_fee, sellFee) === -1
+ : Amounts.cmp(calc.beforeFee, sellFee) === -1
? i18n.str`the total amount to transfer does not cover the fees`
- : Amounts.isZero(amount_credit)
+ : Amounts.isZero(calc.credit)
? i18n.str`the total transfer at destination will be zero`
: undefined,
channel: !form.channel ? i18n.str`required` : undefined,
@@ -408,7 +400,7 @@ function CreateCashout({
id="withdraw-amount"
disabled
name="withdraw-amount"
- value={amount_debit ? Amounts.stringifyValue(amount_debit) : ""}
+ value={Amounts.stringifyValue(calc.debit)}
/>
</div>
</fieldset>
@@ -454,7 +446,7 @@ function CreateCashout({
// type="number"
style={{ color: "black" }}
disabled
- value={Amounts.stringifyValue(credit_before_fee)}
+ value={Amounts.stringifyValue(calc.beforeFee)}
/>
</div>
</fieldset>
@@ -503,7 +495,7 @@ function CreateCashout({
id="withdraw-amount"
disabled
name="withdraw-amount"
- value={amount_credit ? Amounts.stringifyValue(amount_credit) : ""}
+ value={Amounts.stringifyValue(calc.credit)}
/>
</div>
</fieldset>
@@ -584,8 +576,10 @@ function CreateCashout({
if (errors) return;
try {
const res = await createCashout({
- amount_credit: Amounts.stringify(amount_credit),
- amount_debit: Amounts.stringify(amount_debit),
+ amount_credit: `${fiatCurrency}:${Amounts.stringifyValue(
+ calc.credit,
+ )}`,
+ amount_debit: Amounts.stringify(calc.debit),
subject: form.subject,
tan_channel: form.channel,
});
@@ -630,25 +624,6 @@ 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;
@@ -836,6 +811,58 @@ export function ShowCashoutDetails({
);
}
+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);
+}
+
+type TransferCalculation = {
+ debit: AmountJson;
+ credit: AmountJson;
+ beforeFee: AmountJson;
+};
+
+function calculateFromDebit(
+ amount: AmountJson,
+ sellFee: AmountJson,
+ sellRate: number,
+): TransferCalculation {
+ const debit = amount;
+
+ const beforeFee = truncate(Amounts.divide(debit, 1 / sellRate));
+
+ const credit = Amounts.sub(beforeFee, sellFee).amount;
+ return { debit, credit, beforeFee };
+}
+
+function calculateFromCredit(
+ amount: AmountJson,
+ sellFee: AmountJson,
+ sellRate: number,
+): TransferCalculation {
+ const credit = amount;
+
+ const beforeFee = Amounts.add(credit, sellFee).amount;
+
+ const debit = truncate(Amounts.divide(beforeFee, sellRate));
+
+ return { debit, credit, beforeFee };
+}
+
export function assertUnreachable(x: never): never {
throw new Error("Didn't expect to get here");
}
diff --git a/packages/demobank-ui/src/pages/LoginForm.tsx b/packages/demobank-ui/src/pages/LoginForm.tsx
index 2452745a5..16d2373da 100644
--- a/packages/demobank-ui/src/pages/LoginForm.tsx
+++ b/packages/demobank-ui/src/pages/LoginForm.tsx
@@ -14,12 +14,18 @@
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/
-import { useTranslationContext } from "@gnu-taler/web-util/lib/index.browser";
+import {
+ ErrorType,
+ useTranslationContext,
+} from "@gnu-taler/web-util/lib/index.browser";
import { Fragment, h, VNode } from "preact";
import { useEffect, useRef, useState } from "preact/hooks";
import { useBackendContext } from "../context/backend.js";
+import { ErrorMessage } from "../context/pageState.js";
+import { useCredentialsChecker } from "../hooks/backend.js";
import { bankUiSettings } from "../settings.js";
import { undefinedIfEmpty } from "../utils.js";
+import { ErrorBannerFloat } from "./BankFrame.js";
import { USERNAME_REGEX } from "./RegistrationPage.js";
import { ShowInputErrorLabel } from "./ShowInputErrorLabel.js";
@@ -31,6 +37,8 @@ export function LoginForm({ onRegister }: { onRegister: () => void }): VNode {
const [username, setUsername] = useState<string | undefined>();
const [password, setPassword] = useState<string | undefined>();
const { i18n } = useTranslationContext();
+ const testLogin = useCredentialsChecker();
+ const [error, saveError] = useState<ErrorMessage | undefined>();
const ref = useRef<HTMLInputElement>(null);
useEffect(function focusInput() {
ref.current?.focus();
@@ -48,6 +56,9 @@ export function LoginForm({ onRegister }: { onRegister: () => void }): VNode {
return (
<Fragment>
<h1 class="nav">{i18n.str`Welcome to ${bankUiSettings.bankName}!`}</h1>
+ {error && (
+ <ErrorBannerFloat error={error} onClear={() => saveError(undefined)} />
+ )}
<div class="login-div">
<form
class="login-form"
@@ -105,10 +116,41 @@ export function LoginForm({ onRegister }: { onRegister: () => void }): VNode {
type="submit"
class="pure-button pure-button-primary"
disabled={!!errors}
- onClick={(e) => {
+ onClick={async (e) => {
e.preventDefault();
if (!username || !password) return;
- backend.logIn({ username, password });
+ const { valid, cause } = await testLogin(username, password);
+ if (valid) {
+ backend.logIn({ username, password });
+ } else {
+ switch (cause) {
+ case ErrorType.CLIENT: {
+ saveError({
+ title: i18n.str`Wrong credentials or username`,
+ });
+ break;
+ }
+ case ErrorType.SERVER: {
+ saveError({
+ title: i18n.str`Server had a problem, try again later or report.`,
+ });
+ break;
+ }
+ case ErrorType.TIMEOUT: {
+ saveError({
+ title: i18n.str`Could not reach the server, please report.`,
+ });
+ break;
+ }
+ default: {
+ saveError({
+ title: i18n.str`Unexpected error, please report.`,
+ });
+ break;
+ }
+ }
+ backend.logOut();
+ }
setUsername(undefined);
setPassword(undefined);
}}