feat: cashing out for each peer and single page accounting (#69)
* refactor: simplified accounting, removed cashing out * feat: load uncashed amounts for all pears that have settlement * feat: added cashout button * refactor: changed accounting to work with current version of bee * chore: addressed PR review comments * chore: simplified the uncashed expression in balance table
This commit is contained in:
@@ -1,6 +1,5 @@
|
|||||||
import { ReactElement, useState } from 'react'
|
import { ReactElement, useState } from 'react'
|
||||||
import Button from '@material-ui/core/Button'
|
import Button from '@material-ui/core/Button'
|
||||||
import Input from '@material-ui/core/Input'
|
|
||||||
import Dialog from '@material-ui/core/Dialog'
|
import Dialog from '@material-ui/core/Dialog'
|
||||||
import DialogActions from '@material-ui/core/DialogActions'
|
import DialogActions from '@material-ui/core/DialogActions'
|
||||||
import DialogContent from '@material-ui/core/DialogContent'
|
import DialogContent from '@material-ui/core/DialogContent'
|
||||||
@@ -11,10 +10,15 @@ import { Snackbar, Container, CircularProgress } from '@material-ui/core'
|
|||||||
import { beeDebugApi } from '../services/bee'
|
import { beeDebugApi } from '../services/bee'
|
||||||
|
|
||||||
import EthereumAddress from './EthereumAddress'
|
import EthereumAddress from './EthereumAddress'
|
||||||
|
import { fromBZZbaseUnit } from '../utils'
|
||||||
|
|
||||||
export default function DepositModal(): ReactElement {
|
interface Props {
|
||||||
|
peerId: string
|
||||||
|
uncashedAmount: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function DepositModal({ peerId, uncashedAmount }: Props): ReactElement {
|
||||||
const [open, setOpen] = useState<boolean>(false)
|
const [open, setOpen] = useState<boolean>(false)
|
||||||
const [peerId, setPeerId] = useState('')
|
|
||||||
const [loadingCashout, setLoadingCashout] = useState<boolean>(false)
|
const [loadingCashout, setLoadingCashout] = useState<boolean>(false)
|
||||||
const [showToast, setToastVisibility] = useState<boolean>(false)
|
const [showToast, setToastVisibility] = useState<boolean>(false)
|
||||||
const [toastContent, setToastContent] = useState<JSX.Element | null>(null)
|
const [toastContent, setToastContent] = useState<JSX.Element | null>(null)
|
||||||
@@ -67,32 +71,33 @@ export default function DepositModal(): ReactElement {
|
|||||||
<Snackbar anchorOrigin={{ vertical: 'top', horizontal: 'center' }} open={showToast} message={toastContent} />
|
<Snackbar anchorOrigin={{ vertical: 'top', horizontal: 'center' }} open={showToast} message={toastContent} />
|
||||||
<Dialog open={open} onClose={handleClose} aria-labelledby="form-dialog-title">
|
<Dialog open={open} onClose={handleClose} aria-labelledby="form-dialog-title">
|
||||||
<DialogTitle id="form-dialog-title">Cashout Cheque</DialogTitle>
|
<DialogTitle id="form-dialog-title">Cashout Cheque</DialogTitle>
|
||||||
{loadingCashout ? (
|
<DialogContent>
|
||||||
|
<DialogContentText style={{ marginTop: '20px', overflowWrap: 'break-word' }}>
|
||||||
|
{loadingCashout && (
|
||||||
|
<>
|
||||||
|
<span>
|
||||||
|
Cashing out <strong>{fromBZZbaseUnit(uncashedAmount).toFixed(7)}</strong> from Peer{' '}
|
||||||
|
<strong>{peerId}</strong>. Please wait...
|
||||||
|
</span>
|
||||||
<Container style={{ textAlign: 'center', padding: '50px' }}>
|
<Container style={{ textAlign: 'center', padding: '50px' }}>
|
||||||
<CircularProgress />
|
<CircularProgress />
|
||||||
</Container>
|
</Container>
|
||||||
) : (
|
</>
|
||||||
<DialogContent>
|
|
||||||
<DialogContentText style={{ marginTop: '20px' }}>
|
|
||||||
Specify the peer Id of the peer you would like to cashout.
|
|
||||||
</DialogContentText>
|
|
||||||
<Input
|
|
||||||
autoFocus
|
|
||||||
margin="dense"
|
|
||||||
id="peerId"
|
|
||||||
type="text"
|
|
||||||
placeholder="Peer Id"
|
|
||||||
fullWidth
|
|
||||||
onChange={e => setPeerId(e.target.value)}
|
|
||||||
/>
|
|
||||||
</DialogContent>
|
|
||||||
)}
|
)}
|
||||||
|
{!loadingCashout && (
|
||||||
|
<span>
|
||||||
|
Are you sure you want to cashout <strong>{fromBZZbaseUnit(uncashedAmount).toFixed(7)} BZZ</strong> from
|
||||||
|
Peer <strong>{peerId}</strong>?
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</DialogContentText>
|
||||||
|
</DialogContent>
|
||||||
<DialogActions>
|
<DialogActions>
|
||||||
<Button onClick={handleClose} color="primary">
|
<Button onClick={handleClose} color="primary">
|
||||||
Cancel
|
Cancel
|
||||||
</Button>
|
</Button>
|
||||||
<Button onClick={handleCashout} color="primary">
|
<Button onClick={handleCashout} color="primary" disabled={loadingCashout}>
|
||||||
Cashout
|
Yes Cashout
|
||||||
</Button>
|
</Button>
|
||||||
</DialogActions>
|
</DialogActions>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
|||||||
@@ -0,0 +1,93 @@
|
|||||||
|
import { LastCashoutActionResponse, PeerBalance, Settlements } from '@ethersphere/bee-js'
|
||||||
|
import { useEffect, useState } from 'react'
|
||||||
|
import { beeDebugApi } from '../services/bee'
|
||||||
|
import { useApiPeerBalances, useApiSettlements } from './apiHooks'
|
||||||
|
|
||||||
|
interface UseAccountingHook {
|
||||||
|
isLoading: boolean
|
||||||
|
isLoadingUncashed: boolean
|
||||||
|
error: Error | null
|
||||||
|
totalsent: number
|
||||||
|
totalreceived: number
|
||||||
|
accounting: Accounting[] | null
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Merges the balances, settlements and uncashedAmounts arrays into single array which is sorted by uncashed amounts (if any)
|
||||||
|
*
|
||||||
|
* @param balances Balances for all peers
|
||||||
|
* @param settlements Settlements for all peers which has some settlement
|
||||||
|
* @param uncashedAmounts Array of getPeerLastCashout responses which is needed to calculate uncashed amount
|
||||||
|
*
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
function mergeAccounting(
|
||||||
|
balances?: PeerBalance[],
|
||||||
|
settlements?: Settlements[],
|
||||||
|
uncashedAmounts?: LastCashoutActionResponse[],
|
||||||
|
): Accounting[] | null {
|
||||||
|
// Settlements or balances are still loading or there is an error -> return null
|
||||||
|
if (!balances || !settlements) return null
|
||||||
|
|
||||||
|
const accounting: Record<string, Accounting> = {}
|
||||||
|
|
||||||
|
balances.forEach(
|
||||||
|
// Some peers may not have settlement but all have balance (therefore initialize sent, received and uncashed to 0)
|
||||||
|
({ peer, balance }) =>
|
||||||
|
(accounting[peer] = { peer, balance, sent: 0, received: 0, uncashedAmount: 0, total: balance }),
|
||||||
|
)
|
||||||
|
|
||||||
|
settlements.forEach(
|
||||||
|
({ peer, sent, received }) =>
|
||||||
|
(accounting[peer] = { ...accounting[peer], sent, received, total: accounting[peer].balance + received - sent }),
|
||||||
|
)
|
||||||
|
|
||||||
|
// If there are no cheques (and hence last cashout actions), we don't need to sort and can return values right away
|
||||||
|
if (!uncashedAmounts) return Object.values(accounting)
|
||||||
|
|
||||||
|
uncashedAmounts?.forEach(
|
||||||
|
({ peer, cumulativePayout }) => (accounting[peer].uncashedAmount = accounting[peer].received - cumulativePayout),
|
||||||
|
)
|
||||||
|
|
||||||
|
return Object.values(accounting).sort((a, b) => b.uncashedAmount - a.uncashedAmount)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useAccounting = (): UseAccountingHook => {
|
||||||
|
const settlements = useApiSettlements()
|
||||||
|
const balances = useApiPeerBalances()
|
||||||
|
|
||||||
|
const [err, setErr] = useState<Error | null>(null)
|
||||||
|
const [isLoadingUncashed, setIsloadingUncashed] = useState<boolean>(false)
|
||||||
|
const [uncashedAmounts, setUncashedAmounts] = useState<LastCashoutActionResponse[] | undefined>(undefined)
|
||||||
|
|
||||||
|
const error = balances.error || settlements.error || err
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// We don't have any settlements loaded yet or we are already loading/have loaded the uncashed amounts
|
||||||
|
if (isLoadingUncashed || !settlements.settlements || uncashedAmounts || error) return
|
||||||
|
|
||||||
|
setIsloadingUncashed(true)
|
||||||
|
const promises = settlements.settlements.settlements.map(({ peer }) =>
|
||||||
|
beeDebugApi.chequebook.getPeerLastCashout(peer),
|
||||||
|
)
|
||||||
|
Promise.all(promises)
|
||||||
|
.then(setUncashedAmounts)
|
||||||
|
.catch(setErr)
|
||||||
|
.finally(() => setIsloadingUncashed(false))
|
||||||
|
}, [settlements, isLoadingUncashed, uncashedAmounts, error])
|
||||||
|
|
||||||
|
const accounting = mergeAccounting(
|
||||||
|
balances.peerBalances?.balances,
|
||||||
|
settlements.settlements?.settlements,
|
||||||
|
uncashedAmounts,
|
||||||
|
)
|
||||||
|
|
||||||
|
return {
|
||||||
|
isLoading: settlements.isLoadingSettlements || balances.isLoadingPeerBalances,
|
||||||
|
isLoadingUncashed,
|
||||||
|
error,
|
||||||
|
accounting,
|
||||||
|
totalsent: settlements.settlements?.totalsent || 0,
|
||||||
|
totalreceived: settlements.settlements?.totalreceived || 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,34 +1,34 @@
|
|||||||
import { ReactElement } from 'react'
|
import { ReactElement } from 'react'
|
||||||
|
|
||||||
import { createStyles, makeStyles } from '@material-ui/core/styles'
|
import { createStyles, makeStyles } from '@material-ui/core/styles'
|
||||||
import { Card, CardContent, Typography, Grid } from '@material-ui/core/'
|
import { Card, CardContent, Typography, Theme } from '@material-ui/core/'
|
||||||
import { Skeleton } from '@material-ui/lab'
|
import { Skeleton } from '@material-ui/lab'
|
||||||
import WithdrawModal from '../../containers/WithdrawModal'
|
import WithdrawModal from '../../containers/WithdrawModal'
|
||||||
import DepositModal from '../../containers/DepositModal'
|
import DepositModal from '../../containers/DepositModal'
|
||||||
import CashoutModal from '../../components/CashoutModal'
|
|
||||||
|
|
||||||
import { fromBZZbaseUnit } from '../../utils'
|
import { fromBZZbaseUnit } from '../../utils'
|
||||||
|
|
||||||
import type { AllSettlements, ChequebookAddressResponse } from '@ethersphere/bee-js'
|
import type { ChequebookAddressResponse } from '@ethersphere/bee-js'
|
||||||
|
|
||||||
const useStyles = makeStyles(() =>
|
const useStyles = makeStyles((theme: Theme) =>
|
||||||
createStyles({
|
createStyles({
|
||||||
root: {
|
root: {
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
},
|
},
|
||||||
details: {
|
buttons: {
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
|
columnGap: theme.spacing(1),
|
||||||
},
|
},
|
||||||
address: {
|
gridContainer: {
|
||||||
color: 'grey',
|
display: 'flex',
|
||||||
fontWeight: 400,
|
width: '100%',
|
||||||
},
|
marginLeft: theme.spacing(1),
|
||||||
content: {
|
marginRight: theme.spacing(1),
|
||||||
flexGrow: 1,
|
columnGap: theme.spacing(1),
|
||||||
},
|
rowGap: theme.spacing(1),
|
||||||
status: {
|
flex: '0 1 auto',
|
||||||
color: '#fff',
|
flexWrap: 'wrap',
|
||||||
backgroundColor: '#76a9fa',
|
justifyContent: 'space-between',
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
@@ -40,81 +40,58 @@ interface ChequebookBalance {
|
|||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
chequebookAddress: ChequebookAddressResponse | null
|
chequebookAddress: ChequebookAddressResponse | null
|
||||||
isLoadingChequebookAddress: boolean
|
|
||||||
chequebookBalance: ChequebookBalance | null
|
chequebookBalance: ChequebookBalance | null
|
||||||
isLoadingChequebookBalance: boolean
|
totalsent: number
|
||||||
settlements: AllSettlements | null
|
totalreceived: number
|
||||||
isLoadingSettlements: boolean
|
isLoading: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
function AccountCard(props: Props): ReactElement {
|
function AccountCard({ totalreceived, totalsent, chequebookBalance, isLoading }: Props): ReactElement {
|
||||||
const classes = useStyles()
|
const classes = useStyles()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<div style={{ justifyContent: 'space-between', display: 'flex' }}>
|
<div style={{ justifyContent: 'space-between', display: 'flex' }}>
|
||||||
<h2 style={{ marginTop: '0px' }}>Accounting</h2>
|
<h2 style={{ marginTop: '0px' }}>Chequebook</h2>
|
||||||
<div style={{ display: 'flex' }}>
|
<div className={classes.buttons}>
|
||||||
<WithdrawModal />
|
<WithdrawModal />
|
||||||
<DepositModal />
|
<DepositModal />
|
||||||
<CashoutModal />
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Card className={classes.root}>
|
<Card className={classes.root}>
|
||||||
{!props.isLoadingChequebookBalance && !props.isLoadingSettlements && props.chequebookBalance ? (
|
{!isLoading && (
|
||||||
<div className={classes.details}>
|
<CardContent className={classes.gridContainer}>
|
||||||
<CardContent className={classes.content}>
|
<div>
|
||||||
<Grid container spacing={5}>
|
|
||||||
<Grid item>
|
|
||||||
<Typography component="h2" variant="h6" color="primary" gutterBottom>
|
<Typography component="h2" variant="h6" color="primary" gutterBottom>
|
||||||
Total Balance (BZZ)
|
Total Balance
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography component="p" variant="h5">
|
<Typography variant="h5">
|
||||||
{fromBZZbaseUnit(props.chequebookBalance.totalBalance)}
|
{fromBZZbaseUnit(chequebookBalance?.totalBalance || 0).toFixed(7)} BZZ
|
||||||
</Typography>
|
</Typography>
|
||||||
</Grid>
|
|
||||||
<Grid item>
|
|
||||||
<Typography component="h2" variant="h6" color="primary" gutterBottom>
|
|
||||||
Available Balance (BZZ)
|
|
||||||
</Typography>
|
|
||||||
<Typography component="p" variant="h5">
|
|
||||||
{fromBZZbaseUnit(props.chequebookBalance.availableBalance)}
|
|
||||||
</Typography>
|
|
||||||
</Grid>
|
|
||||||
<Grid item>
|
|
||||||
<Typography component="h2" variant="h6" color="primary" gutterBottom>
|
|
||||||
Total Sent / Received (BZZ)
|
|
||||||
</Typography>
|
|
||||||
<Typography component="div" variant="h5">
|
|
||||||
<span style={{ marginRight: '7px' }}>
|
|
||||||
{fromBZZbaseUnit(props.settlements?.totalsent || 0)} /{' '}
|
|
||||||
{fromBZZbaseUnit(props.settlements?.totalreceived || 0)}
|
|
||||||
</span>
|
|
||||||
<span
|
|
||||||
style={{
|
|
||||||
color:
|
|
||||||
props.settlements && props.settlements?.totalsent > props.settlements?.totalreceived
|
|
||||||
? '#c9201f'
|
|
||||||
: '#32c48d',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
(
|
|
||||||
{fromBZZbaseUnit(
|
|
||||||
(props.settlements && props.settlements?.totalsent - props.settlements?.totalreceived) || 0,
|
|
||||||
)}
|
|
||||||
)
|
|
||||||
</span>
|
|
||||||
</Typography>
|
|
||||||
</Grid>
|
|
||||||
</Grid>
|
|
||||||
</CardContent>
|
|
||||||
</div>
|
</div>
|
||||||
) : (
|
<div>
|
||||||
<div className={classes.details}>
|
<Typography component="h2" variant="h6" color="primary" gutterBottom>
|
||||||
<Skeleton width={180} height={110} animation="wave" style={{ marginLeft: '12px', marginRight: '12px' }} />
|
Available Uncommitted Balance
|
||||||
<Skeleton width={180} height={110} animation="wave" style={{ marginLeft: '12px', marginRight: '12px' }} />
|
</Typography>
|
||||||
<Skeleton width={180} height={110} animation="wave" style={{ marginLeft: '12px', marginRight: '12px' }} />
|
<Typography variant="h5">
|
||||||
|
{fromBZZbaseUnit(chequebookBalance?.availableBalance || 0).toFixed(7)} BZZ
|
||||||
|
</Typography>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<Typography component="h2" variant="h6" color="primary" gutterBottom>
|
||||||
|
Total Sent / Received
|
||||||
|
</Typography>
|
||||||
|
<Typography variant="h5">
|
||||||
|
{fromBZZbaseUnit(totalsent).toFixed(7)} / {fromBZZbaseUnit(totalreceived).toFixed(7)} BZZ
|
||||||
|
</Typography>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
)}
|
||||||
|
{isLoading && (
|
||||||
|
<div className={classes.gridContainer}>
|
||||||
|
<Skeleton width={180} height={110} animation="wave" />
|
||||||
|
<Skeleton width={180} height={110} animation="wave" />
|
||||||
<Skeleton width={180} height={110} animation="wave" />
|
<Skeleton width={180} height={110} animation="wave" />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -1,79 +1,89 @@
|
|||||||
import type { ReactElement } from 'react'
|
import type { ReactElement } from 'react'
|
||||||
import { makeStyles } from '@material-ui/core/styles'
|
import { makeStyles } from '@material-ui/core/styles'
|
||||||
import {
|
import { Table, TableBody, TableCell, TableContainer, TableRow, TableHead, Paper } from '@material-ui/core'
|
||||||
Table,
|
|
||||||
TableBody,
|
|
||||||
TableCell,
|
|
||||||
TableContainer,
|
|
||||||
TableRow,
|
|
||||||
TableHead,
|
|
||||||
Paper,
|
|
||||||
Container,
|
|
||||||
CircularProgress,
|
|
||||||
} from '@material-ui/core'
|
|
||||||
|
|
||||||
import { fromBZZbaseUnit } from '../../utils'
|
import { fromBZZbaseUnit } from '../../utils'
|
||||||
|
import ClipboardCopy from '../../components/ClipboardCopy'
|
||||||
|
import CashoutModal from '../../components/CashoutModal'
|
||||||
|
import PeerDetailDrawer from './PeerDetail'
|
||||||
|
|
||||||
const useStyles = makeStyles({
|
const useStyles = makeStyles({
|
||||||
table: {
|
table: {
|
||||||
minWidth: 650,
|
minWidth: 650,
|
||||||
},
|
},
|
||||||
|
values: {
|
||||||
|
textAlign: 'right',
|
||||||
|
fontFamily: 'monospace, monospace',
|
||||||
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
interface PeerBalance {
|
|
||||||
balance: number
|
|
||||||
peer: string
|
|
||||||
}
|
|
||||||
|
|
||||||
interface PeerBalances {
|
|
||||||
balances: Array<PeerBalance>
|
|
||||||
}
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
peerBalances: PeerBalances | null
|
isLoadingUncashed: boolean
|
||||||
loading?: boolean
|
accounting: Accounting[] | null
|
||||||
}
|
}
|
||||||
|
|
||||||
function BalancesTable(props: Props): ReactElement {
|
function BalancesTable({ accounting, isLoadingUncashed }: Props): ReactElement | null {
|
||||||
|
if (accounting === null) return null
|
||||||
const classes = useStyles()
|
const classes = useStyles()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
|
||||||
{props.loading ? (
|
|
||||||
<Container style={{ textAlign: 'center', padding: '50px' }}>
|
|
||||||
<CircularProgress />
|
|
||||||
</Container>
|
|
||||||
) : (
|
|
||||||
<TableContainer component={Paper}>
|
<TableContainer component={Paper}>
|
||||||
<Table className={classes.table} size="small" aria-label="simple table">
|
<Table className={classes.table} size="small" aria-label="Balances Table">
|
||||||
<TableHead>
|
<TableHead>
|
||||||
<TableRow>
|
<TableRow>
|
||||||
<TableCell>Peer</TableCell>
|
<TableCell>Peer</TableCell>
|
||||||
<TableCell style={{ textAlign: 'right' }}>Balance (BZZ)</TableCell>
|
<TableCell align="right">Outstanding Balance</TableCell>
|
||||||
<TableCell align="right"></TableCell>
|
<TableCell align="right">Settlements Sent / Received</TableCell>
|
||||||
|
<TableCell align="right">Total</TableCell>
|
||||||
|
<TableCell align="right">Uncashed Amount</TableCell>
|
||||||
|
<TableCell />
|
||||||
</TableRow>
|
</TableRow>
|
||||||
</TableHead>
|
</TableHead>
|
||||||
<TableBody>
|
<TableBody>
|
||||||
{props.peerBalances?.balances.map((peerBalance: PeerBalance) => (
|
{accounting.map(({ peer, balance, received, sent, uncashedAmount, total }) => (
|
||||||
<TableRow key={peerBalance.peer}>
|
<TableRow key={peer}>
|
||||||
<TableCell>{peerBalance.peer}</TableCell>
|
<TableCell>
|
||||||
<TableCell
|
<div style={{ display: 'flex' }}>
|
||||||
|
<small>
|
||||||
|
<PeerDetailDrawer peerId={peer} />
|
||||||
|
</small>
|
||||||
|
<ClipboardCopy value={peer} />
|
||||||
|
</div>
|
||||||
|
</TableCell>
|
||||||
|
<TableCell className={classes.values}>
|
||||||
|
<span
|
||||||
style={{
|
style={{
|
||||||
color: fromBZZbaseUnit(peerBalance.balance) > 0 ? '#32c48d' : '#c9201f',
|
color: balance > 0 ? '#32c48d' : '#c9201f',
|
||||||
textAlign: 'right',
|
|
||||||
fontFamily: 'monospace, monospace',
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{fromBZZbaseUnit(peerBalance.balance).toFixed(7).toLocaleString()}
|
{fromBZZbaseUnit(balance).toFixed(7).toLocaleString()}
|
||||||
|
</span>{' '}
|
||||||
|
BZZ
|
||||||
|
</TableCell>
|
||||||
|
<TableCell className={classes.values}>
|
||||||
|
-{fromBZZbaseUnit(sent).toFixed(7)} / {fromBZZbaseUnit(received).toFixed(7)} BZZ
|
||||||
|
</TableCell>
|
||||||
|
<TableCell className={classes.values}>
|
||||||
|
<span
|
||||||
|
style={{
|
||||||
|
color: total > 0 ? '#32c48d' : '#c9201f',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{fromBZZbaseUnit(total).toFixed(7)}
|
||||||
|
</span>{' '}
|
||||||
|
BZZ
|
||||||
|
</TableCell>
|
||||||
|
<TableCell className={classes.values}>
|
||||||
|
{isLoadingUncashed && 'loading...'}
|
||||||
|
{!isLoadingUncashed && <>{uncashedAmount > 0 ? fromBZZbaseUnit(uncashedAmount).toFixed(7) : '0'} BZZ</>}
|
||||||
|
</TableCell>
|
||||||
|
<TableCell className={classes.values}>
|
||||||
|
{uncashedAmount > 0 && <CashoutModal uncashedAmount={uncashedAmount} peerId={peer} />}
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell align="right"></TableCell>
|
|
||||||
</TableRow>
|
</TableRow>
|
||||||
))}
|
))}
|
||||||
</TableBody>
|
</TableBody>
|
||||||
</Table>
|
</Table>
|
||||||
</TableContainer>
|
</TableContainer>
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,125 +0,0 @@
|
|||||||
import type { ReactElement } from 'react'
|
|
||||||
import { makeStyles } from '@material-ui/core/styles'
|
|
||||||
import {
|
|
||||||
Table,
|
|
||||||
TableBody,
|
|
||||||
TableCell,
|
|
||||||
TableContainer,
|
|
||||||
TableRow,
|
|
||||||
TableHead,
|
|
||||||
Paper,
|
|
||||||
Container,
|
|
||||||
CircularProgress,
|
|
||||||
} from '@material-ui/core'
|
|
||||||
|
|
||||||
import { fromBZZbaseUnit } from '../../utils'
|
|
||||||
import EthereumAddress from '../../components/EthereumAddress'
|
|
||||||
import ClipboardCopy from '../../components/ClipboardCopy'
|
|
||||||
import PeerDetailDrawer from './PeerDetailDrawer'
|
|
||||||
|
|
||||||
const useStyles = makeStyles({
|
|
||||||
table: {
|
|
||||||
minWidth: 650,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
interface ChequeEvent {
|
|
||||||
beneficiary: string
|
|
||||||
chequebook: string
|
|
||||||
payout: number
|
|
||||||
}
|
|
||||||
|
|
||||||
interface PeerCheque {
|
|
||||||
lastreceived?: ChequeEvent
|
|
||||||
lastsent?: ChequeEvent
|
|
||||||
peer: string
|
|
||||||
}
|
|
||||||
|
|
||||||
interface PeerCheques {
|
|
||||||
lastcheques: Array<PeerCheque>
|
|
||||||
}
|
|
||||||
|
|
||||||
interface Props {
|
|
||||||
peerCheques: PeerCheques | null
|
|
||||||
loading?: boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
function ChequebookTable(props: Props): ReactElement {
|
|
||||||
const classes = useStyles()
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
{props.loading ? (
|
|
||||||
<Container style={{ textAlign: 'center', padding: '50px' }}>
|
|
||||||
<CircularProgress />
|
|
||||||
</Container>
|
|
||||||
) : (
|
|
||||||
<div>
|
|
||||||
<TableContainer component={Paper}>
|
|
||||||
<Table className={classes.table} size="small" aria-label="simple table">
|
|
||||||
<TableHead>
|
|
||||||
<TableRow>
|
|
||||||
<TableCell>Peer</TableCell>
|
|
||||||
<TableCell>Last Received</TableCell>
|
|
||||||
<TableCell>Last Sent</TableCell>
|
|
||||||
<TableCell align="right"></TableCell>
|
|
||||||
</TableRow>
|
|
||||||
</TableHead>
|
|
||||||
<TableBody>
|
|
||||||
{props.peerCheques?.lastcheques.map((peerCheque: PeerCheque) => (
|
|
||||||
<TableRow key={peerCheque.peer}>
|
|
||||||
<TableCell>
|
|
||||||
<div style={{ display: 'flex' }}>
|
|
||||||
<small>
|
|
||||||
<PeerDetailDrawer peerId={peerCheque.peer} />
|
|
||||||
</small>
|
|
||||||
<ClipboardCopy value={peerCheque.peer} />
|
|
||||||
</div>
|
|
||||||
</TableCell>
|
|
||||||
<TableCell style={{ maxWidth: '320px' }}>
|
|
||||||
<p style={{ marginBottom: '0px', fontFamily: 'monospace, monospace', display: 'flex' }}>
|
|
||||||
<span style={{ whiteSpace: 'nowrap', marginRight: '12px', paddingTop: '3px' }}>
|
|
||||||
{peerCheque.lastreceived?.payout
|
|
||||||
? `${fromBZZbaseUnit(peerCheque.lastreceived?.payout).toFixed(7).toLocaleString()} from`
|
|
||||||
: '-'}
|
|
||||||
</span>
|
|
||||||
{peerCheque.lastreceived ? (
|
|
||||||
<EthereumAddress
|
|
||||||
hideBlockie
|
|
||||||
truncate
|
|
||||||
network="goerli"
|
|
||||||
address={peerCheque.lastreceived.beneficiary}
|
|
||||||
/>
|
|
||||||
) : null}
|
|
||||||
</p>
|
|
||||||
</TableCell>
|
|
||||||
<TableCell style={{ maxWidth: '320px' }}>
|
|
||||||
<p style={{ marginBottom: '0px', fontFamily: 'monospace, monospace', display: 'flex' }}>
|
|
||||||
<span style={{ whiteSpace: 'nowrap', marginRight: '12px', paddingTop: '3px' }}>
|
|
||||||
{peerCheque.lastsent?.payout
|
|
||||||
? `${fromBZZbaseUnit(peerCheque.lastsent?.payout).toFixed(7).toLocaleString()} to`
|
|
||||||
: '-'}
|
|
||||||
</span>
|
|
||||||
{peerCheque.lastsent ? (
|
|
||||||
<EthereumAddress
|
|
||||||
hideBlockie
|
|
||||||
truncate
|
|
||||||
network="goerli"
|
|
||||||
address={peerCheque.lastsent.beneficiary}
|
|
||||||
/>
|
|
||||||
) : null}
|
|
||||||
</p>
|
|
||||||
</TableCell>
|
|
||||||
<TableCell align="right"></TableCell>
|
|
||||||
</TableRow>
|
|
||||||
))}
|
|
||||||
</TableBody>
|
|
||||||
</Table>
|
|
||||||
</TableContainer>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default ChequebookTable
|
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
import type { ReactElement } from 'react'
|
||||||
|
import { Typography } from '@material-ui/core'
|
||||||
|
|
||||||
|
function truncStringPortion(str: string, firstCharCount = 10, endCharCount = 10) {
|
||||||
|
return `${str.substring(0, firstCharCount)}...${str.substring(str.length - endCharCount, str.length)}`
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
peerId: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function PeerDetail(props: Props): ReactElement {
|
||||||
|
return (
|
||||||
|
<Typography
|
||||||
|
variant="button"
|
||||||
|
style={{
|
||||||
|
fontFamily: 'monospace, monospace',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{truncStringPortion(props.peerId)}
|
||||||
|
</Typography>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -1,170 +0,0 @@
|
|||||||
import React, { ReactElement, useState } from 'react'
|
|
||||||
import { Paper, Container, Drawer, Button, Typography, CircularProgress, Grid } from '@material-ui/core'
|
|
||||||
import ClipboardCopy from '../../components/ClipboardCopy'
|
|
||||||
import { beeDebugApi } from '../../services/bee'
|
|
||||||
import EthereumAddress from '../../components/EthereumAddress'
|
|
||||||
import { fromBZZbaseUnit } from '../../utils'
|
|
||||||
import { LastCashoutActionResponse, LastChequesForPeerResponse } from '@ethersphere/bee-js'
|
|
||||||
|
|
||||||
function truncStringPortion(str: string, firstCharCount = 10, endCharCount = 10) {
|
|
||||||
let convertedStr = ''
|
|
||||||
convertedStr += str.substring(0, firstCharCount)
|
|
||||||
convertedStr += '.'.repeat(3)
|
|
||||||
convertedStr += str.substring(str.length - endCharCount, str.length)
|
|
||||||
|
|
||||||
return convertedStr
|
|
||||||
}
|
|
||||||
|
|
||||||
interface Props {
|
|
||||||
peerId: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function Index(props: Props): ReactElement {
|
|
||||||
const [open, setOpen] = useState(false)
|
|
||||||
const [peerCashout, setPeerCashout] = useState<LastCashoutActionResponse | null>(null)
|
|
||||||
const [peerCheque, setPeerCheque] = useState<LastChequesForPeerResponse | null>(null)
|
|
||||||
|
|
||||||
const [isLoadingPeerCheque, setIsLoadingPeerCheque] = useState<boolean>(false)
|
|
||||||
const [isLoadingPeerCashout, setIsLoadingPeerCashout] = useState<boolean>(false)
|
|
||||||
|
|
||||||
const handleClickOpen = (peerId: string) => {
|
|
||||||
setIsLoadingPeerCashout(true)
|
|
||||||
beeDebugApi.chequebook
|
|
||||||
.getPeerLastCashout(peerId)
|
|
||||||
.then(res => {
|
|
||||||
setPeerCashout(res)
|
|
||||||
})
|
|
||||||
.catch(() => {
|
|
||||||
// FIXME: handle the error
|
|
||||||
})
|
|
||||||
.finally(() => {
|
|
||||||
setIsLoadingPeerCashout(false)
|
|
||||||
})
|
|
||||||
|
|
||||||
setIsLoadingPeerCheque(true)
|
|
||||||
beeDebugApi.chequebook
|
|
||||||
.getPeerLastCheques(peerId)
|
|
||||||
.then(res => {
|
|
||||||
setPeerCheque(res)
|
|
||||||
})
|
|
||||||
.catch(() => {
|
|
||||||
// FIXME: handle the error
|
|
||||||
})
|
|
||||||
.finally(() => {
|
|
||||||
setIsLoadingPeerCheque(false)
|
|
||||||
})
|
|
||||||
|
|
||||||
setOpen(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleClose = () => {
|
|
||||||
setOpen(false)
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<Button color="primary" onClick={() => handleClickOpen(props.peerId)}>
|
|
||||||
{truncStringPortion(props.peerId)}
|
|
||||||
</Button>
|
|
||||||
<Drawer anchor={'right'} open={open} onClose={handleClose}>
|
|
||||||
<div style={{ padding: '20px' }}>
|
|
||||||
<Typography variant="h5" gutterBottom style={{ display: 'flex' }}>
|
|
||||||
<span>Peer: {truncStringPortion(props.peerId)}</span>
|
|
||||||
<ClipboardCopy value={props.peerId} />
|
|
||||||
</Typography>
|
|
||||||
<Paper>
|
|
||||||
{isLoadingPeerCashout || isLoadingPeerCheque ? (
|
|
||||||
<Container style={{ textAlign: 'center', padding: '50px' }}>
|
|
||||||
<CircularProgress />
|
|
||||||
</Container>
|
|
||||||
) : (
|
|
||||||
<div style={{ textAlign: 'left', padding: '10px' }}>
|
|
||||||
<h3>Last Cheque</h3>
|
|
||||||
<Grid container spacing={1}>
|
|
||||||
<Grid key={1} item xs={12} sm={12} xl={6}>
|
|
||||||
<h5>Last Sent</h5>
|
|
||||||
<p>
|
|
||||||
Payout:
|
|
||||||
<span style={{ marginBottom: '0px', fontFamily: 'monospace, monospace' }}>
|
|
||||||
{' '}
|
|
||||||
{peerCheque?.lastsent?.payout ? fromBZZbaseUnit(peerCheque?.lastsent?.payout) : '-'}
|
|
||||||
</span>
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
Beneficiary:
|
|
||||||
<EthereumAddress network={'goerli'} hideBlockie address={peerCheque?.lastsent?.beneficiary} />
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
Chequebook:
|
|
||||||
<EthereumAddress network={'goerli'} hideBlockie address={peerCheque?.lastsent?.chequebook} />
|
|
||||||
</p>
|
|
||||||
</Grid>
|
|
||||||
<Grid key={1} item xs={12} sm={12} xl={6}>
|
|
||||||
<h5>Last Received</h5>
|
|
||||||
<p>
|
|
||||||
Payout:
|
|
||||||
<span style={{ marginBottom: '0px', fontFamily: 'monospace, monospace' }}>
|
|
||||||
{' '}
|
|
||||||
{peerCheque?.lastreceived?.payout ? fromBZZbaseUnit(peerCheque?.lastreceived?.payout) : '-'}
|
|
||||||
</span>
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
Beneficiary:
|
|
||||||
<EthereumAddress network={'goerli'} hideBlockie address={peerCheque?.lastreceived?.beneficiary} />
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
Chequebook:
|
|
||||||
<EthereumAddress network={'goerli'} hideBlockie address={peerCheque?.lastreceived?.chequebook} />
|
|
||||||
</p>
|
|
||||||
</Grid>
|
|
||||||
</Grid>
|
|
||||||
<h3>Last Cashout</h3>
|
|
||||||
{peerCashout && peerCashout?.cumulativePayout > 0 ? (
|
|
||||||
<div>
|
|
||||||
<p>
|
|
||||||
Cumulative Payout:
|
|
||||||
<span style={{ marginBottom: '0px', fontFamily: 'monospace, monospace' }}>
|
|
||||||
{peerCashout?.cumulativePayout ? fromBZZbaseUnit(peerCashout?.cumulativePayout) : '-'}
|
|
||||||
</span>
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
Last Payout:
|
|
||||||
<span style={{ marginBottom: '0px', fontFamily: 'monospace, monospace' }}>
|
|
||||||
{' '}
|
|
||||||
{peerCashout?.result.lastPayout ? fromBZZbaseUnit(peerCashout?.result.lastPayout) : '-'}
|
|
||||||
</span>
|
|
||||||
<span> {peerCashout?.result.bounced ? 'Bounced' : ''}</span>
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
Beneficiary:
|
|
||||||
<EthereumAddress network={'goerli'} hideBlockie address={peerCashout?.beneficiary} />
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
Chequebook:
|
|
||||||
<EthereumAddress network={'goerli'} hideBlockie address={peerCashout?.chequebook} />
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
Recipient:
|
|
||||||
<EthereumAddress network={'goerli'} hideBlockie address={peerCashout?.result.recipient} />
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
Transaction:
|
|
||||||
<EthereumAddress
|
|
||||||
transaction
|
|
||||||
network={'goerli'}
|
|
||||||
hideBlockie
|
|
||||||
address={peerCashout?.transactionHash}
|
|
||||||
/>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
'None'
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</Paper>
|
|
||||||
</div>
|
|
||||||
</Drawer>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,69 +0,0 @@
|
|||||||
import { ReactElement } from 'react'
|
|
||||||
import { makeStyles } from '@material-ui/core/styles'
|
|
||||||
import {
|
|
||||||
Table,
|
|
||||||
TableBody,
|
|
||||||
TableCell,
|
|
||||||
TableContainer,
|
|
||||||
TableRow,
|
|
||||||
TableHead,
|
|
||||||
Paper,
|
|
||||||
Container,
|
|
||||||
CircularProgress,
|
|
||||||
} from '@material-ui/core'
|
|
||||||
|
|
||||||
import { fromBZZbaseUnit } from '../../utils'
|
|
||||||
|
|
||||||
import type { AllSettlements, Settlements } from '@ethersphere/bee-js'
|
|
||||||
|
|
||||||
const useStyles = makeStyles({
|
|
||||||
table: {
|
|
||||||
minWidth: 650,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
interface Props {
|
|
||||||
nodeSettlements: AllSettlements | null
|
|
||||||
loading?: boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
function SettlementsTable(props: Props): ReactElement {
|
|
||||||
const classes = useStyles()
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
{props.loading ? (
|
|
||||||
<Container style={{ textAlign: 'center', padding: '50px' }}>
|
|
||||||
<CircularProgress />
|
|
||||||
</Container>
|
|
||||||
) : (
|
|
||||||
<TableContainer component={Paper}>
|
|
||||||
<Table className={classes.table} size="small" aria-label="simple table">
|
|
||||||
<TableHead>
|
|
||||||
<TableRow>
|
|
||||||
<TableCell>Peer</TableCell>
|
|
||||||
<TableCell>Received (BZZ)</TableCell>
|
|
||||||
<TableCell>Sent (BZZ)</TableCell>
|
|
||||||
</TableRow>
|
|
||||||
</TableHead>
|
|
||||||
<TableBody>
|
|
||||||
{props.nodeSettlements?.settlements.map((item: Settlements) => (
|
|
||||||
<TableRow key={item.peer}>
|
|
||||||
<TableCell>{item.peer}</TableCell>
|
|
||||||
<TableCell style={{ fontFamily: 'monospace, monospace' }}>
|
|
||||||
{item.received > 0 ? fromBZZbaseUnit(item.received).toFixed(7).toLocaleString() : item.received}
|
|
||||||
</TableCell>
|
|
||||||
<TableCell style={{ fontFamily: 'monospace, monospace' }}>
|
|
||||||
{item.sent > 0 ? fromBZZbaseUnit(item.sent).toFixed(7).toLocaleString() : item.sent}
|
|
||||||
</TableCell>
|
|
||||||
</TableRow>
|
|
||||||
))}
|
|
||||||
</TableBody>
|
|
||||||
</Table>
|
|
||||||
</TableContainer>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default SettlementsTable
|
|
||||||
+17
-125
@@ -1,11 +1,9 @@
|
|||||||
import { ReactElement, useState, ChangeEvent, ReactChild } from 'react'
|
import type { ReactElement } from 'react'
|
||||||
import { withStyles, Theme, createStyles, makeStyles } from '@material-ui/core/styles'
|
import { Theme, createStyles, makeStyles } from '@material-ui/core/styles'
|
||||||
import { Tabs, Tab, Box, Typography, Container, CircularProgress } from '@material-ui/core'
|
import { Container, CircularProgress } from '@material-ui/core'
|
||||||
|
|
||||||
import AccountCard from '../accounting/AccountCard'
|
import AccountCard from '../accounting/AccountCard'
|
||||||
import BalancesTable from './BalancesTable'
|
import BalancesTable from './BalancesTable'
|
||||||
import ChequebookTable from './ChequebookTable'
|
|
||||||
import SettlementsTable from './SettlementsTable'
|
|
||||||
import EthereumAddressCard from '../../components/EthereumAddressCard'
|
import EthereumAddressCard from '../../components/EthereumAddressCard'
|
||||||
import TroubleshootConnectionCard from '../../components/TroubleshootConnectionCard'
|
import TroubleshootConnectionCard from '../../components/TroubleshootConnectionCard'
|
||||||
|
|
||||||
@@ -13,25 +11,10 @@ import {
|
|||||||
useApiNodeAddresses,
|
useApiNodeAddresses,
|
||||||
useApiChequebookAddress,
|
useApiChequebookAddress,
|
||||||
useApiChequebookBalance,
|
useApiChequebookBalance,
|
||||||
useApiPeerBalances,
|
|
||||||
useApiPeerCheques,
|
|
||||||
useApiSettlements,
|
|
||||||
useApiHealth,
|
useApiHealth,
|
||||||
useDebugApiHealth,
|
useDebugApiHealth,
|
||||||
} from '../../hooks/apiHooks'
|
} from '../../hooks/apiHooks'
|
||||||
|
import { useAccounting } from '../../hooks/accounting'
|
||||||
interface TabPanelProps {
|
|
||||||
children?: ReactChild
|
|
||||||
index: number
|
|
||||||
value: number
|
|
||||||
}
|
|
||||||
|
|
||||||
function a11yProps(index: number) {
|
|
||||||
return {
|
|
||||||
id: `simple-tab-${index}`,
|
|
||||||
'aria-controls': `simple-tabpanel-${index}`,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const useStyles = makeStyles((theme: Theme) =>
|
const useStyles = makeStyles((theme: Theme) =>
|
||||||
createStyles({
|
createStyles({
|
||||||
@@ -44,105 +27,33 @@ const useStyles = makeStyles((theme: Theme) =>
|
|||||||
)
|
)
|
||||||
|
|
||||||
export default function Accounting(): ReactElement {
|
export default function Accounting(): ReactElement {
|
||||||
const [value, setValue] = useState(0)
|
|
||||||
const classes = useStyles()
|
const classes = useStyles()
|
||||||
|
|
||||||
const handleChange = (event: ChangeEvent<unknown>, newValue: number) => {
|
|
||||||
setValue(newValue)
|
|
||||||
}
|
|
||||||
|
|
||||||
const { chequebookAddress, isLoadingChequebookAddress } = useApiChequebookAddress()
|
const { chequebookAddress, isLoadingChequebookAddress } = useApiChequebookAddress()
|
||||||
const { chequebookBalance, isLoadingChequebookBalance } = useApiChequebookBalance()
|
const { chequebookBalance, isLoadingChequebookBalance } = useApiChequebookBalance()
|
||||||
const { peerBalances, isLoadingPeerBalances } = useApiPeerBalances()
|
|
||||||
const { nodeAddresses, isLoadingNodeAddresses } = useApiNodeAddresses()
|
const { nodeAddresses, isLoadingNodeAddresses } = useApiNodeAddresses()
|
||||||
const { health, isLoadingHealth } = useApiHealth()
|
const { health, isLoadingHealth } = useApiHealth()
|
||||||
const { nodeHealth, isLoadingNodeHealth } = useDebugApiHealth()
|
const { nodeHealth, isLoadingNodeHealth } = useDebugApiHealth()
|
||||||
|
const { isLoading, totalsent, totalreceived, accounting, isLoadingUncashed, error } = useAccounting()
|
||||||
|
|
||||||
const { peerCheques, isLoadingPeerCheques } = useApiPeerCheques()
|
if (isLoadingHealth || isLoadingNodeHealth) {
|
||||||
const { settlements, isLoadingSettlements } = useApiSettlements()
|
|
||||||
|
|
||||||
function TabPanel(props: TabPanelProps) {
|
|
||||||
const { children, value, index, ...other } = props
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<Container style={{ textAlign: 'center', padding: '50px' }}>
|
||||||
role="tabpanel"
|
<CircularProgress />
|
||||||
hidden={value !== index}
|
</Container>
|
||||||
id={`simple-tabpanel-${index}`}
|
|
||||||
aria-labelledby={`simple-tab-${index}`}
|
|
||||||
{...other}
|
|
||||||
>
|
|
||||||
{value === index && (
|
|
||||||
<Box style={{ marginTop: '20px' }}>
|
|
||||||
<Typography component="div">{children}</Typography>
|
|
||||||
</Box>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const AntTabs = withStyles({
|
if (nodeHealth?.status !== 'ok' || !health) return <TroubleshootConnectionCard />
|
||||||
root: {
|
|
||||||
borderBottom: '1px solid #e8e8e8',
|
|
||||||
},
|
|
||||||
indicator: {
|
|
||||||
backgroundColor: '#3f51b5',
|
|
||||||
},
|
|
||||||
})(Tabs)
|
|
||||||
|
|
||||||
interface StyledTabProps {
|
|
||||||
label: string
|
|
||||||
}
|
|
||||||
|
|
||||||
const AntTab = withStyles((theme: Theme) =>
|
|
||||||
createStyles({
|
|
||||||
root: {
|
|
||||||
textTransform: 'none',
|
|
||||||
minWidth: 72,
|
|
||||||
backgroundColor: 'transparent',
|
|
||||||
fontWeight: theme.typography.fontWeightRegular,
|
|
||||||
marginRight: theme.spacing(4),
|
|
||||||
fontFamily: [
|
|
||||||
'-apple-system',
|
|
||||||
'BlinkMacSystemFont',
|
|
||||||
'"Segoe UI"',
|
|
||||||
'Roboto',
|
|
||||||
'"Helvetica Neue"',
|
|
||||||
'Arial',
|
|
||||||
'sans-serif',
|
|
||||||
'"Apple Color Emoji"',
|
|
||||||
'"Segoe UI Emoji"',
|
|
||||||
'"Segoe UI Symbol"',
|
|
||||||
].join(','),
|
|
||||||
'&:hover': {
|
|
||||||
color: '#3f51b5',
|
|
||||||
opacity: 1,
|
|
||||||
},
|
|
||||||
'&$selected': {
|
|
||||||
color: '#3f51b5',
|
|
||||||
fontWeight: theme.typography.fontWeightMedium,
|
|
||||||
},
|
|
||||||
'&:focus': {
|
|
||||||
color: '#3f51b5',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
)((props: StyledTabProps) => <Tab disableRipple {...props} />)
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
|
||||||
{
|
|
||||||
// FIXME: this should be broken up
|
|
||||||
/* eslint-disable no-nested-ternary */
|
|
||||||
nodeHealth?.status === 'ok' && health ? (
|
|
||||||
<div className={classes.root}>
|
<div className={classes.root}>
|
||||||
<AccountCard
|
<AccountCard
|
||||||
chequebookAddress={chequebookAddress}
|
chequebookAddress={chequebookAddress}
|
||||||
isLoadingChequebookAddress={isLoadingChequebookAddress}
|
isLoading={isLoadingChequebookAddress || isLoading || isLoadingChequebookBalance}
|
||||||
chequebookBalance={chequebookBalance}
|
chequebookBalance={chequebookBalance}
|
||||||
isLoadingChequebookBalance={isLoadingChequebookBalance}
|
totalsent={totalsent}
|
||||||
settlements={settlements}
|
totalreceived={totalreceived}
|
||||||
isLoadingSettlements={isLoadingSettlements}
|
|
||||||
/>
|
/>
|
||||||
<EthereumAddressCard
|
<EthereumAddressCard
|
||||||
nodeAddresses={nodeAddresses}
|
nodeAddresses={nodeAddresses}
|
||||||
@@ -150,31 +61,12 @@ export default function Accounting(): ReactElement {
|
|||||||
chequebookAddress={chequebookAddress}
|
chequebookAddress={chequebookAddress}
|
||||||
isLoadingChequebookAddress={isLoadingChequebookAddress}
|
isLoadingChequebookAddress={isLoadingChequebookAddress}
|
||||||
/>
|
/>
|
||||||
<div>
|
{error && (
|
||||||
<AntTabs style={{ marginTop: '12px' }} value={value} onChange={handleChange} aria-label="ant example">
|
|
||||||
<AntTab label="Balances" {...a11yProps(0)} />
|
|
||||||
<AntTab label="Chequebook" {...a11yProps(1)} />
|
|
||||||
<AntTab label="Settlements" {...a11yProps(2)} />
|
|
||||||
</AntTabs>
|
|
||||||
<TabPanel value={value} index={0}>
|
|
||||||
<BalancesTable peerBalances={peerBalances} loading={isLoadingPeerBalances} />
|
|
||||||
</TabPanel>
|
|
||||||
<TabPanel value={value} index={1}>
|
|
||||||
<ChequebookTable peerCheques={peerCheques} loading={isLoadingPeerCheques} />
|
|
||||||
</TabPanel>
|
|
||||||
<TabPanel value={value} index={2}>
|
|
||||||
<SettlementsTable nodeSettlements={settlements} loading={isLoadingSettlements} />
|
|
||||||
</TabPanel>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
) : isLoadingHealth || isLoadingNodeHealth ? (
|
|
||||||
<Container style={{ textAlign: 'center', padding: '50px' }}>
|
<Container style={{ textAlign: 'center', padding: '50px' }}>
|
||||||
<CircularProgress />
|
Error loading accountin details: {error.message}
|
||||||
</Container>
|
</Container>
|
||||||
) : (
|
)}
|
||||||
<TroubleshootConnectionCard />
|
{!error && <BalancesTable accounting={accounting} isLoadingUncashed={isLoadingUncashed} />}
|
||||||
) /* eslint-enable no-nested-ternary */
|
|
||||||
}
|
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
Vendored
+10
@@ -21,7 +21,17 @@ interface StatusEthereumConnectionHook extends StatusHookCommon {
|
|||||||
interface StatusTopologyHook extends StatusHookCommon {
|
interface StatusTopologyHook extends StatusHookCommon {
|
||||||
topology: Topology | null
|
topology: Topology | null
|
||||||
}
|
}
|
||||||
|
|
||||||
interface StatusChequebookHook extends StatusHookCommon {
|
interface StatusChequebookHook extends StatusHookCommon {
|
||||||
chequebookBalance: ChequebookBalanceResponse | null
|
chequebookBalance: ChequebookBalanceResponse | null
|
||||||
chequebookAddress: ChequebookAddressResponse | null
|
chequebookAddress: ChequebookAddressResponse | null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface Accounting {
|
||||||
|
peer: string
|
||||||
|
uncashedAmount: number
|
||||||
|
balance: number
|
||||||
|
received: number
|
||||||
|
sent: number
|
||||||
|
total: number
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user