Mp 3412 (#487)
* first iteration * finish implementation * finish * fix pr comments * fix: added Card Title to Overview --------- Co-authored-by: Linkie Link <linkielink.dev@gmail.com>
This commit is contained in:
parent
f87403eb4d
commit
50fd39e926
@ -1,6 +1,7 @@
|
|||||||
import { getAccountNftQueryClient } from 'api/cosmwasm-client'
|
import { getAccountNftQueryClient } from 'api/cosmwasm-client'
|
||||||
|
|
||||||
export default async function getAccountIds(address: string): Promise<string[]> {
|
export default async function getAccountIds(address?: string): Promise<string[]> {
|
||||||
|
if (!address) return []
|
||||||
const accountNftQueryClient = await getAccountNftQueryClient()
|
const accountNftQueryClient = await getAccountNftQueryClient()
|
||||||
|
|
||||||
const data = await accountNftQueryClient.tokens({ owner: address })
|
const data = await accountNftQueryClient.tokens({ owner: address })
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
import getWalletAccountIds from 'api/wallets/getAccountIds'
|
|
||||||
import getAccount from 'api/accounts/getAccount'
|
import getAccount from 'api/accounts/getAccount'
|
||||||
|
import getWalletAccountIds from 'api/wallets/getAccountIds'
|
||||||
|
|
||||||
export default async function getAccounts(address: string): Promise<Account[]> {
|
export default async function getAccounts(address?: string): Promise<Account[]> {
|
||||||
|
if (!address) return []
|
||||||
const accountIds: string[] = await getWalletAccountIds(address)
|
const accountIds: string[] = await getWalletAccountIds(address)
|
||||||
|
|
||||||
const $accounts = accountIds.map((accountId) => getAccount(accountId))
|
const $accounts = accountIds.map((accountId) => getAccount(accountId))
|
||||||
|
@ -54,7 +54,7 @@ export default function AccountComposition(props: Props) {
|
|||||||
() => getAccountPositionValues(account, prices),
|
() => getAccountPositionValues(account, prices),
|
||||||
[account, prices],
|
[account, prices],
|
||||||
)
|
)
|
||||||
const positionValue = depositsBalance.plus(lendsBalance).plus(vaultsBalance)
|
const totalBalance = depositsBalance.plus(lendsBalance).plus(vaultsBalance)
|
||||||
|
|
||||||
const [updatedPositionValue, updatedDebtsBalance] = useMemo(() => {
|
const [updatedPositionValue, updatedDebtsBalance] = useMemo(() => {
|
||||||
const [updatedDepositsBalance, updatedLendsBalance, updatedDebtsBalance, updatedVaultsBalance] =
|
const [updatedDepositsBalance, updatedLendsBalance, updatedDebtsBalance, updatedVaultsBalance] =
|
||||||
@ -69,10 +69,7 @@ export default function AccountComposition(props: Props) {
|
|||||||
return [updatedPositionValue, updatedDebtsBalance]
|
return [updatedPositionValue, updatedDebtsBalance]
|
||||||
}, [updatedAccount, prices])
|
}, [updatedAccount, prices])
|
||||||
|
|
||||||
const totalBalance = useMemo(
|
const netWorth = useMemo(() => calculateAccountBalanceValue(account, prices), [account, prices])
|
||||||
() => calculateAccountBalanceValue(account, prices),
|
|
||||||
[account, prices],
|
|
||||||
)
|
|
||||||
const updatedTotalBalance = useMemo(
|
const updatedTotalBalance = useMemo(
|
||||||
() => (updatedAccount ? calculateAccountBalanceValue(updatedAccount, prices) : BN_ZERO),
|
() => (updatedAccount ? calculateAccountBalanceValue(updatedAccount, prices) : BN_ZERO),
|
||||||
[updatedAccount, prices],
|
[updatedAccount, prices],
|
||||||
@ -93,9 +90,9 @@ export default function AccountComposition(props: Props) {
|
|||||||
return (
|
return (
|
||||||
<div className='flex-wrap w-full p-4 pb-1'>
|
<div className='flex-wrap w-full p-4 pb-1'>
|
||||||
<Item
|
<Item
|
||||||
title='Total Position Value'
|
title='Total Balance'
|
||||||
current={positionValue}
|
current={totalBalance}
|
||||||
change={hasChanged ? updatedPositionValue : positionValue}
|
change={hasChanged ? updatedPositionValue : totalBalance}
|
||||||
className='pb-3'
|
className='pb-3'
|
||||||
/>
|
/>
|
||||||
<Item
|
<Item
|
||||||
@ -106,9 +103,9 @@ export default function AccountComposition(props: Props) {
|
|||||||
isDecrease
|
isDecrease
|
||||||
/>
|
/>
|
||||||
<Item
|
<Item
|
||||||
title='Total Balance'
|
title='Net worth'
|
||||||
current={totalBalance}
|
current={netWorth}
|
||||||
change={hasChanged ? updatedTotalBalance : totalBalance}
|
change={hasChanged ? updatedTotalBalance : netWorth}
|
||||||
className='py-3 font-bold border border-transparent border-y-white/20'
|
className='py-3 font-bold border border-transparent border-y-white/20'
|
||||||
/>
|
/>
|
||||||
<Item
|
<Item
|
||||||
|
@ -4,8 +4,8 @@ import { FormattedNumber } from 'components/FormattedNumber'
|
|||||||
import { ArrowRight } from 'components/Icons'
|
import { ArrowRight } from 'components/Icons'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
leverage: BigNumber
|
leverage: number
|
||||||
updatedLeverage: BigNumber | null
|
updatedLeverage: number | null
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function AccountDetailsLeverage(props: Props) {
|
export default function AccountDetailsLeverage(props: Props) {
|
||||||
@ -15,7 +15,7 @@ export default function AccountDetailsLeverage(props: Props) {
|
|||||||
return (
|
return (
|
||||||
<FormattedNumber
|
<FormattedNumber
|
||||||
className={'w-full text-center text-2xs'}
|
className={'w-full text-center text-2xs'}
|
||||||
amount={isNaN(leverage.toNumber()) ? 0 : leverage.toNumber()}
|
amount={isNaN(leverage) ? 0 : leverage}
|
||||||
options={{
|
options={{
|
||||||
maxDecimals: 2,
|
maxDecimals: 2,
|
||||||
minDecimals: 2,
|
minDecimals: 2,
|
||||||
@ -30,7 +30,7 @@ export default function AccountDetailsLeverage(props: Props) {
|
|||||||
<div className='flex'>
|
<div className='flex'>
|
||||||
<FormattedNumber
|
<FormattedNumber
|
||||||
className={'w-full text-center text-2xs'}
|
className={'w-full text-center text-2xs'}
|
||||||
amount={isNaN(leverage.toNumber()) ? 0 : leverage.toNumber()}
|
amount={isNaN(leverage) ? 1 : leverage}
|
||||||
options={{
|
options={{
|
||||||
maxDecimals: 1,
|
maxDecimals: 1,
|
||||||
minDecimals: 1,
|
minDecimals: 1,
|
||||||
@ -42,10 +42,10 @@ export default function AccountDetailsLeverage(props: Props) {
|
|||||||
<FormattedNumber
|
<FormattedNumber
|
||||||
className={classNames(
|
className={classNames(
|
||||||
'w-full text-center text-2xs',
|
'w-full text-center text-2xs',
|
||||||
updatedLeverage.gt(leverage) && 'text-loss',
|
updatedLeverage > leverage && 'text-loss',
|
||||||
updatedLeverage.lt(leverage) && 'text-profit',
|
updatedLeverage < leverage && 'text-profit',
|
||||||
)}
|
)}
|
||||||
amount={isNaN(updatedLeverage.toNumber()) ? 0 : updatedLeverage.toNumber()}
|
amount={isNaN(updatedLeverage) ? 0 : updatedLeverage}
|
||||||
options={{ maxDecimals: 1, minDecimals: 1, rounded: true }}
|
options={{ maxDecimals: 1, minDecimals: 1, rounded: true }}
|
||||||
animate
|
animate
|
||||||
/>
|
/>
|
||||||
|
@ -106,18 +106,21 @@ function AccountDetails(props: Props) {
|
|||||||
Account Health
|
Account Health
|
||||||
</Text>
|
</Text>
|
||||||
</div>
|
</div>
|
||||||
<div className='w-full py-4 border-t border-white/20'>
|
|
||||||
<Text size='2xs' className='mb-0.5 w-full text-center text-white/50'>
|
|
||||||
Leverage
|
|
||||||
</Text>
|
|
||||||
<AccountDetailsLeverage leverage={leverage} updatedLeverage={updatedLeverage} />
|
|
||||||
</div>
|
|
||||||
<div className='w-full py-4 border-t border-white/20'>
|
<div className='w-full py-4 border-t border-white/20'>
|
||||||
<Text size='2xs' className='mb-0.5 w-full text-center text-white/50 whitespace-nowrap'>
|
<Text size='2xs' className='mb-0.5 w-full text-center text-white/50 whitespace-nowrap'>
|
||||||
Net worth
|
Net worth
|
||||||
</Text>
|
</Text>
|
||||||
<DisplayCurrency coin={coin} className='w-full text-center truncate text-2xs ' />
|
<DisplayCurrency coin={coin} className='w-full text-center truncate text-2xs ' />
|
||||||
</div>
|
</div>
|
||||||
|
<div className='w-full py-4 border-t border-white/20'>
|
||||||
|
<Text size='2xs' className='mb-0.5 w-full text-center text-white/50'>
|
||||||
|
Leverage
|
||||||
|
</Text>
|
||||||
|
<AccountDetailsLeverage
|
||||||
|
leverage={leverage.toNumber() || 1}
|
||||||
|
updatedLeverage={updatedLeverage?.toNumber() || null}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
<div className='w-full py-4 border-t border-white/20'>
|
<div className='w-full py-4 border-t border-white/20'>
|
||||||
<Text size='2xs' className='mb-0.5 w-full text-center text-white/50'>
|
<Text size='2xs' className='mb-0.5 w-full text-center text-white/50'>
|
||||||
APR
|
APR
|
||||||
|
@ -1,17 +1,17 @@
|
|||||||
import { useEffect, useState } from 'react'
|
import { useEffect, useState } from 'react'
|
||||||
import { useParams } from 'react-router-dom'
|
|
||||||
|
|
||||||
import AccountFundContent from 'components/Account/AccountFund/AccountFundContent'
|
import AccountFundContent from 'components/Account/AccountFund/AccountFundContent'
|
||||||
import Card from 'components/Card'
|
import Card from 'components/Card'
|
||||||
import { CircularProgress } from 'components/CircularProgress'
|
import { CircularProgress } from 'components/CircularProgress'
|
||||||
import FullOverlayContent from 'components/FullOverlayContent'
|
import FullOverlayContent from 'components/FullOverlayContent'
|
||||||
|
import useAccountId from 'hooks/useAccountId'
|
||||||
import useAccounts from 'hooks/useAccounts'
|
import useAccounts from 'hooks/useAccounts'
|
||||||
import useCurrentAccount from 'hooks/useCurrentAccount'
|
import useCurrentAccount from 'hooks/useCurrentAccount'
|
||||||
import useStore from 'store'
|
import useStore from 'store'
|
||||||
|
|
||||||
export default function AccountFundFullPage() {
|
export default function AccountFundFullPage() {
|
||||||
const address = useStore((s) => s.address)
|
const address = useStore((s) => s.address)
|
||||||
const { accountId } = useParams()
|
const accountId = useAccountId()
|
||||||
|
|
||||||
const { data: accounts, isLoading } = useAccounts(address)
|
const { data: accounts, isLoading } = useAccounts(address)
|
||||||
const currentAccount = useCurrentAccount()
|
const currentAccount = useCurrentAccount()
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import classNames from 'classnames'
|
import classNames from 'classnames'
|
||||||
import { useCallback, useEffect } from 'react'
|
import { useCallback } from 'react'
|
||||||
import { useLocation, useNavigate, useParams } from 'react-router-dom'
|
import { useLocation, useNavigate, useParams } from 'react-router-dom'
|
||||||
|
|
||||||
import AccountCreateFirst from 'components/Account/AccountCreateFirst'
|
import AccountCreateFirst from 'components/Account/AccountCreateFirst'
|
||||||
@ -13,6 +13,7 @@ import Text from 'components/Text'
|
|||||||
import WalletBridges from 'components/Wallet/WalletBridges'
|
import WalletBridges from 'components/Wallet/WalletBridges'
|
||||||
import { DEFAULT_SETTINGS } from 'constants/defaultSettings'
|
import { DEFAULT_SETTINGS } from 'constants/defaultSettings'
|
||||||
import { LEND_ASSETS_KEY } from 'constants/localStore'
|
import { LEND_ASSETS_KEY } from 'constants/localStore'
|
||||||
|
import useAccountId from 'hooks/useAccountId'
|
||||||
import useAutoLend from 'hooks/useAutoLend'
|
import useAutoLend from 'hooks/useAutoLend'
|
||||||
import useCurrentWalletBalance from 'hooks/useCurrentWalletBalance'
|
import useCurrentWalletBalance from 'hooks/useCurrentWalletBalance'
|
||||||
import useLocalStorage from 'hooks/useLocalStorage'
|
import useLocalStorage from 'hooks/useLocalStorage'
|
||||||
@ -33,7 +34,9 @@ interface Props {
|
|||||||
export default function AccountMenuContent(props: Props) {
|
export default function AccountMenuContent(props: Props) {
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
const { pathname } = useLocation()
|
const { pathname } = useLocation()
|
||||||
const { accountId, address } = useParams()
|
const { address } = useParams()
|
||||||
|
const accountId = useAccountId()
|
||||||
|
|
||||||
const createAccount = useStore((s) => s.createAccount)
|
const createAccount = useStore((s) => s.createAccount)
|
||||||
const baseCurrency = useStore((s) => s.baseCurrency)
|
const baseCurrency = useStore((s) => s.baseCurrency)
|
||||||
const [showMenu, setShowMenu] = useToggle()
|
const [showMenu, setShowMenu] = useToggle()
|
||||||
@ -97,10 +100,6 @@ export default function AccountMenuContent(props: Props) {
|
|||||||
}
|
}
|
||||||
}, [checkHasFunds, hasCreditAccounts, setShowMenu, showMenu])
|
}, [checkHasFunds, hasCreditAccounts, setShowMenu, showMenu])
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
useStore.setState({ accounts: props.accounts })
|
|
||||||
}, [props.accounts])
|
|
||||||
|
|
||||||
if (!address) return null
|
if (!address) return null
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -1,148 +0,0 @@
|
|||||||
import classNames from 'classnames'
|
|
||||||
import { Suspense, useCallback, useMemo } from 'react'
|
|
||||||
import { useParams } from 'react-router-dom'
|
|
||||||
|
|
||||||
import AccountBalancesTable from 'components/Account/AccountBalancesTable'
|
|
||||||
import AccountComposition from 'components/Account/AccountComposition'
|
|
||||||
import AccountCreateFirst from 'components/Account/AccountCreateFirst'
|
|
||||||
import Button from 'components/Button'
|
|
||||||
import Card from 'components/Card'
|
|
||||||
import { PlusCircled } from 'components/Icons'
|
|
||||||
import Loading from 'components/Loading'
|
|
||||||
import Text from 'components/Text'
|
|
||||||
import WalletBridges from 'components/Wallet/WalletBridges'
|
|
||||||
import WalletConnectButton from 'components/Wallet/WalletConnectButton'
|
|
||||||
import useAccounts from 'hooks/useAccounts'
|
|
||||||
import useBorrowMarketAssetsTableData from 'hooks/useBorrowMarketAssetsTableData'
|
|
||||||
import useCurrentWalletBalance from 'hooks/useCurrentWalletBalance'
|
|
||||||
import useLendingMarketAssetsTableData from 'hooks/useLendingMarketAssetsTableData'
|
|
||||||
import useStore from 'store'
|
|
||||||
import { defaultFee } from 'utils/constants'
|
|
||||||
import { BN } from 'utils/helpers'
|
|
||||||
|
|
||||||
function ConnectInfo() {
|
|
||||||
return (
|
|
||||||
<Card
|
|
||||||
className='w-full h-fit bg-white/5'
|
|
||||||
title='Portfolio'
|
|
||||||
contentClassName='px-4 py-6 flex justify-center flex-wrap'
|
|
||||||
>
|
|
||||||
<Text size='sm' className='w-full text-center'>
|
|
||||||
You need to be connected to view the porfolio page.
|
|
||||||
</Text>
|
|
||||||
<WalletConnectButton className='mt-4' />
|
|
||||||
</Card>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function Content() {
|
|
||||||
const { address: urlAddress } = useParams()
|
|
||||||
const { data: accounts, isLoading } = useAccounts(urlAddress ?? '')
|
|
||||||
const walletAddress = useStore((s) => s.address)
|
|
||||||
const baseCurrency = useStore((s) => s.baseCurrency)
|
|
||||||
const { availableAssets: borrowAvailableAssets, accountBorrowedAssets } =
|
|
||||||
useBorrowMarketAssetsTableData()
|
|
||||||
const { availableAssets: lendingAvailableAssets, accountLentAssets } =
|
|
||||||
useLendingMarketAssetsTableData()
|
|
||||||
const borrowAssetsData = useMemo(
|
|
||||||
() => [...borrowAvailableAssets, ...accountBorrowedAssets],
|
|
||||||
[borrowAvailableAssets, accountBorrowedAssets],
|
|
||||||
)
|
|
||||||
const lendingAssetsData = useMemo(
|
|
||||||
() => [...lendingAvailableAssets, ...accountLentAssets],
|
|
||||||
[lendingAvailableAssets, accountLentAssets],
|
|
||||||
)
|
|
||||||
const transactionFeeCoinBalance = useCurrentWalletBalance(baseCurrency.denom)
|
|
||||||
|
|
||||||
const checkHasFunds = useCallback(() => {
|
|
||||||
return (
|
|
||||||
transactionFeeCoinBalance &&
|
|
||||||
BN(transactionFeeCoinBalance.amount).isGreaterThan(defaultFee.amount[0].amount)
|
|
||||||
)
|
|
||||||
}, [transactionFeeCoinBalance])
|
|
||||||
|
|
||||||
const handleCreateAccountClick = useCallback(() => {
|
|
||||||
if (!checkHasFunds()) {
|
|
||||||
useStore.setState({ focusComponent: { component: <WalletBridges /> } })
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
useStore.setState({ focusComponent: { component: <AccountCreateFirst /> } })
|
|
||||||
}, [checkHasFunds])
|
|
||||||
|
|
||||||
if (isLoading) return <Fallback />
|
|
||||||
|
|
||||||
if (!walletAddress && !urlAddress) return <ConnectInfo />
|
|
||||||
|
|
||||||
if (!accounts || accounts.length === 0)
|
|
||||||
return (
|
|
||||||
<Card
|
|
||||||
className='w-full h-fit bg-white/5'
|
|
||||||
title='Portfolio'
|
|
||||||
contentClassName='px-4 py-6 flex justify-center flex-wrap'
|
|
||||||
>
|
|
||||||
<Text size='sm' className='w-full text-center'>
|
|
||||||
You need to create an Account first.
|
|
||||||
</Text>
|
|
||||||
<Button
|
|
||||||
className='mt-4'
|
|
||||||
onClick={handleCreateAccountClick}
|
|
||||||
leftIcon={<PlusCircled />}
|
|
||||||
color='primary'
|
|
||||||
>
|
|
||||||
Create Account
|
|
||||||
</Button>
|
|
||||||
</Card>
|
|
||||||
)
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
className={classNames('grid w-full grid-cols-1 gap-4', 'md:grid-cols-2', 'lg:grid-cols-3')}
|
|
||||||
>
|
|
||||||
{accounts.map((account: Account, index: number) => (
|
|
||||||
<Card
|
|
||||||
className='w-full h-fit bg-white/5'
|
|
||||||
title={`Credit Account ${account.id}`}
|
|
||||||
key={index}
|
|
||||||
>
|
|
||||||
<AccountComposition account={account} />
|
|
||||||
<Text className='w-full px-4 py-2 text-white bg-white/10'>Balances</Text>
|
|
||||||
<AccountBalancesTable
|
|
||||||
account={account}
|
|
||||||
borrowingData={borrowAssetsData}
|
|
||||||
lendingData={lendingAssetsData}
|
|
||||||
/>
|
|
||||||
</Card>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function Fallback() {
|
|
||||||
const { address } = useParams()
|
|
||||||
const cardCount = 3
|
|
||||||
if (!address) return <ConnectInfo />
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
className={classNames('grid w-full grid-cols-1 gap-4', 'md:grid-cols-2', 'lg:grid-cols-3')}
|
|
||||||
>
|
|
||||||
{Array.from({ length: cardCount }, (_, i) => (
|
|
||||||
<Card key={i} className='w-full h-fit bg-white/5' title='Account' contentClassName='py-6'>
|
|
||||||
<div className='p-4'>
|
|
||||||
<Loading className='h-4 w-50' />
|
|
||||||
</div>
|
|
||||||
<Text className='w-full px-4 py-2 mt-3 text-white bg-white/10'>Balances</Text>
|
|
||||||
<Loading className='w-full h-4' />
|
|
||||||
</Card>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function AccountOverview() {
|
|
||||||
return (
|
|
||||||
<Suspense fallback={<Fallback />}>
|
|
||||||
<Content />
|
|
||||||
</Suspense>
|
|
||||||
)
|
|
||||||
}
|
|
@ -48,7 +48,7 @@ export default function HealthBar(props: Props) {
|
|||||||
type='info'
|
type='info'
|
||||||
className='flex items-center w-full'
|
className='flex items-center w-full'
|
||||||
>
|
>
|
||||||
<div className={classNames('flex max-w-[184px] max-h-1', props.className)}>
|
<div className={classNames('flex w-full', props.className)}>
|
||||||
<svg version='1.1' xmlns='http://www.w3.org/2000/svg' x='0px' y='0px' viewBox='0 0 184 4'>
|
<svg version='1.1' xmlns='http://www.w3.org/2000/svg' x='0px' y='0px' viewBox='0 0 184 4'>
|
||||||
<mask id='healthBarMask'>
|
<mask id='healthBarMask'>
|
||||||
<path fill='#FFFFFF' d='M0,2c0-1.1,0.9-2,2-2h41.6v4H2C0.9,4,0,3.1,0,2z' />
|
<path fill='#FFFFFF' d='M0,2c0-1.1,0.9-2,2-2h41.6v4H2C0.9,4,0,3.1,0,2z' />
|
||||||
|
@ -1,19 +1,21 @@
|
|||||||
import { useCallback } from 'react'
|
import { useCallback } from 'react'
|
||||||
import { useParams } from 'react-router-dom'
|
|
||||||
|
|
||||||
import AccountCreateFirst from 'components/Account/AccountCreateFirst'
|
import AccountCreateFirst from 'components/Account/AccountCreateFirst'
|
||||||
import { ACCOUNT_MENU_BUTTON_ID } from 'components/Account/AccountMenuContent'
|
import { ACCOUNT_MENU_BUTTON_ID } from 'components/Account/AccountMenuContent'
|
||||||
import Button from 'components/Button'
|
import Button from 'components/Button'
|
||||||
import { Account, PlusCircled } from 'components/Icons'
|
import { Account, PlusCircled } from 'components/Icons'
|
||||||
import WalletConnectButton from 'components/Wallet/WalletConnectButton'
|
import WalletConnectButton from 'components/Wallet/WalletConnectButton'
|
||||||
|
import useAccountId from 'hooks/useAccountId'
|
||||||
|
import useAccountIds from 'hooks/useAccountIds'
|
||||||
import useStore from 'store'
|
import useStore from 'store'
|
||||||
|
|
||||||
export default function ActionButton(props: ButtonProps) {
|
export default function ActionButton(props: ButtonProps) {
|
||||||
const { className, color, variant, size } = props
|
const { className, color, variant, size } = props
|
||||||
const defaultProps = { className, color, variant, size }
|
const defaultProps = { className, color, variant, size }
|
||||||
const address = useStore((s) => s.address)
|
const address = useStore((s) => s.address)
|
||||||
const accounts = useStore((s) => s.accounts)
|
|
||||||
const { accountId } = useParams()
|
const { data: accountIds } = useAccountIds(address || '')
|
||||||
|
const selectedAccountId = useAccountId()
|
||||||
|
|
||||||
const handleCreateAccountClick = useCallback(() => {
|
const handleCreateAccountClick = useCallback(() => {
|
||||||
useStore.setState({ focusComponent: { component: <AccountCreateFirst /> } })
|
useStore.setState({ focusComponent: { component: <AccountCreateFirst /> } })
|
||||||
@ -21,7 +23,7 @@ export default function ActionButton(props: ButtonProps) {
|
|||||||
|
|
||||||
if (!address) return <WalletConnectButton {...defaultProps} />
|
if (!address) return <WalletConnectButton {...defaultProps} />
|
||||||
|
|
||||||
if (accounts && accounts.length === 0)
|
if (accountIds.length === 0) {
|
||||||
return (
|
return (
|
||||||
<Button
|
<Button
|
||||||
onClick={handleCreateAccountClick}
|
onClick={handleCreateAccountClick}
|
||||||
@ -30,8 +32,9 @@ export default function ActionButton(props: ButtonProps) {
|
|||||||
{...defaultProps}
|
{...defaultProps}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
|
}
|
||||||
|
|
||||||
if (!accountId)
|
if (!selectedAccountId) {
|
||||||
return (
|
return (
|
||||||
<Button
|
<Button
|
||||||
text='Select Account'
|
text='Select Account'
|
||||||
@ -41,6 +44,7 @@ export default function ActionButton(props: ButtonProps) {
|
|||||||
{...defaultProps}
|
{...defaultProps}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
|
}
|
||||||
|
|
||||||
return <Button {...props} />
|
return <Button {...props} />
|
||||||
}
|
}
|
||||||
|
@ -1,13 +1,13 @@
|
|||||||
import { Row } from '@tanstack/react-table'
|
import { Row } from '@tanstack/react-table'
|
||||||
import moment from 'moment'
|
import moment from 'moment'
|
||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
import { useParams } from 'react-router-dom'
|
|
||||||
|
|
||||||
import Button from 'components/Button'
|
import Button from 'components/Button'
|
||||||
import { AccountArrowDown, LockLocked, LockUnlocked, Plus } from 'components/Icons'
|
import { AccountArrowDown, LockLocked, LockUnlocked, Plus } from 'components/Icons'
|
||||||
import { Tooltip } from 'components/Tooltip'
|
import { Tooltip } from 'components/Tooltip'
|
||||||
import { DEFAULT_SETTINGS } from 'constants/defaultSettings'
|
import { DEFAULT_SETTINGS } from 'constants/defaultSettings'
|
||||||
import { SLIPPAGE_KEY } from 'constants/localStore'
|
import { SLIPPAGE_KEY } from 'constants/localStore'
|
||||||
|
import useAccountId from 'hooks/useAccountId'
|
||||||
import useLocalStorage from 'hooks/useLocalStorage'
|
import useLocalStorage from 'hooks/useLocalStorage'
|
||||||
import useStore from 'store'
|
import useStore from 'store'
|
||||||
import { VaultStatus } from 'types/enums/vault'
|
import { VaultStatus } from 'types/enums/vault'
|
||||||
@ -19,7 +19,7 @@ interface Props {
|
|||||||
|
|
||||||
export default function VaultExpanded(props: Props) {
|
export default function VaultExpanded(props: Props) {
|
||||||
const vault = props.row.original as DepositedVault
|
const vault = props.row.original as DepositedVault
|
||||||
const { accountId } = useParams()
|
const accountId = useAccountId()
|
||||||
const [isConfirming, setIsConfirming] = useState(false)
|
const [isConfirming, setIsConfirming] = useState(false)
|
||||||
const withdrawFromVaults = useStore((s) => s.withdrawFromVaults)
|
const withdrawFromVaults = useStore((s) => s.withdrawFromVaults)
|
||||||
const [slippage] = useLocalStorage<number>(SLIPPAGE_KEY, DEFAULT_SETTINGS.slippage)
|
const [slippage] = useLocalStorage<number>(SLIPPAGE_KEY, DEFAULT_SETTINGS.slippage)
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
import { useParams } from 'react-router-dom'
|
|
||||||
|
|
||||||
import Button from 'components/Button'
|
import Button from 'components/Button'
|
||||||
import { ChevronRight } from 'components/Icons'
|
import { ChevronRight } from 'components/Icons'
|
||||||
import NotificationBanner from 'components/NotificationBanner'
|
import NotificationBanner from 'components/NotificationBanner'
|
||||||
import { DEFAULT_SETTINGS } from 'constants/defaultSettings'
|
import { DEFAULT_SETTINGS } from 'constants/defaultSettings'
|
||||||
import { SLIPPAGE_KEY } from 'constants/localStore'
|
import { SLIPPAGE_KEY } from 'constants/localStore'
|
||||||
|
import useAccountId from 'hooks/useAccountId'
|
||||||
import useLocalStorage from 'hooks/useLocalStorage'
|
import useLocalStorage from 'hooks/useLocalStorage'
|
||||||
import useStore from 'store'
|
import useStore from 'store'
|
||||||
|
|
||||||
@ -14,7 +14,7 @@ interface Props {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default function VaultUnlockBanner(props: Props) {
|
export default function VaultUnlockBanner(props: Props) {
|
||||||
const { accountId } = useParams()
|
const accountId = useAccountId()
|
||||||
const [isConfirming, setIsConfirming] = useState(false)
|
const [isConfirming, setIsConfirming] = useState(false)
|
||||||
const withdrawFromVaults = useStore((s) => s.withdrawFromVaults)
|
const withdrawFromVaults = useStore((s) => s.withdrawFromVaults)
|
||||||
const [slippage] = useLocalStorage<number>(SLIPPAGE_KEY, DEFAULT_SETTINGS.slippage)
|
const [slippage] = useLocalStorage<number>(SLIPPAGE_KEY, DEFAULT_SETTINGS.slippage)
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import { Suspense, useMemo } from 'react'
|
import { Suspense, useMemo } from 'react'
|
||||||
import { useParams } from 'react-router-dom'
|
|
||||||
|
|
||||||
import Card from 'components/Card'
|
import Card from 'components/Card'
|
||||||
import { VaultTable } from 'components/Earn/Farm/VaultTable'
|
import { VaultTable } from 'components/Earn/Farm/VaultTable'
|
||||||
@ -7,6 +6,7 @@ import VaultUnlockBanner from 'components/Earn/Farm/VaultUnlockBanner'
|
|||||||
import { IS_TESTNET } from 'constants/env'
|
import { IS_TESTNET } from 'constants/env'
|
||||||
import { BN_ZERO } from 'constants/math'
|
import { BN_ZERO } from 'constants/math'
|
||||||
import { TESTNET_VAULTS_META_DATA, VAULTS_META_DATA } from 'constants/vaults'
|
import { TESTNET_VAULTS_META_DATA, VAULTS_META_DATA } from 'constants/vaults'
|
||||||
|
import useAccountId from 'hooks/useAccountId'
|
||||||
import useDepositedVaults from 'hooks/useDepositedVaults'
|
import useDepositedVaults from 'hooks/useDepositedVaults'
|
||||||
import useVaults from 'hooks/useVaults'
|
import useVaults from 'hooks/useVaults'
|
||||||
import { VaultStatus } from 'types/enums/vault'
|
import { VaultStatus } from 'types/enums/vault'
|
||||||
@ -16,7 +16,7 @@ interface Props {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function Content(props: Props) {
|
function Content(props: Props) {
|
||||||
const { accountId } = useParams()
|
const accountId = useAccountId()
|
||||||
const { data: vaults } = useVaults()
|
const { data: vaults } = useVaults()
|
||||||
const { data: depositedVaults } = useDepositedVaults(accountId || '')
|
const { data: depositedVaults } = useDepositedVaults(accountId || '')
|
||||||
const isAvailable = props.type === 'available'
|
const isAvailable = props.type === 'available'
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import { useCallback } from 'react'
|
import { useCallback } from 'react'
|
||||||
import { useParams } from 'react-router-dom'
|
|
||||||
|
|
||||||
import { ACCOUNT_MENU_BUTTON_ID } from 'components/Account/AccountMenuContent'
|
import { ACCOUNT_MENU_BUTTON_ID } from 'components/Account/AccountMenuContent'
|
||||||
import Button from 'components/Button'
|
import Button from 'components/Button'
|
||||||
@ -8,6 +7,7 @@ import { ArrowDownLine, ArrowUpLine, Enter } from 'components/Icons'
|
|||||||
import Text from 'components/Text'
|
import Text from 'components/Text'
|
||||||
import { Tooltip } from 'components/Tooltip'
|
import { Tooltip } from 'components/Tooltip'
|
||||||
import ConditionalWrapper from 'hocs/ConditionalWrapper'
|
import ConditionalWrapper from 'hocs/ConditionalWrapper'
|
||||||
|
import useAccountId from 'hooks/useAccountId'
|
||||||
import useAlertDialog from 'hooks/useAlertDialog'
|
import useAlertDialog from 'hooks/useAlertDialog'
|
||||||
import useAutoLend from 'hooks/useAutoLend'
|
import useAutoLend from 'hooks/useAutoLend'
|
||||||
import useCurrentAccountDeposits from 'hooks/useCurrentAccountDeposits'
|
import useCurrentAccountDeposits from 'hooks/useCurrentAccountDeposits'
|
||||||
@ -30,7 +30,7 @@ export default function LendingActionButtons(props: Props) {
|
|||||||
const { isAutoLendEnabledForCurrentAccount } = useAutoLend()
|
const { isAutoLendEnabledForCurrentAccount } = useAutoLend()
|
||||||
const assetDepositAmount = accountDeposits.find(byDenom(asset.denom))?.amount
|
const assetDepositAmount = accountDeposits.find(byDenom(asset.denom))?.amount
|
||||||
const address = useStore((s) => s.address)
|
const address = useStore((s) => s.address)
|
||||||
const { accountId } = useParams()
|
const accountId = useAccountId()
|
||||||
const hasNoDeposit = !!(!assetDepositAmount && address && accountId)
|
const hasNoDeposit = !!(!assetDepositAmount && address && accountId)
|
||||||
|
|
||||||
const handleWithdraw = useCallback(() => {
|
const handleWithdraw = useCallback(() => {
|
||||||
|
@ -1,8 +1,7 @@
|
|||||||
import { useParams } from 'react-router-dom'
|
|
||||||
|
|
||||||
import Button from 'components/Button'
|
import Button from 'components/Button'
|
||||||
import { NoIcon, YesIcon } from 'components/Modals/AlertDialog/ButtonIcons'
|
import { NoIcon, YesIcon } from 'components/Modals/AlertDialog/ButtonIcons'
|
||||||
import Text from 'components/Text'
|
import Text from 'components/Text'
|
||||||
|
import useAccountId from 'hooks/useAccountId'
|
||||||
import useStore from 'store'
|
import useStore from 'store'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
@ -12,7 +11,7 @@ interface Props {
|
|||||||
|
|
||||||
export default function UnlockModalContent(props: Props) {
|
export default function UnlockModalContent(props: Props) {
|
||||||
const unlock = useStore((s) => s.unlock)
|
const unlock = useStore((s) => s.unlock)
|
||||||
const { accountId } = useParams()
|
const accountId = useAccountId()
|
||||||
|
|
||||||
function onConfirm() {
|
function onConfirm() {
|
||||||
if (!accountId) return
|
if (!accountId) return
|
||||||
|
@ -1,5 +1,3 @@
|
|||||||
import { useParams } from 'react-router-dom'
|
|
||||||
|
|
||||||
import Button from 'components/Button'
|
import Button from 'components/Button'
|
||||||
import { CircularProgress } from 'components/CircularProgress'
|
import { CircularProgress } from 'components/CircularProgress'
|
||||||
import DisplayCurrency from 'components/DisplayCurrency'
|
import DisplayCurrency from 'components/DisplayCurrency'
|
||||||
@ -9,6 +7,7 @@ import Modal from 'components/Modal'
|
|||||||
import Text from 'components/Text'
|
import Text from 'components/Text'
|
||||||
import { DEFAULT_SETTINGS } from 'constants/defaultSettings'
|
import { DEFAULT_SETTINGS } from 'constants/defaultSettings'
|
||||||
import { SLIPPAGE_KEY } from 'constants/localStore'
|
import { SLIPPAGE_KEY } from 'constants/localStore'
|
||||||
|
import useAccountId from 'hooks/useAccountId'
|
||||||
import useLocalStorage from 'hooks/useLocalStorage'
|
import useLocalStorage from 'hooks/useLocalStorage'
|
||||||
import useStore from 'store'
|
import useStore from 'store'
|
||||||
import { BNCoin } from 'types/classes/BNCoin'
|
import { BNCoin } from 'types/classes/BNCoin'
|
||||||
@ -17,7 +16,7 @@ import { demagnify } from 'utils/formatters'
|
|||||||
|
|
||||||
export default function WithdrawFromVaultsModal() {
|
export default function WithdrawFromVaultsModal() {
|
||||||
const modal = useStore((s) => s.withdrawFromVaultsModal)
|
const modal = useStore((s) => s.withdrawFromVaultsModal)
|
||||||
const { accountId } = useParams()
|
const accountId = useAccountId()
|
||||||
const withdrawFromVaults = useStore((s) => s.withdrawFromVaults)
|
const withdrawFromVaults = useStore((s) => s.withdrawFromVaults)
|
||||||
const baseCurrency = useStore((s) => s.baseCurrency)
|
const baseCurrency = useStore((s) => s.baseCurrency)
|
||||||
const [slippage] = useLocalStorage<number>(SLIPPAGE_KEY, DEFAULT_SETTINGS.slippage)
|
const [slippage] = useLocalStorage<number>(SLIPPAGE_KEY, DEFAULT_SETTINGS.slippage)
|
||||||
|
@ -4,11 +4,14 @@ import { useParams } from 'react-router-dom'
|
|||||||
import { menuTree } from 'components/Header/DesktopHeader'
|
import { menuTree } from 'components/Header/DesktopHeader'
|
||||||
import { Logo } from 'components/Icons'
|
import { Logo } from 'components/Icons'
|
||||||
import { NavLink } from 'components/Navigation/NavLink'
|
import { NavLink } from 'components/Navigation/NavLink'
|
||||||
|
import useAccountId from 'hooks/useAccountId'
|
||||||
import useStore from 'store'
|
import useStore from 'store'
|
||||||
import { getRoute } from 'utils/route'
|
import { getRoute } from 'utils/route'
|
||||||
|
|
||||||
export default function DesktopNavigation() {
|
export default function DesktopNavigation() {
|
||||||
const { address, accountId } = useParams()
|
const { address } = useParams()
|
||||||
|
const accountId = useAccountId()
|
||||||
|
|
||||||
const focusComponent = useStore((s) => s.focusComponent)
|
const focusComponent = useStore((s) => s.focusComponent)
|
||||||
|
|
||||||
function getIsActive(pages: string[]) {
|
function getIsActive(pages: string[]) {
|
||||||
|
63
src/components/Portfolio/Account/Balances.tsx
Normal file
63
src/components/Portfolio/Account/Balances.tsx
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
import React, { Suspense } from 'react'
|
||||||
|
|
||||||
|
import AccountBalancesTable from 'components/Account/AccountBalancesTable'
|
||||||
|
import Card from 'components/Card'
|
||||||
|
import TableSkeleton from 'components/TableSkeleton'
|
||||||
|
import Text from 'components/Text'
|
||||||
|
import useAccount from 'hooks/useAccount'
|
||||||
|
import useBorrowMarketAssetsTableData from 'hooks/useBorrowMarketAssetsTableData'
|
||||||
|
import useLendingMarketAssetsTableData from 'hooks/useLendingMarketAssetsTableData'
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
accountId: string
|
||||||
|
}
|
||||||
|
|
||||||
|
function Content(props: Props) {
|
||||||
|
const { data: account } = useAccount(props.accountId, true)
|
||||||
|
|
||||||
|
const { allAssets: borrowAssets } = useBorrowMarketAssetsTableData()
|
||||||
|
const { allAssets: lendingAssets } = useLendingMarketAssetsTableData()
|
||||||
|
|
||||||
|
if (!account || !borrowAssets.length || !lendingAssets.length) {
|
||||||
|
return <Skeleton />
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Skeleton>
|
||||||
|
<AccountBalancesTable
|
||||||
|
account={account}
|
||||||
|
borrowingData={borrowAssets}
|
||||||
|
lendingData={lendingAssets}
|
||||||
|
/>
|
||||||
|
</Skeleton>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function Balances(props: Props) {
|
||||||
|
return (
|
||||||
|
<Suspense fallback={<Skeleton />}>
|
||||||
|
<Content {...props} />
|
||||||
|
</Suspense>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
interface SkeletonProps {
|
||||||
|
children?: React.ReactNode
|
||||||
|
}
|
||||||
|
|
||||||
|
function Skeleton(props: SkeletonProps) {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Text size='2xl' className='mb-8'>
|
||||||
|
Balances
|
||||||
|
</Text>
|
||||||
|
<Card className='mb-4 h-fit w-full bg-white/5'>
|
||||||
|
{props.children ? (
|
||||||
|
props.children
|
||||||
|
) : (
|
||||||
|
<TableSkeleton labels={['Asset', 'Value', 'Size', 'APY']} rowCount={3} />
|
||||||
|
)}
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
26
src/components/Portfolio/Account/BreadCrumbs.tsx
Normal file
26
src/components/Portfolio/Account/BreadCrumbs.tsx
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import { NavLink, useParams } from 'react-router-dom'
|
||||||
|
|
||||||
|
import { ArrowRight } from 'components/Icons'
|
||||||
|
import Text from 'components/Text'
|
||||||
|
import useAccountId from 'hooks/useAccountId'
|
||||||
|
import { getRoute } from 'utils/route'
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
accountId: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function PortfolioAccountPageHeader(props: Props) {
|
||||||
|
const { address } = useParams()
|
||||||
|
const selectedAccountId = useAccountId()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='flex gap-2 items-center pt-4 pb-8 border-b border-white/20'>
|
||||||
|
<NavLink to={getRoute('portfolio', address, selectedAccountId)}>
|
||||||
|
<Text className='text-white/40'>Portfolio</Text>
|
||||||
|
</NavLink>
|
||||||
|
<ArrowRight className='h-3 text-white/60' />
|
||||||
|
<Text tag='span'>Credit Account {props.accountId}</Text>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
137
src/components/Portfolio/Account/Summary.tsx
Normal file
137
src/components/Portfolio/Account/Summary.tsx
Normal file
@ -0,0 +1,137 @@
|
|||||||
|
import React, { Suspense, useMemo } from 'react'
|
||||||
|
|
||||||
|
import HealthBar from 'components/Account/HealthBar'
|
||||||
|
import Card from 'components/Card'
|
||||||
|
import DisplayCurrency from 'components/DisplayCurrency'
|
||||||
|
import { FormattedNumber } from 'components/FormattedNumber'
|
||||||
|
import { Heart } from 'components/Icons'
|
||||||
|
import Loading from 'components/Loading'
|
||||||
|
import Text from 'components/Text'
|
||||||
|
import TitleAndSubCell from 'components/TitleAndSubCell'
|
||||||
|
import { ORACLE_DENOM } from 'constants/oracle'
|
||||||
|
import useAccount from 'hooks/useAccount'
|
||||||
|
import useBorrowMarketAssetsTableData from 'hooks/useBorrowMarketAssetsTableData'
|
||||||
|
import useHealthComputer from 'hooks/useHealthComputer'
|
||||||
|
import useLendingMarketAssetsTableData from 'hooks/useLendingMarketAssetsTableData'
|
||||||
|
import usePrices from 'hooks/usePrices'
|
||||||
|
import { BNCoin } from 'types/classes/BNCoin'
|
||||||
|
import {
|
||||||
|
calculateAccountApr,
|
||||||
|
calculateAccountLeverage,
|
||||||
|
getAccountPositionValues,
|
||||||
|
} from 'utils/accounts'
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
accountId: string
|
||||||
|
}
|
||||||
|
|
||||||
|
function Content(props: Props) {
|
||||||
|
const { data: account } = useAccount(props.accountId, true)
|
||||||
|
const { data: prices } = usePrices()
|
||||||
|
const { health } = useHealthComputer(account)
|
||||||
|
const { allAssets: borrowAssets } = useBorrowMarketAssetsTableData()
|
||||||
|
const { allAssets: lendingAssets } = useLendingMarketAssetsTableData()
|
||||||
|
|
||||||
|
const stats = useMemo(() => {
|
||||||
|
if (!account || !borrowAssets.length || !lendingAssets.length) return STATS
|
||||||
|
|
||||||
|
const [deposits, lends, debts, vaults] = getAccountPositionValues(account, prices)
|
||||||
|
const positionValue = deposits.plus(lends).plus(vaults)
|
||||||
|
const apr = calculateAccountApr(account, borrowAssets, lendingAssets, prices)
|
||||||
|
const leverage = calculateAccountLeverage(account, prices)
|
||||||
|
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
title: (
|
||||||
|
<DisplayCurrency
|
||||||
|
className='text-xl'
|
||||||
|
coin={BNCoin.fromDenomAndBigNumber(ORACLE_DENOM, positionValue)}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
sub: STATS[0].sub,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: (
|
||||||
|
<DisplayCurrency
|
||||||
|
className='text-xl'
|
||||||
|
coin={BNCoin.fromDenomAndBigNumber(ORACLE_DENOM, debts)}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
sub: STATS[1].sub,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: (
|
||||||
|
<DisplayCurrency
|
||||||
|
className='text-xl'
|
||||||
|
coin={BNCoin.fromDenomAndBigNumber(ORACLE_DENOM, positionValue.minus(debts))}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
sub: STATS[2].sub,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: (
|
||||||
|
<FormattedNumber className='text-xl' amount={apr.toNumber()} options={{ suffix: '%' }} />
|
||||||
|
),
|
||||||
|
sub: STATS[3].sub,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: (
|
||||||
|
<FormattedNumber
|
||||||
|
className='text-xl'
|
||||||
|
amount={leverage.toNumber()}
|
||||||
|
options={{ suffix: 'x' }}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
sub: STATS[4].sub,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}, [account, borrowAssets, lendingAssets, prices])
|
||||||
|
|
||||||
|
return <Skeleton stats={stats} health={health} {...props} />
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function Summary(props: Props) {
|
||||||
|
return (
|
||||||
|
<Suspense fallback={<Skeleton stats={STATS} health={0} {...props} />}>
|
||||||
|
<Content {...props} />
|
||||||
|
</Suspense>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
interface SkeletonProps extends Props {
|
||||||
|
stats: { title: React.ReactNode | null; sub: string }[]
|
||||||
|
health: number
|
||||||
|
}
|
||||||
|
|
||||||
|
function Skeleton(props: SkeletonProps) {
|
||||||
|
return (
|
||||||
|
<div className='flex flex-col py-8 gap-8'>
|
||||||
|
<div className='flex justify-between'>
|
||||||
|
<Text size='2xl'>Credit Account {props.accountId}</Text>
|
||||||
|
<div className='flex gap-1 max-w-[300px] flex-grow'>
|
||||||
|
<Heart width={20} />
|
||||||
|
<HealthBar health={props.health} className='h-full' />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className='grid grid-cols-2 gap-4 lg:grid-cols-5 md:grid-cols-4 sm:grid-cols-3'>
|
||||||
|
{props.stats.map((stat) => (
|
||||||
|
<Card key={stat.sub} className='p-6 bg-white/5 flex-grow-1 text-center'>
|
||||||
|
<TitleAndSubCell
|
||||||
|
title={stat.title || <Loading className='w-20 h-6 mx-auto mb-2' />}
|
||||||
|
sub={stat.sub}
|
||||||
|
className='mb-1'
|
||||||
|
/>
|
||||||
|
</Card>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const STATS = [
|
||||||
|
{ title: null, sub: 'Total Balance' },
|
||||||
|
{ title: null, sub: 'Total Debt' },
|
||||||
|
{ title: null, sub: 'Net Worth' },
|
||||||
|
{ title: null, sub: 'APR' },
|
||||||
|
{ title: null, sub: 'Account Leverage' },
|
||||||
|
]
|
37
src/components/Portfolio/Card/Skeleton.tsx
Normal file
37
src/components/Portfolio/Card/Skeleton.tsx
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
import React from 'react'
|
||||||
|
|
||||||
|
import HealthBar from 'components/Account/HealthBar'
|
||||||
|
import Card from 'components/Card'
|
||||||
|
import { Heart } from 'components/Icons'
|
||||||
|
import Loading from 'components/Loading'
|
||||||
|
import Text from 'components/Text'
|
||||||
|
import TitleAndSubCell from 'components/TitleAndSubCell'
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
stats: { title: React.ReactNode; sub: string }[]
|
||||||
|
health: number
|
||||||
|
isCurrent?: boolean
|
||||||
|
accountId?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function Skeleton(props: Props) {
|
||||||
|
return (
|
||||||
|
<Card className='bg-white/5 p-4'>
|
||||||
|
<div className='flex justify-between items-center'>
|
||||||
|
<Text>Credit account {props.accountId || <Loading />}</Text>
|
||||||
|
<Text size='xs' className='text-white/60'>
|
||||||
|
{props.isCurrent && '(current)'}
|
||||||
|
</Text>
|
||||||
|
</div>
|
||||||
|
<div className='flex gap-4 mt-6'>
|
||||||
|
{props.stats.map(({ title, sub }) => (
|
||||||
|
<TitleAndSubCell key={`${props.accountId}-${sub}`} title={title} sub={sub} />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
<div className='flex gap-1 mt-6'>
|
||||||
|
<Heart width={20} />
|
||||||
|
<HealthBar health={props.health} />
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
)
|
||||||
|
}
|
105
src/components/Portfolio/Card/index.tsx
Normal file
105
src/components/Portfolio/Card/index.tsx
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
import classNames from 'classnames'
|
||||||
|
import React, { ReactNode, useMemo } from 'react'
|
||||||
|
import { NavLink } from 'react-router-dom'
|
||||||
|
|
||||||
|
import { FormattedNumber } from 'components/FormattedNumber'
|
||||||
|
import Loading from 'components/Loading'
|
||||||
|
import Skeleton from 'components/Portfolio/Card/Skeleton'
|
||||||
|
import { DEFAULT_SETTINGS } from 'constants/defaultSettings'
|
||||||
|
import { REDUCE_MOTION_KEY } from 'constants/localStore'
|
||||||
|
import { BN_ZERO } from 'constants/math'
|
||||||
|
import useAccount from 'hooks/useAccount'
|
||||||
|
import useAccountId from 'hooks/useAccountId'
|
||||||
|
import useBorrowMarketAssetsTableData from 'hooks/useBorrowMarketAssetsTableData'
|
||||||
|
import useHealthComputer from 'hooks/useHealthComputer'
|
||||||
|
import useLendingMarketAssetsTableData from 'hooks/useLendingMarketAssetsTableData'
|
||||||
|
import useLocalStorage from 'hooks/useLocalStorage'
|
||||||
|
import usePrices from 'hooks/usePrices'
|
||||||
|
import useStore from 'store'
|
||||||
|
import {
|
||||||
|
calculateAccountApr,
|
||||||
|
calculateAccountLeverage,
|
||||||
|
getAccountPositionValues,
|
||||||
|
} from 'utils/accounts'
|
||||||
|
import { getRoute } from 'utils/route'
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
accountId: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function PortfolioCard(props: Props) {
|
||||||
|
const { data: account } = useAccount(props.accountId)
|
||||||
|
const { health } = useHealthComputer(account)
|
||||||
|
const { data: prices } = usePrices()
|
||||||
|
const currentAccountId = useAccountId()
|
||||||
|
const { allAssets: lendingAssets } = useLendingMarketAssetsTableData()
|
||||||
|
const { allAssets: borrowAssets } = useBorrowMarketAssetsTableData()
|
||||||
|
const [reduceMotion] = useLocalStorage<boolean>(REDUCE_MOTION_KEY, DEFAULT_SETTINGS.reduceMotion)
|
||||||
|
const address = useStore((s) => s.address)
|
||||||
|
|
||||||
|
const [deposits, lends, debts, vaults] = useMemo(() => {
|
||||||
|
if (!prices.length || !account) return Array(4).fill(BN_ZERO)
|
||||||
|
return getAccountPositionValues(account, prices)
|
||||||
|
}, [prices, account])
|
||||||
|
|
||||||
|
const leverage = useMemo(() => {
|
||||||
|
if (!prices.length || !account) return BN_ZERO
|
||||||
|
return calculateAccountLeverage(account, prices)
|
||||||
|
}, [account, prices])
|
||||||
|
|
||||||
|
const apr = useMemo(() => {
|
||||||
|
if (!lendingAssets.length || !borrowAssets.length || !prices.length || !account) return null
|
||||||
|
return calculateAccountApr(account, borrowAssets, lendingAssets, prices)
|
||||||
|
}, [lendingAssets, borrowAssets, prices, account])
|
||||||
|
|
||||||
|
const stats: { title: ReactNode; sub: string }[] = useMemo(() => {
|
||||||
|
const isLoaded = account && prices.length && apr !== null
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
title: isLoaded ? (
|
||||||
|
<FormattedNumber
|
||||||
|
amount={deposits.plus(lends).plus(vaults).minus(debts).toNumber()}
|
||||||
|
options={{ prefix: '$' }}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<Loading />
|
||||||
|
),
|
||||||
|
sub: 'Net worth',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: isLoaded ? (
|
||||||
|
<FormattedNumber amount={leverage.toNumber() || 1} options={{ suffix: 'x' }} />
|
||||||
|
) : (
|
||||||
|
<Loading />
|
||||||
|
),
|
||||||
|
sub: 'Leverage',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: isLoaded ? (
|
||||||
|
<FormattedNumber amount={apr.toNumber()} options={{ suffix: '%' }} />
|
||||||
|
) : (
|
||||||
|
<Loading />
|
||||||
|
),
|
||||||
|
sub: 'APR',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}, [account, prices.length, deposits, lends, vaults, debts, leverage, apr])
|
||||||
|
|
||||||
|
if (!account) {
|
||||||
|
return <Skeleton stats={stats} health={health} accountId={props.accountId} />
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<NavLink
|
||||||
|
to={getRoute(`portfolio/${props.accountId}` as Page, address, currentAccountId)}
|
||||||
|
className={classNames('w-full hover:bg-white/5', !reduceMotion && 'transition-all')}
|
||||||
|
>
|
||||||
|
<Skeleton
|
||||||
|
stats={stats}
|
||||||
|
health={health}
|
||||||
|
accountId={props.accountId}
|
||||||
|
isCurrent={props.accountId === currentAccountId}
|
||||||
|
/>
|
||||||
|
</NavLink>
|
||||||
|
)
|
||||||
|
}
|
18
src/components/Portfolio/Overview/ConnectInfo.tsx
Normal file
18
src/components/Portfolio/Overview/ConnectInfo.tsx
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import Card from 'components/Card'
|
||||||
|
import Text from 'components/Text'
|
||||||
|
import WalletConnectButton from 'components/Wallet/WalletConnectButton'
|
||||||
|
|
||||||
|
export default function ConnectInfo() {
|
||||||
|
return (
|
||||||
|
<Card
|
||||||
|
className='w-full h-fit bg-white/5'
|
||||||
|
title='Portfolio'
|
||||||
|
contentClassName='px-4 py-6 flex justify-center flex-wrap'
|
||||||
|
>
|
||||||
|
<Text size='sm' className='w-full text-center'>
|
||||||
|
You need to be connected to view the portfolio page.
|
||||||
|
</Text>
|
||||||
|
<WalletConnectButton className='mt-4' />
|
||||||
|
</Card>
|
||||||
|
)
|
||||||
|
}
|
78
src/components/Portfolio/Overview/index.tsx
Normal file
78
src/components/Portfolio/Overview/index.tsx
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
import classNames from 'classnames'
|
||||||
|
import { useCallback } from 'react'
|
||||||
|
import { useParams } from 'react-router-dom'
|
||||||
|
|
||||||
|
import AccountCreateFirst from 'components/Account/AccountCreateFirst'
|
||||||
|
import Button from 'components/Button'
|
||||||
|
import Card from 'components/Card'
|
||||||
|
import { PlusCircled } from 'components/Icons'
|
||||||
|
import PortfolioCard from 'components/Portfolio/Card'
|
||||||
|
import ConnectInfo from 'components/Portfolio/Overview/ConnectInfo'
|
||||||
|
import Text from 'components/Text'
|
||||||
|
import WalletBridges from 'components/Wallet/WalletBridges'
|
||||||
|
import useAccountIds from 'hooks/useAccountIds'
|
||||||
|
import useCurrentWalletBalance from 'hooks/useCurrentWalletBalance'
|
||||||
|
import useStore from 'store'
|
||||||
|
import { defaultFee } from 'utils/constants'
|
||||||
|
import { BN } from 'utils/helpers'
|
||||||
|
|
||||||
|
export default function Content() {
|
||||||
|
const { address: urlAddress } = useParams()
|
||||||
|
const walletAddress = useStore((s) => s.address)
|
||||||
|
const { data: accountIds, isLoading } = useAccountIds(urlAddress)
|
||||||
|
|
||||||
|
const baseCurrency = useStore((s) => s.baseCurrency)
|
||||||
|
const transactionFeeCoinBalance = useCurrentWalletBalance(baseCurrency.denom)
|
||||||
|
|
||||||
|
const checkHasFunds = useCallback(() => {
|
||||||
|
return (
|
||||||
|
transactionFeeCoinBalance &&
|
||||||
|
BN(transactionFeeCoinBalance.amount).isGreaterThan(defaultFee.amount[0].amount)
|
||||||
|
)
|
||||||
|
}, [transactionFeeCoinBalance])
|
||||||
|
|
||||||
|
const handleCreateAccountClick = useCallback(() => {
|
||||||
|
if (!checkHasFunds()) {
|
||||||
|
useStore.setState({ focusComponent: { component: <WalletBridges /> } })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
useStore.setState({ focusComponent: { component: <AccountCreateFirst /> } })
|
||||||
|
}, [checkHasFunds])
|
||||||
|
|
||||||
|
if (!walletAddress && !urlAddress) return <ConnectInfo />
|
||||||
|
|
||||||
|
if (!isLoading && accountIds?.length === 0) {
|
||||||
|
return (
|
||||||
|
<Card
|
||||||
|
className='w-full h-fit bg-white/5'
|
||||||
|
title='Portfolio'
|
||||||
|
contentClassName='px-4 py-6 flex justify-center flex-wrap'
|
||||||
|
>
|
||||||
|
<Text size='sm' className='w-full text-center'>
|
||||||
|
You need to create an Account first.
|
||||||
|
</Text>
|
||||||
|
<Button
|
||||||
|
className='mt-4'
|
||||||
|
onClick={handleCreateAccountClick}
|
||||||
|
leftIcon={<PlusCircled />}
|
||||||
|
color='primary'
|
||||||
|
>
|
||||||
|
Create Account
|
||||||
|
</Button>
|
||||||
|
</Card>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Card title='Credit Accounts' contentClassName='p-6 pt-4'>
|
||||||
|
<div
|
||||||
|
className={classNames('grid w-full grid-cols-1 gap-4', 'md:grid-cols-2', 'lg:grid-cols-3')}
|
||||||
|
>
|
||||||
|
{accountIds.map((accountId: string, index: number) => {
|
||||||
|
return <PortfolioCard key={accountId} accountId={accountId} />
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
)
|
||||||
|
}
|
@ -1,12 +1,13 @@
|
|||||||
import { Outlet, Route, Routes as RoutesWrapper } from 'react-router-dom'
|
import { Navigate, Outlet, Route, Routes as RoutesWrapper } from 'react-router-dom'
|
||||||
|
|
||||||
|
import Layout from 'pages/_layout'
|
||||||
import BorrowPage from 'pages/BorrowPage'
|
import BorrowPage from 'pages/BorrowPage'
|
||||||
import FarmPage from 'pages/FarmPage'
|
import FarmPage from 'pages/FarmPage'
|
||||||
import LendPage from 'pages/LendPage'
|
import LendPage from 'pages/LendPage'
|
||||||
import MobilePage from 'pages/MobilePage'
|
import MobilePage from 'pages/MobilePage'
|
||||||
|
import PortfolioAccountPage from 'pages/PortfolioAccountPage'
|
||||||
import PortfolioPage from 'pages/PortfolioPage'
|
import PortfolioPage from 'pages/PortfolioPage'
|
||||||
import TradePage from 'pages/TradePage'
|
import TradePage from 'pages/TradePage'
|
||||||
import Layout from 'pages/_layout'
|
|
||||||
|
|
||||||
export default function Routes() {
|
export default function Routes() {
|
||||||
return (
|
return (
|
||||||
@ -26,21 +27,17 @@ export default function Routes() {
|
|||||||
<Route path='/mobile' element={<MobilePage />} />
|
<Route path='/mobile' element={<MobilePage />} />
|
||||||
<Route path='/' element={<TradePage />} />
|
<Route path='/' element={<TradePage />} />
|
||||||
<Route path='/wallets/:address'>
|
<Route path='/wallets/:address'>
|
||||||
<Route path='accounts/:accountId'>
|
|
||||||
<Route path='trade' element={<TradePage />} />
|
|
||||||
<Route path='farm' element={<FarmPage />} />
|
|
||||||
<Route path='lend' element={<LendPage />} />
|
|
||||||
<Route path='borrow' element={<BorrowPage />} />
|
|
||||||
<Route path='portfolio' element={<PortfolioPage />} />
|
|
||||||
<Route path='' element={<TradePage />} />
|
|
||||||
</Route>
|
|
||||||
<Route path='trade' element={<TradePage />} />
|
<Route path='trade' element={<TradePage />} />
|
||||||
<Route path='farm' element={<FarmPage />} />
|
<Route path='farm' element={<FarmPage />} />
|
||||||
<Route path='lend' element={<LendPage />} />
|
<Route path='lend' element={<LendPage />} />
|
||||||
<Route path='borrow' element={<BorrowPage />} />
|
<Route path='borrow' element={<BorrowPage />} />
|
||||||
<Route path='portfolio' element={<PortfolioPage />} />
|
<Route path='portfolio' element={<PortfolioPage />} />
|
||||||
|
<Route path='portfolio/:accountId'>
|
||||||
|
<Route path='' element={<PortfolioAccountPage />} />
|
||||||
|
</Route>
|
||||||
<Route path='' element={<TradePage />} />
|
<Route path='' element={<TradePage />} />
|
||||||
</Route>
|
</Route>
|
||||||
|
<Route path='*' element={<Navigate to='/' />} />
|
||||||
</Route>
|
</Route>
|
||||||
</RoutesWrapper>
|
</RoutesWrapper>
|
||||||
)
|
)
|
||||||
|
77
src/components/TableSkeleton.tsx
Normal file
77
src/components/TableSkeleton.tsx
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
import classNames from 'classnames'
|
||||||
|
import React from 'react'
|
||||||
|
|
||||||
|
import { SortNone } from 'components/Icons'
|
||||||
|
import Loading from 'components/Loading'
|
||||||
|
import Text from 'components/Text'
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
labels: string[]
|
||||||
|
rowCount: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function TableSkeleton(props: Props) {
|
||||||
|
return (
|
||||||
|
<table className='w-full'>
|
||||||
|
<thead className='border-b border-white/5'>
|
||||||
|
<tr>
|
||||||
|
{props.labels.map((label, index) => {
|
||||||
|
return (
|
||||||
|
<th
|
||||||
|
key={label}
|
||||||
|
className={classNames('p-3', index === props.labels.length - 1 && 'pr-4')}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className={classNames(
|
||||||
|
'flex',
|
||||||
|
index === 0 ? 'justify-start' : 'justify-end',
|
||||||
|
'align-center',
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<span className='w-6 h-6 text-white'>
|
||||||
|
<SortNone />
|
||||||
|
</span>
|
||||||
|
<Text
|
||||||
|
tag='span'
|
||||||
|
size='sm'
|
||||||
|
className={classNames(
|
||||||
|
'flex font-normal text-white/70 items-center',
|
||||||
|
index !== 0 && 'justify-end',
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{label}
|
||||||
|
</Text>
|
||||||
|
</div>
|
||||||
|
</th>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{Array(props.rowCount)
|
||||||
|
.fill(null)
|
||||||
|
.map((_, index) => {
|
||||||
|
return (
|
||||||
|
<tr key={index} className='pl-2'>
|
||||||
|
{props.labels.map((_, index2) => {
|
||||||
|
return (
|
||||||
|
<td
|
||||||
|
key={`${index}-${index2}`}
|
||||||
|
className={classNames(
|
||||||
|
index === 0 && 'justify-end',
|
||||||
|
index2 === 0 && 'pl-4',
|
||||||
|
index2 === props.labels.length - 1 && 'pr-4',
|
||||||
|
'p-2 text-right',
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<Loading className={classNames('w-20 h-3', index2 !== 0 && 'ml-auto')} />
|
||||||
|
</td>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</tr>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
)
|
||||||
|
}
|
@ -15,7 +15,7 @@ export default function TitleAndSubCell(props: Props) {
|
|||||||
<Text size='xs' className={props.className} tag='span'>
|
<Text size='xs' className={props.className} tag='span'>
|
||||||
{props.title}
|
{props.title}
|
||||||
</Text>
|
</Text>
|
||||||
<Text size='xs' className={classNames('text-white/50', props.className)} tag='span'>
|
<Text size='xs' className={classNames('text-white/40', props.className)} tag='span'>
|
||||||
{props.sub}
|
{props.sub}
|
||||||
</Text>
|
</Text>
|
||||||
</div>
|
</div>
|
||||||
|
@ -5,7 +5,7 @@ import AccountCreateFirst from 'components/Account/AccountCreateFirst'
|
|||||||
import { CircularProgress } from 'components/CircularProgress'
|
import { CircularProgress } from 'components/CircularProgress'
|
||||||
import FullOverlayContent from 'components/FullOverlayContent'
|
import FullOverlayContent from 'components/FullOverlayContent'
|
||||||
import WalletBridges from 'components/Wallet/WalletBridges'
|
import WalletBridges from 'components/Wallet/WalletBridges'
|
||||||
import useAccounts from 'hooks/useAccounts'
|
import useAccountIds from 'hooks/useAccountIds'
|
||||||
import useWalletBalances from 'hooks/useWalletBalances'
|
import useWalletBalances from 'hooks/useWalletBalances'
|
||||||
import useStore from 'store'
|
import useStore from 'store'
|
||||||
import { byDenom } from 'utils/array'
|
import { byDenom } from 'utils/array'
|
||||||
@ -29,7 +29,7 @@ function Content() {
|
|||||||
const address = useStore((s) => s.address)
|
const address = useStore((s) => s.address)
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
const { pathname } = useLocation()
|
const { pathname } = useLocation()
|
||||||
const { data: accounts, isLoading: isLoadingAccounts } = useAccounts(address)
|
const { data: accountIds, isLoading: isLoadingAccounts } = useAccountIds(address || '')
|
||||||
const { data: walletBalances, isLoading: isLoadingBalances } = useWalletBalances(address)
|
const { data: walletBalances, isLoading: isLoadingBalances } = useWalletBalances(address)
|
||||||
const baseAsset = getBaseAsset()
|
const baseAsset = getBaseAsset()
|
||||||
|
|
||||||
@ -40,17 +40,17 @@ function Content() {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (
|
if (
|
||||||
accounts.length !== 0 &&
|
accountIds.length !== 0 &&
|
||||||
BN(baseBalance).isGreaterThanOrEqualTo(defaultFee.amount[0].amount)
|
BN(baseBalance).isGreaterThanOrEqualTo(defaultFee.amount[0].amount)
|
||||||
) {
|
) {
|
||||||
navigate(getRoute(getPage(pathname), address, accounts[0].id))
|
navigate(getRoute(getPage(pathname), address, accountIds[0]))
|
||||||
useStore.setState({ accounts: accounts, balances: walletBalances, focusComponent: null })
|
useStore.setState({ balances: walletBalances, focusComponent: null })
|
||||||
}
|
}
|
||||||
}, [accounts, baseBalance, navigate, pathname, address, walletBalances])
|
}, [accountIds, baseBalance, navigate, pathname, address, walletBalances])
|
||||||
|
|
||||||
if (isLoadingAccounts || isLoadingBalances) return <FetchLoading />
|
if (isLoadingAccounts || isLoadingBalances) return <FetchLoading />
|
||||||
if (BN(baseBalance).isLessThan(defaultFee.amount[0].amount)) return <WalletBridges />
|
if (BN(baseBalance).isLessThan(defaultFee.amount[0].amount)) return <WalletBridges />
|
||||||
if (accounts.length === 0) return <AccountCreateFirst />
|
if (accountIds.length === 0) return <AccountCreateFirst />
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,9 +2,9 @@ import useSWR from 'swr'
|
|||||||
|
|
||||||
import getAccount from 'api/accounts/getAccount'
|
import getAccount from 'api/accounts/getAccount'
|
||||||
|
|
||||||
export default function useAccounts(accountId?: string) {
|
export default function useAccounts(accountId?: string, suspense?: boolean) {
|
||||||
return useSWR(`account${accountId}`, () => getAccount(accountId || ''), {
|
return useSWR(`account${accountId}`, () => getAccount(accountId || ''), {
|
||||||
suspense: true,
|
suspense: suspense,
|
||||||
revalidateOnFocus: false,
|
revalidateOnFocus: false,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
6
src/hooks/useAccountId.tsx
Normal file
6
src/hooks/useAccountId.tsx
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
import { useSearchParams } from 'react-router-dom'
|
||||||
|
|
||||||
|
export default function useAccountId() {
|
||||||
|
const [searchParams] = useSearchParams()
|
||||||
|
return searchParams.get('accountId')
|
||||||
|
}
|
11
src/hooks/useAccountIds.tsx
Normal file
11
src/hooks/useAccountIds.tsx
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import useSWR from 'swr'
|
||||||
|
|
||||||
|
import getAccountIds from 'api/wallets/getAccountIds'
|
||||||
|
|
||||||
|
export default function useAccountIds(address?: string) {
|
||||||
|
return useSWR(`wallets/${address}/account-ids`, () => getAccountIds(address), {
|
||||||
|
suspense: true,
|
||||||
|
fallback: [],
|
||||||
|
revalidateOnFocus: false,
|
||||||
|
})
|
||||||
|
}
|
@ -1,11 +1,15 @@
|
|||||||
import useSWR from 'swr'
|
import useSWR from 'swr'
|
||||||
|
|
||||||
import getAccounts from 'api/wallets/getAccounts'
|
import getAccounts from 'api/wallets/getAccounts'
|
||||||
|
import useStore from 'store'
|
||||||
|
|
||||||
export default function useAccounts(address?: string) {
|
export default function useAccounts(address?: string) {
|
||||||
return useSWR(`accounts${address}`, () => getAccounts(address || ''), {
|
return useSWR(`accounts${address}`, () => getAccounts(address), {
|
||||||
suspense: true,
|
suspense: true,
|
||||||
fallbackData: [],
|
fallbackData: [],
|
||||||
revalidateOnFocus: false,
|
revalidateOnFocus: false,
|
||||||
|
onSuccess: (accounts) => {
|
||||||
|
useStore.setState({ accounts: accounts })
|
||||||
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { useMemo } from 'react'
|
import { useMemo } from 'react'
|
||||||
|
|
||||||
|
import useCurrentAccountDebts from 'hooks/useCurrentAccountDebts'
|
||||||
import useDepositEnabledMarkets from 'hooks/useDepositEnabledMarkets'
|
import useDepositEnabledMarkets from 'hooks/useDepositEnabledMarkets'
|
||||||
import useMarketBorrowings from 'hooks/useMarketBorrowings'
|
import useMarketBorrowings from 'hooks/useMarketBorrowings'
|
||||||
import useMarketDeposits from 'hooks/useMarketDeposits'
|
import useMarketDeposits from 'hooks/useMarketDeposits'
|
||||||
@ -7,11 +8,11 @@ import useMarketLiquidities from 'hooks/useMarketLiquidities'
|
|||||||
import { byDenom } from 'utils/array'
|
import { byDenom } from 'utils/array'
|
||||||
import { getAssetByDenom } from 'utils/assets'
|
import { getAssetByDenom } from 'utils/assets'
|
||||||
import { BN } from 'utils/helpers'
|
import { BN } from 'utils/helpers'
|
||||||
import useCurrentAccountDebts from 'hooks/useCurrentAccountDebts'
|
|
||||||
|
|
||||||
export default function useBorrowMarketAssetsTableData(): {
|
export default function useBorrowMarketAssetsTableData(): {
|
||||||
accountBorrowedAssets: BorrowMarketTableData[]
|
accountBorrowedAssets: BorrowMarketTableData[]
|
||||||
availableAssets: BorrowMarketTableData[]
|
availableAssets: BorrowMarketTableData[]
|
||||||
|
allAssets: BorrowMarketTableData[]
|
||||||
} {
|
} {
|
||||||
const markets = useDepositEnabledMarkets()
|
const markets = useDepositEnabledMarkets()
|
||||||
const accountDebts = useCurrentAccountDebts()
|
const accountDebts = useCurrentAccountDebts()
|
||||||
@ -45,6 +46,10 @@ export default function useBorrowMarketAssetsTableData(): {
|
|||||||
;(borrowMarketAsset.debt ? accountBorrowedAssets : availableAssets).push(borrowMarketAsset)
|
;(borrowMarketAsset.debt ? accountBorrowedAssets : availableAssets).push(borrowMarketAsset)
|
||||||
})
|
})
|
||||||
|
|
||||||
return { accountBorrowedAssets, availableAssets }
|
return {
|
||||||
|
accountBorrowedAssets,
|
||||||
|
availableAssets,
|
||||||
|
allAssets: [...accountBorrowedAssets, ...availableAssets],
|
||||||
|
}
|
||||||
}, [accountDebts, borrowData, markets, marketDeposits, marketLiquidities])
|
}, [accountDebts, borrowData, markets, marketDeposits, marketLiquidities])
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
import { useParams } from 'react-router-dom'
|
import useAccountId from 'hooks/useAccountId'
|
||||||
|
|
||||||
import useStore from 'store'
|
import useStore from 'store'
|
||||||
|
|
||||||
export default function useCurrentAccount(): Account | undefined {
|
export default function useCurrentAccount(): Account | undefined {
|
||||||
const { accountId } = useParams()
|
const accountId = useAccountId()
|
||||||
|
|
||||||
const accounts = useStore((s) => s.accounts)
|
const accounts = useStore((s) => s.accounts)
|
||||||
return accounts?.find((account) => account.id === accountId)
|
return accounts?.find((account) => account.id === accountId)
|
||||||
}
|
}
|
||||||
|
@ -12,6 +12,7 @@ import { BN } from 'utils/helpers'
|
|||||||
function useLendingMarketAssetsTableData(): {
|
function useLendingMarketAssetsTableData(): {
|
||||||
accountLentAssets: LendingMarketTableData[]
|
accountLentAssets: LendingMarketTableData[]
|
||||||
availableAssets: LendingMarketTableData[]
|
availableAssets: LendingMarketTableData[]
|
||||||
|
allAssets: LendingMarketTableData[]
|
||||||
} {
|
} {
|
||||||
const markets = useDepositEnabledMarkets()
|
const markets = useDepositEnabledMarkets()
|
||||||
const accountLentAmounts = useCurrentAccountLends()
|
const accountLentAmounts = useCurrentAccountLends()
|
||||||
@ -52,7 +53,11 @@ function useLendingMarketAssetsTableData(): {
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
return { accountLentAssets, availableAssets }
|
return {
|
||||||
|
accountLentAssets,
|
||||||
|
availableAssets,
|
||||||
|
allAssets: [...accountLentAssets, ...availableAssets],
|
||||||
|
}
|
||||||
}, [markets, marketDeposits, marketLiquidities, accountLentAmounts, convertAmount])
|
}, [markets, marketDeposits, marketLiquidities, accountLentAmounts, convertAmount])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
27
src/pages/PortfolioAccountPage.tsx
Normal file
27
src/pages/PortfolioAccountPage.tsx
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import { useNavigate, useParams } from 'react-router-dom'
|
||||||
|
|
||||||
|
import Balances from 'components/Portfolio/Account/Balances'
|
||||||
|
import BreadCrumbs from 'components/Portfolio/Account/BreadCrumbs'
|
||||||
|
import Summary from 'components/Portfolio/Account/Summary'
|
||||||
|
import useAccountId from 'hooks/useAccountId'
|
||||||
|
import { getRoute } from 'utils/route'
|
||||||
|
|
||||||
|
export default function PortfolioAccountPage() {
|
||||||
|
const selectedAccountId = useAccountId()
|
||||||
|
const { address, accountId } = useParams()
|
||||||
|
const navigate = useNavigate()
|
||||||
|
|
||||||
|
if (!accountId) {
|
||||||
|
navigate(getRoute('portfolio', address, selectedAccountId))
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<BreadCrumbs accountId={accountId} />
|
||||||
|
<Summary accountId={accountId} />
|
||||||
|
<Balances accountId={accountId} />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
@ -1,4 +1,4 @@
|
|||||||
import AccountOverview from 'components/Account/AccountOverview'
|
import AccountOverview from 'components/Portfolio/Overview'
|
||||||
import PortfolioIntro from 'components/Portfolio/PortfolioIntro'
|
import PortfolioIntro from 'components/Portfolio/PortfolioIntro'
|
||||||
|
|
||||||
export default function PortfolioPage() {
|
export default function PortfolioPage() {
|
||||||
|
2
src/types/interfaces/route.d.ts
vendored
2
src/types/interfaces/route.d.ts
vendored
@ -1 +1 @@
|
|||||||
type Page = 'trade' | 'borrow' | 'farm' | 'lend' | 'portfolio' | 'council'
|
type Page = 'trade' | 'borrow' | 'farm' | 'lend' | 'portfolio' | 'portfolio/{accountId}'
|
||||||
|
@ -1,26 +1,34 @@
|
|||||||
export function getRoute(page: Page, address?: string, accountId?: string) {
|
export function getRoute(page: Page, address?: string, accountId?: string | null) {
|
||||||
let nextUrl = ''
|
let nextUrl = ''
|
||||||
|
|
||||||
if (address) {
|
if (address) {
|
||||||
nextUrl += `/wallets/${address}`
|
nextUrl += `/wallets/${address}`
|
||||||
|
}
|
||||||
|
|
||||||
if (accountId) {
|
nextUrl += `/${page}`
|
||||||
nextUrl += `/accounts/${accountId}`
|
|
||||||
|
let url = new URL(nextUrl, 'https://app.marsprotocol.io')
|
||||||
|
|
||||||
|
if (accountId) {
|
||||||
|
url.searchParams.append('accountId', accountId)
|
||||||
|
}
|
||||||
|
|
||||||
|
return url.pathname + url.search
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getPage(pathname: string): Page {
|
||||||
|
const pages: Page[] = ['trade', 'borrow', 'farm', 'lend', 'portfolio']
|
||||||
|
const segments = pathname.split('/')
|
||||||
|
|
||||||
|
const page = segments.find((segment) => pages.includes(segment as Page))
|
||||||
|
|
||||||
|
if (page) {
|
||||||
|
if (page === 'portfolio') {
|
||||||
|
const path = pathname.split('portfolio')[1]
|
||||||
|
return (page + path) as Page
|
||||||
}
|
}
|
||||||
|
return page as Page
|
||||||
}
|
}
|
||||||
|
|
||||||
return (nextUrl += `/${page}`)
|
return 'trade' as Page
|
||||||
}
|
|
||||||
|
|
||||||
export function getPage(pathname: string) {
|
|
||||||
const pages: Page[] = ['trade', 'borrow', 'farm', 'lend', 'portfolio', 'council']
|
|
||||||
const lastSegment = pathname.split('/').pop() as Page
|
|
||||||
|
|
||||||
if (!lastSegment) return 'trade'
|
|
||||||
|
|
||||||
if (pages.includes(lastSegment)) {
|
|
||||||
return lastSegment
|
|
||||||
}
|
|
||||||
|
|
||||||
return 'trade'
|
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user