set amount for manual withdraw when the qr does not have it

This commit is contained in:
Sebastian 2023-06-27 08:20:49 -03:00
parent 18a3d764de
commit 97a9e92d8b
No known key found for this signature in database
GPG Key ID: 173909D1A5F66069
4 changed files with 135 additions and 15 deletions

View File

@ -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;
} }

View File

@ -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,

View File

@ -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>
); );
} }

View File

@ -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)}