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 { useBackendContext } from "../../context/backend.js";
import { useTranslationContext } from "../../context/translation.js"; import { useTranslationContext } from "../../context/translation.js";
import { useAsyncAsHook } from "../../hooks/useAsyncAsHook.js"; import { useAsyncAsHook } from "../../hooks/useAsyncAsHook.js";
import { RecursiveState } from "../../utils/index.js";
import { Props, State } from "./index.js"; import { Props, State } from "./index.js";
export function useComponentState({ export function useComponentState({
amount: amountStr, amount: amountStr,
onCancel, onCancel,
onSuccess, onSuccess,
}: Props): State { }: Props): RecursiveState<State> {
const api = useBackendContext(); const api = useBackendContext();
const { i18n } = useTranslationContext(); const { i18n } = useTranslationContext();
const { pushAlertOnError } = useAlertContext(); const { pushAlertOnError } = useAlertContext();
@ -49,9 +50,7 @@ export function useComponentState({
); );
const { accounts } = await api.wallet.call( const { accounts } = await api.wallet.call(
WalletApiOperation.ListKnownBankAccounts, WalletApiOperation.ListKnownBankAccounts,
{ { currency },
currency,
},
); );
return { accounts, balances }; return { accounts, balances };
@ -61,13 +60,11 @@ export function useComponentState({
parsed !== undefined parsed !== undefined
? parsed ? parsed
: currency !== undefined : currency !== undefined
? Amounts.zeroOfCurrency(currency) ? Amounts.zeroOfCurrency(currency)
: undefined; : undefined;
// const [accountIdx, setAccountIdx] = useState<number>(0); // const [accountIdx, setAccountIdx] = useState<number>(0);
const [amount, setAmount] = useState<AmountJson>(initialValue ?? ({} as any));
const [selectedAccount, setSelectedAccount] = useState<PaytoUri>(); const [selectedAccount, setSelectedAccount] = useState<PaytoUri>();
const [fee, setFee] = useState<DepositGroupFees | undefined>(undefined);
const [addingAccount, setAddingAccount] = useState(false); const [addingAccount, setAddingAccount] = useState(false);
if (!currency) { if (!currency) {
@ -91,7 +88,12 @@ export function useComponentState({
} }
const { accounts, balances } = hook.response; 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) { if (addingAccount) {
return { return {
@ -139,130 +141,112 @@ export function useComponentState({
const firstAccount = accounts[0].uri; const firstAccount = accounts[0].uri;
const currentAccount = !selectedAccount ? firstAccount : selectedAccount; const currentAccount = !selectedAccount ? firstAccount : selectedAccount;
if (fee === undefined) { return () => {
getFeeForAmount(currentAccount, amount, api.wallet).then((initialFee) => { // eslint-disable-next-line react-hooks/rules-of-hooks
setFee(initialFee); const [amount, setAmount] = useState<AmountJson>(
}); initialValue ?? ({} as any),
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);
const amountStr = Amounts.stringify(amount); const amountStr = Amounts.stringify(amount);
await api.wallet.call(WalletApiOperation.CreateDepositGroup, { const depositPaytoUri = `payto://${currentAccount.targetType}/${currentAccount.targetPath}`;
amount: amountStr,
depositPaytoUri,
});
onSuccess(currency);
}
return { // eslint-disable-next-line react-hooks/rules-of-hooks
status: "ready", const hook = useAsyncAsHook(async () => {
error: undefined, const fee = await api.wallet.call(WalletApiOperation.GetFeeForDeposit, {
currency, amount: amountStr,
amount: { depositPaytoUri,
value: amount, });
onInput: pushAlertOnError(updateAmount),
error: amountError, return { fee };
}, }, [amountStr, depositPaytoUri]);
onAddAccount: {
onClick: pushAlertOnError(async () => { if (!hook) {
setAddingAccount(true); return {
}), status: "loading",
}, error: undefined,
account: { };
list: accountMap, }
value: stringifyPaytoUri(currentAccount), if (hook.hasError) {
onChange: pushAlertOnError(updateAccountFromList), return {
}, status: "error",
currentAccount, error: alertFromError(
cancelHandler: { i18n.str`Could not load fee for amount ${amountStr}`,
onClick: pushAlertOnError(async () => { hook,
onCancel(currency); ),
}), };
}, }
depositHandler: {
onClick: unableToDeposit ? undefined : pushAlertOnError(doSend), const { fee } = hook.response;
},
totalFee, const accountMap = createLabelsForBankAccount(accounts);
totalToDeposit,
// currentAccount, const totalFee =
// parsedAmount, 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 { export function labelForAccountType(id: string): string {
switch (id) { switch (id) {
case "": case "":

View File

@ -264,6 +264,15 @@ describe("DepositPage states", () => {
expect(state.totalFee).deep.eq(Amounts.parseOrThrow(`${currency}:0`)); expect(state.totalFee).deep.eq(Amounts.parseOrThrow(`${currency}:0`));
expect(state.depositHandler.onClick).undefined; 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, TestingContext,
); );
@ -341,7 +350,7 @@ describe("DepositPage states", () => {
expect(state.account.value).eq(accountSelected); expect(state.account.value).eq(accountSelected);
expect(state.amount.value).deep.eq(Amounts.parseOrThrow("EUR:0")); expect(state.amount.value).deep.eq(Amounts.parseOrThrow("EUR:0"));
expect(state.depositHandler.onClick).undefined; 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; expect(state.amount.onInput).not.undefined;
if (!state.amount.onInput) return; if (!state.amount.onInput) return;
@ -359,6 +368,18 @@ describe("DepositPage states", () => {
); );
expect(state.depositHandler.onClick).not.undefined; 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, TestingContext,
); );

View File

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