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'
|
||||
|
||||
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 data = await accountNftQueryClient.tokens({ owner: address })
|
||||
|
@ -1,7 +1,8 @@
|
||||
import getWalletAccountIds from 'api/wallets/getAccountIds'
|
||||
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 $accounts = accountIds.map((accountId) => getAccount(accountId))
|
||||
|
@ -54,7 +54,7 @@ export default function AccountComposition(props: Props) {
|
||||
() => getAccountPositionValues(account, prices),
|
||||
[account, prices],
|
||||
)
|
||||
const positionValue = depositsBalance.plus(lendsBalance).plus(vaultsBalance)
|
||||
const totalBalance = depositsBalance.plus(lendsBalance).plus(vaultsBalance)
|
||||
|
||||
const [updatedPositionValue, updatedDebtsBalance] = useMemo(() => {
|
||||
const [updatedDepositsBalance, updatedLendsBalance, updatedDebtsBalance, updatedVaultsBalance] =
|
||||
@ -69,10 +69,7 @@ export default function AccountComposition(props: Props) {
|
||||
return [updatedPositionValue, updatedDebtsBalance]
|
||||
}, [updatedAccount, prices])
|
||||
|
||||
const totalBalance = useMemo(
|
||||
() => calculateAccountBalanceValue(account, prices),
|
||||
[account, prices],
|
||||
)
|
||||
const netWorth = useMemo(() => calculateAccountBalanceValue(account, prices), [account, prices])
|
||||
const updatedTotalBalance = useMemo(
|
||||
() => (updatedAccount ? calculateAccountBalanceValue(updatedAccount, prices) : BN_ZERO),
|
||||
[updatedAccount, prices],
|
||||
@ -93,9 +90,9 @@ export default function AccountComposition(props: Props) {
|
||||
return (
|
||||
<div className='flex-wrap w-full p-4 pb-1'>
|
||||
<Item
|
||||
title='Total Position Value'
|
||||
current={positionValue}
|
||||
change={hasChanged ? updatedPositionValue : positionValue}
|
||||
title='Total Balance'
|
||||
current={totalBalance}
|
||||
change={hasChanged ? updatedPositionValue : totalBalance}
|
||||
className='pb-3'
|
||||
/>
|
||||
<Item
|
||||
@ -106,9 +103,9 @@ export default function AccountComposition(props: Props) {
|
||||
isDecrease
|
||||
/>
|
||||
<Item
|
||||
title='Total Balance'
|
||||
current={totalBalance}
|
||||
change={hasChanged ? updatedTotalBalance : totalBalance}
|
||||
title='Net worth'
|
||||
current={netWorth}
|
||||
change={hasChanged ? updatedTotalBalance : netWorth}
|
||||
className='py-3 font-bold border border-transparent border-y-white/20'
|
||||
/>
|
||||
<Item
|
||||
|
@ -4,8 +4,8 @@ import { FormattedNumber } from 'components/FormattedNumber'
|
||||
import { ArrowRight } from 'components/Icons'
|
||||
|
||||
interface Props {
|
||||
leverage: BigNumber
|
||||
updatedLeverage: BigNumber | null
|
||||
leverage: number
|
||||
updatedLeverage: number | null
|
||||
}
|
||||
|
||||
export default function AccountDetailsLeverage(props: Props) {
|
||||
@ -15,7 +15,7 @@ export default function AccountDetailsLeverage(props: Props) {
|
||||
return (
|
||||
<FormattedNumber
|
||||
className={'w-full text-center text-2xs'}
|
||||
amount={isNaN(leverage.toNumber()) ? 0 : leverage.toNumber()}
|
||||
amount={isNaN(leverage) ? 0 : leverage}
|
||||
options={{
|
||||
maxDecimals: 2,
|
||||
minDecimals: 2,
|
||||
@ -30,7 +30,7 @@ export default function AccountDetailsLeverage(props: Props) {
|
||||
<div className='flex'>
|
||||
<FormattedNumber
|
||||
className={'w-full text-center text-2xs'}
|
||||
amount={isNaN(leverage.toNumber()) ? 0 : leverage.toNumber()}
|
||||
amount={isNaN(leverage) ? 1 : leverage}
|
||||
options={{
|
||||
maxDecimals: 1,
|
||||
minDecimals: 1,
|
||||
@ -42,10 +42,10 @@ export default function AccountDetailsLeverage(props: Props) {
|
||||
<FormattedNumber
|
||||
className={classNames(
|
||||
'w-full text-center text-2xs',
|
||||
updatedLeverage.gt(leverage) && 'text-loss',
|
||||
updatedLeverage.lt(leverage) && 'text-profit',
|
||||
updatedLeverage > leverage && 'text-loss',
|
||||
updatedLeverage < leverage && 'text-profit',
|
||||
)}
|
||||
amount={isNaN(updatedLeverage.toNumber()) ? 0 : updatedLeverage.toNumber()}
|
||||
amount={isNaN(updatedLeverage) ? 0 : updatedLeverage}
|
||||
options={{ maxDecimals: 1, minDecimals: 1, rounded: true }}
|
||||
animate
|
||||
/>
|
||||
|
@ -106,18 +106,21 @@ function AccountDetails(props: Props) {
|
||||
Account Health
|
||||
</Text>
|
||||
</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'>
|
||||
<Text size='2xs' className='mb-0.5 w-full text-center text-white/50 whitespace-nowrap'>
|
||||
Net worth
|
||||
</Text>
|
||||
<DisplayCurrency coin={coin} className='w-full text-center truncate text-2xs ' />
|
||||
</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'>
|
||||
<Text size='2xs' className='mb-0.5 w-full text-center text-white/50'>
|
||||
APR
|
||||
|
@ -1,17 +1,17 @@
|
||||
import { useEffect, useState } from 'react'
|
||||
import { useParams } from 'react-router-dom'
|
||||
|
||||
import AccountFundContent from 'components/Account/AccountFund/AccountFundContent'
|
||||
import Card from 'components/Card'
|
||||
import { CircularProgress } from 'components/CircularProgress'
|
||||
import FullOverlayContent from 'components/FullOverlayContent'
|
||||
import useAccountId from 'hooks/useAccountId'
|
||||
import useAccounts from 'hooks/useAccounts'
|
||||
import useCurrentAccount from 'hooks/useCurrentAccount'
|
||||
import useStore from 'store'
|
||||
|
||||
export default function AccountFundFullPage() {
|
||||
const address = useStore((s) => s.address)
|
||||
const { accountId } = useParams()
|
||||
const accountId = useAccountId()
|
||||
|
||||
const { data: accounts, isLoading } = useAccounts(address)
|
||||
const currentAccount = useCurrentAccount()
|
||||
|
@ -1,5 +1,5 @@
|
||||
import classNames from 'classnames'
|
||||
import { useCallback, useEffect } from 'react'
|
||||
import { useCallback } from 'react'
|
||||
import { useLocation, useNavigate, useParams } from 'react-router-dom'
|
||||
|
||||
import AccountCreateFirst from 'components/Account/AccountCreateFirst'
|
||||
@ -13,6 +13,7 @@ import Text from 'components/Text'
|
||||
import WalletBridges from 'components/Wallet/WalletBridges'
|
||||
import { DEFAULT_SETTINGS } from 'constants/defaultSettings'
|
||||
import { LEND_ASSETS_KEY } from 'constants/localStore'
|
||||
import useAccountId from 'hooks/useAccountId'
|
||||
import useAutoLend from 'hooks/useAutoLend'
|
||||
import useCurrentWalletBalance from 'hooks/useCurrentWalletBalance'
|
||||
import useLocalStorage from 'hooks/useLocalStorage'
|
||||
@ -33,7 +34,9 @@ interface Props {
|
||||
export default function AccountMenuContent(props: Props) {
|
||||
const navigate = useNavigate()
|
||||
const { pathname } = useLocation()
|
||||
const { accountId, address } = useParams()
|
||||
const { address } = useParams()
|
||||
const accountId = useAccountId()
|
||||
|
||||
const createAccount = useStore((s) => s.createAccount)
|
||||
const baseCurrency = useStore((s) => s.baseCurrency)
|
||||
const [showMenu, setShowMenu] = useToggle()
|
||||
@ -97,10 +100,6 @@ export default function AccountMenuContent(props: Props) {
|
||||
}
|
||||
}, [checkHasFunds, hasCreditAccounts, setShowMenu, showMenu])
|
||||
|
||||
useEffect(() => {
|
||||
useStore.setState({ accounts: props.accounts })
|
||||
}, [props.accounts])
|
||||
|
||||
if (!address) return null
|
||||
|
||||
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'
|
||||
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'>
|
||||
<mask id='healthBarMask'>
|
||||
<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 { useParams } from 'react-router-dom'
|
||||
|
||||
import AccountCreateFirst from 'components/Account/AccountCreateFirst'
|
||||
import { ACCOUNT_MENU_BUTTON_ID } from 'components/Account/AccountMenuContent'
|
||||
import Button from 'components/Button'
|
||||
import { Account, PlusCircled } from 'components/Icons'
|
||||
import WalletConnectButton from 'components/Wallet/WalletConnectButton'
|
||||
import useAccountId from 'hooks/useAccountId'
|
||||
import useAccountIds from 'hooks/useAccountIds'
|
||||
import useStore from 'store'
|
||||
|
||||
export default function ActionButton(props: ButtonProps) {
|
||||
const { className, color, variant, size } = props
|
||||
const defaultProps = { className, color, variant, size }
|
||||
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(() => {
|
||||
useStore.setState({ focusComponent: { component: <AccountCreateFirst /> } })
|
||||
@ -21,7 +23,7 @@ export default function ActionButton(props: ButtonProps) {
|
||||
|
||||
if (!address) return <WalletConnectButton {...defaultProps} />
|
||||
|
||||
if (accounts && accounts.length === 0)
|
||||
if (accountIds.length === 0) {
|
||||
return (
|
||||
<Button
|
||||
onClick={handleCreateAccountClick}
|
||||
@ -30,8 +32,9 @@ export default function ActionButton(props: ButtonProps) {
|
||||
{...defaultProps}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
if (!accountId)
|
||||
if (!selectedAccountId) {
|
||||
return (
|
||||
<Button
|
||||
text='Select Account'
|
||||
@ -41,6 +44,7 @@ export default function ActionButton(props: ButtonProps) {
|
||||
{...defaultProps}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
return <Button {...props} />
|
||||
}
|
||||
|
@ -1,13 +1,13 @@
|
||||
import { Row } from '@tanstack/react-table'
|
||||
import moment from 'moment'
|
||||
import { useState } from 'react'
|
||||
import { useParams } from 'react-router-dom'
|
||||
|
||||
import Button from 'components/Button'
|
||||
import { AccountArrowDown, LockLocked, LockUnlocked, Plus } from 'components/Icons'
|
||||
import { Tooltip } from 'components/Tooltip'
|
||||
import { DEFAULT_SETTINGS } from 'constants/defaultSettings'
|
||||
import { SLIPPAGE_KEY } from 'constants/localStore'
|
||||
import useAccountId from 'hooks/useAccountId'
|
||||
import useLocalStorage from 'hooks/useLocalStorage'
|
||||
import useStore from 'store'
|
||||
import { VaultStatus } from 'types/enums/vault'
|
||||
@ -19,7 +19,7 @@ interface Props {
|
||||
|
||||
export default function VaultExpanded(props: Props) {
|
||||
const vault = props.row.original as DepositedVault
|
||||
const { accountId } = useParams()
|
||||
const accountId = useAccountId()
|
||||
const [isConfirming, setIsConfirming] = useState(false)
|
||||
const withdrawFromVaults = useStore((s) => s.withdrawFromVaults)
|
||||
const [slippage] = useLocalStorage<number>(SLIPPAGE_KEY, DEFAULT_SETTINGS.slippage)
|
||||
|
@ -1,11 +1,11 @@
|
||||
import { useState } from 'react'
|
||||
import { useParams } from 'react-router-dom'
|
||||
|
||||
import Button from 'components/Button'
|
||||
import { ChevronRight } from 'components/Icons'
|
||||
import NotificationBanner from 'components/NotificationBanner'
|
||||
import { DEFAULT_SETTINGS } from 'constants/defaultSettings'
|
||||
import { SLIPPAGE_KEY } from 'constants/localStore'
|
||||
import useAccountId from 'hooks/useAccountId'
|
||||
import useLocalStorage from 'hooks/useLocalStorage'
|
||||
import useStore from 'store'
|
||||
|
||||
@ -14,7 +14,7 @@ interface Props {
|
||||
}
|
||||
|
||||
export default function VaultUnlockBanner(props: Props) {
|
||||
const { accountId } = useParams()
|
||||
const accountId = useAccountId()
|
||||
const [isConfirming, setIsConfirming] = useState(false)
|
||||
const withdrawFromVaults = useStore((s) => s.withdrawFromVaults)
|
||||
const [slippage] = useLocalStorage<number>(SLIPPAGE_KEY, DEFAULT_SETTINGS.slippage)
|
||||
|
@ -1,5 +1,4 @@
|
||||
import { Suspense, useMemo } from 'react'
|
||||
import { useParams } from 'react-router-dom'
|
||||
|
||||
import Card from 'components/Card'
|
||||
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 { BN_ZERO } from 'constants/math'
|
||||
import { TESTNET_VAULTS_META_DATA, VAULTS_META_DATA } from 'constants/vaults'
|
||||
import useAccountId from 'hooks/useAccountId'
|
||||
import useDepositedVaults from 'hooks/useDepositedVaults'
|
||||
import useVaults from 'hooks/useVaults'
|
||||
import { VaultStatus } from 'types/enums/vault'
|
||||
@ -16,7 +16,7 @@ interface Props {
|
||||
}
|
||||
|
||||
function Content(props: Props) {
|
||||
const { accountId } = useParams()
|
||||
const accountId = useAccountId()
|
||||
const { data: vaults } = useVaults()
|
||||
const { data: depositedVaults } = useDepositedVaults(accountId || '')
|
||||
const isAvailable = props.type === 'available'
|
||||
|
@ -1,5 +1,4 @@
|
||||
import { useCallback } from 'react'
|
||||
import { useParams } from 'react-router-dom'
|
||||
|
||||
import { ACCOUNT_MENU_BUTTON_ID } from 'components/Account/AccountMenuContent'
|
||||
import Button from 'components/Button'
|
||||
@ -8,6 +7,7 @@ import { ArrowDownLine, ArrowUpLine, Enter } from 'components/Icons'
|
||||
import Text from 'components/Text'
|
||||
import { Tooltip } from 'components/Tooltip'
|
||||
import ConditionalWrapper from 'hocs/ConditionalWrapper'
|
||||
import useAccountId from 'hooks/useAccountId'
|
||||
import useAlertDialog from 'hooks/useAlertDialog'
|
||||
import useAutoLend from 'hooks/useAutoLend'
|
||||
import useCurrentAccountDeposits from 'hooks/useCurrentAccountDeposits'
|
||||
@ -30,7 +30,7 @@ export default function LendingActionButtons(props: Props) {
|
||||
const { isAutoLendEnabledForCurrentAccount } = useAutoLend()
|
||||
const assetDepositAmount = accountDeposits.find(byDenom(asset.denom))?.amount
|
||||
const address = useStore((s) => s.address)
|
||||
const { accountId } = useParams()
|
||||
const accountId = useAccountId()
|
||||
const hasNoDeposit = !!(!assetDepositAmount && address && accountId)
|
||||
|
||||
const handleWithdraw = useCallback(() => {
|
||||
|
@ -1,8 +1,7 @@
|
||||
import { useParams } from 'react-router-dom'
|
||||
|
||||
import Button from 'components/Button'
|
||||
import { NoIcon, YesIcon } from 'components/Modals/AlertDialog/ButtonIcons'
|
||||
import Text from 'components/Text'
|
||||
import useAccountId from 'hooks/useAccountId'
|
||||
import useStore from 'store'
|
||||
|
||||
interface Props {
|
||||
@ -12,7 +11,7 @@ interface Props {
|
||||
|
||||
export default function UnlockModalContent(props: Props) {
|
||||
const unlock = useStore((s) => s.unlock)
|
||||
const { accountId } = useParams()
|
||||
const accountId = useAccountId()
|
||||
|
||||
function onConfirm() {
|
||||
if (!accountId) return
|
||||
|
@ -1,5 +1,3 @@
|
||||
import { useParams } from 'react-router-dom'
|
||||
|
||||
import Button from 'components/Button'
|
||||
import { CircularProgress } from 'components/CircularProgress'
|
||||
import DisplayCurrency from 'components/DisplayCurrency'
|
||||
@ -9,6 +7,7 @@ import Modal from 'components/Modal'
|
||||
import Text from 'components/Text'
|
||||
import { DEFAULT_SETTINGS } from 'constants/defaultSettings'
|
||||
import { SLIPPAGE_KEY } from 'constants/localStore'
|
||||
import useAccountId from 'hooks/useAccountId'
|
||||
import useLocalStorage from 'hooks/useLocalStorage'
|
||||
import useStore from 'store'
|
||||
import { BNCoin } from 'types/classes/BNCoin'
|
||||
@ -17,7 +16,7 @@ import { demagnify } from 'utils/formatters'
|
||||
|
||||
export default function WithdrawFromVaultsModal() {
|
||||
const modal = useStore((s) => s.withdrawFromVaultsModal)
|
||||
const { accountId } = useParams()
|
||||
const accountId = useAccountId()
|
||||
const withdrawFromVaults = useStore((s) => s.withdrawFromVaults)
|
||||
const baseCurrency = useStore((s) => s.baseCurrency)
|
||||
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 { Logo } from 'components/Icons'
|
||||
import { NavLink } from 'components/Navigation/NavLink'
|
||||
import useAccountId from 'hooks/useAccountId'
|
||||
import useStore from 'store'
|
||||
import { getRoute } from 'utils/route'
|
||||
|
||||
export default function DesktopNavigation() {
|
||||
const { address, accountId } = useParams()
|
||||
const { address } = useParams()
|
||||
const accountId = useAccountId()
|
||||
|
||||
const focusComponent = useStore((s) => s.focusComponent)
|
||||
|
||||
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 FarmPage from 'pages/FarmPage'
|
||||
import LendPage from 'pages/LendPage'
|
||||
import MobilePage from 'pages/MobilePage'
|
||||
import PortfolioAccountPage from 'pages/PortfolioAccountPage'
|
||||
import PortfolioPage from 'pages/PortfolioPage'
|
||||
import TradePage from 'pages/TradePage'
|
||||
import Layout from 'pages/_layout'
|
||||
|
||||
export default function Routes() {
|
||||
return (
|
||||
@ -26,21 +27,17 @@ export default function Routes() {
|
||||
<Route path='/mobile' element={<MobilePage />} />
|
||||
<Route path='/' element={<TradePage />} />
|
||||
<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='farm' element={<FarmPage />} />
|
||||
<Route path='lend' element={<LendPage />} />
|
||||
<Route path='borrow' element={<BorrowPage />} />
|
||||
<Route path='portfolio' element={<PortfolioPage />} />
|
||||
<Route path='portfolio/:accountId'>
|
||||
<Route path='' element={<PortfolioAccountPage />} />
|
||||
</Route>
|
||||
<Route path='' element={<TradePage />} />
|
||||
</Route>
|
||||
<Route path='*' element={<Navigate to='/' />} />
|
||||
</Route>
|
||||
</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'>
|
||||
{props.title}
|
||||
</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}
|
||||
</Text>
|
||||
</div>
|
||||
|
@ -5,7 +5,7 @@ import AccountCreateFirst from 'components/Account/AccountCreateFirst'
|
||||
import { CircularProgress } from 'components/CircularProgress'
|
||||
import FullOverlayContent from 'components/FullOverlayContent'
|
||||
import WalletBridges from 'components/Wallet/WalletBridges'
|
||||
import useAccounts from 'hooks/useAccounts'
|
||||
import useAccountIds from 'hooks/useAccountIds'
|
||||
import useWalletBalances from 'hooks/useWalletBalances'
|
||||
import useStore from 'store'
|
||||
import { byDenom } from 'utils/array'
|
||||
@ -29,7 +29,7 @@ function Content() {
|
||||
const address = useStore((s) => s.address)
|
||||
const navigate = useNavigate()
|
||||
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 baseAsset = getBaseAsset()
|
||||
|
||||
@ -40,17 +40,17 @@ function Content() {
|
||||
|
||||
useEffect(() => {
|
||||
if (
|
||||
accounts.length !== 0 &&
|
||||
accountIds.length !== 0 &&
|
||||
BN(baseBalance).isGreaterThanOrEqualTo(defaultFee.amount[0].amount)
|
||||
) {
|
||||
navigate(getRoute(getPage(pathname), address, accounts[0].id))
|
||||
useStore.setState({ accounts: accounts, balances: walletBalances, focusComponent: null })
|
||||
navigate(getRoute(getPage(pathname), address, accountIds[0]))
|
||||
useStore.setState({ balances: walletBalances, focusComponent: null })
|
||||
}
|
||||
}, [accounts, baseBalance, navigate, pathname, address, walletBalances])
|
||||
}, [accountIds, baseBalance, navigate, pathname, address, walletBalances])
|
||||
|
||||
if (isLoadingAccounts || isLoadingBalances) return <FetchLoading />
|
||||
if (BN(baseBalance).isLessThan(defaultFee.amount[0].amount)) return <WalletBridges />
|
||||
if (accounts.length === 0) return <AccountCreateFirst />
|
||||
if (accountIds.length === 0) return <AccountCreateFirst />
|
||||
return null
|
||||
}
|
||||
|
||||
|
@ -2,9 +2,9 @@ import useSWR from 'swr'
|
||||
|
||||
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 || ''), {
|
||||
suspense: true,
|
||||
suspense: suspense,
|
||||
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 getAccounts from 'api/wallets/getAccounts'
|
||||
import useStore from 'store'
|
||||
|
||||
export default function useAccounts(address?: string) {
|
||||
return useSWR(`accounts${address}`, () => getAccounts(address || ''), {
|
||||
return useSWR(`accounts${address}`, () => getAccounts(address), {
|
||||
suspense: true,
|
||||
fallbackData: [],
|
||||
revalidateOnFocus: false,
|
||||
onSuccess: (accounts) => {
|
||||
useStore.setState({ accounts: accounts })
|
||||
},
|
||||
})
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
import { useMemo } from 'react'
|
||||
|
||||
import useCurrentAccountDebts from 'hooks/useCurrentAccountDebts'
|
||||
import useDepositEnabledMarkets from 'hooks/useDepositEnabledMarkets'
|
||||
import useMarketBorrowings from 'hooks/useMarketBorrowings'
|
||||
import useMarketDeposits from 'hooks/useMarketDeposits'
|
||||
@ -7,11 +8,11 @@ import useMarketLiquidities from 'hooks/useMarketLiquidities'
|
||||
import { byDenom } from 'utils/array'
|
||||
import { getAssetByDenom } from 'utils/assets'
|
||||
import { BN } from 'utils/helpers'
|
||||
import useCurrentAccountDebts from 'hooks/useCurrentAccountDebts'
|
||||
|
||||
export default function useBorrowMarketAssetsTableData(): {
|
||||
accountBorrowedAssets: BorrowMarketTableData[]
|
||||
availableAssets: BorrowMarketTableData[]
|
||||
allAssets: BorrowMarketTableData[]
|
||||
} {
|
||||
const markets = useDepositEnabledMarkets()
|
||||
const accountDebts = useCurrentAccountDebts()
|
||||
@ -45,6 +46,10 @@ export default function useBorrowMarketAssetsTableData(): {
|
||||
;(borrowMarketAsset.debt ? accountBorrowedAssets : availableAssets).push(borrowMarketAsset)
|
||||
})
|
||||
|
||||
return { accountBorrowedAssets, availableAssets }
|
||||
return {
|
||||
accountBorrowedAssets,
|
||||
availableAssets,
|
||||
allAssets: [...accountBorrowedAssets, ...availableAssets],
|
||||
}
|
||||
}, [accountDebts, borrowData, markets, marketDeposits, marketLiquidities])
|
||||
}
|
||||
|
@ -1,9 +1,9 @@
|
||||
import { useParams } from 'react-router-dom'
|
||||
|
||||
import useAccountId from 'hooks/useAccountId'
|
||||
import useStore from 'store'
|
||||
|
||||
export default function useCurrentAccount(): Account | undefined {
|
||||
const { accountId } = useParams()
|
||||
const accountId = useAccountId()
|
||||
|
||||
const accounts = useStore((s) => s.accounts)
|
||||
return accounts?.find((account) => account.id === accountId)
|
||||
}
|
||||
|
@ -12,6 +12,7 @@ import { BN } from 'utils/helpers'
|
||||
function useLendingMarketAssetsTableData(): {
|
||||
accountLentAssets: LendingMarketTableData[]
|
||||
availableAssets: LendingMarketTableData[]
|
||||
allAssets: LendingMarketTableData[]
|
||||
} {
|
||||
const markets = useDepositEnabledMarkets()
|
||||
const accountLentAmounts = useCurrentAccountLends()
|
||||
@ -52,7 +53,11 @@ function useLendingMarketAssetsTableData(): {
|
||||
},
|
||||
)
|
||||
|
||||
return { accountLentAssets, availableAssets }
|
||||
return {
|
||||
accountLentAssets,
|
||||
availableAssets,
|
||||
allAssets: [...accountLentAssets, ...availableAssets],
|
||||
}
|
||||
}, [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'
|
||||
|
||||
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 = ''
|
||||
|
||||
if (address) {
|
||||
nextUrl += `/wallets/${address}`
|
||||
}
|
||||
|
||||
if (accountId) {
|
||||
nextUrl += `/accounts/${accountId}`
|
||||
nextUrl += `/${page}`
|
||||
|
||||
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}`)
|
||||
}
|
||||
|
||||
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'
|
||||
return 'trade' as Page
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user