summaryrefslogtreecommitdiff
path: root/packages/taler-wallet-webextension/src/wallet/DestinationSelection
diff options
context:
space:
mode:
Diffstat (limited to 'packages/taler-wallet-webextension/src/wallet/DestinationSelection')
-rw-r--r--packages/taler-wallet-webextension/src/wallet/DestinationSelection/index.ts93
-rw-r--r--packages/taler-wallet-webextension/src/wallet/DestinationSelection/state.ts167
-rw-r--r--packages/taler-wallet-webextension/src/wallet/DestinationSelection/stories.tsx63
-rw-r--r--packages/taler-wallet-webextension/src/wallet/DestinationSelection/test.ts136
-rw-r--r--packages/taler-wallet-webextension/src/wallet/DestinationSelection/views.tsx413
5 files changed, 872 insertions, 0 deletions
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
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 <http://www.gnu.org/licenses/>
+ */
+
+/**
+ *
+ * @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 <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/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 <http://www.gnu.org/licenses/>
+ */
+
+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 (
+ <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;
+ 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 (
+ <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>
+
+ <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>
+
+ <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>
+ );
+}
+
+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>
+ );
+}