diff --git a/src/components/CashoutModal.tsx b/src/components/CashoutModal.tsx index e542c51..36a7b91 100644 --- a/src/components/CashoutModal.tsx +++ b/src/components/CashoutModal.tsx @@ -7,6 +7,7 @@ import DialogContentText from '@material-ui/core/DialogContentText' import DialogTitle from '@material-ui/core/DialogTitle' import { useSnackbar } from 'notistack' import { ReactElement, useState, useContext } from 'react' +import { Zap } from 'react-feather' import { Context as SettingsContext } from '../providers/Settings' import EthereumAddress from './EthereumAddress' @@ -59,8 +60,8 @@ export default function CheckoutModal({ peerId, uncashedAmount }: Props): ReactE return (
- Cashout Cheque diff --git a/src/components/ExpandableList.tsx b/src/components/ExpandableList.tsx index ef1b398..912adcc 100644 --- a/src/components/ExpandableList.tsx +++ b/src/components/ExpandableList.tsx @@ -19,20 +19,27 @@ const useStyles = makeStyles((theme: Theme) => header: { backgroundColor: theme.palette.background.paper, }, - content: { + contentLevel0: { marginTop: theme.spacing(1), }, + contentLevel12: { + marginTop: theme.spacing(0.25), + }, + infoText: { + color: '#c9c9c9', + }, }), ) interface Props { children?: ReactNode label: ReactNode + info?: ReactNode level?: 0 | 1 | 2 defaultOpen?: boolean } -export default function ExpandableList({ children, label, level, defaultOpen }: Props): ReactElement | null { +export default function ExpandableList({ children, label, level, defaultOpen, info }: Props): ReactElement | null { const classes = useStyles() const [open, setOpen] = useState(Boolean(defaultOpen)) @@ -42,23 +49,33 @@ export default function ExpandableList({ children, label, level, defaultOpen }: let rootLevelClass = '' let typographyVariant: 'h1' | 'h2' | 'h3' = 'h1' + let contentLevelClass = classes.contentLevel0 if (level === 1) { rootLevelClass = classes.rootLevel1 typographyVariant = 'h2' + contentLevelClass = classes.contentLevel12 } else if (level === 2) { rootLevelClass = classes.rootLevel2 typographyVariant = 'h3' + contentLevelClass = classes.contentLevel12 } return (
{label}} /> - {open ? : } +
+ {!open && ( + + {info} + + )} + {open ? : } +
-
{children}
+
{children}
) diff --git a/src/components/ExpandableListItemActions.tsx b/src/components/ExpandableListItemActions.tsx new file mode 100644 index 0000000..497cef7 --- /dev/null +++ b/src/components/ExpandableListItemActions.tsx @@ -0,0 +1,38 @@ +import { ReactElement, ReactNode } from 'react' +import { makeStyles, Theme, createStyles } from '@material-ui/core/styles' +import { Grid } from '@material-ui/core' + +const useStyles = makeStyles((theme: Theme) => + createStyles({ + action: { + marginTop: theme.spacing(0.75), + marginRight: theme.spacing(1), + }, + }), +) + +interface Props { + children: ReactNode | ReactNode[] +} + +export default function ExpandableListItemActions({ children }: Props): ReactElement | null { + const classes = useStyles() + + if (Array.isArray(children)) { + return ( + + {children.map((a, i) => ( + + {a} + + ))} + + ) + } + + return ( + + {children} + + ) +} diff --git a/src/components/WithdrawDepositModal.tsx b/src/components/WithdrawDepositModal.tsx index 84fb785..31592df 100644 --- a/src/components/WithdrawDepositModal.tsx +++ b/src/components/WithdrawDepositModal.tsx @@ -1,4 +1,4 @@ -import { ReactElement, useState } from 'react' +import { ReactElement, ReactNode, useState } from 'react' import Button from '@material-ui/core/Button' import Input from '@material-ui/core/Input' import Dialog from '@material-ui/core/Dialog' @@ -19,6 +19,7 @@ interface Props { max?: BigNumber min?: BigNumber action: (amount: bigint) => Promise + icon?: ReactNode } export default function WithdrawDepositModal({ @@ -29,6 +30,7 @@ export default function WithdrawDepositModal({ max, label, action, + icon, }: Props): ReactElement { const [open, setOpen] = useState(false) const [amount, setAmount] = useState('') @@ -36,8 +38,9 @@ export default function WithdrawDepositModal({ const [amountError, setAmountError] = useState(null) const { enqueueSnackbar } = useSnackbar() - const handleClickOpen = () => { + const handleClickOpen = (e: React.MouseEvent) => { setOpen(true) + e.stopPropagation() } const handleClose = () => { @@ -74,7 +77,7 @@ export default function WithdrawDepositModal({ return (
- diff --git a/src/containers/DepositModal.tsx b/src/containers/DepositModal.tsx index eb8403d..2ec5694 100644 --- a/src/containers/DepositModal.tsx +++ b/src/containers/DepositModal.tsx @@ -1,4 +1,5 @@ import { ReactElement, useContext } from 'react' +import { Download } from 'react-feather' import { Context as SettingsContext } from '../providers/Settings' import WithdrawDepositModal from '../components/WithdrawDepositModal' @@ -13,6 +14,7 @@ export default function DepositModal(): ReactElement { errorMessage="Error with depositing" dialogMessage="Specify the amount of BZZ you would like to withdraw from your node." label="Deposit" + icon={} min={new BigNumber(0)} action={(amount: bigint) => { if (!beeDebugApi) throw new Error('Bee Debug URL is not valid') diff --git a/src/containers/WithdrawModal.tsx b/src/containers/WithdrawModal.tsx index 373bbf5..ce9bbf3 100644 --- a/src/containers/WithdrawModal.tsx +++ b/src/containers/WithdrawModal.tsx @@ -1,4 +1,5 @@ import { ReactElement, useContext } from 'react' +import { Upload } from 'react-feather' import { Context as SettingsContext } from '../providers/Settings' import WithdrawDepositModal from '../components/WithdrawDepositModal' @@ -13,6 +14,7 @@ export default function WithdrawModal(): ReactElement { errorMessage="Error with withdrawing." dialogMessage="Specify the amount of BZZ you would like to withdraw from your node." label="Withdraw" + icon={} min={new BigNumber(0)} action={(amount: bigint) => { if (!beeDebugApi) throw new Error('Bee Debug URL is not valid') diff --git a/src/hooks/accounting.ts b/src/hooks/accounting.ts index d5caf7f..8932cc3 100644 --- a/src/hooks/accounting.ts +++ b/src/hooks/accounting.ts @@ -6,6 +6,7 @@ import { Balance, Settlements, Settlement } from '../types' interface UseAccountingHook { isLoadingUncashed: boolean + totalUncashed: Token accounting: Accounting[] | null } @@ -60,16 +61,21 @@ function mergeAccounting( }), ) - // 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) + // If there are no cheques (and hence last cashout actions) + if (!uncashedAmounts) return Object.values(accounting).sort((a, b) => (a.peer < b.peer ? -1 : 1)) uncashedAmounts?.forEach(({ peer, uncashedAmount }) => { accounting[peer].uncashedAmount = new Token(uncashedAmount) }) - return Object.values(accounting).sort((a, b) => - b.uncashedAmount.toBigNumber.minus(a.uncashedAmount.toBigNumber).toNumber(), - ) + // Return sorted by the uncashed amount first and then by the peer id + return Object.values(accounting).sort((a, b) => { + const diff = b.uncashedAmount.toBigNumber.minus(a.uncashedAmount.toBigNumber).toNumber() + + if (diff !== 0) return diff + + return a.peer < b.peer ? -1 : 1 + }) } export const useAccounting = ( @@ -98,8 +104,14 @@ export const useAccounting = ( const accounting = mergeAccounting(balances, settlements?.settlements, uncashedAmounts) + let totalUncashed: Token = new Token('0') + accounting?.forEach( + ({ uncashedAmount }) => (totalUncashed = new Token(totalUncashed.toBigNumber.plus(uncashedAmount.toBigNumber))), + ) + return { isLoadingUncashed, + totalUncashed, accounting, } } diff --git a/src/pages/accounting/AccountCard.tsx b/src/pages/accounting/AccountCard.tsx deleted file mode 100644 index 7762eaf..0000000 --- a/src/pages/accounting/AccountCard.tsx +++ /dev/null @@ -1,94 +0,0 @@ -import { ReactElement } from 'react' - -import { createStyles, makeStyles } from '@material-ui/core/styles' -import { Card, CardContent, Typography, Theme } from '@material-ui/core/' -import WithdrawModal from '../../containers/WithdrawModal' -import DepositModal from '../../containers/DepositModal' - -import type { ChequebookAddressResponse } from '@ethersphere/bee-js' -import { Token } from '../../models/Token' - -const useStyles = makeStyles((theme: Theme) => - createStyles({ - root: { - display: 'flex', - }, - buttons: { - display: 'flex', - columnGap: theme.spacing(1), - }, - gridContainer: { - display: 'flex', - width: '100%', - marginLeft: theme.spacing(1), - marginRight: theme.spacing(1), - columnGap: theme.spacing(1), - rowGap: theme.spacing(1), - flex: '0 1 auto', - flexWrap: 'wrap', - justifyContent: 'space-between', - }, - chequebookActions: { - justifyContent: 'space-between', - display: 'flex', - marginBottom: theme.spacing(1), - }, - }), -) - -interface ChequebookBalance { - totalBalance: Token - availableBalance: Token -} - -interface Props { - chequebookAddress: ChequebookAddressResponse | null - chequebookBalance: ChequebookBalance | null - totalsent?: Token - totalreceived?: Token -} - -function AccountCard({ totalreceived, totalsent, chequebookBalance }: Props): ReactElement { - const classes = useStyles() - - return ( -
-
- - Chequebook - -
- - -
-
- - - -
- - Total Balance - - {chequebookBalance?.totalBalance.toFixedDecimal()} BZZ -
-
- - Available Uncommitted Balance - - {chequebookBalance?.availableBalance.toFixedDecimal()} BZZ -
-
- - Total Sent / Received - - - {totalsent?.toFixedDecimal()} / {totalreceived?.toFixedDecimal()} BZZ - -
-
-
-
- ) -} - -export default AccountCard diff --git a/src/pages/accounting/BalancesTable.tsx b/src/pages/accounting/BalancesTable.tsx deleted file mode 100644 index 89c4ec3..0000000 --- a/src/pages/accounting/BalancesTable.tsx +++ /dev/null @@ -1,94 +0,0 @@ -import type { ReactElement } from 'react' -import { makeStyles } from '@material-ui/core/styles' -import { Table, TableBody, TableCell, TableContainer, TableRow, TableHead, Paper } from '@material-ui/core' - -import ClipboardCopy from '../../components/ClipboardCopy' -import CashoutModal from '../../components/CashoutModal' -import PeerDetailDrawer from '../../components/PeerDetail' -import { Accounting } from '../../hooks/accounting' - -const useStyles = makeStyles({ - table: { - minWidth: 650, - }, - values: { - textAlign: 'right', - fontFamily: 'monospace, monospace', - }, -}) -interface Props { - isLoadingUncashed: boolean - accounting: Accounting[] | null -} - -function BalancesTable({ accounting, isLoadingUncashed }: Props): ReactElement | null { - if (accounting === null) return null - const classes = useStyles() - - return ( - - - - - Peer - Outstanding Balance - Settlements Sent / Received - Total - Uncashed Amount - - - - - {accounting.map(({ peer, balance, received, sent, uncashedAmount, total }) => ( - - -
- - - - -
-
- - - {balance.toFixedDecimal()} - {' '} - BZZ - - - -{sent.toFixedDecimal()} / {received.toFixedDecimal()} BZZ - - - - {total.toFixedDecimal()} - {' '} - BZZ - - - {isLoadingUncashed && 'loading...'} - {!isLoadingUncashed && ( - <>{uncashedAmount.toBigNumber.isGreaterThan('0') ? uncashedAmount.toFixedDecimal() : '0'} BZZ - )} - - - {uncashedAmount.toBigNumber.isGreaterThan('0') && ( - - )} - -
- ))} -
-
-
- ) -} - -export default BalancesTable diff --git a/src/pages/accounting/PeerBalances.tsx b/src/pages/accounting/PeerBalances.tsx new file mode 100644 index 0000000..6ed9190 --- /dev/null +++ b/src/pages/accounting/PeerBalances.tsx @@ -0,0 +1,52 @@ +import type { ReactElement } from 'react' + +import ExpandableList from '../../components/ExpandableList' +import ExpandableListItem from '../../components/ExpandableListItem' +import ExpandableListItemActions from '../../components/ExpandableListItemActions' +import ExpandableListItemKey from '../../components/ExpandableListItemKey' + +import CashoutModal from '../../components/CashoutModal' +import { Accounting } from '../../hooks/accounting' +import type { Token } from '../../models/Token' + +interface Props { + isLoadingUncashed: boolean + totalUncashed: Token + accounting: Accounting[] | null +} + +export default function PeerBalances({ accounting, isLoadingUncashed, totalUncashed }: Props): ReactElement | null { + return ( + + + {accounting?.map(({ peer, balance, received, sent, uncashedAmount, total }) => ( + + + + + + + {uncashedAmount.toBigNumber.isGreaterThan('0') && ( + + + + )} + + ))} + + ) +} diff --git a/src/pages/accounting/index.tsx b/src/pages/accounting/index.tsx index ba3e9f0..ce720d8 100644 --- a/src/pages/accounting/index.tsx +++ b/src/pages/accounting/index.tsx @@ -1,45 +1,52 @@ import { ReactElement, useContext } from 'react' -import { Theme, createStyles, makeStyles } from '@material-ui/core/styles' -import AccountCard from '../accounting/AccountCard' -import BalancesTable from './BalancesTable' -import EthereumAddressCard from '../../components/EthereumAddressCard' +import PeerBalances from './PeerBalances' import TroubleshootConnectionCard from '../../components/TroubleshootConnectionCard' import { Context as BeeContext } from '../../providers/Bee' import { Context as SettingsContext } from '../../providers/Settings' import { useAccounting } from '../../hooks/accounting' - -const useStyles = makeStyles((theme: Theme) => - createStyles({ - root: { - width: '100%', - display: 'grid', - rowGap: theme.spacing(3), - }, - }), -) +import ExpandableList from '../../components/ExpandableList' +import ExpandableListItem from '../../components/ExpandableListItem' +import ExpandableListItemActions from '../../components/ExpandableListItemActions' +import ExpandableListItemKey from '../../components/ExpandableListItemKey' +import WithdrawModal from '../../containers/WithdrawModal' +import DepositModal from '../../containers/DepositModal' export default function Accounting(): ReactElement { - const classes = useStyles() - const { status, nodeAddresses, chequebookAddress, chequebookBalance, settlements, peerBalances } = useContext(BeeContext) const { beeDebugApi } = useContext(SettingsContext) - const { accounting, isLoadingUncashed } = useAccounting(beeDebugApi, settlements, peerBalances) + const { accounting, totalUncashed, isLoadingUncashed } = useAccounting(beeDebugApi, settlements, peerBalances) if (!status.all) return return ( -
- - - +
+ + + + + + + + + + + + + + +
) } diff --git a/src/theme.tsx b/src/theme.tsx index 13fdc12..f47fe08 100644 --- a/src/theme.tsx +++ b/src/theme.tsx @@ -18,13 +18,13 @@ const componentsOverrides = (theme: Theme) => ({ maxWidthXl: { padding: theme.spacing(8) }, }, MuiButton: { - textSizeLarge: { - padding: theme.spacing(2), - }, + label: { margin: theme.spacing(2) }, + startIcon: { marginLeft: theme.spacing(1) }, + endIcon: { marginRight: theme.spacing(1) }, containedSizeLarge: { - padding: theme.spacing(2), - boxShadow: 'none', + padding: 0, borderRadius: 0, + boxShadow: 'none', '&:hover': { backgroundColor: theme.palette.primary.main, color: 'white', @@ -40,9 +40,12 @@ const componentsOverrides = (theme: Theme) => ({ contained: { backgroundColor: 'white', boxShadow: 'none', + padding: 0, + borderRadius: 0, '&:hover': { backgroundColor: theme.palette.primary.main, color: 'white', + boxShadow: 'none', // https://github.com/mui-org/material-ui/issues/22543 '@media (hover: none)': { backgroundColor: theme.palette.primary.main, @@ -116,7 +119,7 @@ export const theme = createMuiTheme({ default: '#efefef', }, primary: { - light: orange.A200, + light: '#fcf2e8', main: '#dd7700', dark: orange[800], },