summaryrefslogtreecommitdiff
path: root/packages/taler-wallet-webextension/src/wallet/DepositPage/state.ts
diff options
context:
space:
mode:
Diffstat (limited to 'packages/taler-wallet-webextension/src/wallet/DepositPage/state.ts')
-rw-r--r--packages/taler-wallet-webextension/src/wallet/DepositPage/state.ts276
1 files changed, 276 insertions, 0 deletions
diff --git a/packages/taler-wallet-webextension/src/wallet/DepositPage/state.ts b/packages/taler-wallet-webextension/src/wallet/DepositPage/state.ts
new file mode 100644
index 000000000..97b2ab517
--- /dev/null
+++ b/packages/taler-wallet-webextension/src/wallet/DepositPage/state.ts
@@ -0,0 +1,276 @@
+/*
+ This file is part of GNU Taler
+ (C) 2022 Taler Systems S.A.
+
+ GNU Taler is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+
+import {
+ AmountJson,
+ Amounts,
+ DepositGroupFees,
+ KnownBankAccountsInfo,
+ parsePaytoUri,
+ PaytoUri,
+ stringifyPaytoUri,
+} from "@gnu-taler/taler-util";
+import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
+import { useState } from "preact/hooks";
+import { alertFromError, useAlertContext } from "../../context/alert.js";
+import { useBackendContext } from "../../context/backend.js";
+import { useTranslationContext } from "@gnu-taler/web-util/browser";
+import { useAsyncAsHook } from "../../hooks/useAsyncAsHook.js";
+import { RecursiveState } from "../../utils/index.js";
+import { Props, State } from "./index.js";
+
+export function useComponentState({
+ amount: amountStr,
+ onCancel,
+ onSuccess,
+}: Props): RecursiveState<State> {
+ const api = useBackendContext();
+ const { i18n } = useTranslationContext();
+ const { pushAlertOnError } = useAlertContext();
+ const parsed = amountStr === undefined ? undefined : Amounts.parse(amountStr);
+ const currency = parsed !== undefined ? parsed.currency : undefined;
+
+ const hook = useAsyncAsHook(async () => {
+ const { balances } = await api.wallet.call(
+ WalletApiOperation.GetBalances,
+ {},
+ );
+ const { accounts } = await api.wallet.call(
+ WalletApiOperation.ListKnownBankAccounts,
+ { currency },
+ );
+
+ return { accounts, balances };
+ });
+
+ const initialValue =
+ parsed !== undefined
+ ? parsed
+ : currency !== undefined
+ ? Amounts.zeroOfCurrency(currency)
+ : undefined;
+ // const [accountIdx, setAccountIdx] = useState<number>(0);
+ const [selectedAccount, setSelectedAccount] = useState<PaytoUri>();
+
+ const [addingAccount, setAddingAccount] = useState(false);
+
+ if (!currency) {
+ return {
+ status: "amount-or-currency-error",
+ error: undefined,
+ };
+ }
+
+ if (!hook) {
+ return {
+ status: "loading",
+ error: undefined,
+ };
+ }
+ if (hook.hasError) {
+ return {
+ status: "error",
+ error: alertFromError(i18n,
+ i18n.str`Could not load balance information`, hook),
+ };
+ }
+ const { accounts, balances } = hook.response;
+
+ async function updateAccountFromList(accountStr: string): Promise<void> {
+ const uri = !accountStr ? undefined : parsePaytoUri(accountStr);
+ if (uri) {
+ setSelectedAccount(uri);
+ }
+ }
+
+ if (addingAccount) {
+ return {
+ status: "manage-account",
+ error: undefined,
+ currency,
+ onAccountAdded: (p: string) => {
+ updateAccountFromList(p);
+ setAddingAccount(false);
+ hook.retry();
+ },
+ onCancel: () => {
+ setAddingAccount(false);
+ hook.retry();
+ },
+ };
+ }
+
+ const bs = balances.filter((b) => b.available.startsWith(currency));
+ const balance =
+ bs.length > 0
+ ? Amounts.parseOrThrow(bs[0].available)
+ : Amounts.zeroOfCurrency(currency);
+
+ if (Amounts.isZero(balance)) {
+ return {
+ status: "no-enough-balance",
+ error: undefined,
+ currency,
+ };
+ }
+
+ if (accounts.length === 0) {
+ return {
+ status: "no-accounts",
+ error: undefined,
+ currency,
+ onAddAccount: {
+ onClick: pushAlertOnError(async () => {
+ setAddingAccount(true);
+ }),
+ },
+ };
+ }
+ const firstAccount = accounts[0].uri;
+ const currentAccount = !selectedAccount ? firstAccount : selectedAccount;
+
+ return () => {
+ // eslint-disable-next-line react-hooks/rules-of-hooks
+ const [amount, setAmount] = useState<AmountJson>(
+ initialValue ?? ({} as any),
+ );
+ const amountStr = Amounts.stringify(amount);
+ const depositPaytoUri = stringifyPaytoUri(currentAccount);
+
+ // eslint-disable-next-line react-hooks/rules-of-hooks
+ const hook = useAsyncAsHook(async () => {
+ const fee = await api.wallet.call(WalletApiOperation.PrepareDeposit, {
+ amount: amountStr,
+ depositPaytoUri,
+ });
+
+ return { fee };
+ }, [amountStr, depositPaytoUri]);
+
+ if (!hook) {
+ return {
+ status: "loading",
+ error: undefined,
+ };
+ }
+ if (hook.hasError) {
+ return {
+ status: "error",
+ error: alertFromError(
+ i18n,
+ i18n.str`Could not load fee for amount ${amountStr}`,
+ hook,
+ ),
+ };
+ }
+
+ const { fee } = hook.response;
+
+ const accountMap = createLabelsForBankAccount(accounts);
+
+ const totalFee =
+ fee !== undefined
+ ? Amounts.sum([fee.fees.wire, fee.fees.coin, fee.fees.refresh]).amount
+ : Amounts.zeroOfCurrency(currency);
+
+ const totalToDeposit =
+ fee !== undefined
+ ? Amounts.sub(amount, totalFee).amount
+ : Amounts.zeroOfCurrency(currency);
+
+ const isDirty = amount !== initialValue;
+ const amountError = !isDirty
+ ? undefined
+ : Amounts.cmp(balance, amount) === -1
+ ? `Too much, your current balance is ${Amounts.stringifyValue(balance)}`
+ : undefined;
+
+ const unableToDeposit =
+ Amounts.isZero(totalToDeposit) || //deposit may be zero because of fee
+ fee === undefined || //no fee calculated yet
+ amountError !== undefined; //amount field may be invalid
+
+ async function doSend(): Promise<void> {
+ if (!currency) return;
+
+ const depositPaytoUri = stringifyPaytoUri(currentAccount);
+ const amountStr = Amounts.stringify(amount);
+ await api.wallet.call(WalletApiOperation.CreateDepositGroup, {
+ amount: amountStr,
+ depositPaytoUri,
+ });
+ onSuccess(currency);
+ }
+
+ return {
+ status: "ready",
+ error: undefined,
+ currency,
+ amount: {
+ value: amount,
+ onInput: pushAlertOnError(async (a) => setAmount(a)),
+ error: amountError,
+ },
+ onAddAccount: {
+ onClick: pushAlertOnError(async () => {
+ setAddingAccount(true);
+ }),
+ },
+ account: {
+ list: accountMap,
+ value: stringifyPaytoUri(currentAccount),
+ onChange: pushAlertOnError(updateAccountFromList),
+ },
+ currentAccount,
+ cancelHandler: {
+ onClick: pushAlertOnError(async () => {
+ onCancel(currency);
+ }),
+ },
+ depositHandler: {
+ onClick: unableToDeposit ? undefined : pushAlertOnError(doSend),
+ },
+ totalFee,
+ totalToDeposit,
+ };
+ };
+}
+
+export function labelForAccountType(id: string): string {
+ switch (id) {
+ case "":
+ return "Choose one";
+ case "x-taler-bank":
+ return "Taler Bank";
+ case "bitcoin":
+ return "Bitcoin";
+ case "iban":
+ return "IBAN";
+ default:
+ return id;
+ }
+}
+
+export function createLabelsForBankAccount(
+ knownBankAccounts: Array<KnownBankAccountsInfo>,
+): { [value: string]: string } {
+ const initialList: Record<string, string> = {};
+ if (!knownBankAccounts.length) return initialList;
+ return knownBankAccounts.reduce((prev, cur, i) => {
+ prev[stringifyPaytoUri(cur.uri)] = cur.alias;
+ return prev;
+ }, initialList);
+}