summaryrefslogtreecommitdiff
path: root/packages
diff options
context:
space:
mode:
authorSebastian <sebasjm@gmail.com>2023-03-11 18:19:38 -0300
committerSebastian <sebasjm@gmail.com>2023-03-11 18:20:16 -0300
commitc67d94c56e154be4b2cf91572cdc2d8d2da7f8e4 (patch)
treefbb9444857d4e11f348c051b9c470e9295990096 /packages
parentb72729f06535f12af974035b141a30320e75575c (diff)
downloadwallet-core-c67d94c56e154be4b2cf91572cdc2d8d2da7f8e4.tar.gz
wallet-core-c67d94c56e154be4b2cf91572cdc2d8d2da7f8e4.tar.bz2
wallet-core-c67d94c56e154be4b2cf91572cdc2d8d2da7f8e4.zip
fix: #7753
Diffstat (limited to 'packages')
-rw-r--r--packages/demobank-ui/src/declaration.d.ts8
-rw-r--r--packages/demobank-ui/src/hooks/access.ts17
-rw-r--r--packages/demobank-ui/src/hooks/circuit.ts6
-rw-r--r--packages/demobank-ui/src/index.tsx1
-rw-r--r--packages/demobank-ui/src/pages/AccountPage.tsx67
-rw-r--r--packages/demobank-ui/src/pages/BusinessAccount.tsx20
-rw-r--r--packages/demobank-ui/src/pages/PaymentOptions.tsx7
-rw-r--r--packages/demobank-ui/src/pages/PaytoWireTransferForm.tsx35
-rw-r--r--packages/demobank-ui/src/pages/WalletWithdrawForm.tsx25
-rw-r--r--packages/web-util/src/components/utils.ts47
10 files changed, 146 insertions, 87 deletions
diff --git a/packages/demobank-ui/src/declaration.d.ts b/packages/demobank-ui/src/declaration.d.ts
index 8dc5fd8b2..4d8484a4f 100644
--- a/packages/demobank-ui/src/declaration.d.ts
+++ b/packages/demobank-ui/src/declaration.d.ts
@@ -169,6 +169,8 @@ namespace SandboxBackend {
balance: Balance;
// payto://-URI of the account. (New)
paytoUri: string;
+ // Number indicating the max debit allowed for the requesting user.
+ debitThreshold: Amount;
}
interface BankAccountCreateWithdrawalRequest {
// Amount to withdraw.
@@ -369,6 +371,9 @@ namespace SandboxBackend {
// Contains ratios and fees related to buying
// and selling the circuit currency.
ratios_and_fees: RatiosAndFees;
+ // Fiat currency. That is the currency in which
+ // cash-out operations ultimately wire money.
+ fiat_currency: string;
}
interface RatiosAndFees {
// Exchange rate to buy the circuit currency from fiat.
@@ -379,9 +384,6 @@ namespace SandboxBackend {
buy_in_fee: float;
// Fee to subtract after applying the sell ratio.
sell_out_fee: float;
- // Fiat currency. That is the currency in which
- // cash-out operations ultimately wire money.
- fiat_currency: string;
}
interface Cashouts {
// Every string represents a cash-out operation UUID.
diff --git a/packages/demobank-ui/src/hooks/access.ts b/packages/demobank-ui/src/hooks/access.ts
index 8282210d4..750b95fa0 100644
--- a/packages/demobank-ui/src/hooks/access.ts
+++ b/packages/demobank-ui/src/hooks/access.ts
@@ -31,6 +31,7 @@ import {
// FIX default import https://github.com/microsoft/TypeScript/issues/49189
import _useSWR, { SWRHook } from "swr";
+import { Amounts } from "@gnu-taler/taler-util";
const useSWR = _useSWR as unknown as SWRHook;
export function useAccessAPI(): AccessAPI {
@@ -180,7 +181,21 @@ export function useAccountDetails(
keepPreviousData: true,
});
- if (data) return data;
+ //FIXME: remove optional when libeufin sandbox has implemented the feature
+ if (data && typeof data.data.debitThreshold === "undefined") {
+ data.data.debitThreshold = "100";
+ }
+ //FIXME: sandbox server should return amount string
+ if (data) {
+ const d = structuredClone(data);
+ const { currency } = Amounts.parseOrThrow(data.data.balance.amount);
+ d.data.debitThreshold = Amounts.stringify({
+ currency,
+ value: Number.parseInt(d.data.debitThreshold, 10),
+ fraction: 0,
+ });
+ return d;
+ }
if (error) return error.info;
return { loading: true };
}
diff --git a/packages/demobank-ui/src/hooks/circuit.ts b/packages/demobank-ui/src/hooks/circuit.ts
index 423ed1a5b..548862d85 100644
--- a/packages/demobank-ui/src/hooks/circuit.ts
+++ b/packages/demobank-ui/src/hooks/circuit.ts
@@ -299,12 +299,6 @@ export function useRatiosAndFeeConfig(): HttpResponse<
keepPreviousData: true,
});
- if (data) {
- // data.data.ratios_and_fees.sell_out_fee = 2
- if (!data.data.ratios_and_fees.fiat_currency) {
- data.data.ratios_and_fees.fiat_currency = "FIAT";
- }
- }
if (data) return data;
if (error) return error.info;
return { loading: true };
diff --git a/packages/demobank-ui/src/index.tsx b/packages/demobank-ui/src/index.tsx
index a0ce8cb59..2e0f740fe 100644
--- a/packages/demobank-ui/src/index.tsx
+++ b/packages/demobank-ui/src/index.tsx
@@ -15,7 +15,6 @@
*/
import App from "./components/app.js";
-
import { h, render } from "preact";
import "./scss/main.scss";
diff --git a/packages/demobank-ui/src/pages/AccountPage.tsx b/packages/demobank-ui/src/pages/AccountPage.tsx
index bd9a5acd7..c6ec7c88e 100644
--- a/packages/demobank-ui/src/pages/AccountPage.tsx
+++ b/packages/demobank-ui/src/pages/AccountPage.tsx
@@ -20,8 +20,6 @@ import {
useTranslationContext,
} from "@gnu-taler/web-util/lib/index.browser";
import { Fragment, h, VNode } from "preact";
-import { useState } from "preact/hooks";
-import { Cashouts } from "../components/Cashouts/index.js";
import { Transactions } from "../components/Transactions/index.js";
import { useAccountDetails } from "../hooks/access.js";
import { PaymentOptions } from "./PaymentOptions.js";
@@ -44,8 +42,8 @@ export function AccountPage({ account, onLoadNotOk }: Props): VNode {
}
const { data } = result;
- const balance = Amounts.parse(data.balance.amount);
- const errorParsingBalance = !balance;
+ const balance = Amounts.parseOrThrow(data.balance.amount);
+ const debitThreshold = Amounts.parseOrThrow(data.debitThreshold);
const payto = parsePaytoUri(data.paytoUri);
if (!payto || !payto.isKnown || payto.targetType !== "iban") {
return (
@@ -54,7 +52,9 @@ export function AccountPage({ account, onLoadNotOk }: Props): VNode {
}
const accountNumber = payto.iban;
const balanceIsDebit = data.balance.credit_debit_indicator == "debit";
-
+ const limit = balanceIsDebit
+ ? Amounts.sub(debitThreshold, balance).amount
+ : Amounts.add(balance, debitThreshold).amount;
return (
<Fragment>
<div>
@@ -66,44 +66,29 @@ export function AccountPage({ account, onLoadNotOk }: Props): VNode {
</h1>
</div>
- {errorParsingBalance ? (
- <div class="informational informational-fail" style={{ marginTop: 8 }}>
- <div style={{ display: "flex", justifyContent: "space-between" }}>
- <p>
- <b>Server Error: invalid balance</b>
- </p>
- </div>
- <p>Your account is in an invalid state.</p>
- </div>
- ) : (
- <Fragment>
- <section id="assets">
- <div class="asset-summary">
- <h2>{i18n.str`Bank account balance`}</h2>
- {!balance ? (
- <div class="large-amount" style={{ color: "gray" }}>
- Waiting server response...
- </div>
- ) : (
- <div class="large-amount amount">
- {balanceIsDebit ? <b>-</b> : null}
- <span class="value">{`${Amounts.stringifyValue(
- balance,
- )}`}</span>
- &nbsp;
- <span class="currency">{`${balance.currency}`}</span>
- </div>
- )}
+ <section id="assets">
+ <div class="asset-summary">
+ <h2>{i18n.str`Bank account balance`}</h2>
+ {!balance ? (
+ <div class="large-amount" style={{ color: "gray" }}>
+ Waiting server response...
</div>
- </section>
- <section id="payments">
- <div class="payments">
- <h2>{i18n.str`Payments`}</h2>
- <PaymentOptions currency={balance.currency} />
+ ) : (
+ <div class="large-amount amount">
+ {balanceIsDebit ? <b>-</b> : null}
+ <span class="value">{`${Amounts.stringifyValue(balance)}`}</span>
+ &nbsp;
+ <span class="currency">{`${balance.currency}`}</span>
</div>
- </section>
- </Fragment>
- )}
+ )}
+ </div>
+ </section>
+ <section id="payments">
+ <div class="payments">
+ <h2>{i18n.str`Payments`}</h2>
+ <PaymentOptions limit={limit} />
+ </div>
+ </section>
<section style={{ marginTop: "2em" }}>
<div class="active">
diff --git a/packages/demobank-ui/src/pages/BusinessAccount.tsx b/packages/demobank-ui/src/pages/BusinessAccount.tsx
index 9bd799746..128b47114 100644
--- a/packages/demobank-ui/src/pages/BusinessAccount.tsx
+++ b/packages/demobank-ui/src/pages/BusinessAccount.tsx
@@ -212,8 +212,7 @@ function useRatiosAndFeeConfigWithChangeDetection(): HttpResponse<
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);
+ result.data.fiat_currency !== oldResult.fiat_currency);
return {
...result,
@@ -238,16 +237,19 @@ function CreateCashout({
if (!result.ok) return onLoadNotOk(result);
if (!ratiosResult.ok) return onLoadNotOk(ratiosResult);
const config = ratiosResult.data;
- const maybeBalance = Amounts.parse(result.data.balance.amount);
- if (!maybeBalance) return <div>error</div>;
- const balance = maybeBalance;
+ const balance = Amounts.parseOrThrow(result.data.balance.amount);
+ const debitThreshold = Amounts.parseOrThrow(result.data.debitThreshold);
const zero = Amounts.zeroOfCurrency(balance.currency);
+ const balanceIsDebit = result.data.balance.credit_debit_indicator == "debit";
+ const limit = balanceIsDebit
+ ? Amounts.sub(debitThreshold, balance).amount
+ : Amounts.add(balance, debitThreshold).amount;
const sellRate = config.ratios_and_fees.sell_at_ratio;
const sellFee = !config.ratios_and_fees.sell_out_fee
? zero
: Amounts.fromFloat(config.ratios_and_fees.sell_out_fee, balance.currency);
- const fiatCurrency = config.ratios_and_fees.fiat_currency;
+ const fiatCurrency = config.fiat_currency;
if (!sellRate || sellRate < 0) return <div>error rate</div>;
@@ -278,12 +280,12 @@ function CreateCashout({
? i18n.str`required`
: !amount
? i18n.str`could not be parsed`
- : Amounts.cmp(balance, amount_debit) === -1
+ : Amounts.cmp(limit, amount_debit) === -1
? i18n.str`balance is not enough`
: Amounts.cmp(credit_before_fee, sellFee) === -1
- ? i18n.str`amount is not enough`
+ ? i18n.str`the total amount to transfer does not cover the fees`
: Amounts.isZero(amount_credit)
- ? i18n.str`amount is not enough`
+ ? i18n.str`the total transfer at destination will be zero`
: undefined,
channel: !form.channel ? i18n.str`required` : undefined,
});
diff --git a/packages/demobank-ui/src/pages/PaymentOptions.tsx b/packages/demobank-ui/src/pages/PaymentOptions.tsx
index 610efafc0..291f2aa9e 100644
--- a/packages/demobank-ui/src/pages/PaymentOptions.tsx
+++ b/packages/demobank-ui/src/pages/PaymentOptions.tsx
@@ -14,6 +14,7 @@
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/
+import { AmountJson } from "@gnu-taler/taler-util";
import { useTranslationContext } from "@gnu-taler/web-util/lib/index.browser";
import { h, VNode } from "preact";
import { useState } from "preact/hooks";
@@ -25,7 +26,7 @@ import { WalletWithdrawForm } from "./WalletWithdrawForm.js";
* Let the user choose a payment option,
* then specify the details trigger the action.
*/
-export function PaymentOptions({ currency }: { currency: string }): VNode {
+export function PaymentOptions({ limit }: { limit: AmountJson }): VNode {
const { i18n } = useTranslationContext();
const { pageStateSetter } = usePageContext();
@@ -62,7 +63,7 @@ export function PaymentOptions({ currency }: { currency: string }): VNode {
<h3>{i18n.str`Obtain digital cash`}</h3>
<WalletWithdrawForm
focus
- currency={currency}
+ limit={limit}
onSuccess={(data) => {
pageStateSetter((prevState: PageStateType) => ({
...prevState,
@@ -80,7 +81,7 @@ export function PaymentOptions({ currency }: { currency: string }): VNode {
<h3>{i18n.str`Transfer to bank account`}</h3>
<PaytoWireTransferForm
focus
- currency={currency}
+ limit={limit}
onSuccess={() => {
pageStateSetter((prevState: PageStateType) => ({
...prevState,
diff --git a/packages/demobank-ui/src/pages/PaytoWireTransferForm.tsx b/packages/demobank-ui/src/pages/PaytoWireTransferForm.tsx
index f25680481..027f8e25a 100644
--- a/packages/demobank-ui/src/pages/PaytoWireTransferForm.tsx
+++ b/packages/demobank-ui/src/pages/PaytoWireTransferForm.tsx
@@ -15,6 +15,7 @@
*/
import {
+ AmountJson,
Amounts,
buildPayto,
HttpStatusCode,
@@ -30,7 +31,11 @@ import { h, VNode } from "preact";
import { useEffect, useRef, useState } from "preact/hooks";
import { PageStateType } from "../context/pageState.js";
import { useAccessAPI } from "../hooks/access.js";
-import { buildRequestErrorMessage, undefinedIfEmpty } from "../utils.js";
+import {
+ buildRequestErrorMessage,
+ undefinedIfEmpty,
+ validateIBAN,
+} from "../utils.js";
import { ShowInputErrorLabel } from "./ShowInputErrorLabel.js";
const logger = new Logger("PaytoWireTransferForm");
@@ -39,12 +44,12 @@ export function PaytoWireTransferForm({
focus,
onError,
onSuccess,
- currency,
+ limit,
}: {
focus?: boolean;
onError: (e: PageStateType["error"]) => void;
onSuccess: () => void;
- currency: string;
+ limit: AmountJson;
}): VNode {
// const backend = useBackendContext();
// const { pageState, pageStateSetter } = usePageContext(); // NOTE: used for go-back button?
@@ -65,7 +70,8 @@ export function PaytoWireTransferForm({
if (focus) ref.current?.focus();
}, [focus, isRawPayto]);
- let parsedAmount = undefined;
+ const trimmedAmountStr = amount?.trim();
+ const parsedAmount = Amounts.parse(`${limit.currency}:${trimmedAmountStr}`);
const IBAN_REGEX = /^[A-Z][A-Z0-9]*$/;
const errorsWire = undefinedIfEmpty({
@@ -73,14 +79,16 @@ export function PaytoWireTransferForm({
? i18n.str`Missing IBAN`
: !IBAN_REGEX.test(iban)
? i18n.str`IBAN should have just uppercased letters and numbers`
- : undefined,
+ : validateIBAN(iban, i18n),
subject: !subject ? i18n.str`Missing subject` : undefined,
- amount: !amount
+ amount: !trimmedAmountStr
? i18n.str`Missing amount`
- : !(parsedAmount = Amounts.parse(`${currency}:${amount}`))
+ : !parsedAmount
? i18n.str`Amount is not valid`
: Amounts.isZero(parsedAmount)
? i18n.str`Should be greater than 0`
+ : Amounts.cmp(limit, parsedAmount) === -1
+ ? i18n.str`balance is not enough`
: undefined,
});
@@ -143,10 +151,10 @@ export function PaytoWireTransferForm({
type="text"
readonly
class="currency-indicator"
- size={currency?.length}
- maxLength={currency?.length}
+ size={limit.currency.length}
+ maxLength={limit.currency.length}
tabIndex={-1}
- value={currency}
+ value={limit.currency}
/>
&nbsp;
<input
@@ -185,7 +193,7 @@ export function PaytoWireTransferForm({
try {
await createTransaction({
paytoUri,
- amount: `${currency}:${amount}`,
+ amount: `${limit.currency}:${amount}`,
});
onSuccess();
setAmount(undefined);
@@ -257,7 +265,7 @@ export function PaytoWireTransferForm({
? i18n.str`only "IBAN" target are supported`
: !IBAN_REGEX.test(parsed.iban)
? i18n.str`IBAN should have just uppercased letters and numbers`
- : undefined,
+ : validateIBAN(parsed.iban, i18n),
});
return (
@@ -296,7 +304,8 @@ export function PaytoWireTransferForm({
<div style={{ fontSize: "small", marginTop: 4 }}>
Hint:
<code>
- payto://iban/[receiver-iban]?message=[subject]&amount=[{currency}
+ payto://iban/[receiver-iban]?message=[subject]&amount=[
+ {limit.currency}
:X.Y]
</code>
</div>
diff --git a/packages/demobank-ui/src/pages/WalletWithdrawForm.tsx b/packages/demobank-ui/src/pages/WalletWithdrawForm.tsx
index c1ad2f0cf..8bbfe0713 100644
--- a/packages/demobank-ui/src/pages/WalletWithdrawForm.tsx
+++ b/packages/demobank-ui/src/pages/WalletWithdrawForm.tsx
@@ -14,7 +14,12 @@
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/
-import { Amounts, HttpStatusCode, Logger } from "@gnu-taler/taler-util";
+import {
+ AmountJson,
+ Amounts,
+ HttpStatusCode,
+ Logger,
+} from "@gnu-taler/taler-util";
import {
RequestError,
useTranslationContext,
@@ -30,11 +35,11 @@ const logger = new Logger("WalletWithdrawForm");
export function WalletWithdrawForm({
focus,
- currency,
+ limit,
onError,
onSuccess,
}: {
- currency: string;
+ limit: AmountJson;
focus?: boolean;
onError: (e: PageStateType["error"]) => void;
onSuccess: (
@@ -52,20 +57,20 @@ export function WalletWithdrawForm({
if (focus) ref.current?.focus();
}, [focus]);
- // Beware: We never ever want to treat the amount as a float!
-
const trimmedAmountStr = amountStr?.trim();
const parsedAmount = trimmedAmountStr
- ? Amounts.parse(`${currency}:${trimmedAmountStr}`)
+ ? Amounts.parse(`${limit.currency}:${trimmedAmountStr}`)
: undefined;
const errors = undefinedIfEmpty({
amount:
trimmedAmountStr == null
? i18n.str`required`
- : parsedAmount == null
+ : !parsedAmount
? i18n.str`invalid`
+ : Amounts.cmp(limit, parsedAmount) === -1
+ ? i18n.str`balance is not enough`
: undefined,
});
return (
@@ -87,10 +92,10 @@ export function WalletWithdrawForm({
type="text"
readonly
class="currency-indicator"
- size={currency.length}
- maxLength={currency.length}
+ size={limit.currency.length}
+ maxLength={limit.currency.length}
tabIndex={-1}
- value={currency}
+ value={limit.currency}
/>
&nbsp;
<input
diff --git a/packages/web-util/src/components/utils.ts b/packages/web-util/src/components/utils.ts
index 71824e14f..34693f7d7 100644
--- a/packages/web-util/src/components/utils.ts
+++ b/packages/web-util/src/components/utils.ts
@@ -34,3 +34,50 @@ export function compose<SType extends { status: string }, PType>(
return h();
};
}
+
+/**
+ *
+ * @param obj VNode
+ * @returns
+ */
+export function saveVNodeForInspection<T>(obj: T): T {
+ // @ts-ignore
+ window["showVNodeInfo"] = function showVNodeInfo() {
+ inspect(obj);
+ };
+ return obj;
+}
+function inspect(obj: any) {
+ if (!obj) return;
+ if (obj.__c && obj.__c.__H) {
+ const componentName = obj.__c.constructor.name;
+ const hookState = obj.__c.__H;
+ const stateList = hookState.__ as Array<any>;
+ console.log("==============", componentName);
+ stateList.forEach((hook) => {
+ const { __: value, c: context, __h: factory, __H: args } = hook;
+ if (typeof context !== "undefined") {
+ const { __c: contextId } = context;
+ console.log("context:", contextId, hook);
+ } else if (typeof factory === "function") {
+ console.log("memo:", value, "deps:", args);
+ } else if (typeof value === "function") {
+ const effectName = value.name;
+ console.log("effect:", effectName, "deps:", args);
+ } else if (typeof value.current !== "undefined") {
+ const ref = value.current;
+ console.log("ref:", ref instanceof Element ? ref.outerHTML : ref);
+ } else if (value instanceof Array) {
+ console.log("state:", value[0]);
+ } else {
+ console.log(hook);
+ }
+ });
+ }
+ const children = obj.__k;
+ if (children instanceof Array) {
+ children.forEach((e) => inspect(e));
+ } else {
+ inspect(children);
+ }
+}