show deposit transaction info
This commit is contained in:
parent
fc38d0da95
commit
bd57fa46a4
@ -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 "":
|
||||
|
@ -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,
|
||||
);
|
||||
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user