diff options
4 files changed, 135 insertions, 15 deletions
diff --git a/packages/taler-wallet-webextension/src/cta/Withdraw/index.ts b/packages/taler-wallet-webextension/src/cta/Withdraw/index.ts index ae4b3c436..f80e5a648 100644 --- a/packages/taler-wallet-webextension/src/cta/Withdraw/index.ts +++ b/packages/taler-wallet-webextension/src/cta/Withdraw/index.ts @@ -14,10 +14,18 @@ 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 { 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 { useComponentStateFromParams, @@ -37,10 +45,11 @@ export interface PropsFromURI { } export interface PropsFromParams { - talerExchangeWithdrawUri: string; - amount: string; + talerExchangeWithdrawUri: string | undefined; + amount: string | undefined; cancel: () => Promise<void>; onSuccess: (txid: string) => Promise<void>; + onAmountChanged: (amount: AmountString) => Promise<void>; } export type State = @@ -64,7 +73,9 @@ export namespace State { export interface SelectAmount { status: "select-amount"; error: undefined; - currentExchange: ExchangeListItem; + exchangeBaseUrl: string; + confirm: ButtonHandler; + amount: AmountFieldHandler; currency: string; } diff --git a/packages/taler-wallet-webextension/src/cta/Withdraw/state.ts b/packages/taler-wallet-webextension/src/cta/Withdraw/state.ts index f19f32ec0..46a72ac87 100644 --- a/packages/taler-wallet-webextension/src/cta/Withdraw/state.ts +++ b/packages/taler-wallet-webextension/src/cta/Withdraw/state.ts @@ -26,7 +26,7 @@ import { stringifyWithdrawUri, } from "@gnu-taler/taler-util"; 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 { useBackendContext } from "../../context/backend.js"; import { useTranslationContext } from "@gnu-taler/web-util/browser"; @@ -39,16 +39,20 @@ export function useComponentStateFromParams({ talerExchangeWithdrawUri: maybeTalerUri, amount, cancel, + onAmountChanged, onSuccess, }: PropsFromParams): RecursiveState<State> { const api = useBackendContext(); const { i18n } = useTranslationContext(); + const paramsAmount = amount ? Amounts.parse(amount) : undefined; const uriInfoHook = useAsyncAsHook(async () => { const exchanges = await api.wallet.call( WalletApiOperation.ListExchanges, {}, ); - const uri = parseWithdrawExchangeUri(maybeTalerUri); + const uri = maybeTalerUri + ? parseWithdrawExchangeUri(maybeTalerUri) + : undefined; const exchangeByTalerUri = uri?.exchangeBaseUrl; let ex: ExchangeFullDetails | undefined; if (exchangeByTalerUri && uri.exchangePub) { @@ -65,11 +69,8 @@ export function useComponentStateFromParams({ ex = info.exchange; } - const chosenAmount = uri - ? uri.amount - ? Amounts.parseOrThrow(uri.amount) - : Amounts.parseOrThrow(`${ex ? ex.currency : "KUDOS"}:66`) - : Amounts.parseOrThrow(amount); + const chosenAmount = + !uri || !uri.amount ? undefined : Amounts.parse(uri.amount); 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 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( exchange: string, ageRestricted: number | undefined, diff --git a/packages/taler-wallet-webextension/src/cta/Withdraw/views.tsx b/packages/taler-wallet-webextension/src/cta/Withdraw/views.tsx index 57d6238b2..8a01edaaf 100644 --- a/packages/taler-wallet-webextension/src/cta/Withdraw/views.tsx +++ b/packages/taler-wallet-webextension/src/cta/Withdraw/views.tsx @@ -32,6 +32,8 @@ import { WithdrawDetails, } from "../../wallet/Transaction.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 { 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(); return ( <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> ); } diff --git a/packages/taler-wallet-webextension/src/wallet/Application.tsx b/packages/taler-wallet-webextension/src/wallet/Application.tsx index 7d4dafb56..d8cb22bf0 100644 --- a/packages/taler-wallet-webextension/src/wallet/Application.tsx +++ b/packages/taler-wallet-webextension/src/wallet/Application.tsx @@ -400,6 +400,12 @@ export function Application(): VNode { }) => ( <CallToActionTemplate title={i18n.str`Digital cash withdrawal`}> <WithdrawPageFromParams + onAmountChanged={async (e) => { + const page = `${Pages.ctaWithdrawManual({ + amount, + })}?talerUri=${encodeURIComponent(talerUri)}`; + redirectTo(page); + }} talerExchangeWithdrawUri={talerUri} amount={amount} cancel={() => redirectTo(Pages.balance)} |