balance refactor
This commit is contained in:
parent
c34c7d37cb
commit
18c5371d65
@ -238,3 +238,4 @@ export const ErrorBox = styled.div`
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
||||||
|
52
packages/taler-wallet-webextension/src/hooks/useBalances.tsx
Normal file
52
packages/taler-wallet-webextension/src/hooks/useBalances.tsx
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
/*
|
||||||
|
This file is part of TALER
|
||||||
|
(C) 2016 GNUnet e.V.
|
||||||
|
|
||||||
|
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
|
||||||
|
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/>
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { BalancesResponse } from "@gnu-taler/taler-util";
|
||||||
|
import { useEffect, useState } from "preact/hooks";
|
||||||
|
import * as wxApi from "../wxApi";
|
||||||
|
|
||||||
|
|
||||||
|
interface BalancesHookOk {
|
||||||
|
error: false;
|
||||||
|
response: BalancesResponse;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface BalancesHookError {
|
||||||
|
error: true;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type BalancesHook = BalancesHookOk | BalancesHookError | undefined;
|
||||||
|
|
||||||
|
export function useBalances(): BalancesHook {
|
||||||
|
const [balance, setBalance] = useState<BalancesHook>(undefined);
|
||||||
|
console.log('render balance')
|
||||||
|
useEffect(() => {
|
||||||
|
async function checkBalance() {
|
||||||
|
try {
|
||||||
|
const response = await wxApi.getBalance();
|
||||||
|
console.log("got balance", balance);
|
||||||
|
setBalance({ error: false, response });
|
||||||
|
} catch (e) {
|
||||||
|
console.error("could not retrieve balances", e);
|
||||||
|
setBalance({ error: true });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
checkBalance()
|
||||||
|
return wxApi.onUpdateNotification(checkBalance);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return balance;
|
||||||
|
}
|
@ -18,7 +18,7 @@
|
|||||||
import { i18n, Timestamp } from "@gnu-taler/taler-util";
|
import { i18n, Timestamp } from "@gnu-taler/taler-util";
|
||||||
import { ProviderInfo, ProviderPaymentStatus } from "@gnu-taler/taler-wallet-core";
|
import { ProviderInfo, ProviderPaymentStatus } from "@gnu-taler/taler-wallet-core";
|
||||||
import { differenceInMonths, formatDuration, intervalToDuration } from "date-fns";
|
import { differenceInMonths, formatDuration, intervalToDuration } from "date-fns";
|
||||||
import { FunctionalComponent, Fragment, JSX, VNode, AnyComponent } from "preact";
|
import { Fragment, JSX, VNode } from "preact";
|
||||||
import {
|
import {
|
||||||
BoldLight, ButtonPrimary, ButtonSuccess, Centered,
|
BoldLight, ButtonPrimary, ButtonSuccess, Centered,
|
||||||
CenteredText, CenteredTextBold, PopupBox, RowBorderGray,
|
CenteredText, CenteredTextBold, PopupBox, RowBorderGray,
|
||||||
|
114
packages/taler-wallet-webextension/src/popup/Balance.stories.tsx
Normal file
114
packages/taler-wallet-webextension/src/popup/Balance.stories.tsx
Normal file
@ -0,0 +1,114 @@
|
|||||||
|
/*
|
||||||
|
This file is part of GNU Taler
|
||||||
|
(C) 2021 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 { ProviderPaymentType } from '@gnu-taler/taler-wallet-core';
|
||||||
|
import { addDays } from 'date-fns';
|
||||||
|
import { ComponentChild, ComponentChildren, FunctionalComponent, h } from 'preact';
|
||||||
|
import { BalanceView as TestedComponent } from './BalancePage';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
title: 'popup/balance/detail',
|
||||||
|
component: TestedComponent,
|
||||||
|
argTypes: {
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
function createExample<Props>(Component: FunctionalComponent<Props>, props: Partial<Props>) {
|
||||||
|
const r = (args: any) => <Component {...args} />
|
||||||
|
r.args = props
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
export const NotYetLoaded = createExample(TestedComponent, {
|
||||||
|
});
|
||||||
|
|
||||||
|
const NullLink = ({ children }: { children?: ComponentChildren }) => h('a', { children, href: 'javascript:void(0);' })
|
||||||
|
export const GotError = createExample(TestedComponent, {
|
||||||
|
balance: {
|
||||||
|
error: true
|
||||||
|
},
|
||||||
|
Linker: NullLink,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const EmptyBalance = createExample(TestedComponent, {
|
||||||
|
balance: {
|
||||||
|
error: false,
|
||||||
|
response: {
|
||||||
|
balances: []
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Linker: NullLink,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const SomeCoins = createExample(TestedComponent, {
|
||||||
|
balance: {
|
||||||
|
error: false,
|
||||||
|
response: {
|
||||||
|
balances: [{
|
||||||
|
available: 'USD:10.5',
|
||||||
|
hasPendingTransactions: false,
|
||||||
|
pendingIncoming: 'USD:0',
|
||||||
|
pendingOutgoing: 'USD:0',
|
||||||
|
requiresUserInput: false
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Linker: NullLink,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const SomeCoinsAndIncomingMoney = createExample(TestedComponent, {
|
||||||
|
balance: {
|
||||||
|
error: false,
|
||||||
|
response: {
|
||||||
|
balances: [{
|
||||||
|
available: 'USD:2.23',
|
||||||
|
hasPendingTransactions: false,
|
||||||
|
pendingIncoming: 'USD:5.11',
|
||||||
|
pendingOutgoing: 'USD:0',
|
||||||
|
requiresUserInput: false
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Linker: NullLink,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const SomeCoinsInTwoCurrencies = createExample(TestedComponent, {
|
||||||
|
balance: {
|
||||||
|
error: false,
|
||||||
|
response: {
|
||||||
|
balances: [{
|
||||||
|
available: 'USD:2',
|
||||||
|
hasPendingTransactions: false,
|
||||||
|
pendingIncoming: 'USD:5',
|
||||||
|
pendingOutgoing: 'USD:0',
|
||||||
|
requiresUserInput: false
|
||||||
|
},{
|
||||||
|
available: 'EUR:4',
|
||||||
|
hasPendingTransactions: false,
|
||||||
|
pendingIncoming: 'EUR:5',
|
||||||
|
pendingOutgoing: 'EUR:0',
|
||||||
|
requiresUserInput: false
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Linker: NullLink,
|
||||||
|
});
|
@ -1,173 +0,0 @@
|
|||||||
/*
|
|
||||||
This file is part of TALER
|
|
||||||
(C) 2016 GNUnet e.V.
|
|
||||||
|
|
||||||
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
|
|
||||||
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/>
|
|
||||||
*/
|
|
||||||
|
|
||||||
import {
|
|
||||||
Amounts,
|
|
||||||
BalancesResponse,
|
|
||||||
Balance, i18n, AmountJson, amountFractionalBase
|
|
||||||
} from "@gnu-taler/taler-util";
|
|
||||||
import { Component, JSX } from "preact";
|
|
||||||
import { PageLink, renderAmount } from "../renderHtml";
|
|
||||||
import * as wxApi from "../wxApi";
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Render an amount as a large number with a small currency symbol.
|
|
||||||
*/
|
|
||||||
function bigAmount(amount: AmountJson): JSX.Element {
|
|
||||||
const v = amount.value + amount.fraction / amountFractionalBase;
|
|
||||||
return (
|
|
||||||
<span>
|
|
||||||
<span style={{ fontSize: "5em", display: "block" }}>{v}</span>{" "}
|
|
||||||
<span>{amount.currency}</span>
|
|
||||||
</span>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function EmptyBalanceView(): JSX.Element {
|
|
||||||
return (
|
|
||||||
<p><i18n.Translate>
|
|
||||||
You have no balance to show. Need some{" "}
|
|
||||||
<PageLink pageName="/welcome">help</PageLink> getting started?
|
|
||||||
</i18n.Translate></p>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
export class BalancePage extends Component<any, any> {
|
|
||||||
private balance?: BalancesResponse;
|
|
||||||
private gotError = false;
|
|
||||||
private canceler: (() => void) | undefined = undefined;
|
|
||||||
private unmount = false;
|
|
||||||
private updateBalanceRunning = false;
|
|
||||||
|
|
||||||
componentWillMount(): void {
|
|
||||||
this.canceler = wxApi.onUpdateNotification(() => this.updateBalance());
|
|
||||||
this.updateBalance();
|
|
||||||
}
|
|
||||||
|
|
||||||
componentWillUnmount(): void {
|
|
||||||
console.log("component WalletBalanceView will unmount");
|
|
||||||
if (this.canceler) {
|
|
||||||
this.canceler();
|
|
||||||
}
|
|
||||||
this.unmount = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
async updateBalance(): Promise<void> {
|
|
||||||
if (this.updateBalanceRunning) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.updateBalanceRunning = true;
|
|
||||||
let balance: BalancesResponse;
|
|
||||||
try {
|
|
||||||
balance = await wxApi.getBalance();
|
|
||||||
} catch (e) {
|
|
||||||
if (this.unmount) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.gotError = true;
|
|
||||||
console.error("could not retrieve balances", e);
|
|
||||||
this.setState({});
|
|
||||||
return;
|
|
||||||
} finally {
|
|
||||||
this.updateBalanceRunning = false;
|
|
||||||
}
|
|
||||||
if (this.unmount) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.gotError = false;
|
|
||||||
console.log("got balance", balance);
|
|
||||||
this.balance = balance;
|
|
||||||
this.setState({});
|
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
|
|
||||||
console.log(
|
|
||||||
"available: ",
|
|
||||||
entry.pendingIncoming ? renderAmount(entry.available) : null
|
|
||||||
);
|
|
||||||
console.log(
|
|
||||||
"incoming: ",
|
|
||||||
entry.pendingIncoming ? renderAmount(entry.pendingIncoming) : null
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!Amounts.isZero(pendingIncoming)) {
|
|
||||||
incoming = (
|
|
||||||
<span><i18n.Translate>
|
|
||||||
<span style={{ color: "darkgreen" }}>
|
|
||||||
{"+"}
|
|
||||||
{renderAmount(entry.pendingIncoming)}
|
|
||||||
</span>{" "}
|
|
||||||
incoming
|
|
||||||
</i18n.Translate></span>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const l = [incoming, payment].filter((x) => x !== undefined);
|
|
||||||
if (l.length === 0) {
|
|
||||||
return <span />;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (l.length === 1) {
|
|
||||||
return <span>({l})</span>;
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<span>
|
|
||||||
({l[0]}, {l[1]})
|
|
||||||
</span>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
render(): JSX.Element {
|
|
||||||
const wallet = this.balance;
|
|
||||||
if (this.gotError) {
|
|
||||||
return (
|
|
||||||
<div class="balance">
|
|
||||||
<p>{i18n.str`Error: could not retrieve balance information.`}</p>
|
|
||||||
<p>
|
|
||||||
Click <PageLink pageName="welcome.html">here</PageLink> for help and
|
|
||||||
diagnostics.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (!wallet) {
|
|
||||||
return <span></span>;
|
|
||||||
}
|
|
||||||
|
|
||||||
const listing = wallet.balances.map((entry) => {
|
|
||||||
const av = Amounts.parseOrThrow(entry.available);
|
|
||||||
return (
|
|
||||||
<p key={av.currency}>
|
|
||||||
{bigAmount(av)} {this.formatPending(entry)}
|
|
||||||
</p>
|
|
||||||
);
|
|
||||||
});
|
|
||||||
return listing.length > 0 ? (
|
|
||||||
<div class="balance">{listing}</div>
|
|
||||||
) : (
|
|
||||||
<EmptyBalanceView />
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
118
packages/taler-wallet-webextension/src/popup/BalancePage.tsx
Normal file
118
packages/taler-wallet-webextension/src/popup/BalancePage.tsx
Normal file
@ -0,0 +1,118 @@
|
|||||||
|
/*
|
||||||
|
This file is part of TALER
|
||||||
|
(C) 2016 GNUnet e.V.
|
||||||
|
|
||||||
|
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
|
||||||
|
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/>
|
||||||
|
*/
|
||||||
|
|
||||||
|
import {
|
||||||
|
amountFractionalBase, Amounts,
|
||||||
|
|
||||||
|
Balance, BalancesResponse,
|
||||||
|
i18n
|
||||||
|
} from "@gnu-taler/taler-util";
|
||||||
|
import { JSX } from "preact";
|
||||||
|
import { PopupBox, Centered } from "../components/styled/index";
|
||||||
|
import { BalancesHook, useBalances } from "../hooks/useBalances";
|
||||||
|
import { PageLink, renderAmount } from "../renderHtml";
|
||||||
|
|
||||||
|
|
||||||
|
export function BalancePage() {
|
||||||
|
const balance = useBalances()
|
||||||
|
return <BalanceView balance={balance} Linker={PageLink} />
|
||||||
|
}
|
||||||
|
export interface BalanceViewProps {
|
||||||
|
balance: BalancesHook,
|
||||||
|
Linker: typeof PageLink,
|
||||||
|
}
|
||||||
|
export function BalanceView({ balance, Linker }: BalanceViewProps) {
|
||||||
|
if (!balance) {
|
||||||
|
return <span />
|
||||||
|
}
|
||||||
|
|
||||||
|
if (balance.error) {
|
||||||
|
return (
|
||||||
|
<div class="balance">
|
||||||
|
<p>{i18n.str`Error: could not retrieve balance information.`}</p>
|
||||||
|
<p>
|
||||||
|
Click <Linker pageName="welcome.html">here</Linker> for help and
|
||||||
|
diagnostics.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if (balance.response.balances.length === 0) {
|
||||||
|
return (
|
||||||
|
<p><i18n.Translate>
|
||||||
|
You have no balance to show. Need some{" "}
|
||||||
|
<Linker pageName="/welcome">help</Linker> getting started?
|
||||||
|
</i18n.Translate></p>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return <ShowBalances wallet={balance.response} />
|
||||||
|
}
|
||||||
|
|
||||||
|
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" }}>
|
||||||
|
{"+"}
|
||||||
|
{renderAmount(entry.pendingIncoming)}
|
||||||
|
</span>{" "}
|
||||||
|
incoming
|
||||||
|
</i18n.Translate></span>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const l = [incoming, payment].filter((x) => x !== undefined);
|
||||||
|
if (l.length === 0) {
|
||||||
|
return <span />;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (l.length === 1) {
|
||||||
|
return <span>({l})</span>;
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<span>
|
||||||
|
({l[0]}, {l[1]})
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function ShowBalances({ wallet }: { wallet: BalancesResponse }) {
|
||||||
|
return <PopupBox>
|
||||||
|
<section>
|
||||||
|
<Centered>{wallet.balances.map((entry) => {
|
||||||
|
const av = Amounts.parseOrThrow(entry.available);
|
||||||
|
const v = av.value + av.fraction / amountFractionalBase;
|
||||||
|
return (
|
||||||
|
<p key={av.currency}>
|
||||||
|
<span>
|
||||||
|
<span style={{ fontSize: "5em", display: "block" }}>{v}</span>{" "}
|
||||||
|
<span>{av.currency}</span>
|
||||||
|
</span>
|
||||||
|
{formatPending(entry)}
|
||||||
|
</p>
|
||||||
|
);
|
||||||
|
})}</Centered>
|
||||||
|
</section>
|
||||||
|
</PopupBox>
|
||||||
|
}
|
@ -29,7 +29,7 @@ import { DevContextProvider } from "./context/useDevContext";
|
|||||||
import { useTalerActionURL } from "./hooks/useTalerActionURL";
|
import { useTalerActionURL } from "./hooks/useTalerActionURL";
|
||||||
import { strings } from "./i18n/strings";
|
import { strings } from "./i18n/strings";
|
||||||
import { BackupPage } from "./popup/BackupPage";
|
import { BackupPage } from "./popup/BackupPage";
|
||||||
import { BalancePage } from "./popup/Balance";
|
import { BalancePage } from "./popup/BalancePage";
|
||||||
import { DeveloperPage as DeveloperPage } from "./popup/Debug";
|
import { DeveloperPage as DeveloperPage } from "./popup/Debug";
|
||||||
import { HistoryPage } from "./popup/History";
|
import { HistoryPage } from "./popup/History";
|
||||||
import {
|
import {
|
||||||
|
Loading…
Reference in New Issue
Block a user