fix #7496 with unit tests
This commit is contained in:
parent
dcddc4c53a
commit
3577227cc0
@ -21,8 +21,8 @@
|
||||
|
||||
import { expect } from "chai";
|
||||
|
||||
describe("test description", () => {
|
||||
it("should assert", () => {
|
||||
describe("Term of service states", () => {
|
||||
it.skip("should assert", () => {
|
||||
expect([]).deep.equals([]);
|
||||
});
|
||||
});
|
||||
|
@ -25,11 +25,10 @@ import { isFuture, parse } from "date-fns";
|
||||
import { useState } from "preact/hooks";
|
||||
import { useAsyncAsHook } from "../../hooks/useAsyncAsHook.js";
|
||||
import { useSelectedExchange } from "../../hooks/useSelectedExchange.js";
|
||||
import { RecursiveState } from "../../utils/index.js";
|
||||
import { wxApi } from "../../wxApi.js";
|
||||
import { Props, State } from "./index.js";
|
||||
|
||||
type RecursiveState<S extends object> = S | (() => RecursiveState<S>);
|
||||
|
||||
export function useComponentState(
|
||||
{ amount: amountStr, onClose, onSuccess }: Props,
|
||||
api: typeof wxApi,
|
||||
|
@ -21,8 +21,8 @@
|
||||
|
||||
import { expect } from "chai";
|
||||
|
||||
describe("test description", () => {
|
||||
it("should assert", () => {
|
||||
describe("Invoice create state", () => {
|
||||
it.skip("should create some tests", () => {
|
||||
expect([]).deep.equals([]);
|
||||
});
|
||||
});
|
||||
|
@ -21,8 +21,8 @@
|
||||
|
||||
import { expect } from "chai";
|
||||
|
||||
describe("test description", () => {
|
||||
it("should assert", () => {
|
||||
describe("Invoice payment state", () => {
|
||||
it.skip("should create some states", () => {
|
||||
expect([]).deep.equals([]);
|
||||
});
|
||||
});
|
||||
|
@ -21,8 +21,8 @@
|
||||
|
||||
import { expect } from "chai";
|
||||
|
||||
describe("test description", () => {
|
||||
it("should assert", () => {
|
||||
describe("Transfer create states", () => {
|
||||
it.skip("should assert", () => {
|
||||
expect([]).deep.equals([]);
|
||||
});
|
||||
});
|
||||
|
@ -21,8 +21,8 @@
|
||||
|
||||
import { expect } from "chai";
|
||||
|
||||
describe("test description", () => {
|
||||
it("should assert", () => {
|
||||
describe("Transfer pickup states", () => {
|
||||
it.skip("should assert", () => {
|
||||
expect([]).deep.equals([]);
|
||||
});
|
||||
});
|
||||
|
@ -25,11 +25,10 @@ import { TalerError, WalletApiOperation } from "@gnu-taler/taler-wallet-core";
|
||||
import { useState } from "preact/hooks";
|
||||
import { useAsyncAsHook } from "../../hooks/useAsyncAsHook.js";
|
||||
import { useSelectedExchange } from "../../hooks/useSelectedExchange.js";
|
||||
import { RecursiveState } from "../../utils/index.js";
|
||||
import { wxApi } from "../../wxApi.js";
|
||||
import { PropsFromParams, PropsFromURI, State } from "./index.js";
|
||||
|
||||
type RecursiveState<S extends object> = S | (() => RecursiveState<S>);
|
||||
|
||||
export function useComponentStateFromParams(
|
||||
{ amount, cancel, onSuccess }: PropsFromParams,
|
||||
api: typeof wxApi,
|
||||
|
@ -80,7 +80,7 @@ export type StateViewMap<StateType extends { status: string }> = {
|
||||
[S in StateType as S["status"]]: StateFunc<S>;
|
||||
};
|
||||
|
||||
type RecursiveState<S extends object> = S | (() => RecursiveState<S>);
|
||||
export type RecursiveState<S extends object> = S | (() => RecursiveState<S>);
|
||||
|
||||
export function compose<SType extends { status: string }, PType>(
|
||||
name: string,
|
||||
|
@ -48,17 +48,13 @@ import { BackupPage } from "./BackupPage.js";
|
||||
import { DepositPage } from "./DepositPage/index.js";
|
||||
import { ExchangeAddPage } from "./ExchangeAddPage.js";
|
||||
import { HistoryPage } from "./History.js";
|
||||
import { ProviderAddPage } from "./ProviderAddPage.js";
|
||||
import { ProviderDetailPage } from "./ProviderDetailPage.js";
|
||||
import { SettingsPage } from "./Settings.js";
|
||||
import { TransactionPage } from "./Transaction.js";
|
||||
import { WelcomePage } from "./Welcome.js";
|
||||
import { QrReaderPage } from "./QrReader.js";
|
||||
import { platform } from "../platform/api.js";
|
||||
import {
|
||||
DestinationSelectionGetCash,
|
||||
DestinationSelectionSendCash,
|
||||
} from "./DestinationSelection.js";
|
||||
import { DestinationSelectionPage } from "./DestinationSelection/index.js";
|
||||
import { ExchangeSelectionPage } from "./ExchangeSelection/index.js";
|
||||
import { TransferCreatePage } from "../cta/TransferCreate/index.js";
|
||||
import { InvoiceCreatePage } from "../cta/InvoiceCreate/index.js";
|
||||
@ -153,7 +149,8 @@ export function Application(): VNode {
|
||||
<Route path={Pages.exchanges} component={ExchangeSelectionPage} />
|
||||
<Route
|
||||
path={Pages.sendCash.pattern}
|
||||
component={DestinationSelectionSendCash}
|
||||
type="send"
|
||||
component={DestinationSelectionPage}
|
||||
goToWalletBankDeposit={(amount: string) =>
|
||||
redirectTo(Pages.balanceDeposit({ amount }))
|
||||
}
|
||||
@ -163,7 +160,8 @@ export function Application(): VNode {
|
||||
/>
|
||||
<Route
|
||||
path={Pages.receiveCash.pattern}
|
||||
component={DestinationSelectionGetCash}
|
||||
type="get"
|
||||
component={DestinationSelectionPage}
|
||||
goToWalletManualWithdraw={(amount?: string) =>
|
||||
redirectTo(Pages.ctaWithdrawManual({ amount }))
|
||||
}
|
||||
|
@ -0,0 +1,93 @@
|
||||
/*
|
||||
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 { Loading } from "../../components/Loading.js";
|
||||
import { HookError } from "../../hooks/useAsyncAsHook.js";
|
||||
import { AmountFieldHandler, ButtonHandler } from "../../mui/handlers.js";
|
||||
import { compose, StateViewMap } from "../../utils/index.js";
|
||||
import { wxApi } from "../../wxApi.js";
|
||||
import { useComponentState } from "./state.js";
|
||||
import { LoadingUriView, ReadyView, SelectCurrencyView } from "./views.js";
|
||||
|
||||
export type Props = PropsGet | PropsSend;
|
||||
|
||||
interface PropsGet {
|
||||
type: "get";
|
||||
amount?: string;
|
||||
goToWalletManualWithdraw: (amount: string) => void;
|
||||
goToWalletWalletInvoice: (amount: string) => void;
|
||||
}
|
||||
interface PropsSend {
|
||||
type: "send";
|
||||
amount?: string;
|
||||
goToWalletBankDeposit: (amount: string) => void;
|
||||
goToWalletWalletSend: (amount: string) => void;
|
||||
}
|
||||
|
||||
export type State =
|
||||
| State.Loading
|
||||
| State.LoadingUriError
|
||||
| State.Ready
|
||||
| State.SelectCurrency;
|
||||
|
||||
export namespace State {
|
||||
export interface Loading {
|
||||
status: "loading";
|
||||
error: undefined;
|
||||
}
|
||||
|
||||
export interface LoadingUriError {
|
||||
status: "loading-error";
|
||||
error: HookError;
|
||||
}
|
||||
|
||||
export interface SelectCurrency {
|
||||
status: "select-currency";
|
||||
error: undefined;
|
||||
currencies: Record<string, string>;
|
||||
onCurrencySelected: (currency: string) => void;
|
||||
}
|
||||
|
||||
export interface Ready {
|
||||
status: "ready";
|
||||
error: undefined;
|
||||
type: Props["type"];
|
||||
selectCurrency: ButtonHandler;
|
||||
previous: Contact[];
|
||||
goToBank: ButtonHandler;
|
||||
goToWallet: ButtonHandler;
|
||||
amountHandler: AmountFieldHandler;
|
||||
}
|
||||
}
|
||||
|
||||
export type Contact = {
|
||||
icon: string;
|
||||
name: string;
|
||||
description: string;
|
||||
};
|
||||
|
||||
const viewMapping: StateViewMap<State> = {
|
||||
loading: Loading,
|
||||
"loading-error": LoadingUriView,
|
||||
"select-currency": SelectCurrencyView,
|
||||
ready: ReadyView,
|
||||
};
|
||||
|
||||
export const DestinationSelectionPage = compose(
|
||||
"DestinationSelectionPage",
|
||||
(p: Props) => useComponentState(p, wxApi),
|
||||
viewMapping,
|
||||
);
|
@ -0,0 +1,167 @@
|
||||
/*
|
||||
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 { Amounts } from "@gnu-taler/taler-util";
|
||||
import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
|
||||
import { useState } from "preact/hooks";
|
||||
import { useAsyncAsHook } from "../../hooks/useAsyncAsHook.js";
|
||||
import { assertUnreachable, RecursiveState } from "../../utils/index.js";
|
||||
import { wxApi } from "../../wxApi.js";
|
||||
import { Contact, Props, State } from "./index.js";
|
||||
import bankIcon from "../../svg/ri-bank-line.svg";
|
||||
|
||||
export function useComponentState(
|
||||
props: Props,
|
||||
api: typeof wxApi,
|
||||
): RecursiveState<State> {
|
||||
const parsedInitialAmount = !props.amount
|
||||
? undefined
|
||||
: Amounts.parse(props.amount);
|
||||
|
||||
// const initialCurrency = parsedInitialAmount?.currency;
|
||||
|
||||
const [amount, setAmount] = useState(
|
||||
!parsedInitialAmount ? undefined : parsedInitialAmount,
|
||||
);
|
||||
|
||||
//FIXME: get this information from wallet
|
||||
// eslint-disable-next-line no-constant-condition
|
||||
const previous: Contact[] = true
|
||||
? []
|
||||
: [
|
||||
{
|
||||
name: "International Bank",
|
||||
icon: bankIcon, //FIXME: should be decided in the view
|
||||
description: "account ending with 3454",
|
||||
},
|
||||
{
|
||||
name: "Max",
|
||||
icon: bankIcon,
|
||||
description: "account ending with 3454",
|
||||
},
|
||||
{
|
||||
name: "Alex",
|
||||
icon: bankIcon,
|
||||
description: "account ending with 3454",
|
||||
},
|
||||
];
|
||||
|
||||
if (!amount) {
|
||||
return () => {
|
||||
// eslint-disable-next-line react-hooks/rules-of-hooks
|
||||
const hook = useAsyncAsHook(() =>
|
||||
api.wallet.call(WalletApiOperation.ListExchanges, {}),
|
||||
);
|
||||
|
||||
if (!hook) {
|
||||
return {
|
||||
status: "loading",
|
||||
error: undefined,
|
||||
};
|
||||
}
|
||||
if (hook.hasError) {
|
||||
return {
|
||||
status: "loading-error",
|
||||
error: hook,
|
||||
};
|
||||
}
|
||||
const currencies: Record<string, string> = {};
|
||||
hook.response.exchanges.forEach((e) => {
|
||||
if (e.currency) {
|
||||
currencies[e.currency] = e.currency;
|
||||
}
|
||||
});
|
||||
currencies[""] = "Select a currency";
|
||||
|
||||
return {
|
||||
status: "select-currency",
|
||||
error: undefined,
|
||||
onCurrencySelected: (c: string) => {
|
||||
setAmount(Amounts.zeroOfCurrency(c));
|
||||
},
|
||||
currencies,
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
const currencyAndAmount = Amounts.stringify(amount);
|
||||
const invalid = Amounts.isZero(amount);
|
||||
|
||||
switch (props.type) {
|
||||
case "send":
|
||||
return {
|
||||
status: "ready",
|
||||
error: undefined,
|
||||
previous,
|
||||
selectCurrency: {
|
||||
onClick: async () => {
|
||||
setAmount(undefined);
|
||||
},
|
||||
},
|
||||
goToBank: {
|
||||
onClick: invalid
|
||||
? undefined
|
||||
: async () => {
|
||||
props.goToWalletBankDeposit(currencyAndAmount);
|
||||
},
|
||||
},
|
||||
goToWallet: {
|
||||
onClick: invalid
|
||||
? undefined
|
||||
: async () => {
|
||||
props.goToWalletWalletSend(currencyAndAmount);
|
||||
},
|
||||
},
|
||||
amountHandler: {
|
||||
onInput: async (s) => setAmount(s),
|
||||
value: amount,
|
||||
},
|
||||
type: props.type,
|
||||
};
|
||||
case "get":
|
||||
return {
|
||||
status: "ready",
|
||||
error: undefined,
|
||||
previous,
|
||||
selectCurrency: {
|
||||
onClick: async () => {
|
||||
setAmount(undefined);
|
||||
},
|
||||
},
|
||||
goToBank: {
|
||||
onClick: invalid
|
||||
? undefined
|
||||
: async () => {
|
||||
props.goToWalletManualWithdraw(currencyAndAmount);
|
||||
},
|
||||
},
|
||||
goToWallet: {
|
||||
onClick: invalid
|
||||
? undefined
|
||||
: async () => {
|
||||
props.goToWalletWalletInvoice(currencyAndAmount);
|
||||
},
|
||||
},
|
||||
amountHandler: {
|
||||
onInput: async (s) => setAmount(s),
|
||||
value: amount,
|
||||
},
|
||||
type: props.type,
|
||||
};
|
||||
default:
|
||||
assertUnreachable(props);
|
||||
}
|
||||
}
|
@ -19,25 +19,44 @@
|
||||
* @author Sebastian Javier Marchano (sebasjm)
|
||||
*/
|
||||
|
||||
import { createExample } from "../test-utils.js";
|
||||
import {
|
||||
DestinationSelectionGetCash,
|
||||
DestinationSelectionSendCash,
|
||||
SelectCurrencyView,
|
||||
} from "./DestinationSelection.js";
|
||||
import { createExample } from "../../test-utils.js";
|
||||
import { ReadyView, SelectCurrencyView } from "./views.js";
|
||||
|
||||
export default {
|
||||
title: "wallet/destination",
|
||||
};
|
||||
|
||||
export const GetCash = createExample(DestinationSelectionGetCash, {
|
||||
amount: "usd:0",
|
||||
export const GetCash = createExample(ReadyView, {
|
||||
amountHandler: {
|
||||
value: {
|
||||
currency: "EUR",
|
||||
fraction: 0,
|
||||
value: 2,
|
||||
},
|
||||
},
|
||||
goToBank: {},
|
||||
goToWallet: {},
|
||||
previous: [],
|
||||
selectCurrency: {},
|
||||
type: "get",
|
||||
});
|
||||
export const SendCash = createExample(DestinationSelectionSendCash, {
|
||||
amount: "eur:1",
|
||||
export const SendCash = createExample(ReadyView, {
|
||||
amountHandler: {
|
||||
value: {
|
||||
currency: "EUR",
|
||||
fraction: 0,
|
||||
value: 1,
|
||||
},
|
||||
},
|
||||
goToBank: {},
|
||||
goToWallet: {},
|
||||
previous: [],
|
||||
selectCurrency: {},
|
||||
type: "send",
|
||||
});
|
||||
|
||||
export const SelectCurrency = createExample(SelectCurrencyView, {
|
||||
list: {
|
||||
currencies: {
|
||||
"": "Select a currency",
|
||||
USD: "USD",
|
||||
},
|
@ -0,0 +1,136 @@
|
||||
/*
|
||||
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/>
|
||||
*/
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Sebastian Javier Marchano (sebasjm)
|
||||
*/
|
||||
|
||||
import {
|
||||
Amounts,
|
||||
ExchangeEntryStatus,
|
||||
ExchangeListItem,
|
||||
ExchangeTosStatus,
|
||||
} from "@gnu-taler/taler-util";
|
||||
import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
|
||||
import { expect } from "chai";
|
||||
import { createWalletApiMock, mountHook } from "../../test-utils.js";
|
||||
import { useComponentState } from "./state.js";
|
||||
|
||||
const exchangeArs: ExchangeListItem = {
|
||||
currency: "ARS",
|
||||
exchangeBaseUrl: "http://",
|
||||
tosStatus: ExchangeTosStatus.Accepted,
|
||||
exchangeStatus: ExchangeEntryStatus.Ok,
|
||||
paytoUris: [],
|
||||
permanent: true,
|
||||
ageRestrictionOptions: [],
|
||||
};
|
||||
|
||||
describe("Destination selection states", () => {
|
||||
it("should select currency if no amount specified", async () => {
|
||||
const { handler, mock } = createWalletApiMock();
|
||||
|
||||
handler.addWalletCallResponse(
|
||||
WalletApiOperation.ListExchanges,
|
||||
{},
|
||||
{
|
||||
exchanges: [exchangeArs],
|
||||
},
|
||||
);
|
||||
|
||||
const props = {
|
||||
type: "get" as const,
|
||||
goToWalletManualWithdraw: () => {
|
||||
return null;
|
||||
},
|
||||
goToWalletWalletInvoice: () => {
|
||||
null;
|
||||
},
|
||||
};
|
||||
const { pullLastResultOrThrow, waitForStateUpdate, assertNoPendingUpdate } =
|
||||
mountHook(() => useComponentState(props, mock));
|
||||
|
||||
{
|
||||
const state = pullLastResultOrThrow();
|
||||
|
||||
if (state.status !== "loading") expect.fail();
|
||||
if (state.error) expect.fail();
|
||||
}
|
||||
|
||||
expect(await waitForStateUpdate()).true;
|
||||
|
||||
{
|
||||
const state = pullLastResultOrThrow();
|
||||
|
||||
if (state.status !== "select-currency") expect.fail();
|
||||
if (state.error) expect.fail();
|
||||
expect(state.currencies).deep.eq({
|
||||
ARS: "ARS",
|
||||
"": "Select a currency",
|
||||
});
|
||||
|
||||
state.onCurrencySelected(exchangeArs.currency!);
|
||||
}
|
||||
|
||||
expect(await waitForStateUpdate()).true;
|
||||
|
||||
{
|
||||
const state = pullLastResultOrThrow();
|
||||
|
||||
if (state.status !== "ready") expect.fail();
|
||||
if (state.error) expect.fail();
|
||||
expect(state.goToBank.onClick).eq(undefined);
|
||||
expect(state.goToWallet.onClick).eq(undefined);
|
||||
|
||||
expect(state.amountHandler.value).deep.eq(Amounts.parseOrThrow("ARS:0"));
|
||||
}
|
||||
|
||||
await assertNoPendingUpdate();
|
||||
expect(handler.getCallingQueueState()).eq("empty");
|
||||
});
|
||||
|
||||
it("should be possible to start with an amount specified in request params", async () => {
|
||||
const { handler, mock } = createWalletApiMock();
|
||||
|
||||
const props = {
|
||||
type: "get" as const,
|
||||
goToWalletManualWithdraw: () => {
|
||||
return null;
|
||||
},
|
||||
goToWalletWalletInvoice: () => {
|
||||
null;
|
||||
},
|
||||
amount: "ARS:2",
|
||||
};
|
||||
const { pullLastResultOrThrow, waitForStateUpdate, assertNoPendingUpdate } =
|
||||
mountHook(() => useComponentState(props, mock));
|
||||
|
||||
{
|
||||
const state = pullLastResultOrThrow();
|
||||
|
||||
if (state.status !== "ready") expect.fail();
|
||||
if (state.error) expect.fail();
|
||||
expect(state.goToBank.onClick).not.eq(undefined);
|
||||
expect(state.goToWallet.onClick).not.eq(undefined);
|
||||
|
||||
expect(state.amountHandler.value).deep.eq(Amounts.parseOrThrow("ARS:2"));
|
||||
}
|
||||
|
||||
await assertNoPendingUpdate();
|
||||
expect(handler.getCallingQueueState()).eq("empty");
|
||||
});
|
||||
});
|
@ -14,30 +14,70 @@
|
||||
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
import { Amounts } from "@gnu-taler/taler-util";
|
||||
import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
|
||||
import { styled } from "@linaria/react";
|
||||
import { Fragment, h, VNode } from "preact";
|
||||
import { useState } from "preact/hooks";
|
||||
import { AmountField } from "../components/AmountField.js";
|
||||
import { Loading } from "../components/Loading.js";
|
||||
import { LoadingError } from "../components/LoadingError.js";
|
||||
import { SelectList } from "../components/SelectList.js";
|
||||
import { LoadingError } from "../../components/LoadingError.js";
|
||||
import { SelectList } from "../../components/SelectList.js";
|
||||
import {
|
||||
Input,
|
||||
LightText,
|
||||
LinkPrimary,
|
||||
SvgIcon,
|
||||
} from "../components/styled/index.js";
|
||||
import { useTranslationContext } from "../context/translation.js";
|
||||
import { useAsyncAsHook } from "../hooks/useAsyncAsHook.js";
|
||||
import { Button } from "../mui/Button.js";
|
||||
import { Grid } from "../mui/Grid.js";
|
||||
import { Paper } from "../mui/Paper.js";
|
||||
import { Pages } from "../NavigationBar.js";
|
||||
import arrowIcon from "../svg/chevron-down.svg";
|
||||
import bankIcon from "../svg/ri-bank-line.svg";
|
||||
import { wxApi } from "../wxApi.js";
|
||||
} from "../../components/styled/index.js";
|
||||
import { useTranslationContext } from "../../context/translation.js";
|
||||
import { Pages } from "../../NavigationBar.js";
|
||||
import { Contact, State } from "./index.js";
|
||||
import arrowIcon from "../../svg/chevron-down.svg";
|
||||
import { AmountField } from "../../components/AmountField.js";
|
||||
import { Grid } from "../../mui/Grid.js";
|
||||
import { Paper } from "../../mui/Paper.js";
|
||||
import { Button } from "../../mui/Button.js";
|
||||
import { assertUnreachable } from "../../utils/index.js";
|
||||
|
||||
export function LoadingUriView({ error }: State.LoadingUriError): VNode {
|
||||
const { i18n } = useTranslationContext();
|
||||
return (
|
||||
<LoadingError
|
||||
title={<i18n.Translate>Could not load</i18n.Translate>}
|
||||
error={error}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export function SelectCurrencyView({
|
||||
currencies,
|
||||
onCurrencySelected,
|
||||
}: State.SelectCurrency): VNode {
|
||||
const { i18n } = useTranslationContext();
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<h2>
|
||||
<i18n.Translate>
|
||||
Choose a currency to proceed or add another exchange
|
||||
</i18n.Translate>
|
||||
</h2>
|
||||
|
||||
<p>
|
||||
<Input>
|
||||
<SelectList
|
||||
label={<i18n.Translate>Known currencies</i18n.Translate>}
|
||||
list={currencies}
|
||||
name="lang"
|
||||
value={""}
|
||||
onChange={(v) => onCurrencySelected(v)}
|
||||
/>
|
||||
</Input>
|
||||
</p>
|
||||
<div style={{ display: "flex", justifyContent: "space-between" }}>
|
||||
<div />
|
||||
<LinkPrimary href={Pages.settingsExchangeAdd({})}>
|
||||
<i18n.Translate>Add an exchange</i18n.Translate>
|
||||
</LinkPrimary>
|
||||
</div>
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
const Container = styled.div`
|
||||
display: flex;
|
||||
@ -47,23 +87,6 @@ const Container = styled.div`
|
||||
}
|
||||
`;
|
||||
|
||||
interface PropsGet {
|
||||
amount?: string;
|
||||
goToWalletManualWithdraw: (amount: string) => void;
|
||||
goToWalletWalletInvoice: (amount: string) => void;
|
||||
}
|
||||
interface PropsSend {
|
||||
amount?: string;
|
||||
goToWalletBankDeposit: (amount: string) => void;
|
||||
goToWalletWalletSend: (amount: string) => void;
|
||||
}
|
||||
|
||||
type Contact = {
|
||||
icon: string;
|
||||
name: string;
|
||||
description: string;
|
||||
};
|
||||
|
||||
const ContactTable = styled.table`
|
||||
width: 100%;
|
||||
& > tr > td {
|
||||
@ -165,73 +188,193 @@ const CircleDiv = styled.div`
|
||||
border: none;
|
||||
`;
|
||||
|
||||
export function SelectCurrency({
|
||||
onChange,
|
||||
}: {
|
||||
onChange: (s: string) => void;
|
||||
}): VNode {
|
||||
const { i18n } = useTranslationContext();
|
||||
|
||||
const hook = useAsyncAsHook(() =>
|
||||
wxApi.wallet.call(WalletApiOperation.ListExchanges, {}),
|
||||
);
|
||||
|
||||
if (!hook) {
|
||||
return <Loading />;
|
||||
export function ReadyView(props: State.Ready): VNode {
|
||||
switch (props.type) {
|
||||
case "get":
|
||||
return ReadyGetView(props);
|
||||
case "send":
|
||||
return ReadySendView(props);
|
||||
default:
|
||||
assertUnreachable(props.type);
|
||||
}
|
||||
if (hook.hasError) {
|
||||
return (
|
||||
<LoadingError
|
||||
error={hook}
|
||||
title={<i18n.Translate>Could not load list of exchange</i18n.Translate>}
|
||||
/>
|
||||
);
|
||||
}
|
||||
const list: Record<string, string> = {};
|
||||
hook.response.exchanges.forEach((e) => {
|
||||
if (e.currency) {
|
||||
list[e.currency] = e.currency;
|
||||
}
|
||||
});
|
||||
list[""] = "Select a currency";
|
||||
return <SelectCurrencyView onChange={onChange} list={list} />;
|
||||
}
|
||||
|
||||
export function SelectCurrencyView({
|
||||
onChange,
|
||||
list,
|
||||
}: {
|
||||
onChange: (s: string) => void;
|
||||
list: Record<string, string>;
|
||||
}): VNode {
|
||||
export function ReadyGetView({
|
||||
amountHandler,
|
||||
goToBank,
|
||||
goToWallet,
|
||||
selectCurrency,
|
||||
previous,
|
||||
}: State.Ready): VNode {
|
||||
const { i18n } = useTranslationContext();
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<h2>
|
||||
<i18n.Translate>
|
||||
Choose a currency to proceed or add another exchange
|
||||
</i18n.Translate>
|
||||
</h2>
|
||||
<Container>
|
||||
<h1>
|
||||
<i18n.Translate>Specify the amount and the origin</i18n.Translate>
|
||||
</h1>
|
||||
<Grid container columns={2} justifyContent="space-between">
|
||||
<AmountField
|
||||
label={<i18n.Translate>Amount</i18n.Translate>}
|
||||
required
|
||||
handler={amountHandler}
|
||||
/>
|
||||
<Button onClick={selectCurrency.onClick}>
|
||||
<i18n.Translate>Change currency</i18n.Translate>
|
||||
</Button>
|
||||
</Grid>
|
||||
|
||||
<p>
|
||||
<Input>
|
||||
<SelectList
|
||||
label={<i18n.Translate>Known currencies</i18n.Translate>}
|
||||
list={list}
|
||||
name="lang"
|
||||
value={""}
|
||||
onChange={(v) => onChange(v)}
|
||||
/>
|
||||
</Input>
|
||||
</p>
|
||||
<div style={{ display: "flex", justifyContent: "space-between" }}>
|
||||
<div />
|
||||
<LinkPrimary href={Pages.settingsExchangeAdd({})}>
|
||||
<i18n.Translate>Add an exchange</i18n.Translate>
|
||||
</LinkPrimary>
|
||||
<Grid container spacing={1} columns={1}>
|
||||
{previous.length > 0 ? (
|
||||
<Fragment>
|
||||
<p>
|
||||
<i18n.Translate>Use previous origins:</i18n.Translate>
|
||||
</p>
|
||||
<Grid item xs={1}>
|
||||
<Paper style={{ padding: 8 }}>
|
||||
<ContactTable>
|
||||
{previous.map((info, i) => (
|
||||
<tr key={i}>
|
||||
<td>
|
||||
<RowExample
|
||||
info={info}
|
||||
disabled={!amountHandler.onInput}
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</ContactTable>
|
||||
</Paper>
|
||||
</Grid>
|
||||
</Fragment>
|
||||
) : undefined}
|
||||
{previous.length > 0 ? (
|
||||
<Grid item>
|
||||
<p>
|
||||
<i18n.Translate>
|
||||
Or specify the origin of the money
|
||||
</i18n.Translate>
|
||||
</p>
|
||||
</Grid>
|
||||
) : (
|
||||
<Grid item>
|
||||
<p>
|
||||
<i18n.Translate>Specify the origin of the money</i18n.Translate>
|
||||
</p>
|
||||
</Grid>
|
||||
)}
|
||||
<Grid item container columns={2} spacing={1}>
|
||||
<Grid item xs={1}>
|
||||
<Paper style={{ padding: 8 }}>
|
||||
<p>
|
||||
<i18n.Translate>From my bank account</i18n.Translate>
|
||||
</p>
|
||||
<Button onClick={goToBank.onClick}>
|
||||
<i18n.Translate>Withdraw</i18n.Translate>
|
||||
</Button>
|
||||
</Paper>
|
||||
</Grid>
|
||||
<Grid item xs={1}>
|
||||
<Paper style={{ padding: 8 }}>
|
||||
<p>
|
||||
<i18n.Translate>From another wallet</i18n.Translate>
|
||||
</p>
|
||||
<Button onClick={goToWallet.onClick}>
|
||||
<i18n.Translate>Invoice</i18n.Translate>
|
||||
</Button>
|
||||
</Paper>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
export function ReadySendView({
|
||||
amountHandler,
|
||||
goToBank,
|
||||
goToWallet,
|
||||
previous,
|
||||
}: State.Ready): VNode {
|
||||
const { i18n } = useTranslationContext();
|
||||
|
||||
return (
|
||||
<Container>
|
||||
<h1>
|
||||
<i18n.Translate>Specify the amount and the destination</i18n.Translate>
|
||||
</h1>
|
||||
|
||||
<div>
|
||||
<AmountField
|
||||
label={<i18n.Translate>Amount</i18n.Translate>}
|
||||
required
|
||||
handler={amountHandler}
|
||||
/>
|
||||
</div>
|
||||
</Fragment>
|
||||
|
||||
<Grid container spacing={1} columns={1}>
|
||||
{previous.length > 0 ? (
|
||||
<Fragment>
|
||||
<p>
|
||||
<i18n.Translate>Use previous destinations:</i18n.Translate>
|
||||
</p>
|
||||
<Grid item xs={1}>
|
||||
<Paper style={{ padding: 8 }}>
|
||||
<ContactTable>
|
||||
{previous.map((info, i) => (
|
||||
<tr key={i}>
|
||||
<td>
|
||||
<RowExample
|
||||
info={info}
|
||||
disabled={!amountHandler.onInput}
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</ContactTable>
|
||||
</Paper>
|
||||
</Grid>
|
||||
</Fragment>
|
||||
) : undefined}
|
||||
{previous.length > 0 ? (
|
||||
<Grid item>
|
||||
<p>
|
||||
<i18n.Translate>
|
||||
Or specify the destination of the money
|
||||
</i18n.Translate>
|
||||
</p>
|
||||
</Grid>
|
||||
) : (
|
||||
<Grid item>
|
||||
<p>
|
||||
<i18n.Translate>
|
||||
Specify the destination of the money
|
||||
</i18n.Translate>
|
||||
</p>
|
||||
</Grid>
|
||||
)}
|
||||
<Grid item container columns={2} spacing={1}>
|
||||
<Grid item xs={1}>
|
||||
<Paper style={{ padding: 8 }}>
|
||||
<p>
|
||||
<i18n.Translate>To my bank account</i18n.Translate>
|
||||
</p>
|
||||
<Button onClick={goToBank.onClick}>
|
||||
<i18n.Translate>Deposit</i18n.Translate>
|
||||
</Button>
|
||||
</Paper>
|
||||
</Grid>
|
||||
<Grid item xs={1}>
|
||||
<Paper style={{ padding: 8 }}>
|
||||
<p>
|
||||
<i18n.Translate>To another wallet</i18n.Translate>
|
||||
</p>
|
||||
<Button onClick={goToWallet.onClick}>
|
||||
<i18n.Translate>Send</i18n.Translate>
|
||||
</Button>
|
||||
</Paper>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
|
||||
@ -268,271 +411,3 @@ function RowExample({
|
||||
</MediaExample>
|
||||
);
|
||||
}
|
||||
|
||||
export function DestinationSelectionGetCash({
|
||||
amount: initialAmount,
|
||||
goToWalletManualWithdraw,
|
||||
goToWalletWalletInvoice,
|
||||
}: PropsGet): VNode {
|
||||
const parsedInitialAmount = !initialAmount
|
||||
? undefined
|
||||
: Amounts.parse(initialAmount);
|
||||
|
||||
const [currency, setCurrency] = useState(parsedInitialAmount?.currency);
|
||||
|
||||
const [amount, setAmount] = useState(
|
||||
parsedInitialAmount ?? Amounts.zeroOfCurrency(currency ?? "KUDOS"),
|
||||
);
|
||||
|
||||
const { i18n } = useTranslationContext();
|
||||
const previous1: Contact[] = [];
|
||||
const previous2: Contact[] = [
|
||||
{
|
||||
name: "International Bank",
|
||||
icon: bankIcon,
|
||||
description: "account ending with 3454",
|
||||
},
|
||||
{
|
||||
name: "Max",
|
||||
icon: bankIcon,
|
||||
description: "account ending with 3454",
|
||||
},
|
||||
{
|
||||
name: "Alex",
|
||||
icon: bankIcon,
|
||||
description: "account ending with 3454",
|
||||
},
|
||||
];
|
||||
const previous = previous1;
|
||||
|
||||
if (!currency) {
|
||||
return (
|
||||
<div>
|
||||
<SelectCurrency onChange={(c) => setCurrency(c)} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
const currencyAndAmount = Amounts.stringify(amount);
|
||||
const invalid = Amounts.isZero(amount);
|
||||
return (
|
||||
<Container>
|
||||
<h1>
|
||||
<i18n.Translate>Specify the amount and the origin</i18n.Translate>
|
||||
</h1>
|
||||
<Grid container columns={2} justifyContent="space-between">
|
||||
<AmountField
|
||||
label={<i18n.Translate>Amount</i18n.Translate>}
|
||||
required
|
||||
handler={{
|
||||
onInput: async (s) => setAmount(s),
|
||||
value: amount,
|
||||
}}
|
||||
/>
|
||||
<Button onClick={async () => setCurrency(undefined)}>
|
||||
<i18n.Translate>Change currency</i18n.Translate>
|
||||
</Button>
|
||||
</Grid>
|
||||
|
||||
<Grid container spacing={1} columns={1}>
|
||||
{previous.length > 0 ? (
|
||||
<Fragment>
|
||||
<p>
|
||||
<i18n.Translate>Use previous origins:</i18n.Translate>
|
||||
</p>
|
||||
<Grid item xs={1}>
|
||||
<Paper style={{ padding: 8 }}>
|
||||
<ContactTable>
|
||||
{previous.map((info, i) => (
|
||||
<tr key={i}>
|
||||
<td>
|
||||
<RowExample info={info} disabled={invalid} />
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</ContactTable>
|
||||
</Paper>
|
||||
</Grid>
|
||||
</Fragment>
|
||||
) : undefined}
|
||||
{previous.length > 0 ? (
|
||||
<Grid item>
|
||||
<p>
|
||||
<i18n.Translate>
|
||||
Or specify the origin of the money
|
||||
</i18n.Translate>
|
||||
</p>
|
||||
</Grid>
|
||||
) : (
|
||||
<Grid item>
|
||||
<p>
|
||||
<i18n.Translate>Specify the origin of the money</i18n.Translate>
|
||||
</p>
|
||||
</Grid>
|
||||
)}
|
||||
<Grid item container columns={2} spacing={1}>
|
||||
<Grid item xs={1}>
|
||||
<Paper style={{ padding: 8 }}>
|
||||
<p>
|
||||
<i18n.Translate>From my bank account</i18n.Translate>
|
||||
</p>
|
||||
<Button
|
||||
disabled={invalid}
|
||||
onClick={async () =>
|
||||
goToWalletManualWithdraw(currencyAndAmount)
|
||||
}
|
||||
>
|
||||
<i18n.Translate>Withdraw</i18n.Translate>
|
||||
</Button>
|
||||
</Paper>
|
||||
</Grid>
|
||||
<Grid item xs={1}>
|
||||
<Paper style={{ padding: 8 }}>
|
||||
<p>
|
||||
<i18n.Translate>From another wallet</i18n.Translate>
|
||||
</p>
|
||||
<Button
|
||||
disabled={invalid}
|
||||
onClick={async () => goToWalletWalletInvoice(currencyAndAmount)}
|
||||
>
|
||||
<i18n.Translate>Invoice</i18n.Translate>
|
||||
</Button>
|
||||
</Paper>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
|
||||
export function DestinationSelectionSendCash({
|
||||
amount: initialAmount,
|
||||
goToWalletBankDeposit,
|
||||
goToWalletWalletSend,
|
||||
}: PropsSend): VNode {
|
||||
const parsedInitialAmount = !initialAmount
|
||||
? undefined
|
||||
: Amounts.parse(initialAmount);
|
||||
|
||||
const currency = parsedInitialAmount?.currency;
|
||||
|
||||
const [amount, setAmount] = useState(
|
||||
parsedInitialAmount ?? Amounts.zeroOfCurrency(currency ?? "KUDOS"),
|
||||
);
|
||||
const { i18n } = useTranslationContext();
|
||||
const previous1: Contact[] = [];
|
||||
const previous2: Contact[] = [
|
||||
{
|
||||
name: "International Bank",
|
||||
icon: bankIcon,
|
||||
description: "account ending with 3454",
|
||||
},
|
||||
{
|
||||
name: "Max",
|
||||
icon: bankIcon,
|
||||
description: "account ending with 3454",
|
||||
},
|
||||
{
|
||||
name: "Alex",
|
||||
icon: bankIcon,
|
||||
description: "account ending with 3454",
|
||||
},
|
||||
];
|
||||
const previous = previous1;
|
||||
|
||||
if (!currency) {
|
||||
return (
|
||||
<div>
|
||||
<i18n.Translate>currency not provided</i18n.Translate>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
const currencyAndAmount = Amounts.stringify(amount);
|
||||
//const parsedAmount = Amounts.parse(currencyAndAmount);
|
||||
const invalid = Amounts.isZero(amount);
|
||||
return (
|
||||
<Container>
|
||||
<h1>
|
||||
<i18n.Translate>Specify the amount and the destination</i18n.Translate>
|
||||
</h1>
|
||||
|
||||
<div>
|
||||
<AmountField
|
||||
label={<i18n.Translate>Amount</i18n.Translate>}
|
||||
required
|
||||
handler={{
|
||||
onInput: async (s) => setAmount(s),
|
||||
value: amount,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<Grid container spacing={1} columns={1}>
|
||||
{previous.length > 0 ? (
|
||||
<Fragment>
|
||||
<p>
|
||||
<i18n.Translate>Use previous destinations:</i18n.Translate>
|
||||
</p>
|
||||
<Grid item xs={1}>
|
||||
<Paper style={{ padding: 8 }}>
|
||||
<ContactTable>
|
||||
{previous.map((info, i) => (
|
||||
<tr key={i}>
|
||||
<td>
|
||||
<RowExample info={info} disabled={invalid} />
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</ContactTable>
|
||||
</Paper>
|
||||
</Grid>
|
||||
</Fragment>
|
||||
) : undefined}
|
||||
{previous.length > 0 ? (
|
||||
<Grid item>
|
||||
<p>
|
||||
<i18n.Translate>
|
||||
Or specify the destination of the money
|
||||
</i18n.Translate>
|
||||
</p>
|
||||
</Grid>
|
||||
) : (
|
||||
<Grid item>
|
||||
<p>
|
||||
<i18n.Translate>
|
||||
Specify the destination of the money
|
||||
</i18n.Translate>
|
||||
</p>
|
||||
</Grid>
|
||||
)}
|
||||
<Grid item container columns={2} spacing={1}>
|
||||
<Grid item xs={1}>
|
||||
<Paper style={{ padding: 8 }}>
|
||||
<p>
|
||||
<i18n.Translate>To my bank account</i18n.Translate>
|
||||
</p>
|
||||
<Button
|
||||
disabled={invalid}
|
||||
onClick={async () => goToWalletBankDeposit(currencyAndAmount)}
|
||||
>
|
||||
<i18n.Translate>Deposit</i18n.Translate>
|
||||
</Button>
|
||||
</Paper>
|
||||
</Grid>
|
||||
<Grid item xs={1}>
|
||||
<Paper style={{ padding: 8 }}>
|
||||
<p>
|
||||
<i18n.Translate>To another wallet</i18n.Translate>
|
||||
</p>
|
||||
<Button
|
||||
disabled={invalid}
|
||||
onClick={async () => goToWalletWalletSend(currencyAndAmount)}
|
||||
>
|
||||
<i18n.Translate>Send</i18n.Translate>
|
||||
</Button>
|
||||
</Paper>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Container>
|
||||
);
|
||||
}
|
@ -21,8 +21,8 @@
|
||||
|
||||
import { expect } from "chai";
|
||||
|
||||
describe("test description", () => {
|
||||
it("should assert", () => {
|
||||
describe("Manage Account states", () => {
|
||||
it.skip("should create some tests", () => {
|
||||
expect([]).deep.equals([]);
|
||||
});
|
||||
});
|
||||
|
@ -21,8 +21,8 @@
|
||||
|
||||
import { expect } from "chai";
|
||||
|
||||
describe("test description", () => {
|
||||
it("should assert", () => {
|
||||
describe("Notifications states", () => {
|
||||
it.skip("should create some tests", () => {
|
||||
expect([]).deep.equals([]);
|
||||
});
|
||||
});
|
||||
|
@ -33,7 +33,7 @@ import * as a14 from "./Welcome.stories.js";
|
||||
import * as a15 from "./AddNewActionView.stories.js";
|
||||
import * as a16 from "./DeveloperPage.stories.js";
|
||||
import * as a17 from "./QrReader.stories.js";
|
||||
import * as a18 from "./DestinationSelection.stories.js";
|
||||
import * as a18 from "./DestinationSelection/stories.js";
|
||||
import * as a19 from "./ExchangeSelection/stories.js";
|
||||
import * as a20 from "./ManageAccount/stories.js";
|
||||
import * as a21 from "./Notifications/stories.js";
|
||||
|
Loading…
Reference in New Issue
Block a user