fix #7791
This commit is contained in:
parent
d5c5c7463e
commit
8eee38d559
@ -122,7 +122,7 @@ export function AlertView({
|
|||||||
<Alert title={alert.message} severity={alert.type} onClose={onClose}>
|
<Alert title={alert.message} severity={alert.type} onClose={onClose}>
|
||||||
<div style={{ display: "flex", flexDirection: "column" }}>
|
<div style={{ display: "flex", flexDirection: "column" }}>
|
||||||
<div>{alert.description}</div>
|
<div>{alert.description}</div>
|
||||||
{alert.type === "error" ? (
|
{alert.type === "error" && alert.cause !== undefined ? (
|
||||||
<AlertContext context={alert.context} cause={alert.cause} />
|
<AlertContext context={alert.context} cause={alert.cause} />
|
||||||
) : undefined}
|
) : undefined}
|
||||||
</div>
|
</div>
|
||||||
|
@ -40,8 +40,8 @@ export interface ErrorAlert {
|
|||||||
message: TranslatedString;
|
message: TranslatedString;
|
||||||
description: TranslatedString | VNode;
|
description: TranslatedString | VNode;
|
||||||
type: "error";
|
type: "error";
|
||||||
context: object;
|
context: object | undefined;
|
||||||
cause: any;
|
cause: any | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
type Type = {
|
type Type = {
|
||||||
|
@ -37,7 +37,7 @@ export type State =
|
|||||||
| State.LoadingUriError
|
| State.LoadingUriError
|
||||||
| State.Ready
|
| State.Ready
|
||||||
| SelectExchangeState.Selecting
|
| SelectExchangeState.Selecting
|
||||||
| SelectExchangeState.NoExchange;
|
| SelectExchangeState.NoExchangeFound;
|
||||||
|
|
||||||
export namespace State {
|
export namespace State {
|
||||||
export interface Loading {
|
export interface Loading {
|
||||||
@ -70,7 +70,7 @@ export namespace State {
|
|||||||
const viewMapping: StateViewMap<State> = {
|
const viewMapping: StateViewMap<State> = {
|
||||||
loading: Loading,
|
loading: Loading,
|
||||||
error: ErrorAlertView,
|
error: ErrorAlertView,
|
||||||
"no-exchange": NoExchangesView,
|
"no-exchange-found": NoExchangesView,
|
||||||
"selecting-exchange": ExchangeSelectionPage,
|
"selecting-exchange": ExchangeSelectionPage,
|
||||||
ready: ReadyView,
|
ready: ReadyView,
|
||||||
};
|
};
|
||||||
|
@ -46,7 +46,7 @@ export interface PropsFromParams {
|
|||||||
export type State =
|
export type State =
|
||||||
| State.Loading
|
| State.Loading
|
||||||
| State.LoadingUriError
|
| State.LoadingUriError
|
||||||
| SelectExchangeState.NoExchange
|
| SelectExchangeState.NoExchangeFound
|
||||||
| SelectExchangeState.Selecting
|
| SelectExchangeState.Selecting
|
||||||
| State.Success;
|
| State.Success;
|
||||||
|
|
||||||
@ -84,7 +84,7 @@ export namespace State {
|
|||||||
const viewMapping: StateViewMap<State> = {
|
const viewMapping: StateViewMap<State> = {
|
||||||
loading: Loading,
|
loading: Loading,
|
||||||
error: ErrorAlertView,
|
error: ErrorAlertView,
|
||||||
"no-exchange": NoExchangesView,
|
"no-exchange-found": NoExchangesView,
|
||||||
"selecting-exchange": ExchangeSelectionPage,
|
"selecting-exchange": ExchangeSelectionPage,
|
||||||
success: SuccessView,
|
success: SuccessView,
|
||||||
};
|
};
|
||||||
|
@ -118,12 +118,12 @@ export function useComponentStateFromURI({
|
|||||||
talerWithdrawUri,
|
talerWithdrawUri,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
const { amount, defaultExchangeBaseUrl } = uriInfo;
|
const { amount, defaultExchangeBaseUrl, possibleExchanges } = uriInfo;
|
||||||
return {
|
return {
|
||||||
talerWithdrawUri,
|
talerWithdrawUri,
|
||||||
amount: Amounts.parseOrThrow(amount),
|
amount: Amounts.parseOrThrow(amount),
|
||||||
thisExchange: defaultExchangeBaseUrl,
|
thisExchange: defaultExchangeBaseUrl,
|
||||||
exchanges: uriInfo.possibleExchanges,
|
exchanges: possibleExchanges,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -191,12 +191,12 @@ function exchangeSelectionState(
|
|||||||
talerWithdrawUri: string | undefined,
|
talerWithdrawUri: string | undefined,
|
||||||
chosenAmount: AmountJson,
|
chosenAmount: AmountJson,
|
||||||
exchangeList: ExchangeListItem[],
|
exchangeList: ExchangeListItem[],
|
||||||
defaultExchange: string | undefined,
|
exchangeSuggestedByTheBank: string | undefined,
|
||||||
): RecursiveState<State> {
|
): RecursiveState<State> {
|
||||||
const api = useBackendContext();
|
const api = useBackendContext();
|
||||||
const selectedExchange = useSelectedExchange({
|
const selectedExchange = useSelectedExchange({
|
||||||
currency: chosenAmount.currency,
|
currency: chosenAmount.currency,
|
||||||
defaultExchange,
|
defaultExchange: exchangeSuggestedByTheBank,
|
||||||
list: exchangeList,
|
list: exchangeList,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -122,7 +122,7 @@ describe("Withdraw CTA states", () => {
|
|||||||
expect(status).equals("loading");
|
expect(status).equals("loading");
|
||||||
},
|
},
|
||||||
({ status, error }) => {
|
({ status, error }) => {
|
||||||
expect(status).equals("no-exchange");
|
expect(status).equals("no-exchange-found");
|
||||||
expect(error).undefined;
|
expect(error).undefined;
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
@ -19,13 +19,14 @@ import { useState } from "preact/hooks";
|
|||||||
import { useAlertContext } from "../context/alert.js";
|
import { useAlertContext } from "../context/alert.js";
|
||||||
import { ButtonHandler } from "../mui/handlers.js";
|
import { ButtonHandler } from "../mui/handlers.js";
|
||||||
|
|
||||||
type State = State.Ready | State.NoExchange | State.Selecting;
|
type State = State.Ready | State.NoExchangeFound | State.Selecting;
|
||||||
|
|
||||||
export namespace State {
|
export namespace State {
|
||||||
export interface NoExchange {
|
export interface NoExchangeFound {
|
||||||
status: "no-exchange";
|
status: "no-exchange-found";
|
||||||
error: undefined;
|
error: undefined;
|
||||||
currency: string | undefined;
|
currency: string;
|
||||||
|
defaultExchange: string | undefined;
|
||||||
}
|
}
|
||||||
export interface Ready {
|
export interface Ready {
|
||||||
status: "ready";
|
status: "ready";
|
||||||
@ -39,7 +40,7 @@ export namespace State {
|
|||||||
onCancel: () => Promise<void>;
|
onCancel: () => Promise<void>;
|
||||||
list: ExchangeListItem[];
|
list: ExchangeListItem[];
|
||||||
currency: string;
|
currency: string;
|
||||||
currentExchange: string;
|
initialValue: string;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -64,31 +65,35 @@ export function useSelectedExchange({
|
|||||||
|
|
||||||
if (!list.length) {
|
if (!list.length) {
|
||||||
return {
|
return {
|
||||||
status: "no-exchange",
|
status: "no-exchange-found",
|
||||||
error: undefined,
|
error: undefined,
|
||||||
currency: undefined,
|
currency,
|
||||||
|
defaultExchange,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const listCurrency = list.filter((e) => e.currency === currency);
|
const exchangesWithThisCurrency = list.filter((e) => e.currency === currency);
|
||||||
if (!listCurrency.length) {
|
if (!exchangesWithThisCurrency.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-found",
|
||||||
error: undefined,
|
error: undefined,
|
||||||
currency,
|
currency,
|
||||||
|
defaultExchange,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isSelecting) {
|
if (isSelecting) {
|
||||||
const currentExchange =
|
const currentExchange =
|
||||||
selectedExchange ?? defaultExchange ?? listCurrency[0].exchangeBaseUrl;
|
selectedExchange ??
|
||||||
|
defaultExchange ??
|
||||||
|
exchangesWithThisCurrency[0].exchangeBaseUrl;
|
||||||
return {
|
return {
|
||||||
status: "selecting-exchange",
|
status: "selecting-exchange",
|
||||||
error: undefined,
|
error: undefined,
|
||||||
list: listCurrency,
|
list: exchangesWithThisCurrency,
|
||||||
currency,
|
currency,
|
||||||
currentExchange: currentExchange,
|
initialValue: currentExchange,
|
||||||
onSelection: async (exchangeBaseUrl: string) => {
|
onSelection: async (exchangeBaseUrl: string) => {
|
||||||
setIsSelecting(false);
|
setIsSelecting(false);
|
||||||
setSelectedExchange(exchangeBaseUrl);
|
setSelectedExchange(exchangeBaseUrl);
|
||||||
@ -131,6 +136,6 @@ export function useSelectedExchange({
|
|||||||
doSelect: {
|
doSelect: {
|
||||||
onClick: pushAlertOnError(async () => setIsSelecting(true)),
|
onClick: pushAlertOnError(async () => setIsSelecting(true)),
|
||||||
},
|
},
|
||||||
selected: listCurrency[0],
|
selected: exchangesWithThisCurrency[0],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -38,7 +38,7 @@ import {
|
|||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
list: ExchangeListItem[];
|
list: ExchangeListItem[];
|
||||||
currentExchange: string;
|
initialValue: string;
|
||||||
onCancel: () => Promise<void>;
|
onCancel: () => Promise<void>;
|
||||||
onSelection: (exchange: string) => Promise<void>;
|
onSelection: (exchange: string) => Promise<void>;
|
||||||
}
|
}
|
||||||
@ -50,7 +50,7 @@ export type State =
|
|||||||
| State.Comparing
|
| State.Comparing
|
||||||
| State.ShowingTos
|
| State.ShowingTos
|
||||||
| State.ShowingPrivacy
|
| State.ShowingPrivacy
|
||||||
| SelectExchangeState.NoExchange;
|
| SelectExchangeState.NoExchangeFound;
|
||||||
|
|
||||||
export namespace State {
|
export namespace State {
|
||||||
export interface Loading {
|
export interface Loading {
|
||||||
@ -102,7 +102,7 @@ const viewMapping: StateViewMap<State> = {
|
|||||||
loading: Loading,
|
loading: Loading,
|
||||||
error: ErrorAlertView,
|
error: ErrorAlertView,
|
||||||
comparing: ComparingView,
|
comparing: ComparingView,
|
||||||
"no-exchange": NoExchangesView,
|
"no-exchange-found": NoExchangesView,
|
||||||
"showing-tos": TosContentView,
|
"showing-tos": TosContentView,
|
||||||
"showing-privacy": PrivacyContentView,
|
"showing-privacy": PrivacyContentView,
|
||||||
ready: ReadyView,
|
ready: ReadyView,
|
||||||
|
@ -30,37 +30,37 @@ export function useComponentState({
|
|||||||
onCancel,
|
onCancel,
|
||||||
onSelection,
|
onSelection,
|
||||||
list: exchanges,
|
list: exchanges,
|
||||||
currentExchange,
|
initialValue,
|
||||||
}: Props): State {
|
}: Props): State {
|
||||||
const api = useBackendContext();
|
const api = useBackendContext();
|
||||||
const { pushAlertOnError } = useAlertContext();
|
const { pushAlertOnError } = useAlertContext();
|
||||||
const { i18n } = useTranslationContext();
|
const { i18n } = useTranslationContext();
|
||||||
const initialValue = exchanges.findIndex(
|
const initialValueIdx = exchanges.findIndex(
|
||||||
(e) => e.exchangeBaseUrl === currentExchange,
|
(e) => e.exchangeBaseUrl === initialValue,
|
||||||
);
|
);
|
||||||
if (initialValue === -1) {
|
if (initialValueIdx === -1) {
|
||||||
throw Error(
|
throw Error(
|
||||||
`wrong usage of ExchangeSelection component, currentExchange '${currentExchange}' is not in the list of exchanges`,
|
`wrong usage of ExchangeSelection component, currentExchange '${initialValue}' is not in the list of exchanges`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
const [value, setValue] = useState(String(initialValue));
|
const [value, setValue] = useState(String(initialValueIdx));
|
||||||
|
|
||||||
const selectedIdx = parseInt(value, 10);
|
const selectedIdx = parseInt(value, 10);
|
||||||
const selectedExchange =
|
const selectedExchange = exchanges[selectedIdx];
|
||||||
exchanges.length == 0 ? undefined : exchanges[selectedIdx];
|
|
||||||
|
|
||||||
const comparingExchanges = selectedIdx !== initialValue;
|
const comparingExchanges = selectedIdx !== initialValueIdx;
|
||||||
|
|
||||||
const initialExchange = comparingExchanges
|
const initialExchange = comparingExchanges
|
||||||
? exchanges[initialValue]
|
? exchanges[initialValueIdx]
|
||||||
: undefined;
|
: undefined;
|
||||||
|
|
||||||
const hook = useAsyncAsHook(async () => {
|
const hook = useAsyncAsHook(async () => {
|
||||||
const selected = !selectedExchange
|
const selected = await api.wallet.call(
|
||||||
? undefined
|
WalletApiOperation.GetExchangeDetailedInfo,
|
||||||
: await api.wallet.call(WalletApiOperation.GetExchangeDetailedInfo, {
|
{
|
||||||
exchangeBaseUrl: selectedExchange.exchangeBaseUrl,
|
exchangeBaseUrl: selectedExchange.exchangeBaseUrl,
|
||||||
});
|
},
|
||||||
|
);
|
||||||
|
|
||||||
const original = !initialExchange
|
const original = !initialExchange
|
||||||
? undefined
|
? undefined
|
||||||
@ -70,7 +70,7 @@ export function useComponentState({
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
exchanges,
|
exchanges,
|
||||||
selected: selected?.exchange,
|
selected: selected.exchange,
|
||||||
original: original?.exchange,
|
original: original?.exchange,
|
||||||
};
|
};
|
||||||
}, [selectedExchange, initialExchange]);
|
}, [selectedExchange, initialExchange]);
|
||||||
@ -98,14 +98,6 @@ export function useComponentState({
|
|||||||
|
|
||||||
const { selected, original } = hook.response;
|
const { selected, original } = hook.response;
|
||||||
|
|
||||||
if (selectedExchange === undefined || !selected) {
|
|
||||||
return {
|
|
||||||
status: "no-exchange",
|
|
||||||
error: undefined,
|
|
||||||
currency: undefined,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const exchangeMap = exchanges.reduce(
|
const exchangeMap = exchanges.reduce(
|
||||||
(prev, cur, idx) => ({ ...prev, [String(idx)]: cur.exchangeBaseUrl }),
|
(prev, cur, idx) => ({ ...prev, [String(idx)]: cur.exchangeBaseUrl }),
|
||||||
{} as Record<string, string>,
|
{} as Record<string, string>,
|
||||||
|
@ -20,12 +20,17 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { tests } from "@gnu-taler/web-util/lib/index.browser";
|
import { tests } from "@gnu-taler/web-util/lib/index.browser";
|
||||||
import { ComparingView, ReadyView } from "./views.js";
|
import { ComparingView, ReadyView, NoExchangesView } from "./views.js";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
title: "select exchange",
|
title: "select exchange",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const NoExchangeFound = tests.createExample(NoExchangesView, {
|
||||||
|
currency: "USD",
|
||||||
|
defaultExchange: "https://exchange.taler.ar",
|
||||||
|
});
|
||||||
|
|
||||||
export const Bitcoin1 = tests.createExample(ReadyView, {
|
export const Bitcoin1 = tests.createExample(ReadyView, {
|
||||||
exchanges: {
|
exchanges: {
|
||||||
list: { "0": "https://exchange.taler.ar" },
|
list: { "0": "https://exchange.taler.ar" },
|
||||||
|
@ -141,28 +141,33 @@ export function TosContentView({
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function NoExchangesView({
|
export function NoExchangesView({
|
||||||
|
defaultExchange,
|
||||||
currency,
|
currency,
|
||||||
}: SelectExchangeState.NoExchange): VNode {
|
}: SelectExchangeState.NoExchangeFound): VNode {
|
||||||
const { i18n } = useTranslationContext();
|
const { i18n } = useTranslationContext();
|
||||||
if (!currency) {
|
|
||||||
return (
|
|
||||||
<AlertView
|
|
||||||
alert={{
|
|
||||||
type: "warning",
|
|
||||||
message: i18n.str`Could not find any exchange `,
|
|
||||||
description: i18n.str`You are trying to withdraw coins but there is no exchange and the bank didn't suggested one.`,
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return (
|
return (
|
||||||
<AlertView
|
<Fragment>
|
||||||
alert={{
|
<p>
|
||||||
type: "warning",
|
<AlertView
|
||||||
message: i18n.str`Could not find any exchange `,
|
alert={{
|
||||||
description: i18n.str`You are trying to withdraw coins for the currency ${currency} but there is no exchange registered in this wallet and the bank didn't suggested one.`,
|
type: "error",
|
||||||
}}
|
message: i18n.str`There is no exchange available for currency ${currency}`,
|
||||||
/>
|
description: i18n.str`You can add more exchanges from the settings.`,
|
||||||
|
cause: undefined,
|
||||||
|
context: undefined,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</p>
|
||||||
|
{defaultExchange && (
|
||||||
|
<AlertView
|
||||||
|
alert={{
|
||||||
|
type: "warning",
|
||||||
|
message: i18n.str`Exchange ${defaultExchange} is not available`,
|
||||||
|
description: i18n.str`Exchange status can view accessed from the settings.`,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Fragment>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user