diff options
17 files changed, 576 insertions, 290 deletions
diff --git a/packages/taler-wallet-webextension/src/components/TermsOfService/test.ts b/packages/taler-wallet-webextension/src/components/TermsOfService/test.ts index eae4d4ca2..170e7cad8 100644 --- a/packages/taler-wallet-webextension/src/components/TermsOfService/test.ts +++ b/packages/taler-wallet-webextension/src/components/TermsOfService/test.ts @@ -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([]); }); }); diff --git a/packages/taler-wallet-webextension/src/cta/InvoiceCreate/state.ts b/packages/taler-wallet-webextension/src/cta/InvoiceCreate/state.ts index 4ce81dad8..6007b5193 100644 --- a/packages/taler-wallet-webextension/src/cta/InvoiceCreate/state.ts +++ b/packages/taler-wallet-webextension/src/cta/InvoiceCreate/state.ts @@ -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, diff --git a/packages/taler-wallet-webextension/src/cta/InvoiceCreate/test.ts b/packages/taler-wallet-webextension/src/cta/InvoiceCreate/test.ts index eae4d4ca2..3ebedfd5a 100644 --- a/packages/taler-wallet-webextension/src/cta/InvoiceCreate/test.ts +++ b/packages/taler-wallet-webextension/src/cta/InvoiceCreate/test.ts @@ -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([]); }); }); diff --git a/packages/taler-wallet-webextension/src/cta/InvoicePay/test.ts b/packages/taler-wallet-webextension/src/cta/InvoicePay/test.ts index eae4d4ca2..4a3d08ed0 100644 --- a/packages/taler-wallet-webextension/src/cta/InvoicePay/test.ts +++ b/packages/taler-wallet-webextension/src/cta/InvoicePay/test.ts @@ -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([]); }); }); diff --git a/packages/taler-wallet-webextension/src/cta/TransferCreate/test.ts b/packages/taler-wallet-webextension/src/cta/TransferCreate/test.ts index eae4d4ca2..be753e492 100644 --- a/packages/taler-wallet-webextension/src/cta/TransferCreate/test.ts +++ b/packages/taler-wallet-webextension/src/cta/TransferCreate/test.ts @@ -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([]); }); }); diff --git a/packages/taler-wallet-webextension/src/cta/TransferPickup/test.ts b/packages/taler-wallet-webextension/src/cta/TransferPickup/test.ts index eae4d4ca2..fa5b6979a 100644 --- a/packages/taler-wallet-webextension/src/cta/TransferPickup/test.ts +++ b/packages/taler-wallet-webextension/src/cta/TransferPickup/test.ts @@ -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([]); }); }); diff --git a/packages/taler-wallet-webextension/src/cta/Withdraw/state.ts b/packages/taler-wallet-webextension/src/cta/Withdraw/state.ts index 016027163..9bb29fbd6 100644 --- a/packages/taler-wallet-webextension/src/cta/Withdraw/state.ts +++ b/packages/taler-wallet-webextension/src/cta/Withdraw/state.ts @@ -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, diff --git a/packages/taler-wallet-webextension/src/utils/index.ts b/packages/taler-wallet-webextension/src/utils/index.ts index 23cfc7730..c2d7c10a8 100644 --- a/packages/taler-wallet-webextension/src/utils/index.ts +++ b/packages/taler-wallet-webextension/src/utils/index.ts @@ -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, diff --git a/packages/taler-wallet-webextension/src/wallet/Application.tsx b/packages/taler-wallet-webextension/src/wallet/Application.tsx index 6362f1924..d150ebfaf 100644 --- a/packages/taler-wallet-webextension/src/wallet/Application.tsx +++ b/packages/taler-wallet-webextension/src/wallet/Application.tsx @@ -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 })) } diff --git a/packages/taler-wallet-webextension/src/wallet/DestinationSelection/index.ts b/packages/taler-wallet-webextension/src/wallet/DestinationSelection/index.ts new file mode 100644 index 000000000..492da193b --- /dev/null +++ b/packages/taler-wallet-webextension/src/wallet/DestinationSelection/index.ts @@ -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, +); diff --git a/packages/taler-wallet-webextension/src/wallet/DestinationSelection/state.ts b/packages/taler-wallet-webextension/src/wallet/DestinationSelection/state.ts new file mode 100644 index 000000000..fe02151de --- /dev/null +++ b/packages/taler-wallet-webextension/src/wallet/DestinationSelection/state.ts @@ -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); + } +} diff --git a/packages/taler-wallet-webextension/src/wallet/DestinationSelection.stories.tsx b/packages/taler-wallet-webextension/src/wallet/DestinationSelection/stories.tsx index 166b2c007..b8d868683 100644 --- a/packages/taler-wallet-webextension/src/wallet/DestinationSelection.stories.tsx +++ b/packages/taler-wallet-webextension/src/wallet/DestinationSelection/stories.tsx @@ -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", }, diff --git a/packages/taler-wallet-webextension/src/wallet/DestinationSelection/test.ts b/packages/taler-wallet-webextension/src/wallet/DestinationSelection/test.ts new file mode 100644 index 000000000..c2aa04849 --- /dev/null +++ b/packages/taler-wallet-webextension/src/wallet/DestinationSelection/test.ts @@ -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"); + }); +}); diff --git a/packages/taler-wallet-webextension/src/wallet/DestinationSelection.tsx b/packages/taler-wallet-webextension/src/wallet/DestinationSelection/views.tsx index 7e4c775e6..ba8d65ffa 100644 --- a/packages/taler-wallet-webextension/src/wallet/DestinationSelection.tsx +++ b/packages/taler-wallet-webextension/src/wallet/DestinationSelection/views.tsx @@ -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,156 +188,26 @@ 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> - - <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> - </div> - </Fragment> - ); -} - -function RowExample({ - info, - disabled, -}: { - info: Contact; - disabled?: boolean; -}): VNode { - return ( - <MediaExample data-disabled={disabled}> - <MediaLeft> - <CircleDiv> - <SvgIcon - title={info.name} - dangerouslySetInnerHTML={{ __html: info.icon }} - color="currentColor" - /> - </CircleDiv> - </MediaLeft> - <MediaBody> - <span>{info.name}</span> - <LightText>{info.description}</LightText> - </MediaBody> - <MediaRight> - <SvgIcon - title="Select this contact" - dangerouslySetInnerHTML={{ __html: arrowIcon }} - color="currentColor" - transform="rotate(-90deg)" - /> - </MediaRight> - </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> @@ -323,12 +216,9 @@ export function DestinationSelectionGetCash({ <AmountField label={<i18n.Translate>Amount</i18n.Translate>} required - handler={{ - onInput: async (s) => setAmount(s), - value: amount, - }} + handler={amountHandler} /> - <Button onClick={async () => setCurrency(undefined)}> + <Button onClick={selectCurrency.onClick}> <i18n.Translate>Change currency</i18n.Translate> </Button> </Grid> @@ -345,7 +235,10 @@ export function DestinationSelectionGetCash({ {previous.map((info, i) => ( <tr key={i}> <td> - <RowExample info={info} disabled={invalid} /> + <RowExample + info={info} + disabled={!amountHandler.onInput} + /> </td> </tr> ))} @@ -375,12 +268,7 @@ export function DestinationSelectionGetCash({ <p> <i18n.Translate>From my bank account</i18n.Translate> </p> - <Button - disabled={invalid} - onClick={async () => - goToWalletManualWithdraw(currencyAndAmount) - } - > + <Button onClick={goToBank.onClick}> <i18n.Translate>Withdraw</i18n.Translate> </Button> </Paper> @@ -390,10 +278,7 @@ export function DestinationSelectionGetCash({ <p> <i18n.Translate>From another wallet</i18n.Translate> </p> - <Button - disabled={invalid} - onClick={async () => goToWalletWalletInvoice(currencyAndAmount)} - > + <Button onClick={goToWallet.onClick}> <i18n.Translate>Invoice</i18n.Translate> </Button> </Paper> @@ -403,52 +288,14 @@ export function DestinationSelectionGetCash({ </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"), - ); +export function ReadySendView({ + amountHandler, + goToBank, + goToWallet, + previous, +}: State.Ready): VNode { 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> @@ -459,10 +306,7 @@ export function DestinationSelectionSendCash({ <AmountField label={<i18n.Translate>Amount</i18n.Translate>} required - handler={{ - onInput: async (s) => setAmount(s), - value: amount, - }} + handler={amountHandler} /> </div> @@ -478,7 +322,10 @@ export function DestinationSelectionSendCash({ {previous.map((info, i) => ( <tr key={i}> <td> - <RowExample info={info} disabled={invalid} /> + <RowExample + info={info} + disabled={!amountHandler.onInput} + /> </td> </tr> ))} @@ -510,10 +357,7 @@ export function DestinationSelectionSendCash({ <p> <i18n.Translate>To my bank account</i18n.Translate> </p> - <Button - disabled={invalid} - onClick={async () => goToWalletBankDeposit(currencyAndAmount)} - > + <Button onClick={goToBank.onClick}> <i18n.Translate>Deposit</i18n.Translate> </Button> </Paper> @@ -523,10 +367,7 @@ export function DestinationSelectionSendCash({ <p> <i18n.Translate>To another wallet</i18n.Translate> </p> - <Button - disabled={invalid} - onClick={async () => goToWalletWalletSend(currencyAndAmount)} - > + <Button onClick={goToWallet.onClick}> <i18n.Translate>Send</i18n.Translate> </Button> </Paper> @@ -536,3 +377,37 @@ export function DestinationSelectionSendCash({ </Container> ); } + +function RowExample({ + info, + disabled, +}: { + info: Contact; + disabled?: boolean; +}): VNode { + return ( + <MediaExample data-disabled={disabled}> + <MediaLeft> + <CircleDiv> + <SvgIcon + title={info.name} + dangerouslySetInnerHTML={{ __html: info.icon }} + color="currentColor" + /> + </CircleDiv> + </MediaLeft> + <MediaBody> + <span>{info.name}</span> + <LightText>{info.description}</LightText> + </MediaBody> + <MediaRight> + <SvgIcon + title="Select this contact" + dangerouslySetInnerHTML={{ __html: arrowIcon }} + color="currentColor" + transform="rotate(-90deg)" + /> + </MediaRight> + </MediaExample> + ); +} diff --git a/packages/taler-wallet-webextension/src/wallet/ManageAccount/test.ts b/packages/taler-wallet-webextension/src/wallet/ManageAccount/test.ts index eae4d4ca2..868269ec0 100644 --- a/packages/taler-wallet-webextension/src/wallet/ManageAccount/test.ts +++ b/packages/taler-wallet-webextension/src/wallet/ManageAccount/test.ts @@ -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([]); }); }); diff --git a/packages/taler-wallet-webextension/src/wallet/Notifications/test.ts b/packages/taler-wallet-webextension/src/wallet/Notifications/test.ts index eae4d4ca2..c4ce1efc7 100644 --- a/packages/taler-wallet-webextension/src/wallet/Notifications/test.ts +++ b/packages/taler-wallet-webextension/src/wallet/Notifications/test.ts @@ -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([]); }); }); diff --git a/packages/taler-wallet-webextension/src/wallet/index.stories.tsx b/packages/taler-wallet-webextension/src/wallet/index.stories.tsx index 20de1a3c3..554527108 100644 --- a/packages/taler-wallet-webextension/src/wallet/index.stories.tsx +++ b/packages/taler-wallet-webextension/src/wallet/index.stories.tsx @@ -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"; |