summaryrefslogtreecommitdiff
path: root/packages/taler-wallet-webextension/src/wallet/ExchangeSelection/state.ts
diff options
context:
space:
mode:
Diffstat (limited to 'packages/taler-wallet-webextension/src/wallet/ExchangeSelection/state.ts')
-rw-r--r--packages/taler-wallet-webextension/src/wallet/ExchangeSelection/state.ts242
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,
+ };
+}