summaryrefslogtreecommitdiff
path: root/packages/taler-wallet-webextension/src/popup/BalancePage.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'packages/taler-wallet-webextension/src/popup/BalancePage.tsx')
-rw-r--r--packages/taler-wallet-webextension/src/popup/BalancePage.tsx294
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>
+ );
}