From 3577227cc0ff0f9e0c422ae34c4407d88e98ec21 Mon Sep 17 00:00:00 2001 From: Sebastian Date: Mon, 28 Nov 2022 09:29:14 -0300 Subject: fix #7496 with unit tests --- .../src/components/TermsOfService/test.ts | 4 +- .../src/cta/InvoiceCreate/state.ts | 3 +- .../src/cta/InvoiceCreate/test.ts | 4 +- .../src/cta/InvoicePay/test.ts | 4 +- .../src/cta/TransferCreate/test.ts | 4 +- .../src/cta/TransferPickup/test.ts | 4 +- .../src/cta/Withdraw/state.ts | 3 +- .../taler-wallet-webextension/src/utils/index.ts | 2 +- .../src/wallet/Application.tsx | 12 +- .../src/wallet/DestinationSelection.stories.tsx | 44 -- .../src/wallet/DestinationSelection.tsx | 538 --------------------- .../src/wallet/DestinationSelection/index.ts | 93 ++++ .../src/wallet/DestinationSelection/state.ts | 167 +++++++ .../src/wallet/DestinationSelection/stories.tsx | 63 +++ .../src/wallet/DestinationSelection/test.ts | 136 ++++++ .../src/wallet/DestinationSelection/views.tsx | 413 ++++++++++++++++ .../src/wallet/ManageAccount/test.ts | 4 +- .../src/wallet/Notifications/test.ts | 4 +- .../src/wallet/index.stories.tsx | 2 +- 19 files changed, 895 insertions(+), 609 deletions(-) delete mode 100644 packages/taler-wallet-webextension/src/wallet/DestinationSelection.stories.tsx delete mode 100644 packages/taler-wallet-webextension/src/wallet/DestinationSelection.tsx create mode 100644 packages/taler-wallet-webextension/src/wallet/DestinationSelection/index.ts create mode 100644 packages/taler-wallet-webextension/src/wallet/DestinationSelection/state.ts create mode 100644 packages/taler-wallet-webextension/src/wallet/DestinationSelection/stories.tsx create mode 100644 packages/taler-wallet-webextension/src/wallet/DestinationSelection/test.ts create mode 100644 packages/taler-wallet-webextension/src/wallet/DestinationSelection/views.tsx 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 | (() => RecursiveState); - 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 | (() => RecursiveState); - 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 = { [S in StateType as S["status"]]: StateFunc; }; -type RecursiveState = S | (() => RecursiveState); +export type RecursiveState = S | (() => RecursiveState); export function compose( 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 { redirectTo(Pages.balanceDeposit({ amount })) } @@ -163,7 +160,8 @@ export function Application(): VNode { /> redirectTo(Pages.ctaWithdrawManual({ amount })) } diff --git a/packages/taler-wallet-webextension/src/wallet/DestinationSelection.stories.tsx b/packages/taler-wallet-webextension/src/wallet/DestinationSelection.stories.tsx deleted file mode 100644 index 166b2c007..000000000 --- a/packages/taler-wallet-webextension/src/wallet/DestinationSelection.stories.tsx +++ /dev/null @@ -1,44 +0,0 @@ -/* - 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 - */ - -/** - * - * @author Sebastian Javier Marchano (sebasjm) - */ - -import { createExample } from "../test-utils.js"; -import { - DestinationSelectionGetCash, - DestinationSelectionSendCash, - SelectCurrencyView, -} from "./DestinationSelection.js"; - -export default { - title: "wallet/destination", -}; - -export const GetCash = createExample(DestinationSelectionGetCash, { - amount: "usd:0", -}); -export const SendCash = createExample(DestinationSelectionSendCash, { - amount: "eur:1", -}); -export const SelectCurrency = createExample(SelectCurrencyView, { - list: { - "": "Select a currency", - USD: "USD", - }, -}); diff --git a/packages/taler-wallet-webextension/src/wallet/DestinationSelection.tsx b/packages/taler-wallet-webextension/src/wallet/DestinationSelection.tsx deleted file mode 100644 index 7e4c775e6..000000000 --- a/packages/taler-wallet-webextension/src/wallet/DestinationSelection.tsx +++ /dev/null @@ -1,538 +0,0 @@ -/* - 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 - */ - -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 { - 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"; - -const Container = styled.div` - display: flex; - flex-direction: column; - & > * { - margin: 8px; - } -`; - -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 { - padding: 8px; - & > div:not([data-disabled]):hover { - background-color: lightblue; - } - color: black; - div[data-disabled] > * { - color: gray; - } - } - - & > tr:nth-child(2n) { - background: #ebebeb; - } -`; - -const MediaExample = styled.div` - text-size-adjust: 100%; - color: inherit; - font-family: inherit; - font-size: inherit; - line-height: inherit; - text-transform: none; - text-align: left; - box-sizing: border-box; - align-items: center; - display: flex; - padding: 8px 8px; - - &[data-disabled]:hover { - cursor: inherit; - } - cursor: pointer; -`; - -const MediaLeft = styled.div` - text-size-adjust: 100%; - - color: inherit; - font-family: inherit; - font-size: inherit; - line-height: inherit; - text-transform: none; - text-align: left; - box-sizing: border-box; - padding-right: 8px; - display: block; -`; - -const MediaBody = styled.div` - text-size-adjust: 100%; - - font-family: inherit; - text-transform: none; - text-align: left; - box-sizing: border-box; - flex: 1 1; - font-size: 14px; - font-weight: 500; - line-height: 1.42857; -`; -const MediaRight = styled.div` - text-size-adjust: 100%; - - color: inherit; - font-family: inherit; - font-size: inherit; - line-height: inherit; - text-transform: none; - text-align: left; - box-sizing: border-box; - padding-left: 8px; -`; - -const CircleDiv = styled.div` - box-sizing: border-box; - align-items: center; - background-position: 50%; - background-repeat: no-repeat; - background-size: cover; - border-radius: 50%; - display: flex; - justify-content: center; - margin-left: auto; - margin-right: auto; - overflow: hidden; - text-align: center; - text-decoration: none; - text-transform: uppercase; - transition: background-color 0.15s ease, border-color 0.15s ease, - color 0.15s ease; - font-size: 16px; - background-color: #86a7bd1a; - height: 40px; - line-height: 40px; - width: 40px; - 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 ; - } - if (hook.hasError) { - return ( - Could not load list of exchange} - /> - ); - } - const list: Record = {}; - hook.response.exchanges.forEach((e) => { - if (e.currency) { - list[e.currency] = e.currency; - } - }); - list[""] = "Select a currency"; - return ; -} - -export function SelectCurrencyView({ - onChange, - list, -}: { - onChange: (s: string) => void; - list: Record; -}): VNode { - const { i18n } = useTranslationContext(); - - return ( - -

- - Choose a currency to proceed or add another exchange - -

- -

- - Known currencies} - list={list} - name="lang" - value={""} - onChange={(v) => onChange(v)} - /> - -

-
-
- - Add an exchange - -
- - ); -} - -function RowExample({ - info, - disabled, -}: { - info: Contact; - disabled?: boolean; -}): VNode { - return ( - - - - - - - - {info.name} - {info.description} - - - - - - ); -} - -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 ( -
- setCurrency(c)} /> -
- ); - } - const currencyAndAmount = Amounts.stringify(amount); - const invalid = Amounts.isZero(amount); - return ( - -

- Specify the amount and the origin -

- - Amount} - required - handler={{ - onInput: async (s) => setAmount(s), - value: amount, - }} - /> - - - - - {previous.length > 0 ? ( - -

- Use previous origins: -

- - - - {previous.map((info, i) => ( - - - - - - ))} - - - -
- ) : undefined} - {previous.length > 0 ? ( - -

- - Or specify the origin of the money - -

-
- ) : ( - -

- Specify the origin of the money -

-
- )} - - - -

- From my bank account -

- -
-
- - -

- From another wallet -

- -
-
-
-
-
- ); -} - -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 ( -
- currency not provided -
- ); - } - const currencyAndAmount = Amounts.stringify(amount); - //const parsedAmount = Amounts.parse(currencyAndAmount); - const invalid = Amounts.isZero(amount); - return ( - -

- Specify the amount and the destination -

- -
- Amount} - required - handler={{ - onInput: async (s) => setAmount(s), - value: amount, - }} - /> -
- - - {previous.length > 0 ? ( - -

- Use previous destinations: -

- - - - {previous.map((info, i) => ( - - - - - - ))} - - - -
- ) : undefined} - {previous.length > 0 ? ( - -

- - Or specify the destination of the money - -

-
- ) : ( - -

- - Specify the destination of the money - -

-
- )} - - - -

- To my bank account -

- -
-
- - -

- To another wallet -

- -
-
-
-
-
- ); -} 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 + */ + +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; + 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 = { + 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 + */ + +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 { + 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 = {}; + 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 new file mode 100644 index 000000000..b8d868683 --- /dev/null +++ b/packages/taler-wallet-webextension/src/wallet/DestinationSelection/stories.tsx @@ -0,0 +1,63 @@ +/* + 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 + */ + +/** + * + * @author Sebastian Javier Marchano (sebasjm) + */ + +import { createExample } from "../../test-utils.js"; +import { ReadyView, SelectCurrencyView } from "./views.js"; + +export default { + title: "wallet/destination", +}; + +export const GetCash = createExample(ReadyView, { + amountHandler: { + value: { + currency: "EUR", + fraction: 0, + value: 2, + }, + }, + goToBank: {}, + goToWallet: {}, + previous: [], + selectCurrency: {}, + type: "get", +}); +export const SendCash = createExample(ReadyView, { + amountHandler: { + value: { + currency: "EUR", + fraction: 0, + value: 1, + }, + }, + goToBank: {}, + goToWallet: {}, + previous: [], + selectCurrency: {}, + type: "send", +}); + +export const SelectCurrency = createExample(SelectCurrencyView, { + 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 + */ + +/** + * + * @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/views.tsx b/packages/taler-wallet-webextension/src/wallet/DestinationSelection/views.tsx new file mode 100644 index 000000000..ba8d65ffa --- /dev/null +++ b/packages/taler-wallet-webextension/src/wallet/DestinationSelection/views.tsx @@ -0,0 +1,413 @@ +/* + 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 + */ + +import { styled } from "@linaria/react"; +import { Fragment, h, VNode } from "preact"; +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 { 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 ( + Could not load} + error={error} + /> + ); +} + +export function SelectCurrencyView({ + currencies, + onCurrencySelected, +}: State.SelectCurrency): VNode { + const { i18n } = useTranslationContext(); + + return ( + +

+ + Choose a currency to proceed or add another exchange + +

+ +

+ + Known currencies} + list={currencies} + name="lang" + value={""} + onChange={(v) => onCurrencySelected(v)} + /> + +

+
+
+ + Add an exchange + +
+ + ); +} + +const Container = styled.div` + display: flex; + flex-direction: column; + & > * { + margin: 8px; + } +`; + +const ContactTable = styled.table` + width: 100%; + & > tr > td { + padding: 8px; + & > div:not([data-disabled]):hover { + background-color: lightblue; + } + color: black; + div[data-disabled] > * { + color: gray; + } + } + + & > tr:nth-child(2n) { + background: #ebebeb; + } +`; + +const MediaExample = styled.div` + text-size-adjust: 100%; + color: inherit; + font-family: inherit; + font-size: inherit; + line-height: inherit; + text-transform: none; + text-align: left; + box-sizing: border-box; + align-items: center; + display: flex; + padding: 8px 8px; + + &[data-disabled]:hover { + cursor: inherit; + } + cursor: pointer; +`; + +const MediaLeft = styled.div` + text-size-adjust: 100%; + + color: inherit; + font-family: inherit; + font-size: inherit; + line-height: inherit; + text-transform: none; + text-align: left; + box-sizing: border-box; + padding-right: 8px; + display: block; +`; + +const MediaBody = styled.div` + text-size-adjust: 100%; + + font-family: inherit; + text-transform: none; + text-align: left; + box-sizing: border-box; + flex: 1 1; + font-size: 14px; + font-weight: 500; + line-height: 1.42857; +`; +const MediaRight = styled.div` + text-size-adjust: 100%; + + color: inherit; + font-family: inherit; + font-size: inherit; + line-height: inherit; + text-transform: none; + text-align: left; + box-sizing: border-box; + padding-left: 8px; +`; + +const CircleDiv = styled.div` + box-sizing: border-box; + align-items: center; + background-position: 50%; + background-repeat: no-repeat; + background-size: cover; + border-radius: 50%; + display: flex; + justify-content: center; + margin-left: auto; + margin-right: auto; + overflow: hidden; + text-align: center; + text-decoration: none; + text-transform: uppercase; + transition: background-color 0.15s ease, border-color 0.15s ease, + color 0.15s ease; + font-size: 16px; + background-color: #86a7bd1a; + height: 40px; + line-height: 40px; + width: 40px; + border: none; +`; + +export function ReadyView(props: State.Ready): VNode { + switch (props.type) { + case "get": + return ReadyGetView(props); + case "send": + return ReadySendView(props); + default: + assertUnreachable(props.type); + } +} +export function ReadyGetView({ + amountHandler, + goToBank, + goToWallet, + selectCurrency, + previous, +}: State.Ready): VNode { + const { i18n } = useTranslationContext(); + + return ( + +

+ Specify the amount and the origin +

+ + Amount} + required + handler={amountHandler} + /> + + + + + {previous.length > 0 ? ( + +

+ Use previous origins: +

+ + + + {previous.map((info, i) => ( + + + + + + ))} + + + +
+ ) : undefined} + {previous.length > 0 ? ( + +

+ + Or specify the origin of the money + +

+
+ ) : ( + +

+ Specify the origin of the money +

+
+ )} + + + +

+ From my bank account +

+ +
+
+ + +

+ From another wallet +

+ +
+
+
+
+
+ ); +} +export function ReadySendView({ + amountHandler, + goToBank, + goToWallet, + previous, +}: State.Ready): VNode { + const { i18n } = useTranslationContext(); + + return ( + +

+ Specify the amount and the destination +

+ +
+ Amount} + required + handler={amountHandler} + /> +
+ + + {previous.length > 0 ? ( + +

+ Use previous destinations: +

+ + + + {previous.map((info, i) => ( + + + + + + ))} + + + +
+ ) : undefined} + {previous.length > 0 ? ( + +

+ + Or specify the destination of the money + +

+
+ ) : ( + +

+ + Specify the destination of the money + +

+
+ )} + + + +

+ To my bank account +

+ +
+
+ + +

+ To another wallet +

+ +
+
+
+
+
+ ); +} + +function RowExample({ + info, + disabled, +}: { + info: Contact; + disabled?: boolean; +}): VNode { + return ( + + + + + + + + {info.name} + {info.description} + + + + + + ); +} 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"; -- cgit v1.2.3