diff options
Diffstat (limited to 'packages/taler-wallet-webextension/src/popup/BalancePage.tsx')
-rw-r--r-- | packages/taler-wallet-webextension/src/popup/BalancePage.tsx | 294 |
1 files changed, 167 insertions, 127 deletions
diff --git a/packages/taler-wallet-webextension/src/popup/BalancePage.tsx b/packages/taler-wallet-webextension/src/popup/BalancePage.tsx index 8e5c5c42e..93770312e 100644 --- a/packages/taler-wallet-webextension/src/popup/BalancePage.tsx +++ b/packages/taler-wallet-webextension/src/popup/BalancePage.tsx @@ -1,155 +1,195 @@ /* - This file is part of TALER - (C) 2016 GNUnet e.V. + This file is part of GNU Taler + (C) 2022 Taler Systems S.A. - TALER is free software; you can redistribute it and/or modify it under the + 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. - TALER is distributed in the hope that it will be useful, but WITHOUT ANY + 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 - TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> */ import { - amountFractionalBase, Amounts, - Balance, BalancesResponse, - i18n + Amounts, + NotificationType, + WalletBalance, } from "@gnu-taler/taler-util"; -import { JSX, h, Fragment } from "preact"; -import { ErrorMessage } from "../components/ErrorMessage"; -import { PopupBox, Centered, ButtonPrimary, ErrorBox, Middle } from "../components/styled/index"; -import { BalancesHook, useBalances } from "../hooks/useBalances"; -import { PageLink, renderAmount } from "../renderHtml"; - - -export function BalancePage({ goToWalletManualWithdraw }: { goToWalletManualWithdraw: () => void }) { - const balance = useBalances() - return <BalanceView balance={balance} Linker={PageLink} goToWalletManualWithdraw={goToWalletManualWithdraw} /> -} -export interface BalanceViewProps { - balance: BalancesHook; - Linker: typeof PageLink; - goToWalletManualWithdraw: () => void; +import { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; +import { useTranslationContext } from "@gnu-taler/web-util/browser"; +import { Fragment, VNode, h } from "preact"; +import { useEffect, useState } from "preact/hooks"; +import { BalanceTable } from "../components/BalanceTable.js"; +import { ErrorAlertView } from "../components/CurrentAlerts.js"; +import { Loading } from "../components/Loading.js"; +import { MultiActionButton } from "../components/MultiActionButton.js"; +import { + ErrorAlert, + alertFromError, + useAlertContext, +} from "../context/alert.js"; +import { useBackendContext } from "../context/backend.js"; +import { useAsyncAsHook } from "../hooks/useAsyncAsHook.js"; +import { Button } from "../mui/Button.js"; +import { ButtonHandler } from "../mui/handlers.js"; +import { StateViewMap, compose } from "../utils/index.js"; +import { AddNewActionView } from "../wallet/AddNewActionView.js"; +import { NoBalanceHelp } from "./NoBalanceHelp.js"; + +export interface Props { + goToWalletDeposit: (currency: string) => Promise<void>; + goToWalletHistory: (currency: string) => Promise<void>; + goToWalletManualWithdraw: () => Promise<void>; } -function formatPending(entry: Balance): JSX.Element { - let incoming: JSX.Element | undefined; - let payment: JSX.Element | undefined; - - const available = Amounts.parseOrThrow(entry.available); - const pendingIncoming = Amounts.parseOrThrow(entry.pendingIncoming); - const pendingOutgoing = Amounts.parseOrThrow(entry.pendingOutgoing); - - if (!Amounts.isZero(pendingIncoming)) { - incoming = ( - <span><i18n.Translate> - <span style={{ color: "darkgreen" }} title="incoming amount"> - {"+"} - {renderAmount(entry.pendingIncoming)} - </span>{" "} - </i18n.Translate></span> - ); +export type State = State.Loading | State.Error | State.Action | State.Balances; + +export namespace State { + export interface Loading { + status: "loading"; + error: undefined; } - if (!Amounts.isZero(pendingOutgoing)) { - payment = ( - <span><i18n.Translate> - <span style={{ color: "darkred" }} title="outgoing amount"> - {"-"} - {renderAmount(entry.pendingOutgoing)} - </span>{" "} - </i18n.Translate></span> - ); + + export interface Error { + status: "error"; + error: ErrorAlert; } - const l = [incoming, payment].filter((x) => x !== undefined); - if (l.length === 0) { - return <span />; + export interface Action { + status: "action"; + error: undefined; + cancel: ButtonHandler; } - if (l.length === 1) { - return <span>{l}</span>; + export interface Balances { + status: "balance"; + error: undefined; + balances: WalletBalance[]; + addAction: ButtonHandler; + goToWalletDeposit: (currency: string) => Promise<void>; + goToWalletHistory: (currency: string) => Promise<void>; + goToWalletManualWithdraw: ButtonHandler; } - return ( - <span> - {l[0]}, {l[1]} - </span> +} + +function useComponentState({ + goToWalletDeposit, + goToWalletHistory, + goToWalletManualWithdraw, +}: Props): State { + const api = useBackendContext(); + const { i18n } = useTranslationContext(); + const { pushAlertOnError } = useAlertContext(); + const [addingAction, setAddingAction] = useState(false); + const state = useAsyncAsHook(() => + api.wallet.call(WalletApiOperation.GetBalances, {}), ); + + useEffect(() => + api.listener.onUpdateNotification( + [NotificationType.TransactionStateTransition], + state?.retry, + ), + ); + + if (!state) { + return { + status: "loading", + error: undefined, + }; + } + if (state.hasError) { + return { + status: "error", + error: alertFromError( i18n, + i18n.str`Could not load the balance`, state), + }; + } + if (addingAction) { + return { + status: "action", + error: undefined, + cancel: { + onClick: pushAlertOnError(async () => setAddingAction(false)), + }, + }; + } + return { + status: "balance", + error: undefined, + balances: state.response.balances, + addAction: { + onClick: pushAlertOnError(async () => setAddingAction(true)), + }, + goToWalletManualWithdraw: { + onClick: pushAlertOnError(goToWalletManualWithdraw), + }, + goToWalletDeposit, + goToWalletHistory, + }; } +const viewMapping: StateViewMap<State> = { + loading: Loading, + error: ErrorAlertView, + action: ActionView, + balance: BalanceView, +}; + +export const BalancePage = compose( + "BalancePage", + (p: Props) => useComponentState(p), + viewMapping, +); + +function ActionView({ cancel }: State.Action): VNode { + return <AddNewActionView onCancel={cancel.onClick!} />; +} -export function BalanceView({ balance, Linker, goToWalletManualWithdraw }: BalanceViewProps) { - - function Content() { - if (!balance) { - return <span /> - } - - if (balance.hasError) { - return (<section> - <ErrorBox>{balance.message}</ErrorBox> - <p> - Click <Linker pageName="welcome">here</Linker> for help and - diagnostics. - </p> - </section>) - } - if (balance.response.balances.length === 0) { - return (<section data-expanded> - <Middle> - <p><i18n.Translate> - You have no balance to show. Need some{" "} - <Linker pageName="/welcome">help</Linker> getting started? - </i18n.Translate></p> - </Middle> - </section>) - } - return <section data-expanded data-centered> - <table style={{width:'100%'}}>{balance.response.balances.map((entry) => { - const av = Amounts.parseOrThrow(entry.available); - // Create our number formatter. - let formatter; - try { - formatter = new Intl.NumberFormat('en-US', { - style: 'currency', - currency: av.currency, - currencyDisplay: 'symbol' - // These options are needed to round to whole numbers if that's what you want. - //minimumFractionDigits: 0, // (this suffices for whole numbers, but will print 2500.10 as $2,500.1) - //maximumFractionDigits: 0, // (causes 2500.99 to be printed as $2,501) - }); - } catch { - formatter = new Intl.NumberFormat('en-US', { - // style: 'currency', - // currency: av.currency, - // These options are needed to round to whole numbers if that's what you want. - //minimumFractionDigits: 0, // (this suffices for whole numbers, but will print 2500.10 as $2,500.1) - //maximumFractionDigits: 0, // (causes 2500.99 to be printed as $2,501) - }); - } - - const v = formatter.format(av.value + av.fraction / amountFractionalBase); - const fontSize = v.length < 8 ? '3em' : (v.length < 13 ? '2em' : '1em') - return (<tr> - <td style={{ height: 50, fontSize, width: '60%', textAlign: 'right', padding: 0 }}>{v}</td> - <td style={{ maxWidth: '2em', overflowX: 'hidden' }}>{av.currency}</td> - <td style={{ fontSize: 'small', color: 'gray' }}>{formatPending(entry)}</td> - </tr> - ); - })}</table> - </section> +export function BalanceView(state: State.Balances): VNode { + const { i18n } = useTranslationContext(); + const currencyWithNonZeroAmount = state.balances + .filter((b) => !Amounts.isZero(b.available)) + .map((b) => { + b.flags + return b.available.split(":")[0] + }); + + if (state.balances.length === 0) { + return ( + <NoBalanceHelp + goToWalletManualWithdraw={state.goToWalletManualWithdraw} + /> + ); } - return <PopupBox> - {/* <section> */} - <Content /> - {/* </section> */} - <footer> - <div /> - <ButtonPrimary onClick={goToWalletManualWithdraw}>Withdraw</ButtonPrimary> - </footer> - </PopupBox> + return ( + <Fragment> + <section> + <BalanceTable + balances={state.balances} + goToWalletHistory={state.goToWalletHistory} + /> + </section> + <footer style={{ justifyContent: "space-between" }}> + <Button + variant="contained" + onClick={state.goToWalletManualWithdraw.onClick} + > + <i18n.Translate>Add</i18n.Translate> + </Button> + {currencyWithNonZeroAmount.length > 0 && ( + <MultiActionButton + label={(s) => i18n.str`Send ${s}`} + actions={currencyWithNonZeroAmount} + onClick={(c) => state.goToWalletDeposit(c)} + /> + )} + </footer> + </Fragment> + ); } |