wallet-core/packages/taler-wallet-webextension/src/wallet/DepositPage/state.ts

241 lines
6.7 KiB
TypeScript
Raw Normal View History

2022-09-23 20:18:18 +02:00
/*
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 { useState } from "preact/hooks";
import { useAsyncAsHook } from "../../hooks/useAsyncAsHook.js";
import * as wxApi from "../../wxApi.js";
import { Props, State } from "./index.js";
export function useComponentState({ amount: amountStr, currency: currencyStr, onCancel, onSuccess }: Props, api: typeof wxApi): State {
const parsed = amountStr === undefined ? undefined : Amounts.parse(amountStr);
const currency = parsed !== undefined ? parsed.currency : currencyStr;
const hook = useAsyncAsHook(async () => {
const { balances } = await api.getBalance();
const { accounts } = await api.listKnownBankAccounts(currency);
return { accounts, balances };
});
const initialValue =
parsed !== undefined ? Amounts.stringifyValue(parsed) : "0";
// const [accountIdx, setAccountIdx] = useState<number>(0);
const [amount, setAmount] = useState(initialValue);
const [selectedAccount, setSelectedAccount] = useState<
PaytoUri | undefined
>();
const [fee, setFee] = useState<DepositGroupFees | undefined>(undefined);
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: "loading-error",
error: hook,
}
}
const { accounts, balances } = hook.response;
const parsedAmount = Amounts.parse(`${currency}:${amount}`);
if (addingAccount) {
return {
status: "adding-account",
error: undefined,
currency,
onAccountAdded: (p: string) => {
updateAccountFromList(p);
setAddingAccount(false);
hook.retry()
},
onCancel: () => {
setAddingAccount(false);
}
,
}
}
const bs = balances.filter((b) => b.available.startsWith(currency));
const balance =
bs.length > 0
? Amounts.parseOrThrow(bs[0].available)
: Amounts.getZero(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: async () => { setAddingAccount(true) }
},
}
}
const accountMap = createLabelsForBankAccount(accounts);
accountMap[""] = "Select one account..."
async function updateAccountFromList(accountStr: string): Promise<void> {
// const newSelected = !accountMap[accountStr] ? undefined : accountMap[accountStr];
// if (!newSelected) return;
const uri = !accountStr ? undefined : parsePaytoUri(accountStr);
setSelectedAccount(uri);
if (uri && parsedAmount) {
try {
const result = await getFeeForAmount(uri, parsedAmount, api);
setFee(result);
} catch (e) {
setFee(undefined);
}
}
}
async function updateAmount(numStr: string): Promise<void> {
setAmount(numStr);
const parsed = Amounts.parse(`${currency}:${numStr}`);
if (parsed && selectedAccount) {
try {
const result = await getFeeForAmount(selectedAccount, parsed, api);
setFee(result);
} catch (e) {
setFee(undefined);
}
}
}
const totalFee =
fee !== undefined
? Amounts.sum([fee.wire, fee.coin, fee.refresh]).amount
: Amounts.getZero(currency);
const totalToDeposit = parsedAmount && fee !== undefined
? Amounts.sub(parsedAmount, totalFee).amount
: Amounts.getZero(currency);
const isDirty = amount !== initialValue;
const amountError = !isDirty
? undefined
: !parsedAmount
? "Invalid amount"
: Amounts.cmp(balance, parsedAmount) === -1
? `Too much, your current balance is ${Amounts.stringifyValue(balance)}`
: undefined;
const unableToDeposit =
!parsedAmount ||
selectedAccount === undefined ||
Amounts.isZero(totalToDeposit) ||
fee === undefined ||
amountError !== undefined;
async function doSend(): Promise<void> {
if (!selectedAccount || !parsedAmount || !currency) return;
const account = `payto://${selectedAccount.targetType}/${selectedAccount.targetPath}`;
const amount = Amounts.stringify(parsedAmount);
await api.createDepositGroup(account, amount);
onSuccess(currency);
}
return {
status: "ready",
error: undefined,
currency,
amount: {
value: String(amount),
onInput: updateAmount,
error: amountError,
},
onAddAccount: {
onClick: async () => { setAddingAccount(true) }
},
account: {
list: accountMap,
value: !selectedAccount ? "" : stringifyPaytoUri(selectedAccount),
onChange: updateAccountFromList,
},
selectedAccount,
cancelHandler: {
onClick: async () => {
onCancel(currency);
},
},
depositHandler: {
onClick: unableToDeposit ? undefined : doSend,
},
totalFee,
totalToDeposit,
// currentAccount,
// parsedAmount,
};
}
async function getFeeForAmount(
p: PaytoUri,
a: AmountJson,
api: typeof wxApi,
): Promise<DepositGroupFees> {
const account = `payto://${p.targetType}/${p.targetPath}`;
const amount = Amounts.stringify(a);
return await api.getFeeForDeposit(account, amount);
}
export function labelForAccountType(id: 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);
}