From 24cac493dded00ef40e0e30a0d2263e4f35c3e29 Mon Sep 17 00:00:00 2001 From: Sebastian Date: Wed, 4 Jan 2023 11:24:58 -0300 Subject: fix #7522 --- .../src/NavigationBar.tsx | 31 +- .../src/components/Banner.stories.tsx | 76 ++- .../src/components/Banner.tsx | 40 +- .../src/components/PaymentButtons.tsx | 153 +++++ .../src/components/PendingTransactions.tsx | 102 ++- .../src/components/ProductList.tsx | 89 +++ .../src/components/styled/index.tsx | 2 +- .../src/context/backend.ts | 2 +- .../src/cta/InvoicePay/views.tsx | 4 +- .../src/cta/Payment/views.tsx | 251 +------ .../src/cta/Refund/views.tsx | 2 +- .../src/cta/Withdraw/views.tsx | 16 +- .../src/popup/Application.tsx | 246 ++++--- .../src/serviceWorkerHttpLib.ts | 4 +- packages/taler-wallet-webextension/src/stories.tsx | 8 +- .../taler-wallet-webextension/src/utils/index.ts | 2 +- .../src/wallet/AddBackupProvider/index.ts | 1 - .../src/wallet/AddBackupProvider/state.ts | 1 - .../src/wallet/AddBackupProvider/test.ts | 1 - .../src/wallet/Application.tsx | 741 ++++++++++++--------- .../src/wallet/DeveloperPage.tsx | 33 +- 21 files changed, 997 insertions(+), 808 deletions(-) create mode 100644 packages/taler-wallet-webextension/src/components/PaymentButtons.tsx create mode 100644 packages/taler-wallet-webextension/src/components/ProductList.tsx (limited to 'packages/taler-wallet-webextension/src') diff --git a/packages/taler-wallet-webextension/src/NavigationBar.tsx b/packages/taler-wallet-webextension/src/NavigationBar.tsx index ab36af376..1c26450f7 100644 --- a/packages/taler-wallet-webextension/src/NavigationBar.tsx +++ b/packages/taler-wallet-webextension/src/NavigationBar.tsx @@ -45,6 +45,7 @@ import warningIcon from "./svg/warning_24px.svg"; * @author sebasjm */ +// eslint-disable-next-line @typescript-eslint/ban-types type PageLocation = { pattern: string; (params: DynamicPart): string; @@ -62,6 +63,7 @@ function replaceAll( return result; } +// eslint-disable-next-line @typescript-eslint/ban-types function pageDefinition(pattern: string): PageLocation { const patternParams = pattern.match(/(:[\w?]*)/g); if (!patternParams) @@ -133,7 +135,8 @@ export const Pages = { ), }; -export function PopupNavBar({ path = "" }: { path?: string }): VNode { +export type PopupNavBarOptions = "balance" | "backup" | "dev"; +export function PopupNavBar({ path }: { path?: PopupNavBarOptions }): VNode { const api = useBackendContext(); const hook = useAsyncAsHook(async () => { return await api.wallet.call( @@ -146,13 +149,10 @@ export function PopupNavBar({ path = "" }: { path?: string }): VNode { const { i18n } = useTranslationContext(); return ( - + Balance - + Backup
@@ -185,8 +185,8 @@ export function PopupNavBar({ path = "" }: { path?: string }): VNode { ); } - -export function WalletNavBar({ path = "" }: { path?: string }): VNode { +export type WalletNavBarOptions = "balance" | "backup" | "dev"; +export function WalletNavBar({ path }: { path?: WalletNavBarOptions }): VNode { const { i18n } = useTranslationContext(); const api = useBackendContext(); @@ -196,21 +196,16 @@ export function WalletNavBar({ path = "" }: { path?: string }): VNode { {}, ); }); - const attentionCount = !hook || hook.hasError ? 0 : hook.response.total; + const attentionCount = + (!hook || hook.hasError ? 0 : hook.response?.total) ?? 0; return ( - + Balance - + Backup @@ -223,7 +218,7 @@ export function WalletNavBar({ path = "" }: { path?: string }): VNode { )} - + Dev diff --git a/packages/taler-wallet-webextension/src/components/Banner.stories.tsx b/packages/taler-wallet-webextension/src/components/Banner.stories.tsx index 39012480b..60b100478 100644 --- a/packages/taler-wallet-webextension/src/components/Banner.stories.tsx +++ b/packages/taler-wallet-webextension/src/components/Banner.stories.tsx @@ -65,23 +65,25 @@ export const BasicExample = (): VNode => (

, - description: ( - - You have lost connection to the internet. This app is offline. - - ), - }, - ]} + // elements={[ + // { + // icon: , + // description: ( + // + // You have lost connection to the internet. This app is offline. + // + // ), + // }, + // ]} confirm={{ label: "turn on wifi", action: async () => { return; }, }} - /> + > +
+ ); @@ -92,31 +94,33 @@ export const PendingOperation = (): VNode => ( - P - - ), - description: ( - - - EUR 37.95 - -   - - 5 feb 2022 - - ), - }, - ]} - /> + // elements={[ + // { + // icon: ( + // + // P + // + // ), + // description: ( + // + // + // EUR 37.95 + // + //   + // - 5 feb 2022 + // + // ), + // }, + // ]} + > + asd + ); diff --git a/packages/taler-wallet-webextension/src/components/Banner.tsx b/packages/taler-wallet-webextension/src/components/Banner.tsx index f95647d42..a91fd384f 100644 --- a/packages/taler-wallet-webextension/src/components/Banner.tsx +++ b/packages/taler-wallet-webextension/src/components/Banner.tsx @@ -13,21 +13,20 @@ You should have received a copy of the GNU General Public License along with GNU Taler; see the file COPYING. If not, see */ -import { h, Fragment, VNode, JSX } from "preact"; -import { Divider } from "../mui/Divider.js"; +import { ComponentChildren, Fragment, h, JSX, VNode } from "preact"; import { Button } from "../mui/Button.js"; -import { Typography } from "../mui/Typography.js"; -import { Avatar } from "../mui/Avatar.js"; +import { Divider } from "../mui/Divider.js"; import { Grid } from "../mui/Grid.js"; import { Paper } from "../mui/Paper.js"; interface Props extends JSX.HTMLAttributes { titleHead?: VNode; - elements: { - icon?: VNode; - description: VNode; - action?: () => void; - }[]; + children: ComponentChildren; + // elements: { + // icon?: VNode; + // description: VNode; + // action?: () => void; + // }[]; confirm?: { label: string; action: () => Promise; @@ -36,8 +35,9 @@ interface Props extends JSX.HTMLAttributes { export function Banner({ titleHead, - elements, + children, confirm, + href, ...rest }: Props): VNode { return ( @@ -49,25 +49,7 @@ export function Banner({ )} - {elements.map((e, i) => ( - - {e.icon && ( - - {e.icon} - - )} - {e.description} - - ))} + {children} {confirm && ( diff --git a/packages/taler-wallet-webextension/src/components/PaymentButtons.tsx b/packages/taler-wallet-webextension/src/components/PaymentButtons.tsx new file mode 100644 index 000000000..def1e16eb --- /dev/null +++ b/packages/taler-wallet-webextension/src/components/PaymentButtons.tsx @@ -0,0 +1,153 @@ +/* + 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 { + AmountJson, + Amounts, + PreparePayResult, + PreparePayResultType, +} from "@gnu-taler/taler-util"; +import { Fragment, h, VNode } from "preact"; +import { useState } from "preact/hooks"; +import { Amount } from "./Amount.js"; +import { Part } from "./Part.js"; +import { QR } from "./QR.js"; +import { LinkSuccess, WarningBox } from "./styled/index.js"; +import { useTranslationContext } from "../context/translation.js"; +import { Button } from "../mui/Button.js"; +import { ButtonHandler } from "../mui/handlers.js"; +import { assertUnreachable } from "../utils/index.js"; + +interface Props { + payStatus: PreparePayResult; + payHandler: ButtonHandler | undefined; + balance: AmountJson | undefined; + uri: string; + amount: AmountJson; + goToWalletManualWithdraw: (currency: string) => Promise; +} + +export function PaymentButtons({ + payStatus, + uri, + payHandler, + balance, + amount, + goToWalletManualWithdraw, +}: Props): VNode { + const { i18n } = useTranslationContext(); + if (payStatus.status === PreparePayResultType.PaymentPossible) { + const privateUri = `${uri}&n=${payStatus.noncePriv}`; + + return ( + +
+ +
+ +
+ ); + } + + if (payStatus.status === PreparePayResultType.InsufficientBalance) { + let BalanceMessage = ""; + if (!balance) { + BalanceMessage = i18n.str`You have no balance for this currency. Withdraw digital cash first.`; + } else { + const balanceShouldBeEnough = Amounts.cmp(balance, amount) !== -1; + if (balanceShouldBeEnough) { + BalanceMessage = i18n.str`Could not find enough coins to pay. Even if you have enough ${balance.currency} some restriction may apply.`; + } else { + BalanceMessage = i18n.str`Your current balance is not enough.`; + } + } + const uriPrivate = `${uri}&n=${payStatus.noncePriv}`; + + return ( + +
+ {BalanceMessage} +
+
+ +
+ +
+ ); + } + if (payStatus.status === PreparePayResultType.AlreadyConfirmed) { + return ( + +
+ {payStatus.paid && payStatus.contractTerms.fulfillment_message && ( + Merchant message} + text={payStatus.contractTerms.fulfillment_message} + kind="neutral" + /> + )} +
+ {!payStatus.paid && } +
+ ); + } + + assertUnreachable(payStatus); +} + +function PayWithMobile({ uri }: { uri: string }): VNode { + const { i18n } = useTranslationContext(); + + const [showQR, setShowQR] = useState(false); + + return ( +
+ setShowQR((qr) => !qr)}> + {!showQR ? ( + Pay with a mobile phone + ) : ( + Hide QR + )} + + {showQR && ( +
+ + + Scan the QR code or   + + click here + + +
+ )} +
+ ); +} diff --git a/packages/taler-wallet-webextension/src/components/PendingTransactions.tsx b/packages/taler-wallet-webextension/src/components/PendingTransactions.tsx index 85b43fb4e..e41ff2836 100644 --- a/packages/taler-wallet-webextension/src/components/PendingTransactions.tsx +++ b/packages/taler-wallet-webextension/src/components/PendingTransactions.tsx @@ -26,6 +26,7 @@ import { useBackendContext } from "../context/backend.js"; import { useTranslationContext } from "../context/translation.js"; import { useAsyncAsHook } from "../hooks/useAsyncAsHook.js"; import { Avatar } from "../mui/Avatar.js"; +import { Grid } from "../mui/Grid.js"; import { Typography } from "../mui/Typography.js"; import Banner from "./Banner.js"; import { Time } from "./Time.js"; @@ -34,6 +35,11 @@ interface Props extends JSX.HTMLAttributes { goToTransaction: (id: string) => Promise; } +/** + * this cache will save the tx from the previous render + */ +const cache = { tx: [] as Transaction[] }; + export function PendingTransactions({ goToTransaction }: Props): VNode { const api = useBackendContext(); const state = useAsyncAsHook(() => @@ -49,12 +55,13 @@ export function PendingTransactions({ goToTransaction }: Props): VNode { const transactions = !state || state.hasError - ? [] + ? cache.tx : state.response.transactions.filter((t) => t.pending); - if (!state || state.hasError || !transactions.length) { + if (!transactions.length) { return ; } + cache.tx = transactions; return ( PENDING OPERATIONS} +
3 ? "scroll" : "hidden", + display: "flex", + justifyContent: "center", }} - elements={transactions.map((t) => { - const amount = Amounts.parseOrThrow(t.amountEffective); - return { - icon: ( - + PENDING OPERATIONS} + style={{ + backgroundColor: "lightcyan", + maxHeight: 150, + padding: 8, + flexGrow: 1, + maxWidth: 500, + overflowY: transactions.length > 3 ? "scroll" : "hidden", + }} + > + {transactions.map((t, i) => { + const amount = Amounts.parseOrThrow(t.amountEffective); + return ( + { + goToTransaction(t.transactionId); }} > - {t.type.substring(0, 1)} - - ), - action: () => goToTransaction(t.transactionId), - description: ( - - - {amount.currency} {Amounts.stringifyValue(amount)} - -  -  - - ), - }; - })} - /> + + + {t.type.substring(0, 1)} + + + + + + {amount.currency} {Amounts.stringifyValue(amount)} + +  -  + + + ); + })} + +
); } diff --git a/packages/taler-wallet-webextension/src/components/ProductList.tsx b/packages/taler-wallet-webextension/src/components/ProductList.tsx new file mode 100644 index 000000000..a78733179 --- /dev/null +++ b/packages/taler-wallet-webextension/src/components/ProductList.tsx @@ -0,0 +1,89 @@ +/* + 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, Product } from "@gnu-taler/taler-util"; +import { Fragment, h, VNode } from "preact"; +import { SmallLightText } from "./styled/index.js"; +import { useTranslationContext } from "../context/translation.js"; + +export function ProductList({ products }: { products: Product[] }): VNode { + const { i18n } = useTranslationContext(); + return ( + + + List of products + +
+ {products.map((p, i) => { + if (p.price) { + const pPrice = Amounts.parseOrThrow(p.price); + return ( +
+
+ +
+
+
+ {p.quantity ?? 1} x {p.description}{" "} + + {Amounts.stringify(pPrice)} + +
+
+ + {Amounts.stringify( + Amounts.mult(pPrice, p.quantity ?? 1).amount, + )} + +
+
+
+ ); + } + return ( +
+
+ +
+
+
+ {p.quantity ?? 1} x {p.description} +
+
+ Total + {` `} + {p.price ? ( + `${Amounts.stringifyValue( + Amounts.mult( + Amounts.parseOrThrow(p.price), + p.quantity ?? 1, + ).amount, + )} ${p}` + ) : ( + free + )} +
+
+
+ ); + })} +
+
+ ); +} diff --git a/packages/taler-wallet-webextension/src/components/styled/index.tsx b/packages/taler-wallet-webextension/src/components/styled/index.tsx index 7a3c27c73..8e98f75eb 100644 --- a/packages/taler-wallet-webextension/src/components/styled/index.tsx +++ b/packages/taler-wallet-webextension/src/components/styled/index.tsx @@ -159,7 +159,7 @@ export const Middle = styled.div` height: 100%; `; -export const PopupBox = styled.div<{ noPadding?: boolean; devMode?: boolean }>` +export const PopupBox = styled.div<{ noPadding?: boolean }>` height: 290px; width: 500px; overflow-y: visible; diff --git a/packages/taler-wallet-webextension/src/context/backend.ts b/packages/taler-wallet-webextension/src/context/backend.ts index e00a70080..280fb266d 100644 --- a/packages/taler-wallet-webextension/src/context/backend.ts +++ b/packages/taler-wallet-webextension/src/context/backend.ts @@ -29,7 +29,7 @@ const initial = wxApi; const Context = createContext(initial); -type Props = Partial & { +type Props = Partial & { children: ComponentChildren; }; diff --git a/packages/taler-wallet-webextension/src/cta/InvoicePay/views.tsx b/packages/taler-wallet-webextension/src/cta/InvoicePay/views.tsx index 8484680bf..a53fa881a 100644 --- a/packages/taler-wallet-webextension/src/cta/InvoicePay/views.tsx +++ b/packages/taler-wallet-webextension/src/cta/InvoicePay/views.tsx @@ -23,7 +23,7 @@ import { Part } from "../../components/Part.js"; import { Link, SubTitle, WalletAction } from "../../components/styled/index.js"; import { Time } from "../../components/Time.js"; import { useTranslationContext } from "../../context/translation.js"; -import { ButtonsSection } from "../Payment/views.js"; +import { PaymentButtons } from "../../components/PaymentButtons"; import { State } from "./index.js"; export function LoadingUriView({ error }: State.LoadingUriError): VNode { @@ -83,7 +83,7 @@ export function ReadyView( kind="neutral" /> - - - - - Digital cash payment - - +
- {/* {state.payStatus.status !== PreparePayResultType.InsufficientBalance && - Amounts.isNonZero(totalFees) && ( - Total to pay} - text={} - kind="negative" - /> - )} - Purchase amount} - text={} - kind="neutral" - /> - {Amounts.isNonZero(totalFees) && ( - - Fee} - text={} - kind="negative" - /> - - )} */} Purchase} text={contractTerms.summary} @@ -125,9 +75,6 @@ export function BaseView(state: SupportedStates): VNode { text={} kind="neutral" /> - {/*
{JSON.stringify(price)}
-
-
{JSON.stringify(state.payStatus, undefined, 2)}
*/} Details} text={ @@ -166,7 +113,7 @@ export function BaseView(state: SupportedStates): VNode { /> )}
- Cancel - - ); -} - -export function ProductList({ products }: { products: Product[] }): VNode { - const { i18n } = useTranslationContext(); - return ( - - - List of products - -
- {products.map((p, i) => { - if (p.price) { - const pPrice = Amounts.parseOrThrow(p.price); - return ( -
-
- -
-
-
- {p.quantity ?? 1} x {p.description}{" "} - - {Amounts.stringify(pPrice)} - -
-
- - {Amounts.stringify( - Amounts.mult(pPrice, p.quantity ?? 1).amount, - )} - -
-
-
- ); - } - return ( -
-
- -
-
-
- {p.quantity ?? 1} x {p.description} -
-
- Total - {` `} - {p.price ? ( - `${Amounts.stringifyValue( - Amounts.mult( - Amounts.parseOrThrow(p.price), - p.quantity ?? 1, - ).amount, - )} ${p}` - ) : ( - free - )} -
-
-
- ); - })} -
); } @@ -284,124 +162,3 @@ function ShowImportantMessage({ state }: { state: SupportedStates }): VNode { return ; } - -export function PayWithMobile({ uri }: { uri: string }): VNode { - const { i18n } = useTranslationContext(); - - const [showQR, setShowQR] = useState(false); - - return ( -
- setShowQR((qr) => !qr)}> - {!showQR ? ( - Pay with a mobile phone - ) : ( - Hide QR - )} - - {showQR && ( -
- - - Scan the QR code or   - - click here - - -
- )} -
- ); -} - -interface ButtonSectionProps { - payStatus: PreparePayResult; - payHandler: ButtonHandler | undefined; - balance: AmountJson | undefined; - uri: string; - amount: AmountJson; - goToWalletManualWithdraw: (currency: string) => Promise; -} - -export function ButtonsSection({ - payStatus, - uri, - payHandler, - balance, - amount, - goToWalletManualWithdraw, -}: ButtonSectionProps): VNode { - const { i18n } = useTranslationContext(); - if (payStatus.status === PreparePayResultType.PaymentPossible) { - const privateUri = `${uri}&n=${payStatus.noncePriv}`; - - return ( - -
- -
- -
- ); - } - - if (payStatus.status === PreparePayResultType.InsufficientBalance) { - let BalanceMessage = ""; - if (!balance) { - BalanceMessage = i18n.str`You have no balance for this currency. Withdraw digital cash first.`; - } else { - const balanceShouldBeEnough = Amounts.cmp(balance, amount) !== -1; - if (balanceShouldBeEnough) { - BalanceMessage = i18n.str`Could not find enough coins to pay. Even if you have enough ${balance.currency} some restriction may apply.`; - } else { - BalanceMessage = i18n.str`Your current balance is not enough.`; - } - } - const uriPrivate = `${uri}&n=${payStatus.noncePriv}`; - - return ( - -
- {BalanceMessage} -
-
- -
- -
- ); - } - if (payStatus.status === PreparePayResultType.AlreadyConfirmed) { - return ( - -
- {payStatus.paid && payStatus.contractTerms.fulfillment_message && ( - Merchant message} - text={payStatus.contractTerms.fulfillment_message} - kind="neutral" - /> - )} -
- {!payStatus.paid && } -
- ); - } - - assertUnreachable(payStatus); -} diff --git a/packages/taler-wallet-webextension/src/cta/Refund/views.tsx b/packages/taler-wallet-webextension/src/cta/Refund/views.tsx index 4b5ff70dd..a55bc43dd 100644 --- a/packages/taler-wallet-webextension/src/cta/Refund/views.tsx +++ b/packages/taler-wallet-webextension/src/cta/Refund/views.tsx @@ -23,7 +23,7 @@ import { Part } from "../../components/Part.js"; import { Link, SubTitle, WalletAction } from "../../components/styled/index.js"; import { useTranslationContext } from "../../context/translation.js"; import { Button } from "../../mui/Button.js"; -import { ProductList } from "../Payment/views.js"; +import { ProductList } from "../../components/ProductList.js"; import { State } from "./index.js"; export function LoadingUriView({ error }: State.LoadingUriError): VNode { diff --git a/packages/taler-wallet-webextension/src/cta/Withdraw/views.tsx b/packages/taler-wallet-webextension/src/cta/Withdraw/views.tsx index 5c35151c8..9dbe24b7e 100644 --- a/packages/taler-wallet-webextension/src/cta/Withdraw/views.tsx +++ b/packages/taler-wallet-webextension/src/cta/Withdraw/views.tsx @@ -14,12 +14,12 @@ GNU Taler; see the file COPYING. If not, see */ +import { ExchangeTosStatus } from "@gnu-taler/taler-util"; import { Fragment, h, VNode } from "preact"; import { useState } from "preact/hooks"; import { Amount } from "../../components/Amount.js"; import { ErrorTalerOperation } from "../../components/ErrorTalerOperation.js"; import { LoadingError } from "../../components/LoadingError.js"; -import { LogoHeader } from "../../components/LogoHeader.js"; import { Part } from "../../components/Part.js"; import { QR } from "../../components/QR.js"; import { SelectList } from "../../components/SelectList.js"; @@ -27,17 +27,14 @@ import { Input, Link, LinkSuccess, - SubTitle, SvgIcon, - WalletAction, } from "../../components/styled/index.js"; +import { TermsOfService } from "../../components/TermsOfService/index.js"; import { useTranslationContext } from "../../context/translation.js"; import { Button } from "../../mui/Button.js"; import editIcon from "../../svg/edit_24px.svg"; import { ExchangeDetails, WithdrawDetails } from "../../wallet/Transaction.js"; -import { TermsOfService } from "../../components/TermsOfService/index.js"; import { State } from "./index.js"; -import { ExchangeTosStatus } from "@gnu-taler/taler-util"; export function LoadingUriView({ error }: State.LoadingUriError): VNode { const { i18n } = useTranslationContext(); @@ -68,12 +65,7 @@ export function SuccessView(state: State.Success): VNode { const currentTosVersionIsAccepted = state.currentExchange.tosStatus === ExchangeTosStatus.Accepted; return ( - - - - Digital cash withdrawal - - + {state.doWithdrawal.error && ( Cancel - +
); } diff --git a/packages/taler-wallet-webextension/src/popup/Application.tsx b/packages/taler-wallet-webextension/src/popup/Application.tsx index 8186c6790..9cae0d048 100644 --- a/packages/taler-wallet-webextension/src/popup/Application.tsx +++ b/packages/taler-wallet-webextension/src/popup/Application.tsx @@ -21,7 +21,7 @@ */ import { createHashHistory } from "history"; -import { Fragment, h, VNode } from "preact"; +import { ComponentChildren, Fragment, h, VNode } from "preact"; import Router, { route, Route } from "preact-router"; import { Match } from "preact-router/match"; import { useEffect, useState } from "preact/hooks"; @@ -34,15 +34,28 @@ import { useTranslationContext, } from "../context/translation.js"; import { useTalerActionURL } from "../hooks/useTalerActionURL.js"; -import { Pages, PopupNavBar } from "../NavigationBar.js"; +import { PopupNavBarOptions, Pages, PopupNavBar } from "../NavigationBar.js"; import { platform } from "../platform/api.js"; import { BackupPage } from "../wallet/BackupPage.js"; import { ProviderDetailPage } from "../wallet/ProviderDetailPage.js"; import { BalancePage } from "./BalancePage.js"; import { TalerActionFound } from "./TalerActionFound.js"; -function CheckTalerActionComponent(): VNode { - const [action] = useTalerActionURL(); +export function Application(): VNode { + return ( + + + + + + + + ); +} +function ApplicationView(): VNode { + const hash_history = createHashHistory(); + + const [action, setDismissed] = useTalerActionURL(); const actionUri = action?.uri; @@ -52,116 +65,110 @@ function CheckTalerActionComponent(): VNode { } }, [actionUri]); - return ; -} + async function redirectToTxInfo(tid: string): Promise { + redirectTo(Pages.balanceTransaction({ tid })); + } -export function Application(): VNode { - const hash_history = createHashHistory(); return ( - - - {({ devMode }: { devMode: boolean }) => ( - - - redirectTo(Pages.balanceTransaction({ tid })) + + ( + + redirectTo(Pages.receiveCash({}))} + goToWalletDeposit={(currency: string) => + redirectTo(Pages.sendCash({ amount: `${currency}:0` })) + } + goToWalletHistory={(currency: string) => + redirectTo(Pages.balanceHistory({ currency })) } /> - - {({ path }: { path: string }) => } - - - - - - redirectTo(Pages.receiveCash({})) - } - goToWalletDeposit={(currency: string) => - redirectTo(Pages.sendCash({ amount: `${currency}:0` })) - } - goToWalletHistory={(currency: string) => - redirectTo(Pages.balanceHistory({ currency })) - } - /> - - { - setDismissed(true); - return redirectTo(Pages.balance); - }} - /> - ); - }} - /> - - redirectTo(Pages.backupProviderAdd)} - /> - redirectTo(Pages.backup)} - /> - - - - - - - - - - - - - - - - - - + )} - - + /> + + + { + setDismissed(true); + return redirectTo(Pages.balance); + }} + /> + + ); + }} + /> + + ( + + redirectTo(Pages.backupProviderAdd)} + /> + + )} + /> + ( + + + redirectTo(`${Pages.ctaPay}?talerPayUri=${uri}`) + } + onWithdraw={(amount: string) => + redirectTo(Pages.receiveCash({ amount })) + } + pid={pid} + onBack={() => redirectTo(Pages.backup)} + /> + + )} + /> + + + + + + + + + + + + + + + + + ); } @@ -195,3 +202,24 @@ function Redirect({ to }: { to: string }): null { }); return null; } + +function PopupTemplate({ + path, + children, + goToTransaction, +}: { + path?: PopupNavBarOptions; + children: ComponentChildren; + goToTransaction?: (id: string) => Promise; +}): VNode { + return ( + + {/* */} + {goToTransaction ? ( + + ) : undefined} + + {children} + + ); +} diff --git a/packages/taler-wallet-webextension/src/serviceWorkerHttpLib.ts b/packages/taler-wallet-webextension/src/serviceWorkerHttpLib.ts index c9327b8e6..82d11a15a 100644 --- a/packages/taler-wallet-webextension/src/serviceWorkerHttpLib.ts +++ b/packages/taler-wallet-webextension/src/serviceWorkerHttpLib.ts @@ -69,7 +69,7 @@ export class ServiceWorkerHttpLib implements HttpRequestLibrary { } else if (ArrayBuffer.isView(requestBody)) { myBody = requestBody; } else if (typeof requestBody === "object") { - myBody = JSON.stringify(myBody); + myBody = JSON.stringify(requestBody); } else { throw Error("unsupported request body type"); } @@ -127,8 +127,6 @@ export class ServiceWorkerHttpLib implements HttpRequestLibrary { }); } - // FIXME: "Content-Type: application/json" goes here, - // after Sebastian suggestion. postJson( url: string, body: any, diff --git a/packages/taler-wallet-webextension/src/stories.tsx b/packages/taler-wallet-webextension/src/stories.tsx index 8834b8084..a7b8a4d06 100644 --- a/packages/taler-wallet-webextension/src/stories.tsx +++ b/packages/taler-wallet-webextension/src/stories.tsx @@ -20,7 +20,11 @@ */ import { Fragment, FunctionComponent, h } from "preact"; import { LogoHeader } from "./components/LogoHeader.js"; -import { PopupBox, WalletBox } from "./components/styled/index.js"; +import { + PopupBox, + WalletAction, + WalletBox, +} from "./components/styled/index.js"; import { strings } from "./i18n/strings.js"; import { PopupNavBar, WalletNavBar } from "./NavigationBar.js"; @@ -72,7 +76,7 @@ function getWrapperForGroup(group: string): FunctionComponent { return function WalletWrapper({ children }: any) { return ( - {children} + {children} ); }; diff --git a/packages/taler-wallet-webextension/src/utils/index.ts b/packages/taler-wallet-webextension/src/utils/index.ts index c2d7c10a8..ad4eabf15 100644 --- a/packages/taler-wallet-webextension/src/utils/index.ts +++ b/packages/taler-wallet-webextension/src/utils/index.ts @@ -74,7 +74,7 @@ export async function queryToSlashKeys(url: string): Promise { return timeout(3000, query); } -export type StateFunc = (p: S) => VNode; +export type StateFunc = (p: S) => VNode | null; export type StateViewMap = { [S in StateType as S["status"]]: StateFunc; diff --git a/packages/taler-wallet-webextension/src/wallet/AddBackupProvider/index.ts b/packages/taler-wallet-webextension/src/wallet/AddBackupProvider/index.ts index 94020069b..10fcd84ce 100644 --- a/packages/taler-wallet-webextension/src/wallet/AddBackupProvider/index.ts +++ b/packages/taler-wallet-webextension/src/wallet/AddBackupProvider/index.ts @@ -32,7 +32,6 @@ import { } from "./views.js"; export interface Props { - currency: string; onBack: () => Promise; onComplete: (pid: string) => Promise; onPaymentRequired: (uri: string) => Promise; diff --git a/packages/taler-wallet-webextension/src/wallet/AddBackupProvider/state.ts b/packages/taler-wallet-webextension/src/wallet/AddBackupProvider/state.ts index 32c48be91..1b30ed0cd 100644 --- a/packages/taler-wallet-webextension/src/wallet/AddBackupProvider/state.ts +++ b/packages/taler-wallet-webextension/src/wallet/AddBackupProvider/state.ts @@ -144,7 +144,6 @@ function useUrlState( } export function useComponentState({ - currency, onBack, onComplete, onPaymentRequired, diff --git a/packages/taler-wallet-webextension/src/wallet/AddBackupProvider/test.ts b/packages/taler-wallet-webextension/src/wallet/AddBackupProvider/test.ts index 9abb672fa..3241a3ab0 100644 --- a/packages/taler-wallet-webextension/src/wallet/AddBackupProvider/test.ts +++ b/packages/taler-wallet-webextension/src/wallet/AddBackupProvider/test.ts @@ -26,7 +26,6 @@ import { Props } from "./index.js"; import { useComponentState } from "./state.js"; const props: Props = { - currency: "KUDOS", onBack: nullFunction, onComplete: nullFunction, onPaymentRequired: nullFunction, diff --git a/packages/taler-wallet-webextension/src/wallet/Application.tsx b/packages/taler-wallet-webextension/src/wallet/Application.tsx index d150ebfaf..8b77e152c 100644 --- a/packages/taler-wallet-webextension/src/wallet/Application.tsx +++ b/packages/taler-wallet-webextension/src/wallet/Application.tsx @@ -20,352 +20,452 @@ * @author sebasjm */ +import { TranslatedString } from "@gnu-taler/taler-util"; import { createHashHistory } from "history"; -import { Fragment, h, VNode } from "preact"; +import { ComponentChildren, Fragment, h, VNode } from "preact"; import Router, { route, Route } from "preact-router"; -import Match from "preact-router/match"; -import { useEffect, useState } from "preact/hooks"; +import { useEffect } from "preact/hooks"; import { LogoHeader } from "../components/LogoHeader.js"; import PendingTransactions from "../components/PendingTransactions.js"; -import { SuccessBox, WalletBox } from "../components/styled/index.js"; +import { + SubTitle, + WalletAction, + WalletBox, +} from "../components/styled/index.js"; import { DevContextProvider } from "../context/devContext.js"; import { IoCProviderForRuntime } from "../context/iocContext.js"; import { TranslationProvider, useTranslationContext, } from "../context/translation.js"; +import { DepositPage as DepositPageCTA } from "../cta/Deposit/index.js"; +import { InvoiceCreatePage } from "../cta/InvoiceCreate/index.js"; +import { InvoicePayPage } from "../cta/InvoicePay/index.js"; import { PaymentPage } from "../cta/Payment/index.js"; +import { RecoveryPage } from "../cta/Recovery/index.js"; import { RefundPage } from "../cta/Refund/index.js"; import { TipPage } from "../cta/Tip/index.js"; +import { TransferCreatePage } from "../cta/TransferCreate/index.js"; +import { TransferPickupPage } from "../cta/TransferPickup/index.js"; import { WithdrawPageFromParams, WithdrawPageFromURI, } from "../cta/Withdraw/index.js"; -import { DepositPage as DepositPageCTA } from "../cta/Deposit/index.js"; -import { Pages, WalletNavBar } from "../NavigationBar.js"; -import { DeveloperPage } from "./DeveloperPage.js"; +import { WalletNavBarOptions, Pages, WalletNavBar } from "../NavigationBar.js"; +import { platform } from "../platform/api.js"; +import { AddBackupProviderPage } from "./AddBackupProvider/index.js"; import { BackupPage } from "./BackupPage.js"; import { DepositPage } from "./DepositPage/index.js"; +import { DestinationSelectionPage } from "./DestinationSelection/index.js"; +import { DeveloperPage } from "./DeveloperPage.js"; import { ExchangeAddPage } from "./ExchangeAddPage.js"; import { HistoryPage } from "./History.js"; +import { NotificationsPage } from "./Notifications/index.js"; import { ProviderDetailPage } from "./ProviderDetailPage.js"; +import { QrReaderPage } from "./QrReader.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 { 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"; -import { TransferPickupPage } from "../cta/TransferPickup/index.js"; -import { InvoicePayPage } from "../cta/InvoicePay/index.js"; -import { RecoveryPage } from "../cta/Recovery/index.js"; -import { AddBackupProviderPage } from "./AddBackupProvider/index.js"; -import { NotificationsPage } from "./Notifications/index.js"; export function Application(): VNode { - const [globalNotification, setGlobalNotification] = useState< - VNode | undefined - >(undefined); - const hash_history = createHashHistory(); - function clearNotification(): void { - setGlobalNotification(undefined); - } - function clearNotificationWhenMovingOut(): void { - // const movingOutFromNotification = - // globalNotification && e.url !== globalNotification.to; - if (globalNotification) { - //&& movingOutFromNotification) { - setGlobalNotification(undefined); - } - } const { i18n } = useTranslationContext(); + const hash_history = createHashHistory(); + async function redirectToTxInfo(tid: string): Promise { + redirectTo(Pages.balanceTransaction({ tid })); + } return ( - {/* won't work in the first render if is not called first */} - {/* https://github.com/preactjs/preact-router/issues/415 */} - - {({ path }: { path: string }) => { - if (path && path.startsWith("/cta")) return; - return ( - - - - {shouldShowPendingOperations(path) && ( -
- - redirectTo(Pages.balanceTransaction({ tid })) - } - /> -
- )} -
- ); - }} -
-
- - {globalNotification && ( - -
{globalNotification}
-
- )} - - - - {/** - * BALANCE - */} + ( + + + + )} + /> - - redirectTo(Pages.sendCash({ amount: `${currency}:0` })) - } - goToWalletManualWithdraw={(currency?: string) => - redirectTo( - Pages.receiveCash({ - amount: !currency ? undefined : `${currency}:0`, - }), - ) - } - /> - - - redirectTo(Pages.balanceDeposit({ amount })) - } - goToWalletWalletSend={(amount: string) => - redirectTo(Pages.ctaTransferCreate({ amount })) - } - /> - - redirectTo(Pages.ctaWithdrawManual({ amount })) - } - goToWalletWalletInvoice={(amount?: string) => - redirectTo(Pages.ctaInvoiceCreate({ amount })) - } - /> + ( + + { + platform.openWalletURIFromPopup(talerActionUrl); + }} + /> + + )} + /> - - redirectTo(Pages.balanceHistory({ currency })) - } - /> + ( + + + + )} + /> + ( + + + + )} + /> + {/** + * SETTINGS + */} + ( + + redirectTo(Pages.balance)} /> + + )} + /> - { - redirectTo(Pages.balanceHistory({ currency })); - }} - onSuccess={(currency: string) => { - redirectTo(Pages.balanceHistory({ currency })); - setGlobalNotification( - - All done, your transaction is in progress - , - ); - }} - /> - {/** - * PENDING - */} - { - platform.openWalletURIFromPopup(talerActionUrl); - }} - /> + ( + + + redirectTo(Pages.sendCash({ amount: `${currency}:0` })) + } + goToWalletManualWithdraw={(currency?: string) => + redirectTo( + Pages.receiveCash({ + amount: !currency ? undefined : `${currency}:0`, + }), + ) + } + /> + + )} + /> + ( + + + redirectTo(Pages.balanceDeposit({ amount })) + } + goToWalletWalletSend={(amount: string) => + redirectTo(Pages.ctaTransferCreate({ amount })) + } + /> + + )} + /> + ( + + + redirectTo(Pages.ctaWithdrawManual({ amount })) + } + goToWalletWalletInvoice={(amount?: string) => + redirectTo(Pages.ctaInvoiceCreate({ amount })) + } + /> + + )} + /> - - + ( + + + redirectTo(Pages.balanceHistory({ currency })) + } + /> + + )} + /> - {/** - * BACKUP - */} - redirectTo(Pages.backupProviderAdd)} - /> - - redirectTo(`${Pages.ctaPay}?talerPayUri=${uri}`) - } - onWithdraw={(amount: string) => - redirectTo(Pages.receiveCash({ amount })) - } - onBack={() => redirectTo(Pages.backup)} - /> - - redirectTo(`${Pages.ctaPay}?talerPayUri=${uri}`) - } - onComplete={(pid: string) => - redirectTo(Pages.backupProviderDetail({ pid })) - } - onBack={() => redirectTo(Pages.backup)} - /> + ( + + { + redirectTo(Pages.balanceHistory({ currency })); + }} + onSuccess={(currency: string) => { + redirectTo(Pages.balanceHistory({ currency })); + }} + /> + + )} + /> - {/** - * SETTINGS - */} - redirectTo(Pages.balance)} - /> + ( + + redirectTo(Pages.backupProviderAdd)} + /> + + )} + /> + ( + + + redirectTo(`${Pages.ctaPay}?talerPayUri=${uri}`) + } + onWithdraw={(amount: string) => + redirectTo(Pages.receiveCash({ amount })) + } + onBack={() => redirectTo(Pages.backup)} + /> + + )} + /> + ( + + + redirectTo(`${Pages.ctaPay}?talerPayUri=${uri}`) + } + onComplete={(pid: string) => + redirectTo(Pages.backupProviderDetail({ pid })) + } + onBack={() => redirectTo(Pages.backup)} + /> + + )} + /> - {/** - * DEV - */} + {/** + * DEV + */} + ( + + + + )} + /> - + {/** + * CALL TO ACTION + */} + ( + + + redirectTo(Pages.receiveCash({ amount })) + } + cancel={() => redirectTo(Pages.balance)} + onSuccess={(tid: string) => + redirectTo(Pages.balanceTransaction({ tid })) + } + /> + + )} + /> + ( + + redirectTo(Pages.balance)} + onSuccess={(tid: string) => + redirectTo(Pages.balanceTransaction({ tid })) + } + /> + + )} + /> + ( + + redirectTo(Pages.balance)} + onSuccess={(tid: string) => + redirectTo(Pages.balanceTransaction({ tid })) + } + /> + + )} + /> + ( + + redirectTo(Pages.balance)} + onSuccess={(tid: string) => + redirectTo(Pages.balanceTransaction({ tid })) + } + /> + + )} + /> + ( + + redirectTo(Pages.balance)} + onSuccess={(tid: string) => + redirectTo(Pages.balanceTransaction({ tid })) + } + /> + + )} + /> + ( + + redirectTo(Pages.balance)} + onSuccess={(tid: string) => + redirectTo(Pages.balanceTransaction({ tid })) + } + /> + + )} + /> + ( + + redirectTo(Pages.balance)} + onSuccess={(tid: string) => + redirectTo(Pages.balanceTransaction({ tid })) + } + /> + + )} + /> + ( + + redirectTo(Pages.balance)} + onSuccess={(tid: string) => + redirectTo(Pages.balanceTransaction({ tid })) + } + /> + + )} + /> + ( + + + redirectTo(Pages.receiveCash({ amount })) + } + onClose={() => redirectTo(Pages.balance)} + onSuccess={(tid: string) => + redirectTo(Pages.balanceTransaction({ tid })) + } + /> + + )} + /> + ( + + redirectTo(Pages.balance)} + onSuccess={(tid: string) => + redirectTo(Pages.balanceTransaction({ tid })) + } + /> + + )} + /> + ( + + redirectTo(Pages.balance)} + onSuccess={() => redirectTo(Pages.backup)} + /> + + )} + /> - {/** - * CALL TO ACTION - */} - - redirectTo(Pages.receiveCash({ amount })) - } - cancel={() => redirectTo(Pages.balance)} - onSuccess={(tid: string) => - redirectTo(Pages.balanceTransaction({ tid })) - } - /> - redirectTo(Pages.balance)} - onSuccess={(tid: string) => - redirectTo(Pages.balanceTransaction({ tid })) - } - /> - redirectTo(Pages.balance)} - onSuccess={(tid: string) => - redirectTo(Pages.balanceTransaction({ tid })) - } - /> - redirectTo(Pages.balance)} - onSuccess={(tid: string) => - redirectTo(Pages.balanceTransaction({ tid })) - } - /> - redirectTo(Pages.balance)} - onSuccess={(tid: string) => - redirectTo(Pages.balanceTransaction({ tid })) - } - /> - redirectTo(Pages.balance)} - onSuccess={(tid: string) => - redirectTo(Pages.balanceTransaction({ tid })) - } - /> - redirectTo(Pages.balance)} - onSuccess={(tid: string) => - redirectTo(Pages.balanceTransaction({ tid })) - } - /> - redirectTo(Pages.balance)} - onSuccess={(tid: string) => - redirectTo(Pages.balanceTransaction({ tid })) - } - /> - - redirectTo(Pages.receiveCash({ amount })) - } - onClose={() => redirectTo(Pages.balance)} - onSuccess={(tid: string) => - redirectTo(Pages.balanceTransaction({ tid })) - } - /> - redirectTo(Pages.balance)} - onSuccess={(tid: string) => - redirectTo(Pages.balanceTransaction({ tid })) - } - /> - redirectTo(Pages.balance)} - onSuccess={() => redirectTo(Pages.backup)} - /> + {/** + * NOT FOUND + * all redirects should be at the end + */} + } + /> - {/** - * NOT FOUND - * all redirects should be at the end - */} - - - - -
+ } + /> +
@@ -403,3 +503,40 @@ function shouldShowPendingOperations(url: string): boolean { Pages.backup, ].some((p) => matchesRoute(url, p)); } + +function CallToActionTemplate({ + title, + children, +}: { + title: TranslatedString; + children: ComponentChildren; +}): VNode { + return ( + + + {title} + {children} + + ); +} + +function WalletTemplate({ + path, + children, + goToTransaction, +}: { + path?: WalletNavBarOptions; + children: ComponentChildren; + goToTransaction?: (id: string) => Promise; +}): VNode { + return ( + + + + {goToTransaction ? ( + + ) : undefined} + {children} + + ); +} diff --git a/packages/taler-wallet-webextension/src/wallet/DeveloperPage.tsx b/packages/taler-wallet-webextension/src/wallet/DeveloperPage.tsx index 4805c03ca..74e7ce611 100644 --- a/packages/taler-wallet-webextension/src/wallet/DeveloperPage.tsx +++ b/packages/taler-wallet-webextension/src/wallet/DeveloperPage.tsx @@ -92,6 +92,7 @@ type CoinsInfo = CoinDumpJson["coins"]; type CalculatedCoinfInfo = { ageKeysCount: number | undefined; denom_value: number; + denom_fraction: number; //remain_value: number; status: string; from_refresh: boolean; @@ -151,7 +152,8 @@ export function View({ } prev[cur.exchange_base_url].push({ ageKeysCount: cur.ageCommitmentProof?.proof.privateKeys.length, - denom_value: parseFloat(Amounts.stringifyValue(denom)), + denom_value: denom.value, + denom_fraction: denom.fraction, // remain_value: parseFloat( // Amounts.stringifyValue(Amounts.parseOrThrow(cur.remaining_value)), // ), @@ -340,7 +342,10 @@ export function View({ {Object.keys(money_by_exchange).map((ex, idx) => { const allcoins = money_by_exchange[ex]; allcoins.sort((a, b) => { - return b.denom_value - a.denom_value; + if (b.denom_value !== a.denom_value) { + return b.denom_value - a.denom_value; + } + return b.denom_fraction - a.denom_fraction; }); const coins = allcoins.reduce( @@ -407,11 +412,31 @@ function ShowAllCoins({ const { i18n } = useTranslationContext(); const [collapsedSpent, setCollapsedSpent] = useState(true); const [collapsedUnspent, setCollapsedUnspent] = useState(false); - const total = coins.usable.reduce((prev, cur) => prev + cur.denom_value, 0); + const totalUsable = coins.usable.reduce( + (prev, cur) => + Amounts.add(prev, { + currency: "NONE", + fraction: cur.denom_fraction, + value: cur.denom_value, + }).amount, + Amounts.zeroOfCurrency("NONE"), + ); + const totalSpent = coins.spent.reduce( + (prev, cur) => + Amounts.add(prev, { + currency: "NONE", + fraction: cur.denom_fraction, + value: cur.denom_value, + }).amount, + Amounts.zeroOfCurrency("NONE"), + ); return (

- {ex}: {total} {currencies[ex]} + {ex}: {Amounts.stringifyValue(totalUsable)} {currencies[ex]} +

+

+ spent: {Amounts.stringifyValue(totalSpent)} {currencies[ex]}

setCollapsedUnspent(true)}> -- cgit v1.2.3