diff options
Diffstat (limited to 'packages/taler-wallet-webextension/src/wallet')
14 files changed, 360 insertions, 476 deletions
diff --git a/packages/taler-wallet-webextension/src/wallet/Application.tsx b/packages/taler-wallet-webextension/src/wallet/Application.tsx index caed45d33..253d5fba7 100644 --- a/packages/taler-wallet-webextension/src/wallet/Application.tsx +++ b/packages/taler-wallet-webextension/src/wallet/Application.tsx @@ -60,8 +60,10 @@ import { DestinationSelectionSendCash, } from "./DestinationSelection.js"; import { ExchangeSelectionPage } from "./ExchangeSelection/index.js"; -import { InvoicePage } from "./Invoice/index.js"; -import { SendPage } from "./Send/index.js"; +import { TransferCreatePage } from "../cta/TransferCreate/index.js"; +import { InvoiceCreatePage } from "../cta/InvoiceCreate/index.js"; +import { TransferPickupPage } from "../cta/TransferPickup/index.js"; +import { InvoicePayPage } from "../cta/InvoicePay/index.js"; export function Application(): VNode { const [globalNotification, setGlobalNotification] = useState< @@ -153,7 +155,7 @@ export function Application(): VNode { redirectTo(Pages.balanceDeposit({ amount })) } goToWalletWalletSend={(amount: string) => - redirectTo(Pages.send({ amount })) + redirectTo(Pages.ctaTransferCreate({ amount })) } /> <Route @@ -163,11 +165,9 @@ export function Application(): VNode { redirectTo(Pages.ctaWithdrawManual({ amount })) } goToWalletWalletInvoice={(amount?: string) => - redirectTo(Pages.invoice({ amount })) + redirectTo(Pages.ctaInvoiceCreate({ amount })) } /> - <Route path={Pages.invoice.pattern} component={InvoicePage} /> - <Route path={Pages.send.pattern} component={SendPage} /> <Route path={Pages.balanceTransaction.pattern} @@ -275,6 +275,20 @@ export function Application(): VNode { component={DepositPageCTA} cancel={() => redirectTo(Pages.balance)} /> + <Route + path={Pages.ctaInvoiceCreate.pattern} + component={InvoiceCreatePage} + /> + <Route + path={Pages.ctaTransferCreate.pattern} + component={TransferCreatePage} + /> + + <Route path={Pages.ctaInvoicePay} component={InvoicePayPage} /> + <Route + path={Pages.ctaTransferPickup} + component={TransferPickupPage} + /> {/** * NOT FOUND diff --git a/packages/taler-wallet-webextension/src/wallet/History.stories.tsx b/packages/taler-wallet-webextension/src/wallet/History.stories.tsx index 9577bb9d7..335d5ea9c 100644 --- a/packages/taler-wallet-webextension/src/wallet/History.stories.tsx +++ b/packages/taler-wallet-webextension/src/wallet/History.stories.tsx @@ -25,6 +25,10 @@ import { TransactionCommon, TransactionDeposit, TransactionPayment, + TransactionPeerPullCredit, + TransactionPeerPullDebit, + TransactionPeerPushCredit, + TransactionPeerPushDebit, TransactionRefresh, TransactionRefund, TransactionTip, @@ -118,6 +122,31 @@ const exampleData = { }, refundPending: undefined, } as TransactionRefund, + push_credit: { + ...commonTransaction(), + type: TransactionType.PeerPushCredit, + + exchangeBaseUrl: "https://exchange.taler.net", + } as TransactionPeerPushCredit, + push_debit: { + ...commonTransaction(), + type: TransactionType.PeerPushDebit, + talerUri: + "taler://pay-push/exchange.taler.ar/HS585JK0QCXHJ8Z8QWZA3EBAY5WY7XNC1RR2MHJXSH2Z4WP0YPJ0", + exchangeBaseUrl: "https://exchange.taler.net", + } as TransactionPeerPushDebit, + pull_credit: { + ...commonTransaction(), + type: TransactionType.PeerPullCredit, + talerUri: + "taler://pay-push/exchange.taler.ar/HS585JK0QCXHJ8Z8QWZA3EBAY5WY7XNC1RR2MHJXSH2Z4WP0YPJ0", + exchangeBaseUrl: "https://exchange.taler.net", + } as TransactionPeerPullCredit, + pull_debit: { + ...commonTransaction(), + type: TransactionType.PeerPullDebit, + exchangeBaseUrl: "https://exchange.taler.net", + } as TransactionPeerPullDebit, }; export const NoBalance = createExample(TestedComponent, { @@ -327,3 +356,21 @@ export const FiveOfficialCurrenciesWithHighValue = createExample( ], }, ); + +export const PeerToPeer = createExample(TestedComponent, { + transactions: [ + exampleData.pull_credit, + exampleData.pull_debit, + exampleData.push_credit, + exampleData.push_debit, + ], + balances: [ + { + available: "USD:10", + pendingIncoming: "USD:0", + pendingOutgoing: "USD:0", + hasPendingTransactions: false, + requiresUserInput: false, + }, + ], +}); diff --git a/packages/taler-wallet-webextension/src/wallet/Invoice/index.ts b/packages/taler-wallet-webextension/src/wallet/Invoice/index.ts deleted file mode 100644 index 20d902e65..000000000 --- a/packages/taler-wallet-webextension/src/wallet/Invoice/index.ts +++ /dev/null @@ -1,66 +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 <http://www.gnu.org/licenses/> - */ - -import { Loading } from "../../components/Loading.js"; -import { HookError } from "../../hooks/useAsyncAsHook.js"; -import { compose, StateViewMap } from "../../utils/index.js"; -import { LoadingUriView, ReadyView } from "./views.js"; -import * as wxApi from "../../wxApi.js"; -import { useComponentState } from "./state.js"; -import { AmountJson } from "@gnu-taler/taler-util"; -import { TextFieldHandler } from "../../mui/handlers.js"; - -export interface Props { - p: string; -} - -export type State = - | State.Loading - | State.LoadingUriError - | State.Ready; - -export namespace State { - - export interface Loading { - status: "loading"; - error: undefined; - } - - export interface LoadingUriError { - status: "loading-uri"; - error: HookError; - } - - export interface BaseInfo { - error: undefined; - } - export interface Ready extends BaseInfo { - status: "ready"; - amount: AmountJson; - subject: TextFieldHandler; - error: undefined; - } -} - -const viewMapping: StateViewMap<State> = { - loading: Loading, - "loading-uri": LoadingUriView, - "ready": ReadyView, -}; - - -export const InvoicePage = compose("InvoicePage", (p: Props) => useComponentState(p, wxApi), viewMapping) - diff --git a/packages/taler-wallet-webextension/src/wallet/Invoice/state.ts b/packages/taler-wallet-webextension/src/wallet/Invoice/state.ts deleted file mode 100644 index 48cfd359b..000000000 --- a/packages/taler-wallet-webextension/src/wallet/Invoice/state.ts +++ /dev/null @@ -1,37 +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 <http://www.gnu.org/licenses/> - */ - -import { Amounts } from "@gnu-taler/taler-util"; -import { useState } from "preact/hooks"; -import * as wxApi from "../../wxApi.js"; -import { Props, State } from "./index.js"; - -export function useComponentState( - { p }: Props, - api: typeof wxApi, -): State { - const [subject, setSubject] = useState(""); - const amount = Amounts.parseOrThrow("ARS:0") - return { - status: "ready", - subject: { - value: subject, - onInput: async (e) => setSubject(e) - }, - amount, - error: undefined, - } -} diff --git a/packages/taler-wallet-webextension/src/wallet/Invoice/stories.tsx b/packages/taler-wallet-webextension/src/wallet/Invoice/stories.tsx deleted file mode 100644 index 75f78be1d..000000000 --- a/packages/taler-wallet-webextension/src/wallet/Invoice/stories.tsx +++ /dev/null @@ -1,29 +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 <http://www.gnu.org/licenses/> - */ - -/** - * - * @author Sebastian Javier Marchano (sebasjm) - */ - -import { createExample } from "../../test-utils.js"; -import { ReadyView } from "./views.js"; - -export default { - title: "wallet/invoice", -}; - -export const Ready = createExample(ReadyView, {}); diff --git a/packages/taler-wallet-webextension/src/wallet/Invoice/test.ts b/packages/taler-wallet-webextension/src/wallet/Invoice/test.ts deleted file mode 100644 index 631e76d01..000000000 --- a/packages/taler-wallet-webextension/src/wallet/Invoice/test.ts +++ /dev/null @@ -1,31 +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 <http://www.gnu.org/licenses/> - */ - -/** - * - * @author Sebastian Javier Marchano (sebasjm) - */ - -import { expect } from "chai"; - -describe("test description", () => { - - it("should assert", () => { - - expect([]).deep.equals([]) - }); -}) - diff --git a/packages/taler-wallet-webextension/src/wallet/Invoice/views.tsx b/packages/taler-wallet-webextension/src/wallet/Invoice/views.tsx deleted file mode 100644 index 94e8f8625..000000000 --- a/packages/taler-wallet-webextension/src/wallet/Invoice/views.tsx +++ /dev/null @@ -1,56 +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 <http://www.gnu.org/licenses/> - */ - -import { Amounts } from "@gnu-taler/taler-util"; -import { styled } from "@linaria/react"; -import { h, VNode } from "preact"; -import { LoadingError } from "../../components/LoadingError.js"; -import { useTranslationContext } from "../../context/translation.js"; -import { Button } from "../../mui/Button.js"; -import { TextField } from "../../mui/TextField.js"; -import { State } from "./index.js"; - -export function LoadingUriView({ error }: State.LoadingUriError): VNode { - const { i18n } = useTranslationContext(); - - return ( - <LoadingError - title={<i18n.Translate>Could not load</i18n.Translate>} - error={error} - /> - ); -} - -const Container = styled.div``; - -export function ReadyView({ amount, subject }: State.Ready): VNode { - const { i18n } = useTranslationContext(); - - return ( - <Container> - <p>Creating an invoice of {Amounts.stringify(amount)}</p> - <TextField - label="Subject" - variant="filled" - required - value={subject.value} - onChange={subject.onInput} - /> - <p>to:</p> - <Button>Scan QR code</Button> - </Container> - ); -} diff --git a/packages/taler-wallet-webextension/src/wallet/Send/index.ts b/packages/taler-wallet-webextension/src/wallet/Send/index.ts deleted file mode 100644 index fb69c6280..000000000 --- a/packages/taler-wallet-webextension/src/wallet/Send/index.ts +++ /dev/null @@ -1,67 +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 <http://www.gnu.org/licenses/> - */ - -import { Loading } from "../../components/Loading.js"; -import { HookError } from "../../hooks/useAsyncAsHook.js"; -import { compose, StateViewMap } from "../../utils/index.js"; -import { LoadingUriView, ReadyView } from "./views.js"; -import * as wxApi from "../../wxApi.js"; -import { useComponentState } from "./state.js"; -import { AmountJson } from "@gnu-taler/taler-util"; -import { SelectFieldHandler, TextFieldHandler } from "../../mui/handlers.js"; - -export interface Props { - p: string; -} - -export type State = - | State.Loading - | State.LoadingUriError - | State.Ready; - -export namespace State { - - export interface Loading { - status: "loading"; - error: undefined; - } - - export interface LoadingUriError { - status: "loading-uri"; - error: HookError; - } - - export interface BaseInfo { - error: undefined; - } - export interface Ready extends BaseInfo { - status: "ready"; - amount: AmountJson; - exchange: SelectFieldHandler, - subject: TextFieldHandler, - error: undefined; - } -} - -const viewMapping: StateViewMap<State> = { - loading: Loading, - "loading-uri": LoadingUriView, - "ready": ReadyView, -}; - - -export const SendPage = compose("SendPage", (p: Props) => useComponentState(p, wxApi), viewMapping) - diff --git a/packages/taler-wallet-webextension/src/wallet/Send/state.ts b/packages/taler-wallet-webextension/src/wallet/Send/state.ts deleted file mode 100644 index 1359c1804..000000000 --- a/packages/taler-wallet-webextension/src/wallet/Send/state.ts +++ /dev/null @@ -1,66 +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 <http://www.gnu.org/licenses/> - */ - -import { Amounts } from "@gnu-taler/taler-util"; -import { useState } from "preact/hooks"; -import { useAsyncAsHook } from "../../hooks/useAsyncAsHook.js"; -import * as wxApi from "../../wxApi.js"; -import { Props, State } from "./index.js"; - -export function useComponentState( - { p }: Props, - api: typeof wxApi, -): State { - const [subject, setSubject] = useState(""); - const amount = Amounts.parseOrThrow("ARS:0") - - const hook = useAsyncAsHook(api.listExchanges); - const [exchangeIdx, setExchangeIdx] = useState("0") - - if (!hook) { - return { - status: "loading", - error: undefined, - } - } - if (hook.hasError) { - return { - status: "loading-uri", - error: hook, - }; - } - - const exchanges = hook.response.exchanges; - const exchangeMap = exchanges.reduce((prev, cur, idx) => ({ ...prev, [cur.exchangeBaseUrl]: String(idx) }), {} as Record<string, string>) - const selected = exchanges[Number(exchangeIdx)]; - - return { - status: "ready", - exchange: { - list: exchangeMap, - value: exchangeIdx, - onChange: async (v) => { - setExchangeIdx(v) - } - }, - subject: { - value: subject, - onInput: async (e) => setSubject(e) - }, - amount, - error: undefined, - } -} diff --git a/packages/taler-wallet-webextension/src/wallet/Send/stories.tsx b/packages/taler-wallet-webextension/src/wallet/Send/stories.tsx deleted file mode 100644 index 75f78be1d..000000000 --- a/packages/taler-wallet-webextension/src/wallet/Send/stories.tsx +++ /dev/null @@ -1,29 +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 <http://www.gnu.org/licenses/> - */ - -/** - * - * @author Sebastian Javier Marchano (sebasjm) - */ - -import { createExample } from "../../test-utils.js"; -import { ReadyView } from "./views.js"; - -export default { - title: "wallet/invoice", -}; - -export const Ready = createExample(ReadyView, {}); diff --git a/packages/taler-wallet-webextension/src/wallet/Send/test.ts b/packages/taler-wallet-webextension/src/wallet/Send/test.ts deleted file mode 100644 index 631e76d01..000000000 --- a/packages/taler-wallet-webextension/src/wallet/Send/test.ts +++ /dev/null @@ -1,31 +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 <http://www.gnu.org/licenses/> - */ - -/** - * - * @author Sebastian Javier Marchano (sebasjm) - */ - -import { expect } from "chai"; - -describe("test description", () => { - - it("should assert", () => { - - expect([]).deep.equals([]) - }); -}) - diff --git a/packages/taler-wallet-webextension/src/wallet/Send/views.tsx b/packages/taler-wallet-webextension/src/wallet/Send/views.tsx deleted file mode 100644 index 63310f443..000000000 --- a/packages/taler-wallet-webextension/src/wallet/Send/views.tsx +++ /dev/null @@ -1,58 +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 <http://www.gnu.org/licenses/> - */ - -import { Amounts } from "@gnu-taler/taler-util"; -import { styled } from "@linaria/react"; -import { h, VNode } from "preact"; -import { useState } from "preact/hooks"; -import { LoadingError } from "../../components/LoadingError.js"; -import { SelectList } from "../../components/SelectList.js"; -import { Input } from "../../components/styled/index.js"; -import { useTranslationContext } from "../../context/translation.js"; -import { Button } from "../../mui/Button.js"; -import { TextField } from "../../mui/TextField.js"; -import { State } from "./index.js"; - -export function LoadingUriView({ error }: State.LoadingUriError): VNode { - const { i18n } = useTranslationContext(); - - return ( - <LoadingError - title={<i18n.Translate>Could not load</i18n.Translate>} - error={error} - /> - ); -} - -const Container = styled.div``; - -export function ReadyView({ amount, exchange, subject }: State.Ready): VNode { - const { i18n } = useTranslationContext(); - return ( - <Container> - <p>Sending {Amounts.stringify(amount)}</p> - <TextField - label="Subject" - variant="filled" - required - value={subject.value} - onChange={subject.onInput} - /> - <p>to:</p> - <Button>Scan QR code</Button> - </Container> - ); -} diff --git a/packages/taler-wallet-webextension/src/wallet/Transaction.stories.tsx b/packages/taler-wallet-webextension/src/wallet/Transaction.stories.tsx index ba61e35f3..6c591611a 100644 --- a/packages/taler-wallet-webextension/src/wallet/Transaction.stories.tsx +++ b/packages/taler-wallet-webextension/src/wallet/Transaction.stories.tsx @@ -25,6 +25,10 @@ import { TransactionCommon, TransactionDeposit, TransactionPayment, + TransactionPeerPullCredit, + TransactionPeerPullDebit, + TransactionPeerPushCredit, + TransactionPeerPushDebit, TransactionRefresh, TransactionRefund, TransactionTip, @@ -139,6 +143,30 @@ const exampleData = { }, refundPending: undefined, } as TransactionRefund, + push_credit: { + ...commonTransaction, + type: TransactionType.PeerPushCredit, + exchangeBaseUrl: "https://exchange.taler.net", + } as TransactionPeerPushCredit, + push_debit: { + ...commonTransaction, + type: TransactionType.PeerPushDebit, + talerUri: + "taler://pay-push/exchange.taler.ar/HS585JK0QCXHJ8Z8QWZA3EBAY5WY7XNC1RR2MHJXSH2Z4WP0YPJ0", + exchangeBaseUrl: "https://exchange.taler.net", + } as TransactionPeerPushDebit, + pull_credit: { + ...commonTransaction, + type: TransactionType.PeerPullCredit, + talerUri: + "taler://pay-push/exchange.taler.ar/HS585JK0QCXHJ8Z8QWZA3EBAY5WY7XNC1RR2MHJXSH2Z4WP0YPJ0", + exchangeBaseUrl: "https://exchange.taler.net", + } as TransactionPeerPullCredit, + pull_debit: { + ...commonTransaction, + type: TransactionType.PeerPullDebit, + exchangeBaseUrl: "https://exchange.taler.net", + } as TransactionPeerPullDebit, }; const transactionError = { @@ -498,3 +526,19 @@ export const RefundError = createExample(TestedComponent, { export const RefundPending = createExample(TestedComponent, { transaction: { ...exampleData.refund, pending: true }, }); + +export const InvoiceCredit = createExample(TestedComponent, { + transaction: { ...exampleData.pull_credit }, +}); + +export const InvoiceDebit = createExample(TestedComponent, { + transaction: { ...exampleData.pull_debit }, +}); + +export const TransferCredit = createExample(TestedComponent, { + transaction: { ...exampleData.push_credit }, +}); + +export const TransferDebit = createExample(TestedComponent, { + transaction: { ...exampleData.push_debit }, +}); diff --git a/packages/taler-wallet-webextension/src/wallet/Transaction.tsx b/packages/taler-wallet-webextension/src/wallet/Transaction.tsx index ff3b70b65..c8c4e3ae1 100644 --- a/packages/taler-wallet-webextension/src/wallet/Transaction.tsx +++ b/packages/taler-wallet-webextension/src/wallet/Transaction.tsx @@ -45,6 +45,7 @@ import { ErrorTalerOperation } from "../components/ErrorTalerOperation.js"; import { Loading } from "../components/Loading.js"; import { LoadingError } from "../components/LoadingError.js"; import { Kind, Part, PartCollapsible, PartPayto } from "../components/Part.js"; +import { QR } from "../components/QR.js"; import { ShowFullContractTermPopup } from "../components/ShowFullContractTermPopup.js"; import { CenteredDialog, @@ -557,6 +558,172 @@ export function TransactionView({ ); } + function ShowQrWithCopy({ text }: { text: string }): VNode { + const [showing, setShowing] = useState(false); + async function copy(): Promise<void> { + navigator.clipboard.writeText(text); + } + async function toggle(): Promise<void> { + setShowing((s) => !s); + } + if (showing) { + return ( + <div> + <QR text={text} /> + <Button onClick={copy}>copy</Button> + <Button onClick={toggle}>hide qr</Button> + </div> + ); + } + return ( + <div> + <div>{text.substring(0, 64)}...</div> + <Button onClick={copy}>copy</Button> + <Button onClick={toggle}>show qr</Button> + </div> + ); + } + + if (transaction.type === TransactionType.PeerPullCredit) { + const total = Amounts.parseOrThrow(transaction.amountEffective); + return ( + <TransactionTemplate> + <Header + timestamp={transaction.timestamp} + type={i18n.str`Credit`} + total={total} + kind="positive" + > + Invoice + </Header> + + <Part + title={<i18n.Translate>Exchange</i18n.Translate>} + text={transaction.exchangeBaseUrl} + kind="neutral" + /> + <Part + title={<i18n.Translate>URI</i18n.Translate>} + text={<ShowQrWithCopy text={transaction.talerUri} />} + kind="neutral" + /> + <Part + title={<i18n.Translate>Details</i18n.Translate>} + text={ + <InvoiceDetails + amount={{ + effective: Amounts.parseOrThrow(transaction.amountEffective), + raw: Amounts.parseOrThrow(transaction.amountRaw), + }} + /> + } + /> + </TransactionTemplate> + ); + } + + if (transaction.type === TransactionType.PeerPullDebit) { + const total = Amounts.parseOrThrow(transaction.amountEffective); + return ( + <TransactionTemplate> + <Header + timestamp={transaction.timestamp} + type={i18n.str`Debit`} + total={total} + kind="negative" + > + Invoice + </Header> + + <Part + title={<i18n.Translate>Exchange</i18n.Translate>} + text={transaction.exchangeBaseUrl} + kind="neutral" + /> + <Part + title={<i18n.Translate>Details</i18n.Translate>} + text={ + <InvoiceDetails + amount={{ + effective: Amounts.parseOrThrow(transaction.amountEffective), + raw: Amounts.parseOrThrow(transaction.amountRaw), + }} + /> + } + /> + </TransactionTemplate> + ); + } + if (transaction.type === TransactionType.PeerPushDebit) { + const total = Amounts.parseOrThrow(transaction.amountEffective); + return ( + <TransactionTemplate> + <Header + timestamp={transaction.timestamp} + type={i18n.str`Debit`} + total={total} + kind="negative" + > + Transfer + </Header> + + <Part + title={<i18n.Translate>Exchange</i18n.Translate>} + text={transaction.exchangeBaseUrl} + kind="neutral" + /> + <Part + title={<i18n.Translate>URI</i18n.Translate>} + text={<QR text={transaction.talerUri} />} + kind="neutral" + /> + <Part + title={<i18n.Translate>Details</i18n.Translate>} + text={ + <TransferDetails + amount={{ + effective: Amounts.parseOrThrow(transaction.amountEffective), + raw: Amounts.parseOrThrow(transaction.amountRaw), + }} + /> + } + /> + </TransactionTemplate> + ); + } + + if (transaction.type === TransactionType.PeerPushCredit) { + const total = Amounts.parseOrThrow(transaction.amountEffective); + return ( + <TransactionTemplate> + <Header + timestamp={transaction.timestamp} + type={i18n.str`Credit`} + total={total} + kind="positive" + > + Transfer + </Header> + + <Part + title={<i18n.Translate>Exchange</i18n.Translate>} + text={transaction.exchangeBaseUrl} + kind="neutral" + /> + <Part + title={<i18n.Translate>Details</i18n.Translate>} + text={ + <TransferDetails + amount={{ + effective: Amounts.parseOrThrow(transaction.amountEffective), + raw: Amounts.parseOrThrow(transaction.amountRaw), + }} + /> + } + /> + </TransactionTemplate> + ); + } return <div />; } @@ -736,6 +903,88 @@ export interface AmountWithFee { raw: AmountJson; } +export function InvoiceDetails({ amount }: { amount: AmountWithFee }): VNode { + const { i18n } = useTranslationContext(); + + const fee = Amounts.sub(amount.raw, amount.effective).amount; + + const maxFrac = [amount.raw, amount.effective, fee] + .map((a) => Amounts.maxFractionalDigits(a)) + .reduce((c, p) => Math.max(c, p), 0); + + return ( + <PurchaseDetailsTable> + <tr> + <td>Invoice</td> + <td> + <Amount value={amount.raw} maxFracSize={maxFrac} /> + </td> + </tr> + + {Amounts.isNonZero(fee) && ( + <tr> + <td>Transaction fees</td> + <td> + <Amount value={fee} negative maxFracSize={maxFrac} /> + </td> + </tr> + )} + <tr> + <td colSpan={2}> + <hr /> + </td> + </tr> + <tr> + <td>Total</td> + <td> + <Amount value={amount.effective} maxFracSize={maxFrac} /> + </td> + </tr> + </PurchaseDetailsTable> + ); +} + +export function TransferDetails({ amount }: { amount: AmountWithFee }): VNode { + const { i18n } = useTranslationContext(); + + const fee = Amounts.sub(amount.raw, amount.effective).amount; + + const maxFrac = [amount.raw, amount.effective, fee] + .map((a) => Amounts.maxFractionalDigits(a)) + .reduce((c, p) => Math.max(c, p), 0); + + return ( + <PurchaseDetailsTable> + <tr> + <td>Transfer</td> + <td> + <Amount value={amount.raw} maxFracSize={maxFrac} /> + </td> + </tr> + + {Amounts.isNonZero(fee) && ( + <tr> + <td>Transaction fees</td> + <td> + <Amount value={fee} negative maxFracSize={maxFrac} /> + </td> + </tr> + )} + <tr> + <td colSpan={2}> + <hr /> + </td> + </tr> + <tr> + <td>Total</td> + <td> + <Amount value={amount.effective} maxFracSize={maxFrac} /> + </td> + </tr> + </PurchaseDetailsTable> + ); +} + export function WithdrawDetails({ amount }: { amount: AmountWithFee }): VNode { const { i18n } = useTranslationContext(); |