set amount for manual withdraw when the qr does not have it
This commit is contained in:
parent
18a3d764de
commit
97a9e92d8b
@ -14,10 +14,18 @@
|
|||||||
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
|
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { AmountJson, ExchangeListItem } from "@gnu-taler/taler-util";
|
import {
|
||||||
|
AmountJson,
|
||||||
|
AmountString,
|
||||||
|
ExchangeListItem,
|
||||||
|
} from "@gnu-taler/taler-util";
|
||||||
import { Loading } from "../../components/Loading.js";
|
import { Loading } from "../../components/Loading.js";
|
||||||
import { State as SelectExchangeState } from "../../hooks/useSelectedExchange.js";
|
import { State as SelectExchangeState } from "../../hooks/useSelectedExchange.js";
|
||||||
import { ButtonHandler, SelectFieldHandler } from "../../mui/handlers.js";
|
import {
|
||||||
|
AmountFieldHandler,
|
||||||
|
ButtonHandler,
|
||||||
|
SelectFieldHandler,
|
||||||
|
} from "../../mui/handlers.js";
|
||||||
import { StateViewMap, compose } from "../../utils/index.js";
|
import { StateViewMap, compose } from "../../utils/index.js";
|
||||||
import {
|
import {
|
||||||
useComponentStateFromParams,
|
useComponentStateFromParams,
|
||||||
@ -37,10 +45,11 @@ export interface PropsFromURI {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface PropsFromParams {
|
export interface PropsFromParams {
|
||||||
talerExchangeWithdrawUri: string;
|
talerExchangeWithdrawUri: string | undefined;
|
||||||
amount: string;
|
amount: string | undefined;
|
||||||
cancel: () => Promise<void>;
|
cancel: () => Promise<void>;
|
||||||
onSuccess: (txid: string) => Promise<void>;
|
onSuccess: (txid: string) => Promise<void>;
|
||||||
|
onAmountChanged: (amount: AmountString) => Promise<void>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type State =
|
export type State =
|
||||||
@ -64,7 +73,9 @@ export namespace State {
|
|||||||
export interface SelectAmount {
|
export interface SelectAmount {
|
||||||
status: "select-amount";
|
status: "select-amount";
|
||||||
error: undefined;
|
error: undefined;
|
||||||
currentExchange: ExchangeListItem;
|
exchangeBaseUrl: string;
|
||||||
|
confirm: ButtonHandler;
|
||||||
|
amount: AmountFieldHandler;
|
||||||
currency: string;
|
currency: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -26,7 +26,7 @@ import {
|
|||||||
stringifyWithdrawUri,
|
stringifyWithdrawUri,
|
||||||
} 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 { useState } from "preact/hooks";
|
import { useEffect, useState } from "preact/hooks";
|
||||||
import { alertFromError, useAlertContext } from "../../context/alert.js";
|
import { alertFromError, useAlertContext } from "../../context/alert.js";
|
||||||
import { useBackendContext } from "../../context/backend.js";
|
import { useBackendContext } from "../../context/backend.js";
|
||||||
import { useTranslationContext } from "@gnu-taler/web-util/browser";
|
import { useTranslationContext } from "@gnu-taler/web-util/browser";
|
||||||
@ -39,16 +39,20 @@ export function useComponentStateFromParams({
|
|||||||
talerExchangeWithdrawUri: maybeTalerUri,
|
talerExchangeWithdrawUri: maybeTalerUri,
|
||||||
amount,
|
amount,
|
||||||
cancel,
|
cancel,
|
||||||
|
onAmountChanged,
|
||||||
onSuccess,
|
onSuccess,
|
||||||
}: PropsFromParams): RecursiveState<State> {
|
}: PropsFromParams): RecursiveState<State> {
|
||||||
const api = useBackendContext();
|
const api = useBackendContext();
|
||||||
const { i18n } = useTranslationContext();
|
const { i18n } = useTranslationContext();
|
||||||
|
const paramsAmount = amount ? Amounts.parse(amount) : undefined;
|
||||||
const uriInfoHook = useAsyncAsHook(async () => {
|
const uriInfoHook = useAsyncAsHook(async () => {
|
||||||
const exchanges = await api.wallet.call(
|
const exchanges = await api.wallet.call(
|
||||||
WalletApiOperation.ListExchanges,
|
WalletApiOperation.ListExchanges,
|
||||||
{},
|
{},
|
||||||
);
|
);
|
||||||
const uri = parseWithdrawExchangeUri(maybeTalerUri);
|
const uri = maybeTalerUri
|
||||||
|
? parseWithdrawExchangeUri(maybeTalerUri)
|
||||||
|
: undefined;
|
||||||
const exchangeByTalerUri = uri?.exchangeBaseUrl;
|
const exchangeByTalerUri = uri?.exchangeBaseUrl;
|
||||||
let ex: ExchangeFullDetails | undefined;
|
let ex: ExchangeFullDetails | undefined;
|
||||||
if (exchangeByTalerUri && uri.exchangePub) {
|
if (exchangeByTalerUri && uri.exchangePub) {
|
||||||
@ -65,11 +69,8 @@ export function useComponentStateFromParams({
|
|||||||
|
|
||||||
ex = info.exchange;
|
ex = info.exchange;
|
||||||
}
|
}
|
||||||
const chosenAmount = uri
|
const chosenAmount =
|
||||||
? uri.amount
|
!uri || !uri.amount ? undefined : Amounts.parse(uri.amount);
|
||||||
? Amounts.parseOrThrow(uri.amount)
|
|
||||||
: Amounts.parseOrThrow(`${ex ? ex.currency : "KUDOS"}:66`)
|
|
||||||
: Amounts.parseOrThrow(amount);
|
|
||||||
return { amount: chosenAmount, exchanges, exchange: ex };
|
return { amount: chosenAmount, exchanges, exchange: ex };
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -85,10 +86,76 @@ export function useComponentStateFromParams({
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const chosenAmount = uriInfoHook.response.amount;
|
useEffect(() => {
|
||||||
|
uriInfoHook?.retry();
|
||||||
|
}, [amount]);
|
||||||
|
|
||||||
const exchangeByTalerUri = uriInfoHook.response.exchange?.exchangeBaseUrl;
|
const exchangeByTalerUri = uriInfoHook.response.exchange?.exchangeBaseUrl;
|
||||||
const exchangeList = uriInfoHook.response.exchanges.exchanges;
|
const exchangeList = uriInfoHook.response.exchanges.exchanges;
|
||||||
|
|
||||||
|
const maybeAmount = uriInfoHook.response.amount ?? paramsAmount;
|
||||||
|
|
||||||
|
if (!maybeAmount) {
|
||||||
|
const exchangeBaseUrl =
|
||||||
|
uriInfoHook.response.exchange?.exchangeBaseUrl ??
|
||||||
|
(exchangeList.length > 0 ? exchangeList[0].exchangeBaseUrl : undefined);
|
||||||
|
const currency =
|
||||||
|
uriInfoHook.response.exchange?.currency ??
|
||||||
|
(exchangeList.length > 0 ? exchangeList[0].currency : undefined);
|
||||||
|
|
||||||
|
if (!exchangeBaseUrl) {
|
||||||
|
return {
|
||||||
|
status: "error",
|
||||||
|
error: {
|
||||||
|
message: i18n.str`Can't withdraw from exchange`,
|
||||||
|
description: i18n.str`Missing base URL`,
|
||||||
|
cause: undefined,
|
||||||
|
context: {},
|
||||||
|
type: "error",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (!currency) {
|
||||||
|
return {
|
||||||
|
status: "error",
|
||||||
|
error: {
|
||||||
|
message: i18n.str`Can't withdraw from exchange`,
|
||||||
|
description: i18n.str`Missing unknown currency`,
|
||||||
|
cause: undefined,
|
||||||
|
context: {},
|
||||||
|
type: "error",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return () => {
|
||||||
|
const { pushAlertOnError } = useAlertContext();
|
||||||
|
const [amount, setAmount] = useState<AmountJson>(
|
||||||
|
Amounts.zeroOfCurrency(currency),
|
||||||
|
);
|
||||||
|
const isValid = Amounts.isNonZero(amount);
|
||||||
|
return {
|
||||||
|
status: "select-amount",
|
||||||
|
currency,
|
||||||
|
exchangeBaseUrl,
|
||||||
|
error: undefined,
|
||||||
|
confirm: {
|
||||||
|
onClick: isValid
|
||||||
|
? pushAlertOnError(async () => {
|
||||||
|
onAmountChanged(Amounts.stringify(amount));
|
||||||
|
})
|
||||||
|
: undefined,
|
||||||
|
},
|
||||||
|
amount: {
|
||||||
|
value: amount,
|
||||||
|
onInput: pushAlertOnError(async (e) => {
|
||||||
|
setAmount(e);
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
const chosenAmount = maybeAmount;
|
||||||
|
|
||||||
async function doManualWithdraw(
|
async function doManualWithdraw(
|
||||||
exchange: string,
|
exchange: string,
|
||||||
ageRestricted: number | undefined,
|
ageRestricted: number | undefined,
|
||||||
|
@ -32,6 +32,8 @@ import {
|
|||||||
WithdrawDetails,
|
WithdrawDetails,
|
||||||
} from "../../wallet/Transaction.js";
|
} from "../../wallet/Transaction.js";
|
||||||
import { State } from "./index.js";
|
import { State } from "./index.js";
|
||||||
|
import { Grid } from "../../mui/Grid.js";
|
||||||
|
import { AmountField } from "../../components/AmountField.js";
|
||||||
|
|
||||||
export function SuccessView(state: State.Success): VNode {
|
export function SuccessView(state: State.Success): VNode {
|
||||||
const { i18n } = useTranslationContext();
|
const { i18n } = useTranslationContext();
|
||||||
@ -143,11 +145,45 @@ function WithdrawWithMobile({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function SelectAmountView({ currency }: State.SelectAmount): VNode {
|
export function SelectAmountView({
|
||||||
|
currency,
|
||||||
|
amount,
|
||||||
|
exchangeBaseUrl,
|
||||||
|
confirm,
|
||||||
|
}: State.SelectAmount): VNode {
|
||||||
const { i18n } = useTranslationContext();
|
const { i18n } = useTranslationContext();
|
||||||
return (
|
return (
|
||||||
<Fragment>
|
<Fragment>
|
||||||
<p>select the amount for ${currency}</p>
|
<section style={{ textAlign: "left" }}>
|
||||||
|
<Part
|
||||||
|
title={
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<i18n.Translate>Exchange</i18n.Translate>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
text={<ExchangeDetails exchange={exchangeBaseUrl} />}
|
||||||
|
kind="neutral"
|
||||||
|
big
|
||||||
|
/>
|
||||||
|
<Grid container columns={2} justifyContent="space-between">
|
||||||
|
<AmountField label={i18n.str`Amount`} required handler={amount} />
|
||||||
|
</Grid>
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
<Button
|
||||||
|
variant="contained"
|
||||||
|
color="info"
|
||||||
|
disabled={!confirm.onClick}
|
||||||
|
onClick={confirm.onClick}
|
||||||
|
>
|
||||||
|
<i18n.Translate>See details</i18n.Translate>
|
||||||
|
</Button>
|
||||||
|
</section>
|
||||||
</Fragment>
|
</Fragment>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -400,6 +400,12 @@ export function Application(): VNode {
|
|||||||
}) => (
|
}) => (
|
||||||
<CallToActionTemplate title={i18n.str`Digital cash withdrawal`}>
|
<CallToActionTemplate title={i18n.str`Digital cash withdrawal`}>
|
||||||
<WithdrawPageFromParams
|
<WithdrawPageFromParams
|
||||||
|
onAmountChanged={async (e) => {
|
||||||
|
const page = `${Pages.ctaWithdrawManual({
|
||||||
|
amount,
|
||||||
|
})}?talerUri=${encodeURIComponent(talerUri)}`;
|
||||||
|
redirectTo(page);
|
||||||
|
}}
|
||||||
talerExchangeWithdrawUri={talerUri}
|
talerExchangeWithdrawUri={talerUri}
|
||||||
amount={amount}
|
amount={amount}
|
||||||
cancel={() => redirectTo(Pages.balance)}
|
cancel={() => redirectTo(Pages.balance)}
|
||||||
|
Loading…
Reference in New Issue
Block a user