exchange selection for invoices and some fixes
This commit is contained in:
parent
7adaeff0a5
commit
859991a40c
@ -14,14 +14,19 @@
|
|||||||
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, TalerErrorDetail } from "@gnu-taler/taler-util";
|
||||||
import { Loading } from "../../components/Loading.js";
|
import { Loading } from "../../components/Loading.js";
|
||||||
import { HookError } from "../../hooks/useAsyncAsHook.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 { 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 * as wxApi from "../../wxApi.js";
|
||||||
import { useComponentState } from "./state.js";
|
import { useComponentState } from "./state.js";
|
||||||
import { AmountJson, TalerErrorDetail } from "@gnu-taler/taler-util";
|
import { LoadingUriView, ReadyView } from "./views.js";
|
||||||
import { ButtonHandler, TextFieldHandler } from "../../mui/handlers.js";
|
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
amount: string;
|
amount: string;
|
||||||
@ -29,7 +34,12 @@ export interface Props {
|
|||||||
onSuccess: (tx: string) => Promise<void>;
|
onSuccess: (tx: string) => Promise<void>;
|
||||||
}
|
}
|
||||||
|
|
||||||
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 namespace State {
|
||||||
export interface Loading {
|
export interface Loading {
|
||||||
@ -63,6 +73,8 @@ export namespace State {
|
|||||||
const viewMapping: StateViewMap<State> = {
|
const viewMapping: StateViewMap<State> = {
|
||||||
loading: Loading,
|
loading: Loading,
|
||||||
"loading-uri": LoadingUriView,
|
"loading-uri": LoadingUriView,
|
||||||
|
"no-exchange": NoExchangesView,
|
||||||
|
"selecting-exchange": ExchangeSelectionPage,
|
||||||
ready: ReadyView,
|
ready: ReadyView,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -14,26 +14,24 @@
|
|||||||
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/>
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
/* eslint-disable react-hooks/rules-of-hooks */
|
||||||
import { Amounts, TalerErrorDetail } from "@gnu-taler/taler-util";
|
import { Amounts, TalerErrorDetail } from "@gnu-taler/taler-util";
|
||||||
import { TalerError } from "@gnu-taler/taler-wallet-core";
|
import { TalerError } from "@gnu-taler/taler-wallet-core";
|
||||||
import { useState } from "preact/hooks";
|
import { useState } from "preact/hooks";
|
||||||
import { useAsyncAsHook } from "../../hooks/useAsyncAsHook.js";
|
import { useAsyncAsHook } from "../../hooks/useAsyncAsHook.js";
|
||||||
|
import { useSelectedExchange } from "../../hooks/useSelectedExchange.js";
|
||||||
import * as wxApi from "../../wxApi.js";
|
import * as wxApi from "../../wxApi.js";
|
||||||
import { Props, State } from "./index.js";
|
import { Props, State } from "./index.js";
|
||||||
|
|
||||||
|
type RecursiveState<S extends object> = S | (() => RecursiveState<S>)
|
||||||
|
|
||||||
export function useComponentState(
|
export function useComponentState(
|
||||||
{ amount: amountStr, onClose, onSuccess }: Props,
|
{ amount: amountStr, onClose, onSuccess }: Props,
|
||||||
api: typeof wxApi,
|
api: typeof wxApi,
|
||||||
): State {
|
): RecursiveState<State> {
|
||||||
const amount = Amounts.parseOrThrow(amountStr);
|
const amount = Amounts.parseOrThrow(amountStr);
|
||||||
|
|
||||||
const [subject, setSubject] = useState("");
|
|
||||||
|
|
||||||
const hook = useAsyncAsHook(api.listExchanges);
|
const hook = useAsyncAsHook(api.listExchanges);
|
||||||
const [exchangeIdx, setExchangeIdx] = useState("0");
|
|
||||||
const [operationError, setOperationError] = useState<
|
|
||||||
TalerErrorDetail | undefined
|
|
||||||
>(undefined);
|
|
||||||
|
|
||||||
if (!hook) {
|
if (!hook) {
|
||||||
return {
|
return {
|
||||||
@ -48,56 +46,68 @@ export function useComponentState(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const exchanges = hook.response.exchanges.filter(
|
const exchangeList = hook.response.exchanges
|
||||||
(e) => e.currency === amount.currency,
|
|
||||||
);
|
|
||||||
const exchangeMap = exchanges.reduce(
|
|
||||||
(prev, cur, idx) => ({ ...prev, [String(idx)]: cur.exchangeBaseUrl }),
|
|
||||||
{} as Record<string, string>,
|
|
||||||
);
|
|
||||||
const selected = exchanges[Number(exchangeIdx)];
|
|
||||||
|
|
||||||
async function accept(): Promise<void> {
|
return () => {
|
||||||
try {
|
const [subject, setSubject] = useState("");
|
||||||
const resp = await api.initiatePeerPullPayment({
|
|
||||||
amount: Amounts.stringify(amount),
|
|
||||||
exchangeBaseUrl: selected.exchangeBaseUrl,
|
|
||||||
partialContractTerms: {
|
|
||||||
summary: subject,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
onSuccess(resp.transactionId);
|
const [operationError, setOperationError] = useState<
|
||||||
} catch (e) {
|
TalerErrorDetail | undefined
|
||||||
if (e instanceof TalerError) {
|
>(undefined);
|
||||||
setOperationError(e.errorDetail);
|
|
||||||
}
|
|
||||||
console.error(e);
|
const selectedExchange = useSelectedExchange({ currency: amount.currency, defaultExchange: undefined, list: exchangeList })
|
||||||
throw Error("error trying to accept");
|
|
||||||
|
if (selectedExchange.status !== 'ready') {
|
||||||
|
return selectedExchange
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const exchange = selectedExchange.selected
|
||||||
|
|
||||||
|
async function accept(): Promise<void> {
|
||||||
|
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,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
@ -17,25 +17,25 @@
|
|||||||
import { AmountJson } from "@gnu-taler/taler-util";
|
import { AmountJson } from "@gnu-taler/taler-util";
|
||||||
import { Loading } from "../../components/Loading.js";
|
import { Loading } from "../../components/Loading.js";
|
||||||
import { HookError } from "../../hooks/useAsyncAsHook.js";
|
import { HookError } from "../../hooks/useAsyncAsHook.js";
|
||||||
|
import {
|
||||||
|
State as SelectExchangeState
|
||||||
|
} from "../../hooks/useSelectedExchange.js";
|
||||||
import { ButtonHandler, SelectFieldHandler } from "../../mui/handlers.js";
|
import { ButtonHandler, SelectFieldHandler } from "../../mui/handlers.js";
|
||||||
import { compose, StateViewMap } from "../../utils/index.js";
|
import { compose, StateViewMap } from "../../utils/index.js";
|
||||||
import * as wxApi from "../../wxApi.js";
|
import * as wxApi from "../../wxApi.js";
|
||||||
import { Props as TermsOfServiceSectionProps } from "../TermsOfServiceSection.js";
|
import { Props as TermsOfServiceSectionProps } from "../TermsOfServiceSection.js";
|
||||||
import {
|
import {
|
||||||
useComponentStateFromParams,
|
useComponentStateFromParams,
|
||||||
useComponentStateFromURI,
|
useComponentStateFromURI
|
||||||
} from "./state.js";
|
} from "./state.js";
|
||||||
import {
|
|
||||||
State as SelectExchangeState
|
|
||||||
} from "../../hooks/useSelectedExchange.js";
|
|
||||||
|
|
||||||
|
import { ExchangeSelectionPage } from "../../wallet/ExchangeSelection/index.js";
|
||||||
import {
|
import {
|
||||||
LoadingExchangeView,
|
|
||||||
LoadingInfoView,
|
LoadingInfoView,
|
||||||
LoadingUriView,
|
LoadingUriView,
|
||||||
SuccessView,
|
SuccessView
|
||||||
} from "./views.js";
|
} from "./views.js";
|
||||||
import { ExchangeSelectionPage } from "../../wallet/ExchangeSelection/index.js";
|
import { NoExchangesView } from "../../wallet/ExchangeSelection/views.js";
|
||||||
|
|
||||||
export interface PropsFromURI {
|
export interface PropsFromURI {
|
||||||
talerWithdrawUri: string | undefined;
|
talerWithdrawUri: string | undefined;
|
||||||
@ -52,8 +52,8 @@ export interface PropsFromParams {
|
|||||||
export type State =
|
export type State =
|
||||||
| State.Loading
|
| State.Loading
|
||||||
| State.LoadingUriError
|
| State.LoadingUriError
|
||||||
| State.LoadingExchangeError
|
|
||||||
| State.LoadingInfoError
|
| State.LoadingInfoError
|
||||||
|
| SelectExchangeState.NoExchange
|
||||||
| SelectExchangeState.Selecting
|
| SelectExchangeState.Selecting
|
||||||
| State.Success;
|
| State.Success;
|
||||||
|
|
||||||
@ -66,10 +66,6 @@ export namespace State {
|
|||||||
status: "loading-error";
|
status: "loading-error";
|
||||||
error: HookError;
|
error: HookError;
|
||||||
}
|
}
|
||||||
export interface LoadingExchangeError {
|
|
||||||
status: "no-exchange";
|
|
||||||
error: undefined,
|
|
||||||
}
|
|
||||||
export interface LoadingInfoError {
|
export interface LoadingInfoError {
|
||||||
status: "loading-info";
|
status: "loading-info";
|
||||||
error: HookError;
|
error: HookError;
|
||||||
@ -100,8 +96,8 @@ export namespace State {
|
|||||||
const viewMapping: StateViewMap<State> = {
|
const viewMapping: StateViewMap<State> = {
|
||||||
loading: Loading,
|
loading: Loading,
|
||||||
"loading-error": LoadingUriView,
|
"loading-error": LoadingUriView,
|
||||||
"no-exchange": LoadingExchangeView,
|
|
||||||
"loading-info": LoadingInfoView,
|
"loading-info": LoadingInfoView,
|
||||||
|
"no-exchange": NoExchangesView,
|
||||||
"selecting-exchange": ExchangeSelectionPage,
|
"selecting-exchange": ExchangeSelectionPage,
|
||||||
success: SuccessView,
|
success: SuccessView,
|
||||||
};
|
};
|
||||||
|
@ -36,8 +36,6 @@ export function useComponentStateFromParams(
|
|||||||
return { amount: Amounts.parseOrThrow(amount), exchanges };
|
return { amount: Amounts.parseOrThrow(amount), exchanges };
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log("uri info", uriInfoHook)
|
|
||||||
|
|
||||||
if (!uriInfoHook) return { status: "loading", error: undefined };
|
if (!uriInfoHook) return { status: "loading", error: undefined };
|
||||||
|
|
||||||
if (uriInfoHook.hasError) {
|
if (uriInfoHook.hasError) {
|
||||||
@ -80,7 +78,6 @@ export function useComponentStateFromURI(
|
|||||||
return { talerWithdrawUri, amount: Amounts.parseOrThrow(amount), thisExchange: defaultExchangeBaseUrl, exchanges };
|
return { talerWithdrawUri, amount: Amounts.parseOrThrow(amount), thisExchange: defaultExchangeBaseUrl, exchanges };
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log("uri info", uriInfoHook)
|
|
||||||
if (!uriInfoHook) return { status: "loading", error: undefined };
|
if (!uriInfoHook) return { status: "loading", error: undefined };
|
||||||
|
|
||||||
if (uriInfoHook.hasError) {
|
if (uriInfoHook.hasError) {
|
||||||
@ -111,20 +108,11 @@ type ManualOrManagedWithdrawFunction = (exchange: string, ageRestricted: number
|
|||||||
|
|
||||||
function exchangeSelectionState(doWithdraw: ManualOrManagedWithdrawFunction, cancel: () => Promise<void>, onSuccess: (txid: string) => Promise<void>, talerWithdrawUri: string | undefined, chosenAmount: AmountJson, exchangeList: ExchangeListItem[], defaultExchange: string | undefined, api: typeof wxApi,): RecursiveState<State> {
|
function exchangeSelectionState(doWithdraw: ManualOrManagedWithdrawFunction, cancel: () => Promise<void>, onSuccess: (txid: string) => Promise<void>, talerWithdrawUri: string | undefined, chosenAmount: AmountJson, exchangeList: ExchangeListItem[], defaultExchange: string | undefined, api: typeof wxApi,): RecursiveState<State> {
|
||||||
|
|
||||||
//FIXME: use substates here
|
|
||||||
const selectedExchange = useSelectedExchange({ currency: chosenAmount.currency, defaultExchange, list: exchangeList })
|
const selectedExchange = useSelectedExchange({ currency: chosenAmount.currency, defaultExchange, list: exchangeList })
|
||||||
|
|
||||||
if (selectedExchange.status === 'no-exchange') {
|
if (selectedExchange.status !== 'ready') {
|
||||||
return {
|
|
||||||
status: "no-exchange",
|
|
||||||
error: undefined,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (selectedExchange.status === 'selecting-exchange') {
|
|
||||||
return selectedExchange
|
return selectedExchange
|
||||||
}
|
}
|
||||||
console.log("exchange selected", selectedExchange.selected)
|
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
|
|
||||||
@ -142,7 +130,7 @@ function exchangeSelectionState(doWithdraw: ManualOrManagedWithdrawFunction, can
|
|||||||
|
|
||||||
return { state };
|
return { state };
|
||||||
}, []);
|
}, []);
|
||||||
console.log("terms", terms)
|
|
||||||
/**
|
/**
|
||||||
* With the exchange and amount, ask the wallet the information
|
* With the exchange and amount, ask the wallet the information
|
||||||
* about the withdrawal
|
* about the withdrawal
|
||||||
|
@ -53,16 +53,6 @@ export function LoadingUriView({ error }: State.LoadingUriError): VNode {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function LoadingExchangeView(p: State.LoadingExchangeError): VNode {
|
|
||||||
const { i18n } = useTranslationContext();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<ErrorMessage
|
|
||||||
title={<i18n.Translate>Could not get a default exchange, please check configuration</i18n.Translate>}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function LoadingInfoView({ error }: State.LoadingInfoError): VNode {
|
export function LoadingInfoView({ error }: State.LoadingInfoError): VNode {
|
||||||
const { i18n } = useTranslationContext();
|
const { i18n } = useTranslationContext();
|
||||||
|
|
||||||
|
@ -24,6 +24,7 @@ export namespace State {
|
|||||||
export interface NoExchange {
|
export interface NoExchange {
|
||||||
status: "no-exchange"
|
status: "no-exchange"
|
||||||
error: undefined;
|
error: undefined;
|
||||||
|
currency: string | undefined;
|
||||||
}
|
}
|
||||||
export interface Ready {
|
export interface Ready {
|
||||||
status: "ready",
|
status: "ready",
|
||||||
@ -59,25 +60,27 @@ export function useSelectedExchange({ currency, defaultExchange, list }: Props):
|
|||||||
return {
|
return {
|
||||||
status: "no-exchange",
|
status: "no-exchange",
|
||||||
error: undefined,
|
error: undefined,
|
||||||
|
currency: undefined,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const firstByCurrency = list.find((e) => e.currency === currency)
|
const listCurrency = list.filter((e) => e.currency === currency)
|
||||||
if (!firstByCurrency) {
|
if (!listCurrency.length) {
|
||||||
// there should be at least one exchange for this currency
|
// there should be at least one exchange for this currency
|
||||||
return {
|
return {
|
||||||
status: "no-exchange",
|
status: "no-exchange",
|
||||||
error: undefined,
|
error: undefined,
|
||||||
|
currency,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
if (isSelecting) {
|
if (isSelecting) {
|
||||||
const currentExchange = selectedExchange ?? defaultExchange ?? firstByCurrency.exchangeBaseUrl;
|
const currentExchange = selectedExchange ?? defaultExchange ?? listCurrency[0].exchangeBaseUrl;
|
||||||
return {
|
return {
|
||||||
status: "selecting-exchange",
|
status: "selecting-exchange",
|
||||||
error: undefined,
|
error: undefined,
|
||||||
list,
|
list: listCurrency,
|
||||||
currency,
|
currency,
|
||||||
currentExchange: currentExchange,
|
currentExchange: currentExchange,
|
||||||
onSelection: async (exchangeBaseUrl: string) => {
|
onSelection: async (exchangeBaseUrl: string) => {
|
||||||
@ -120,6 +123,6 @@ export function useSelectedExchange({ currency, defaultExchange, list }: Props):
|
|||||||
doSelect: {
|
doSelect: {
|
||||||
onClick: async () => setIsSelecting(true)
|
onClick: async () => setIsSelecting(true)
|
||||||
},
|
},
|
||||||
selected: firstByCurrency
|
selected: listCurrency[0]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -41,13 +41,16 @@ export interface Props {
|
|||||||
onCancel: () => Promise<void>;
|
onCancel: () => Promise<void>;
|
||||||
onSelection: (exchange: string) => Promise<void>;
|
onSelection: (exchange: string) => Promise<void>;
|
||||||
}
|
}
|
||||||
|
import {
|
||||||
|
State as SelectExchangeState
|
||||||
|
} from "../../hooks/useSelectedExchange.js";
|
||||||
|
|
||||||
export type State =
|
export type State =
|
||||||
| State.Loading
|
| State.Loading
|
||||||
| State.LoadingUriError
|
| State.LoadingUriError
|
||||||
| State.Ready
|
| State.Ready
|
||||||
| State.Comparing
|
| State.Comparing
|
||||||
| State.NoExchanges;
|
| SelectExchangeState.NoExchange;
|
||||||
|
|
||||||
export namespace State {
|
export namespace State {
|
||||||
export interface Loading {
|
export interface Loading {
|
||||||
@ -66,11 +69,6 @@ export namespace State {
|
|||||||
error: undefined;
|
error: undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface NoExchanges {
|
|
||||||
status: "no-exchanges";
|
|
||||||
error: undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface Ready extends BaseInfo {
|
export interface Ready extends BaseInfo {
|
||||||
status: "ready";
|
status: "ready";
|
||||||
timeline: OperationMap<FeeDescription[]>;
|
timeline: OperationMap<FeeDescription[]>;
|
||||||
@ -89,7 +87,7 @@ const viewMapping: StateViewMap<State> = {
|
|||||||
loading: Loading,
|
loading: Loading,
|
||||||
"error-loading": ErrorLoadingView,
|
"error-loading": ErrorLoadingView,
|
||||||
comparing: ComparingView,
|
comparing: ComparingView,
|
||||||
"no-exchanges": NoExchangesView,
|
"no-exchange": NoExchangesView,
|
||||||
ready: ReadyView,
|
ready: ReadyView,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -47,7 +47,7 @@ export function useComponentState(
|
|||||||
? undefined
|
? undefined
|
||||||
: await api.getExchangeDetailedInfo(initialExchange.exchangeBaseUrl);
|
: await api.getExchangeDetailedInfo(initialExchange.exchangeBaseUrl);
|
||||||
return { exchanges, selected, original };
|
return { exchanges, selected, original };
|
||||||
});
|
}, [value]);
|
||||||
|
|
||||||
if (!hook) {
|
if (!hook) {
|
||||||
return {
|
return {
|
||||||
@ -67,13 +67,14 @@ export function useComponentState(
|
|||||||
if (!selected) {
|
if (!selected) {
|
||||||
//!selected <=> exchanges.length === 0
|
//!selected <=> exchanges.length === 0
|
||||||
return {
|
return {
|
||||||
status: "no-exchanges",
|
status: "no-exchange",
|
||||||
error: undefined,
|
error: undefined,
|
||||||
|
currency: undefined,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const exchangeMap = exchanges.reduce(
|
const exchangeMap = exchanges.reduce(
|
||||||
(prev, cur, idx) => ({ ...prev, [cur.exchangeBaseUrl]: String(idx) }),
|
(prev, cur, idx) => ({ ...prev, [String(idx)]: cur.exchangeBaseUrl }),
|
||||||
{} as Record<string, string>,
|
{} as Record<string, string>,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -31,6 +31,9 @@ import { useTranslationContext } from "../../context/translation.js";
|
|||||||
import { Button } from "../../mui/Button.js";
|
import { Button } from "../../mui/Button.js";
|
||||||
import arrowDown from "../../svg/chevron-down.svg";
|
import arrowDown from "../../svg/chevron-down.svg";
|
||||||
import { State } from "./index.js";
|
import { State } from "./index.js";
|
||||||
|
import {
|
||||||
|
State as SelectExchangeState
|
||||||
|
} from "../../hooks/useSelectedExchange.js";
|
||||||
|
|
||||||
const ButtonGroup = styled.div`
|
const ButtonGroup = styled.div`
|
||||||
& > button {
|
& > 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();
|
const { i18n } = useTranslationContext();
|
||||||
|
if (!currency) {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<i18n.Translate>could not find any exchange</i18n.Translate>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<i18n.Translate>no exchanges</i18n.Translate>
|
<i18n.Translate>could not find any exchange for the currency {currency}</i18n.Translate>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user