show deposit transaction info

This commit is contained in:
Sebastian 2023-01-15 17:49:57 -03:00
parent fc38d0da95
commit bd57fa46a4
No known key found for this signature in database
GPG Key ID: BE4FF68352439FC1
3 changed files with 173 additions and 142 deletions

View File

@ -29,13 +29,14 @@ import { alertFromError, useAlertContext } from "../../context/alert.js";
import { useBackendContext } from "../../context/backend.js";
import { useTranslationContext } from "../../context/translation.js";
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): State {
}: Props): RecursiveState<State> {
const api = useBackendContext();
const { i18n } = useTranslationContext();
const { pushAlertOnError } = useAlertContext();
@ -49,9 +50,7 @@ export function useComponentState({
);
const { accounts } = await api.wallet.call(
WalletApiOperation.ListKnownBankAccounts,
{
currency,
},
{ currency },
);
return { accounts, balances };
@ -61,13 +60,11 @@ export function useComponentState({
parsed !== undefined
? parsed
: currency !== undefined
? Amounts.zeroOfCurrency(currency)
: undefined;
? Amounts.zeroOfCurrency(currency)
: undefined;
// const [accountIdx, setAccountIdx] = useState<number>(0);
const [amount, setAmount] = useState<AmountJson>(initialValue ?? ({} as any));
const [selectedAccount, setSelectedAccount] = useState<PaytoUri>();
const [fee, setFee] = useState<DepositGroupFees | undefined>(undefined);
const [addingAccount, setAddingAccount] = useState(false);
if (!currency) {
@ -91,7 +88,12 @@ export function useComponentState({
}
const { accounts, balances } = hook.response;
// const parsedAmount = Amounts.parse(`${currency}:${amount}`);
async function updateAccountFromList(accountStr: string): Promise<void> {
const uri = !accountStr ? undefined : parsePaytoUri(accountStr);
if (uri) {
setSelectedAccount(uri);
}
}
if (addingAccount) {
return {
@ -139,130 +141,112 @@ export function useComponentState({
const firstAccount = accounts[0].uri;
const currentAccount = !selectedAccount ? firstAccount : selectedAccount;
if (fee === undefined) {
getFeeForAmount(currentAccount, amount, api.wallet).then((initialFee) => {
setFee(initialFee);
});
return {
status: "loading",
error: undefined,
};
}
const accountMap = createLabelsForBankAccount(accounts);
async function updateAccountFromList(accountStr: string): Promise<void> {
const uri = !accountStr ? undefined : parsePaytoUri(accountStr);
if (uri) {
try {
const result = await getFeeForAmount(uri, amount, api.wallet);
setSelectedAccount(uri);
setFee(result);
} catch (e) {
setSelectedAccount(uri);
setFee(undefined);
}
}
}
async function updateAmount(newAmount: AmountJson): Promise<void> {
// const parsed = Amounts.parse(`${currency}:${numStr}`);
try {
const result = await getFeeForAmount(
currentAccount,
newAmount,
api.wallet,
);
setAmount(newAmount);
setFee(result);
} catch (e) {
setAmount(newAmount);
setFee(undefined);
}
}
const totalFee =
fee !== undefined
? Amounts.sum([fee.wire, fee.coin, fee.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);
return () => {
// eslint-disable-next-line react-hooks/rules-of-hooks
const [amount, setAmount] = useState<AmountJson>(
initialValue ?? ({} as any),
);
const amountStr = Amounts.stringify(amount);
await api.wallet.call(WalletApiOperation.CreateDepositGroup, {
amount: amountStr,
depositPaytoUri,
});
onSuccess(currency);
}
const depositPaytoUri = `payto://${currentAccount.targetType}/${currentAccount.targetPath}`;
return {
status: "ready",
error: undefined,
currency,
amount: {
value: amount,
onInput: pushAlertOnError(updateAmount),
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,
// currentAccount,
// parsedAmount,
// eslint-disable-next-line react-hooks/rules-of-hooks
const hook = useAsyncAsHook(async () => {
const fee = await api.wallet.call(WalletApiOperation.GetFeeForDeposit, {
amount: amountStr,
depositPaytoUri,
});
return { fee };
}, [amountStr, depositPaytoUri]);
if (!hook) {
return {
status: "loading",
error: undefined,
};
}
if (hook.hasError) {
return {
status: "error",
error: alertFromError(
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.wire, fee.coin, fee.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,
};
};
}
async function getFeeForAmount(
p: PaytoUri,
a: AmountJson,
wallet: ReturnType<typeof useBackendContext>["wallet"],
): Promise<DepositGroupFees> {
const depositPaytoUri = `payto://${p.targetType}/${p.targetPath}`;
const amount = Amounts.stringify(a);
return await wallet.call(WalletApiOperation.GetFeeForDeposit, {
amount,
depositPaytoUri,
});
}
export function labelForAccountType(id: string): string {
switch (id) {
case "":

View File

@ -264,6 +264,15 @@ describe("DepositPage states", () => {
expect(state.totalFee).deep.eq(Amounts.parseOrThrow(`${currency}:0`));
expect(state.depositHandler.onClick).undefined;
},
(state) => {
if (state.status !== "ready") expect.fail();
expect(state.cancelHandler.onClick).not.undefined;
expect(state.currency).eq(currency);
expect(state.account.value).eq(accountSelected);
expect(state.amount.value).deep.eq(Amounts.parseOrThrow("EUR:0"));
expect(state.totalFee).deep.eq(Amounts.parseOrThrow(`${currency}:0`));
expect(state.depositHandler.onClick).undefined;
},
],
TestingContext,
);
@ -341,7 +350,7 @@ describe("DepositPage states", () => {
expect(state.account.value).eq(accountSelected);
expect(state.amount.value).deep.eq(Amounts.parseOrThrow("EUR:0"));
expect(state.depositHandler.onClick).undefined;
expect(state.totalFee).deep.eq(Amounts.parseOrThrow(`${currency}:3`));
expect(state.totalFee).deep.eq(Amounts.parseOrThrow(`${currency}:0`));
expect(state.amount.onInput).not.undefined;
if (!state.amount.onInput) return;
@ -359,6 +368,18 @@ describe("DepositPage states", () => {
);
expect(state.depositHandler.onClick).not.undefined;
},
(state) => {
if (state.status !== "ready") expect.fail();
expect(state.cancelHandler.onClick).not.undefined;
expect(state.currency).eq(currency);
expect(state.account.value).eq(accountSelected);
expect(state.amount.value).deep.eq(Amounts.parseOrThrow("EUR:10"));
expect(state.totalFee).deep.eq(Amounts.parseOrThrow(`${currency}:3`));
expect(state.totalToDeposit).deep.eq(
Amounts.parseOrThrow(`${currency}:7`),
);
expect(state.depositHandler.onClick).not.undefined;
},
],
TestingContext,
);

View File

@ -38,7 +38,7 @@ import {
} from "@gnu-taler/taler-util";
import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
import { styled } from "@linaria/react";
import { differenceInSeconds } from "date-fns";
import { differenceInSeconds, isAfter, isFuture, isPast } from "date-fns";
import { ComponentChildren, Fragment, h, VNode } from "preact";
import { useEffect, useState } from "preact/hooks";
import emptyImg from "../../static/img/empty.png";
@ -641,6 +641,11 @@ export function TransactionView({
if (transaction.type === TransactionType.Deposit) {
const total = Amounts.parseOrThrow(transaction.amountRaw);
const payto = parsePaytoUri(transaction.targetPaytoUri);
const wireTime = AbsoluteTime.fromTimestamp(
transaction.wireTransferDeadline,
);
const shouldBeWired = wireTime.t_ms !== "never" && isPast(wireTime.t_ms);
return (
<TransactionTemplate
transaction={transaction}
@ -663,18 +668,39 @@ export function TransactionView({
text={<DepositDetails transaction={transaction} />}
kind="neutral"
/>
<Part
title={i18n.str`Wire transfer deadline`}
text={
<Time
timestamp={AbsoluteTime.fromTimestamp(
transaction.wireTransferDeadline,
)}
format="dd MMMM yyyy 'at' HH:mm"
/>
}
kind="neutral"
/>
{!shouldBeWired ? (
<Part
title={i18n.str`Wire transfer deadline`}
text={
<Time timestamp={wireTime} format="dd MMMM yyyy 'at' HH:mm" />
}
kind="neutral"
/>
) : transaction.wireTransferProgress === 0 ? (
<AlertView
alert={{
type: "warning",
message: i18n.str`Wire transfer is not initiated`,
description: i18n.str` `,
}}
/>
) : transaction.wireTransferProgress === 100 ? (
<AlertView
alert={{
type: "success",
message: i18n.str`Wire transfer completed`,
description: i18n.str` `,
}}
/>
) : (
<AlertView
alert={{
type: "info",
message: i18n.str`Wire transfer in progress`,
description: i18n.str` `,
}}
/>
)}
</TransactionTemplate>
);
}