From 859991a40c4a7757d874f9ae6e6db7b76145a3c3 Mon Sep 17 00:00:00 2001 From: Sebastian Date: Tue, 20 Sep 2022 20:26:41 -0300 Subject: [PATCH] exchange selection for invoices and some fixes --- .../src/cta/InvoiceCreate/index.ts | 20 ++- .../src/cta/InvoiceCreate/state.ts | 118 ++++++++++-------- .../src/cta/Withdraw/index.ts | 22 ++-- .../src/cta/Withdraw/state.ts | 16 +-- .../src/cta/Withdraw/views.tsx | 10 -- .../src/hooks/useSelectedExchange.ts | 13 +- .../src/wallet/ExchangeSelection/index.ts | 12 +- .../src/wallet/ExchangeSelection/state.ts | 7 +- .../src/wallet/ExchangeSelection/views.tsx | 16 ++- 9 files changed, 122 insertions(+), 112 deletions(-) diff --git a/packages/taler-wallet-webextension/src/cta/InvoiceCreate/index.ts b/packages/taler-wallet-webextension/src/cta/InvoiceCreate/index.ts index 2bee51669..61f286d1f 100644 --- a/packages/taler-wallet-webextension/src/cta/InvoiceCreate/index.ts +++ b/packages/taler-wallet-webextension/src/cta/InvoiceCreate/index.ts @@ -14,14 +14,19 @@ GNU Taler; see the file COPYING. If not, see */ +import { AmountJson, TalerErrorDetail } from "@gnu-taler/taler-util"; import { Loading } from "../../components/Loading.js"; import { HookError } from "../../hooks/useAsyncAsHook.js"; +import { + State as SelectExchangeState +} from "../../hooks/useSelectedExchange.js"; +import { ButtonHandler, TextFieldHandler } from "../../mui/handlers.js"; import { compose, StateViewMap } from "../../utils/index.js"; -import { LoadingUriView, ReadyView } from "./views.js"; +import { ExchangeSelectionPage } from "../../wallet/ExchangeSelection/index.js"; +import { NoExchangesView } from "../../wallet/ExchangeSelection/views.js"; import * as wxApi from "../../wxApi.js"; import { useComponentState } from "./state.js"; -import { AmountJson, TalerErrorDetail } from "@gnu-taler/taler-util"; -import { ButtonHandler, TextFieldHandler } from "../../mui/handlers.js"; +import { LoadingUriView, ReadyView } from "./views.js"; export interface Props { amount: string; @@ -29,7 +34,12 @@ export interface Props { onSuccess: (tx: string) => Promise; } -export type State = State.Loading | State.LoadingUriError | State.Ready; +export type State = State.Loading + | State.LoadingUriError + | State.Ready + | SelectExchangeState.Selecting + | SelectExchangeState.NoExchange + ; export namespace State { export interface Loading { @@ -63,6 +73,8 @@ export namespace State { const viewMapping: StateViewMap = { loading: Loading, "loading-uri": LoadingUriView, + "no-exchange": NoExchangesView, + "selecting-exchange": ExchangeSelectionPage, ready: ReadyView, }; diff --git a/packages/taler-wallet-webextension/src/cta/InvoiceCreate/state.ts b/packages/taler-wallet-webextension/src/cta/InvoiceCreate/state.ts index 9b67b4414..4f75e982d 100644 --- a/packages/taler-wallet-webextension/src/cta/InvoiceCreate/state.ts +++ b/packages/taler-wallet-webextension/src/cta/InvoiceCreate/state.ts @@ -14,26 +14,24 @@ GNU Taler; see the file COPYING. If not, see */ +/* eslint-disable react-hooks/rules-of-hooks */ import { Amounts, TalerErrorDetail } from "@gnu-taler/taler-util"; import { TalerError } from "@gnu-taler/taler-wallet-core"; import { useState } from "preact/hooks"; import { useAsyncAsHook } from "../../hooks/useAsyncAsHook.js"; +import { useSelectedExchange } from "../../hooks/useSelectedExchange.js"; import * as wxApi from "../../wxApi.js"; import { Props, State } from "./index.js"; +type RecursiveState = S | (() => RecursiveState) + export function useComponentState( { amount: amountStr, onClose, onSuccess }: Props, api: typeof wxApi, -): State { +): RecursiveState { const amount = Amounts.parseOrThrow(amountStr); - const [subject, setSubject] = useState(""); - const hook = useAsyncAsHook(api.listExchanges); - const [exchangeIdx, setExchangeIdx] = useState("0"); - const [operationError, setOperationError] = useState< - TalerErrorDetail | undefined - >(undefined); if (!hook) { return { @@ -48,56 +46,68 @@ export function useComponentState( }; } - const exchanges = hook.response.exchanges.filter( - (e) => e.currency === amount.currency, - ); - const exchangeMap = exchanges.reduce( - (prev, cur, idx) => ({ ...prev, [String(idx)]: cur.exchangeBaseUrl }), - {} as Record, - ); - const selected = exchanges[Number(exchangeIdx)]; + const exchangeList = hook.response.exchanges - async function accept(): Promise { - try { - const resp = await api.initiatePeerPullPayment({ - amount: Amounts.stringify(amount), - exchangeBaseUrl: selected.exchangeBaseUrl, - partialContractTerms: { - summary: subject, - }, - }); + return () => { + const [subject, setSubject] = useState(""); - onSuccess(resp.transactionId); - } catch (e) { - if (e instanceof TalerError) { - setOperationError(e.errorDetail); - } - console.error(e); - throw Error("error trying to accept"); + const [operationError, setOperationError] = useState< + TalerErrorDetail | undefined + >(undefined); + + + const selectedExchange = useSelectedExchange({ currency: amount.currency, defaultExchange: undefined, list: exchangeList }) + + if (selectedExchange.status !== 'ready') { + return selectedExchange } + + const exchange = selectedExchange.selected + + async function accept(): Promise { + try { + const resp = await api.initiatePeerPullPayment({ + amount: Amounts.stringify(amount), + exchangeBaseUrl: exchange.exchangeBaseUrl, + partialContractTerms: { + summary: subject, + }, + }); + + onSuccess(resp.transactionId); + } catch (e) { + if (e instanceof TalerError) { + setOperationError(e.errorDetail); + } + console.error(e); + throw Error("error trying to accept"); + } + } + + return { + status: "ready", + subject: { + error: !subject ? "cant be empty" : undefined, + value: subject, + onInput: async (e) => setSubject(e), + }, + doSelectExchange: selectedExchange.doSelect, + invalid: !subject || Amounts.isZero(amount), + exchangeUrl: exchange.exchangeBaseUrl, + create: { + onClick: accept, + }, + cancel: { + onClick: onClose, + }, + chosenAmount: amount, + toBeReceived: amount, + error: undefined, + operationError, + }; } - return { - status: "ready", - subject: { - error: !subject ? "cant be empty" : undefined, - value: subject, - onInput: async (e) => setSubject(e), - }, - doSelectExchange: { - //FIX - }, - invalid: !subject || Amounts.isZero(amount), - exchangeUrl: selected.exchangeBaseUrl, - create: { - onClick: accept, - }, - cancel: { - onClick: onClose, - }, - chosenAmount: amount, - toBeReceived: amount, - error: undefined, - operationError, - }; + + + } diff --git a/packages/taler-wallet-webextension/src/cta/Withdraw/index.ts b/packages/taler-wallet-webextension/src/cta/Withdraw/index.ts index d38c27a2f..9de9c693a 100644 --- a/packages/taler-wallet-webextension/src/cta/Withdraw/index.ts +++ b/packages/taler-wallet-webextension/src/cta/Withdraw/index.ts @@ -17,25 +17,25 @@ import { AmountJson } from "@gnu-taler/taler-util"; import { Loading } from "../../components/Loading.js"; import { HookError } from "../../hooks/useAsyncAsHook.js"; +import { + State as SelectExchangeState +} from "../../hooks/useSelectedExchange.js"; import { ButtonHandler, SelectFieldHandler } from "../../mui/handlers.js"; import { compose, StateViewMap } from "../../utils/index.js"; import * as wxApi from "../../wxApi.js"; import { Props as TermsOfServiceSectionProps } from "../TermsOfServiceSection.js"; import { useComponentStateFromParams, - useComponentStateFromURI, + useComponentStateFromURI } from "./state.js"; -import { - State as SelectExchangeState -} from "../../hooks/useSelectedExchange.js"; +import { ExchangeSelectionPage } from "../../wallet/ExchangeSelection/index.js"; import { - LoadingExchangeView, LoadingInfoView, LoadingUriView, - SuccessView, + SuccessView } from "./views.js"; -import { ExchangeSelectionPage } from "../../wallet/ExchangeSelection/index.js"; +import { NoExchangesView } from "../../wallet/ExchangeSelection/views.js"; export interface PropsFromURI { talerWithdrawUri: string | undefined; @@ -52,8 +52,8 @@ export interface PropsFromParams { export type State = | State.Loading | State.LoadingUriError - | State.LoadingExchangeError | State.LoadingInfoError + | SelectExchangeState.NoExchange | SelectExchangeState.Selecting | State.Success; @@ -66,10 +66,6 @@ export namespace State { status: "loading-error"; error: HookError; } - export interface LoadingExchangeError { - status: "no-exchange"; - error: undefined, - } export interface LoadingInfoError { status: "loading-info"; error: HookError; @@ -100,8 +96,8 @@ export namespace State { const viewMapping: StateViewMap = { loading: Loading, "loading-error": LoadingUriView, - "no-exchange": LoadingExchangeView, "loading-info": LoadingInfoView, + "no-exchange": NoExchangesView, "selecting-exchange": ExchangeSelectionPage, success: SuccessView, }; diff --git a/packages/taler-wallet-webextension/src/cta/Withdraw/state.ts b/packages/taler-wallet-webextension/src/cta/Withdraw/state.ts index 2e68d056e..5b5c11182 100644 --- a/packages/taler-wallet-webextension/src/cta/Withdraw/state.ts +++ b/packages/taler-wallet-webextension/src/cta/Withdraw/state.ts @@ -36,8 +36,6 @@ export function useComponentStateFromParams( return { amount: Amounts.parseOrThrow(amount), exchanges }; }); - console.log("uri info", uriInfoHook) - if (!uriInfoHook) return { status: "loading", error: undefined }; if (uriInfoHook.hasError) { @@ -80,7 +78,6 @@ export function useComponentStateFromURI( return { talerWithdrawUri, amount: Amounts.parseOrThrow(amount), thisExchange: defaultExchangeBaseUrl, exchanges }; }); - console.log("uri info", uriInfoHook) if (!uriInfoHook) return { status: "loading", error: undefined }; if (uriInfoHook.hasError) { @@ -111,20 +108,11 @@ type ManualOrManagedWithdrawFunction = (exchange: string, ageRestricted: number function exchangeSelectionState(doWithdraw: ManualOrManagedWithdrawFunction, cancel: () => Promise, onSuccess: (txid: string) => Promise, talerWithdrawUri: string | undefined, chosenAmount: AmountJson, exchangeList: ExchangeListItem[], defaultExchange: string | undefined, api: typeof wxApi,): RecursiveState { - //FIXME: use substates here const selectedExchange = useSelectedExchange({ currency: chosenAmount.currency, defaultExchange, list: exchangeList }) - if (selectedExchange.status === 'no-exchange') { - return { - status: "no-exchange", - error: undefined, - } - } - - if (selectedExchange.status === 'selecting-exchange') { + if (selectedExchange.status !== 'ready') { return selectedExchange } - console.log("exchange selected", selectedExchange.selected) return () => { @@ -142,7 +130,7 @@ function exchangeSelectionState(doWithdraw: ManualOrManagedWithdrawFunction, can return { state }; }, []); - console.log("terms", terms) + /** * With the exchange and amount, ask the wallet the information * about the withdrawal diff --git a/packages/taler-wallet-webextension/src/cta/Withdraw/views.tsx b/packages/taler-wallet-webextension/src/cta/Withdraw/views.tsx index 82d6090e5..1e8284739 100644 --- a/packages/taler-wallet-webextension/src/cta/Withdraw/views.tsx +++ b/packages/taler-wallet-webextension/src/cta/Withdraw/views.tsx @@ -53,16 +53,6 @@ export function LoadingUriView({ error }: State.LoadingUriError): VNode { ); } -export function LoadingExchangeView(p: State.LoadingExchangeError): VNode { - const { i18n } = useTranslationContext(); - - return ( - Could not get a default exchange, please check configuration} - /> - ); -} - export function LoadingInfoView({ error }: State.LoadingInfoError): VNode { const { i18n } = useTranslationContext(); diff --git a/packages/taler-wallet-webextension/src/hooks/useSelectedExchange.ts b/packages/taler-wallet-webextension/src/hooks/useSelectedExchange.ts index d9085153e..7219c30d2 100644 --- a/packages/taler-wallet-webextension/src/hooks/useSelectedExchange.ts +++ b/packages/taler-wallet-webextension/src/hooks/useSelectedExchange.ts @@ -24,6 +24,7 @@ export namespace State { export interface NoExchange { status: "no-exchange" error: undefined; + currency: string | undefined; } export interface Ready { status: "ready", @@ -59,25 +60,27 @@ export function useSelectedExchange({ currency, defaultExchange, list }: Props): return { status: "no-exchange", error: undefined, + currency: undefined, } } - const firstByCurrency = list.find((e) => e.currency === currency) - if (!firstByCurrency) { + const listCurrency = list.filter((e) => e.currency === currency) + if (!listCurrency.length) { // there should be at least one exchange for this currency return { status: "no-exchange", error: undefined, + currency, } } if (isSelecting) { - const currentExchange = selectedExchange ?? defaultExchange ?? firstByCurrency.exchangeBaseUrl; + const currentExchange = selectedExchange ?? defaultExchange ?? listCurrency[0].exchangeBaseUrl; return { status: "selecting-exchange", error: undefined, - list, + list: listCurrency, currency, currentExchange: currentExchange, onSelection: async (exchangeBaseUrl: string) => { @@ -120,6 +123,6 @@ export function useSelectedExchange({ currency, defaultExchange, list }: Props): doSelect: { onClick: async () => setIsSelecting(true) }, - selected: firstByCurrency + selected: listCurrency[0] } } diff --git a/packages/taler-wallet-webextension/src/wallet/ExchangeSelection/index.ts b/packages/taler-wallet-webextension/src/wallet/ExchangeSelection/index.ts index 2834028c6..4b28904fb 100644 --- a/packages/taler-wallet-webextension/src/wallet/ExchangeSelection/index.ts +++ b/packages/taler-wallet-webextension/src/wallet/ExchangeSelection/index.ts @@ -41,13 +41,16 @@ export interface Props { onCancel: () => Promise; onSelection: (exchange: string) => Promise; } +import { + State as SelectExchangeState +} from "../../hooks/useSelectedExchange.js"; export type State = | State.Loading | State.LoadingUriError | State.Ready | State.Comparing - | State.NoExchanges; + | SelectExchangeState.NoExchange; export namespace State { export interface Loading { @@ -66,11 +69,6 @@ export namespace State { error: undefined; } - export interface NoExchanges { - status: "no-exchanges"; - error: undefined; - } - export interface Ready extends BaseInfo { status: "ready"; timeline: OperationMap; @@ -89,7 +87,7 @@ const viewMapping: StateViewMap = { loading: Loading, "error-loading": ErrorLoadingView, comparing: ComparingView, - "no-exchanges": NoExchangesView, + "no-exchange": NoExchangesView, ready: ReadyView, }; diff --git a/packages/taler-wallet-webextension/src/wallet/ExchangeSelection/state.ts b/packages/taler-wallet-webextension/src/wallet/ExchangeSelection/state.ts index db6138f8e..0279f6514 100644 --- a/packages/taler-wallet-webextension/src/wallet/ExchangeSelection/state.ts +++ b/packages/taler-wallet-webextension/src/wallet/ExchangeSelection/state.ts @@ -47,7 +47,7 @@ export function useComponentState( ? undefined : await api.getExchangeDetailedInfo(initialExchange.exchangeBaseUrl); return { exchanges, selected, original }; - }); + }, [value]); if (!hook) { return { @@ -67,13 +67,14 @@ export function useComponentState( if (!selected) { //!selected <=> exchanges.length === 0 return { - status: "no-exchanges", + status: "no-exchange", error: undefined, + currency: undefined, }; } const exchangeMap = exchanges.reduce( - (prev, cur, idx) => ({ ...prev, [cur.exchangeBaseUrl]: String(idx) }), + (prev, cur, idx) => ({ ...prev, [String(idx)]: cur.exchangeBaseUrl }), {} as Record, ); diff --git a/packages/taler-wallet-webextension/src/wallet/ExchangeSelection/views.tsx b/packages/taler-wallet-webextension/src/wallet/ExchangeSelection/views.tsx index dd85dff46..47554bfcd 100644 --- a/packages/taler-wallet-webextension/src/wallet/ExchangeSelection/views.tsx +++ b/packages/taler-wallet-webextension/src/wallet/ExchangeSelection/views.tsx @@ -31,6 +31,9 @@ import { useTranslationContext } from "../../context/translation.js"; import { Button } from "../../mui/Button.js"; import arrowDown from "../../svg/chevron-down.svg"; import { State } from "./index.js"; +import { + State as SelectExchangeState +} from "../../hooks/useSelectedExchange.js"; const ButtonGroup = styled.div` & > button { @@ -112,11 +115,20 @@ export function ErrorLoadingView({ error }: State.LoadingUriError): VNode { ); } -export function NoExchangesView(state: State.NoExchanges): VNode { + + +export function NoExchangesView({currency}: SelectExchangeState.NoExchange): VNode { const { i18n } = useTranslationContext(); + if (!currency) { + return ( +
+ could not find any exchange +
+ ); + } return (
- no exchanges + could not find any exchange for the currency {currency}
); }