feat: add loading state to wallet balance (#508)
* feat: add loading state to wallet balance * refactor: extract the wallet balance into a provider
This commit is contained in:
+21
-18
@@ -13,6 +13,7 @@ import { Provider as PlatformProvider } from './providers/Platform'
|
||||
import { Provider as SettingsProvider } from './providers/Settings'
|
||||
import { Provider as StampsProvider } from './providers/Stamps'
|
||||
import { Provider as TopUpProvider } from './providers/TopUp'
|
||||
import { Provider as BalanceProvider } from './providers/WalletBalance'
|
||||
import BaseRouter from './routes'
|
||||
import { theme } from './theme'
|
||||
import { config } from './config'
|
||||
@@ -43,24 +44,26 @@ const App = ({ beeApiUrl, beeDebugApiUrl, lockedApiSettings, isBeeDesktop }: Pro
|
||||
>
|
||||
<TopUpProvider>
|
||||
<BeeProvider>
|
||||
<StampsProvider>
|
||||
<FileProvider>
|
||||
<FeedsProvider>
|
||||
<PlatformProvider>
|
||||
<SnackbarProvider preventDuplicate anchorOrigin={{ horizontal: 'right', vertical: 'bottom' }}>
|
||||
<Router>
|
||||
<>
|
||||
<CssBaseline />
|
||||
<Dashboard>
|
||||
<BaseRouter />
|
||||
</Dashboard>
|
||||
</>
|
||||
</Router>
|
||||
</SnackbarProvider>
|
||||
</PlatformProvider>
|
||||
</FeedsProvider>
|
||||
</FileProvider>
|
||||
</StampsProvider>
|
||||
<BalanceProvider>
|
||||
<StampsProvider>
|
||||
<FileProvider>
|
||||
<FeedsProvider>
|
||||
<PlatformProvider>
|
||||
<SnackbarProvider preventDuplicate anchorOrigin={{ horizontal: 'right', vertical: 'bottom' }}>
|
||||
<Router>
|
||||
<>
|
||||
<CssBaseline />
|
||||
<Dashboard>
|
||||
<BaseRouter />
|
||||
</Dashboard>
|
||||
</>
|
||||
</Router>
|
||||
</SnackbarProvider>
|
||||
</PlatformProvider>
|
||||
</FeedsProvider>
|
||||
</FileProvider>
|
||||
</StampsProvider>
|
||||
</BalanceProvider>
|
||||
</BeeProvider>
|
||||
</TopUpProvider>
|
||||
</SettingsProvider>
|
||||
|
||||
@@ -13,13 +13,15 @@ import { SwarmButton } from '../../../components/SwarmButton'
|
||||
import TroubleshootConnectionCard from '../../../components/TroubleshootConnectionCard'
|
||||
import { CheckState, Context as BeeContext } from '../../../providers/Bee'
|
||||
import { Context as SettingsContext } from '../../../providers/Settings'
|
||||
import { Context as BalanceProvider } from '../../../providers/WalletBalance'
|
||||
import { ROUTES } from '../../../routes'
|
||||
import { AccountNavigation } from '../AccountNavigation'
|
||||
import { Header } from '../Header'
|
||||
|
||||
export function AccountWallet(): ReactElement {
|
||||
const { balance, nodeAddresses, nodeInfo, status } = useContext(BeeContext)
|
||||
const { nodeAddresses, nodeInfo, status } = useContext(BeeContext)
|
||||
const { isBeeDesktop } = useContext(SettingsContext)
|
||||
const { balance } = useContext(BalanceProvider)
|
||||
|
||||
const navigate = useNavigate()
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ import ExpandableListItemKey from '../../components/ExpandableListItemKey'
|
||||
import { HistoryHeader } from '../../components/HistoryHeader'
|
||||
import { Loading } from '../../components/Loading'
|
||||
import { SwarmButton } from '../../components/SwarmButton'
|
||||
import { Context as BeeContext } from '../../providers/Bee'
|
||||
import { Context as BalanceProvider } from '../../providers/WalletBalance'
|
||||
import { Context as TopUpContext } from '../../providers/TopUp'
|
||||
import { Context as SettingsContext } from '../../providers/Settings'
|
||||
import { createGiftWallet } from '../../utils/desktop'
|
||||
@@ -24,7 +24,7 @@ const GIFT_WALLET_FUND_BZZ_AMOUNT = Token.fromDecimal('0.5', 16)
|
||||
export default function Index(): ReactElement {
|
||||
const { giftWallets, addGiftWallet } = useContext(TopUpContext)
|
||||
const { provider } = useContext(SettingsContext)
|
||||
const { balance } = useContext(BeeContext)
|
||||
const { balance } = useContext(BalanceProvider)
|
||||
|
||||
const [loading, setLoading] = useState(false)
|
||||
const [balances, setBalances] = useState<ResolvedWallet[]>([])
|
||||
|
||||
@@ -6,6 +6,7 @@ import Upload from 'remixicon-react/UploadLineIcon'
|
||||
|
||||
import { Context as BeeContext } from '../../providers/Bee'
|
||||
import { Context as SettingsContext } from '../../providers/Settings'
|
||||
import { Context as BalanceProvider } from '../../providers/WalletBalance'
|
||||
import Card from '../../components/Card'
|
||||
import Map from '../../components/Map'
|
||||
import ExpandableListItem from '../../components/ExpandableListItem'
|
||||
@@ -24,15 +25,24 @@ export default function Status(): ReactElement {
|
||||
latestBeeVersionUrl,
|
||||
topology,
|
||||
nodeInfo,
|
||||
balance,
|
||||
chequebookBalance,
|
||||
wallet,
|
||||
chainId,
|
||||
} = useContext(BeeContext)
|
||||
const { isBeeDesktop } = useContext(SettingsContext)
|
||||
const { balance, error } = useContext(BalanceProvider)
|
||||
const { beeDesktopVersion } = useIsBeeDesktop()
|
||||
const { newBeeDesktopVersion } = useNewBeeDesktopVersion(isBeeDesktop)
|
||||
const navigate = useNavigate()
|
||||
|
||||
let balanceText = 'Loading...'
|
||||
|
||||
if (error) {
|
||||
balanceText = 'Could not load...'
|
||||
console.error(error) // eslint-disable-line
|
||||
} else if (balance) {
|
||||
balanceText = `${balance.bzz.toSignificantDigits(4)} xBZZ | ${balance.dai.toSignificantDigits(4)} xDAI`
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'stretch', alignContent: 'stretch' }}>
|
||||
@@ -46,7 +56,7 @@ export default function Status(): ReactElement {
|
||||
onClick: () => navigate(ROUTES.ACCOUNT_WALLET),
|
||||
}}
|
||||
icon={<Wallet />}
|
||||
title={`${balance?.bzz.toSignificantDigits(4)} xBZZ | ${balance?.dai.toSignificantDigits(4)} xDAI`}
|
||||
title={balanceText}
|
||||
subtitle="Current wallet balance."
|
||||
status="ok"
|
||||
/>
|
||||
@@ -89,7 +99,7 @@ export default function Status(): ReactElement {
|
||||
icon={<ExchangeFunds />}
|
||||
title={
|
||||
chequebookBalance?.availableBalance
|
||||
? `${chequebookBalance.availableBalance.toFixedDecimal(4)} xBZZ`
|
||||
? `${chequebookBalance.availableBalance.toSignificantDigits(4)} xBZZ`
|
||||
: 'No available balance.'
|
||||
}
|
||||
subtitle="Chequebook not setup."
|
||||
@@ -149,7 +159,7 @@ export default function Status(): ReactElement {
|
||||
}
|
||||
/>
|
||||
<ExpandableListItem label="Mode" value={nodeInfo?.beeMode} />
|
||||
{wallet && <ExpandableListItem label="Blockchain network" value={chainIdToName(wallet.chainID)} />}
|
||||
{chainId && <ExpandableListItem label="Blockchain network" value={chainIdToName(chainId)} />}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import { Loading } from '../../components/Loading'
|
||||
import { SwarmButton } from '../../components/SwarmButton'
|
||||
import { SwarmDivider } from '../../components/SwarmDivider'
|
||||
import { Context } from '../../providers/Bee'
|
||||
import { Context as BalanceProvider } from '../../providers/WalletBalance'
|
||||
import { TopUpProgressIndicator } from './TopUpProgressIndicator'
|
||||
|
||||
const MINIMUM_XDAI = '0.5'
|
||||
@@ -21,7 +22,8 @@ interface Props {
|
||||
}
|
||||
|
||||
export default function Index({ header, title, p, next }: Props): ReactElement {
|
||||
const { nodeAddresses, balance } = useContext(Context)
|
||||
const { nodeAddresses } = useContext(Context)
|
||||
const { balance } = useContext(BalanceProvider)
|
||||
const navigate = useNavigate()
|
||||
|
||||
if (!balance || !nodeAddresses) {
|
||||
|
||||
@@ -13,6 +13,7 @@ import { SwarmButton } from '../../components/SwarmButton'
|
||||
import { SwarmDivider } from '../../components/SwarmDivider'
|
||||
import { Context as BeeContext } from '../../providers/Bee'
|
||||
import { Context as SettingsContext } from '../../providers/Settings'
|
||||
import { Context as BalanceProvider } from '../../providers/WalletBalance'
|
||||
import { ROUTES } from '../../routes'
|
||||
import { sleepMs } from '../../utils'
|
||||
import { restartBeeNode, upgradeToLightNode } from '../../utils/desktop'
|
||||
@@ -20,9 +21,9 @@ import { ResolvedWallet } from '../../utils/wallet'
|
||||
import { BeeModes } from '@ethersphere/bee-js'
|
||||
|
||||
export function GiftCardFund(): ReactElement {
|
||||
const { isBeeDesktop } = useContext(SettingsContext)
|
||||
const { nodeAddresses, balance, nodeInfo } = useContext(BeeContext)
|
||||
const { provider, providerUrl } = useContext(SettingsContext)
|
||||
const { nodeAddresses, nodeInfo } = useContext(BeeContext)
|
||||
const { isBeeDesktop, provider, providerUrl } = useContext(SettingsContext)
|
||||
const { balance } = useContext(BalanceProvider)
|
||||
|
||||
const [loading, setLoading] = useState(false)
|
||||
const [wallet, setWallet] = useState<ResolvedWallet | null>(null)
|
||||
|
||||
@@ -16,6 +16,7 @@ import { SwarmDivider } from '../../components/SwarmDivider'
|
||||
import { SwarmTextInput } from '../../components/SwarmTextInput'
|
||||
import { BzzToken } from '../../models/BzzToken'
|
||||
import { DaiToken } from '../../models/DaiToken'
|
||||
import { Context as BalanceProvider } from '../../providers/WalletBalance'
|
||||
import { Context as BeeContext } from '../../providers/Bee'
|
||||
import { Context as SettingsContext } from '../../providers/Settings'
|
||||
import { ROUTES } from '../../routes'
|
||||
@@ -36,9 +37,9 @@ export function Swap({ header }: Props): ReactElement {
|
||||
const [userInputSwap, setUserInputSwap] = useState<string | null>(null)
|
||||
const [price, setPrice] = useState(DaiToken.fromDecimal('0.6', 18))
|
||||
|
||||
const { providerUrl } = useContext(SettingsContext)
|
||||
const { balance, nodeAddresses, nodeInfo } = useContext(BeeContext)
|
||||
const { isBeeDesktop } = useContext(SettingsContext)
|
||||
const { providerUrl, isBeeDesktop } = useContext(SettingsContext)
|
||||
const { nodeAddresses, nodeInfo } = useContext(BeeContext)
|
||||
const { balance } = useContext(BalanceProvider)
|
||||
|
||||
const navigate = useNavigate()
|
||||
const { enqueueSnackbar } = useSnackbar()
|
||||
|
||||
@@ -12,6 +12,7 @@ import { SwarmButton } from '../../components/SwarmButton'
|
||||
import { ROUTES } from '../../routes'
|
||||
import { CheckState, Context as BeeContext } from '../../providers/Bee'
|
||||
import { Context as SettingsContext } from '../../providers/Settings'
|
||||
import { Context as BalanceProvider } from '../../providers/WalletBalance'
|
||||
import { BeeModes } from '@ethersphere/bee-js'
|
||||
import { restartBeeNode, upgradeToLightNode } from '../../utils/desktop'
|
||||
import { Loading } from '../../components/Loading'
|
||||
@@ -39,7 +40,8 @@ export default function TopUp(): ReactElement {
|
||||
const navigate = useNavigate()
|
||||
const styles = useStyles()
|
||||
const { isBeeDesktop } = useContext(SettingsContext)
|
||||
const { balance, nodeInfo, status } = useContext(BeeContext)
|
||||
const { nodeInfo, status } = useContext(BeeContext)
|
||||
const { balance } = useContext(BalanceProvider)
|
||||
const { providerUrl } = useContext(SettingsContext)
|
||||
const [loading, setLoading] = useState(false)
|
||||
const { enqueueSnackbar } = useSnackbar()
|
||||
|
||||
+7
-25
@@ -8,7 +8,6 @@ import {
|
||||
NodeInfo,
|
||||
Peer,
|
||||
Topology,
|
||||
WalletBalance,
|
||||
} from '@ethersphere/bee-js'
|
||||
import { createContext, ReactChild, ReactElement, useContext, useEffect, useState } from 'react'
|
||||
import semver from 'semver'
|
||||
@@ -16,7 +15,6 @@ import PackageJson from '../../package.json'
|
||||
import { useLatestBeeRelease } from '../hooks/apiHooks'
|
||||
import { Token } from '../models/Token'
|
||||
import type { Balance, ChequebookBalance, Settlements } from '../types'
|
||||
import { WalletAddress } from '../utils/wallet'
|
||||
import { Context as SettingsContext } from './Settings'
|
||||
|
||||
const REFRESH_WHEN_OK = 30_000
|
||||
@@ -46,7 +44,6 @@ interface Status {
|
||||
|
||||
interface ContextInterface {
|
||||
status: Status
|
||||
balance: WalletAddress | null
|
||||
latestPublishedVersion?: string
|
||||
latestUserVersion?: string
|
||||
latestUserVersionExact?: string
|
||||
@@ -65,7 +62,7 @@ interface ContextInterface {
|
||||
peerCheques: LastChequesResponse | null
|
||||
settlements: Settlements | null
|
||||
chainState: ChainState | null
|
||||
wallet: WalletBalance | null
|
||||
chainId: number | null
|
||||
latestBeeRelease: LatestBeeRelease | null
|
||||
isLoading: boolean
|
||||
lastUpdate: number | null
|
||||
@@ -84,7 +81,6 @@ const initialValues: ContextInterface = {
|
||||
topology: { isEnabled: false, checkState: CheckState.ERROR },
|
||||
chequebook: { isEnabled: false, checkState: CheckState.ERROR },
|
||||
},
|
||||
balance: null,
|
||||
latestPublishedVersion: undefined,
|
||||
latestUserVersion: undefined,
|
||||
latestUserVersionExact: undefined,
|
||||
@@ -103,7 +99,7 @@ const initialValues: ContextInterface = {
|
||||
peerCheques: null,
|
||||
settlements: null,
|
||||
chainState: null,
|
||||
wallet: null,
|
||||
chainId: null,
|
||||
latestBeeRelease: null,
|
||||
isLoading: true,
|
||||
lastUpdate: null,
|
||||
@@ -191,7 +187,7 @@ function getStatus(
|
||||
let isRefreshing = false
|
||||
|
||||
export function Provider({ children }: Props): ReactElement {
|
||||
const { beeApi, beeDebugApi, provider } = useContext(SettingsContext)
|
||||
const { beeApi, beeDebugApi } = useContext(SettingsContext)
|
||||
const [apiHealth, setApiHealth] = useState<boolean>(false)
|
||||
const [debugApiHealth, setDebugApiHealth] = useState<Health | null>(null)
|
||||
const [nodeAddresses, setNodeAddresses] = useState<NodeAddresses | null>(null)
|
||||
@@ -204,8 +200,7 @@ export function Provider({ children }: Props): ReactElement {
|
||||
const [peerCheques, setPeerCheques] = useState<LastChequesResponse | null>(null)
|
||||
const [settlements, setSettlements] = useState<Settlements | null>(null)
|
||||
const [chainState, setChainState] = useState<ChainState | null>(null)
|
||||
const [walletAddress, setWalletAddress] = useState<WalletAddress | null>(initialValues.balance)
|
||||
const [wallet, setWallet] = useState<WalletBalance | null>(null)
|
||||
const [chainId, setChainId] = useState<number | null>(null)
|
||||
|
||||
const { latestBeeRelease } = useLatestBeeRelease()
|
||||
|
||||
@@ -244,18 +239,6 @@ export function Provider({ children }: Props): ReactElement {
|
||||
if (beeDebugApi !== null) refresh()
|
||||
}, [beeDebugApi]) // eslint-disable-line react-hooks/exhaustive-deps
|
||||
|
||||
useEffect(() => {
|
||||
if (nodeAddresses?.ethereum && provider) {
|
||||
WalletAddress.make(nodeAddresses.ethereum, provider).then(setWalletAddress)
|
||||
}
|
||||
}, [nodeAddresses, provider])
|
||||
|
||||
useEffect(() => {
|
||||
const interval = setInterval(() => walletAddress?.refresh().then(setWalletAddress), REFRESH_WHEN_OK)
|
||||
|
||||
return () => clearInterval(interval)
|
||||
}, [walletAddress])
|
||||
|
||||
const refresh = async () => {
|
||||
// Don't want to refresh when already refreshing
|
||||
if (isRefreshing) return
|
||||
@@ -361,8 +344,8 @@ export function Provider({ children }: Props): ReactElement {
|
||||
// Wallet
|
||||
beeDebugApi
|
||||
.getWalletBalance({ timeout: TIMEOUT })
|
||||
.then(setWallet)
|
||||
.catch(() => setWallet(null)),
|
||||
.then(({ chainID }) => setChainId(chainID))
|
||||
.catch(() => setChainId(null)),
|
||||
|
||||
// Chequebook balance
|
||||
chequeBalanceWrapper()
|
||||
@@ -429,7 +412,6 @@ export function Provider({ children }: Props): ReactElement {
|
||||
<Context.Provider
|
||||
value={{
|
||||
status,
|
||||
balance: walletAddress,
|
||||
latestUserVersion,
|
||||
latestUserVersionExact,
|
||||
latestPublishedVersion,
|
||||
@@ -454,7 +436,7 @@ export function Provider({ children }: Props): ReactElement {
|
||||
peerCheques,
|
||||
settlements,
|
||||
chainState,
|
||||
wallet,
|
||||
chainId,
|
||||
latestBeeRelease,
|
||||
isLoading,
|
||||
lastUpdate,
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
import { createContext, ReactChild, ReactElement, useContext, useEffect, useState } from 'react'
|
||||
import { Context as SettingsContext } from './Settings'
|
||||
import { Context as BeeContext } from './Bee'
|
||||
import { WalletAddress } from '../utils/wallet'
|
||||
|
||||
interface ContextInterface {
|
||||
balance: WalletAddress | null
|
||||
error: Error | null
|
||||
isLoading: boolean
|
||||
lastUpdate: number | null
|
||||
start: (frequency?: number) => void
|
||||
stop: () => void
|
||||
refresh: () => Promise<void>
|
||||
}
|
||||
|
||||
const initialValues: ContextInterface = {
|
||||
balance: null,
|
||||
error: null,
|
||||
isLoading: false,
|
||||
lastUpdate: null,
|
||||
start: () => {}, // eslint-disable-line
|
||||
stop: () => {}, // eslint-disable-line
|
||||
refresh: () => Promise.reject(),
|
||||
}
|
||||
|
||||
export const Context = createContext<ContextInterface>(initialValues)
|
||||
export const Consumer = Context.Consumer
|
||||
|
||||
interface Props {
|
||||
children: ReactChild
|
||||
}
|
||||
|
||||
export function Provider({ children }: Props): ReactElement {
|
||||
const { provider } = useContext(SettingsContext)
|
||||
const { nodeAddresses } = useContext(BeeContext)
|
||||
const [balance, setBalance] = useState<WalletAddress | null>(initialValues.balance)
|
||||
const [error, setError] = useState<Error | null>(initialValues.error)
|
||||
const [isLoading, setIsLoading] = useState<boolean>(initialValues.isLoading)
|
||||
const [lastUpdate, setLastUpdate] = useState<number | null>(initialValues.lastUpdate)
|
||||
const [frequency, setFrequency] = useState<number | null>(null)
|
||||
|
||||
useEffect(() => {
|
||||
if (nodeAddresses?.ethereum && provider) {
|
||||
WalletAddress.make(nodeAddresses.ethereum, provider).then(setBalance)
|
||||
} else {
|
||||
setBalance(null)
|
||||
}
|
||||
}, [nodeAddresses, provider])
|
||||
|
||||
const refresh = async () => {
|
||||
// Don't want to refresh when already refreshing
|
||||
if (isLoading) return
|
||||
|
||||
if (!balance) return
|
||||
|
||||
try {
|
||||
setIsLoading(true)
|
||||
|
||||
setBalance(await balance.refresh())
|
||||
setLastUpdate(Date.now())
|
||||
} catch (e) {
|
||||
setError(e as Error)
|
||||
} finally {
|
||||
setIsLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
const start = (freq = 30000) => setFrequency(freq)
|
||||
const stop = () => setFrequency(null)
|
||||
|
||||
// Start the update loop
|
||||
useEffect(() => {
|
||||
refresh()
|
||||
|
||||
// Start autorefresh only if the frequency is set
|
||||
if (frequency) {
|
||||
const interval = setInterval(refresh, frequency)
|
||||
|
||||
return () => clearInterval(interval)
|
||||
}
|
||||
}, [frequency]) // eslint-disable-line react-hooks/exhaustive-deps
|
||||
|
||||
return (
|
||||
<Context.Provider value={{ balance, error, isLoading, lastUpdate, start, stop, refresh }}>
|
||||
{children}
|
||||
</Context.Provider>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user