diff options
Diffstat (limited to 'packages/taler-wallet-webextension/src/wallet/ExchangeSelection/state.ts')
-rw-r--r-- | packages/taler-wallet-webextension/src/wallet/ExchangeSelection/state.ts | 242 |
1 files changed, 242 insertions, 0 deletions
diff --git a/packages/taler-wallet-webextension/src/wallet/ExchangeSelection/state.ts b/packages/taler-wallet-webextension/src/wallet/ExchangeSelection/state.ts new file mode 100644 index 000000000..d70b62de0 --- /dev/null +++ b/packages/taler-wallet-webextension/src/wallet/ExchangeSelection/state.ts @@ -0,0 +1,242 @@ +/* + This file is part of GNU Taler + (C) 2022 Taler Systems S.A. + + GNU Taler is free software; you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + */ + +import { DenomOperationMap, FeeDescription } from "@gnu-taler/taler-util"; +import { + WalletApiOperation, + createPairTimeline, +} from "@gnu-taler/taler-wallet-core"; +import { useTranslationContext } from "@gnu-taler/web-util/browser"; +import { useState } from "preact/hooks"; +import { alertFromError, useAlertContext } from "../../context/alert.js"; +import { useBackendContext } from "../../context/backend.js"; +import { useAsyncAsHook } from "../../hooks/useAsyncAsHook.js"; +import { Props, State } from "./index.js"; + +export function useComponentState({ + onCancel, + onSelection, + list: exchanges, + initialValue, +}: Props): State { + const api = useBackendContext(); + const { pushAlertOnError } = useAlertContext(); + const { i18n } = useTranslationContext(); + const initialValueIdx = exchanges.findIndex( + (e) => e.exchangeBaseUrl === initialValue, + ); + if (initialValueIdx === -1) { + throw Error( + `wrong usage of ExchangeSelection component, currentExchange '${initialValue}' is not in the list of exchanges`, + ); + } + const [value, setValue] = useState(String(initialValueIdx)); + + const selectedIdx = parseInt(value, 10); + const selectedExchange = exchanges[selectedIdx]; + + const comparingExchanges = selectedIdx !== initialValueIdx; + + const initialExchange = comparingExchanges + ? exchanges[initialValueIdx] + : undefined; + + const hook = useAsyncAsHook(async () => { + const selected = await api.wallet.call( + WalletApiOperation.GetExchangeDetailedInfo, + { + exchangeBaseUrl: selectedExchange.exchangeBaseUrl, + }, + ); + + const original = !initialExchange + ? undefined + : await api.wallet.call(WalletApiOperation.GetExchangeDetailedInfo, { + exchangeBaseUrl: initialExchange.exchangeBaseUrl, + }); + + return { + exchanges, + selected: selected.exchange, + original: original?.exchange, + }; + }, [selectedExchange, initialExchange]); + + const [showingTos, setShowingTos] = useState<string | undefined>(undefined); + const [showingPrivacy, setShowingPrivacy] = useState<string | undefined>( + undefined, + ); + + if (!hook) { + return { + status: "loading", + error: undefined, + }; + } + if (hook.hasError) { + return { + status: "error", + error: alertFromError( + i18n, + i18n.str`Could not load exchange details info`, + hook, + ), + }; + } + + const { selected, original } = hook.response; + + const exchangeMap = exchanges.reduce( + (prev, cur, idx) => ({ ...prev, [String(idx)]: cur.exchangeBaseUrl }), + {} as Record<string, string>, + ); + + if (showingPrivacy) { + return { + status: "showing-privacy", + onClose: { + onClick: pushAlertOnError(async () => setShowingPrivacy(undefined)), + }, + exchangeUrl: showingPrivacy, + }; + } + if (showingTos) { + return { + status: "showing-tos", + onClose: { + onClick: pushAlertOnError(async () => setShowingTos(undefined)), + }, + exchangeUrl: showingTos, + }; + } + + if (!comparingExchanges || !original) { + // !original <=> selected == original + return { + status: "ready", + exchanges: { + list: exchangeMap, + value: value, + onChange: pushAlertOnError(async (v) => { + setValue(v); + }), + }, + error: undefined, + onClose: { + onClick: pushAlertOnError(onCancel), + }, + selected, + onShowPrivacy: { + onClick: pushAlertOnError(async () => { + setShowingPrivacy(selected.exchangeBaseUrl); + }), + }, + onShowTerms: { + onClick: pushAlertOnError(async () => { + setShowingTos(selected.exchangeBaseUrl); + }), + }, + }; + } + + // this may be expensive, useMemo + const coinOperationTimeline: DenomOperationMap<FeeDescription[]> = { + deposit: createPairTimeline( + selected.denomFees.deposit, + original.denomFees.deposit, + ), + refresh: createPairTimeline( + selected.denomFees.refresh, + original.denomFees.refresh, + ), + refund: createPairTimeline( + selected.denomFees.refund, + original.denomFees.refund, + ), + withdraw: createPairTimeline( + selected.denomFees.withdraw, + original.denomFees.withdraw, + ), + }; + + const globalFeeTimeline = createPairTimeline( + selected.globalFees, + original.globalFees, + ); + + const allWireType = Object.keys(selected.transferFees).concat( + Object.keys(original.transferFees), + ); + + const wireFeeTimeline: Record<string, FeeDescription[]> = {}; + + const missingWireTYpe: string[] = []; + const newWireType: string[] = []; + + for (const wire of allWireType) { + const selectedWire = selected.transferFees[wire]; + const originalWire = original.transferFees[wire]; + + if (!selectedWire) { + newWireType.push(wire); + continue; + } + if (!originalWire) { + missingWireTYpe.push(wire); + continue; + } + + wireFeeTimeline[wire] = createPairTimeline(selectedWire, originalWire); + } + + return { + status: "comparing", + exchanges: { + list: exchangeMap, + value: value, + onChange: pushAlertOnError(async (v) => { + setValue(v); + }), + }, + error: undefined, + onReset: { + onClick: pushAlertOnError(async () => { + setValue(String(initialValue)); + }), + }, + onSelect: { + onClick: pushAlertOnError(async () => { + onSelection(selected.exchangeBaseUrl); + }), + }, + onShowPrivacy: { + onClick: pushAlertOnError(async () => { + setShowingPrivacy(selected.exchangeBaseUrl); + }), + }, + onShowTerms: { + onClick: pushAlertOnError(async () => { + setShowingTos(selected.exchangeBaseUrl); + }), + }, + selected, + coinOperationTimeline, + wireFeeTimeline, + globalFeeTimeline, + missingWireTYpe, + newWireType, + }; +} |