Pre migration adjustments (#506)
* fix: added close button to accountDetails * fix: fixed the AccountList to load async * fix: fixed the heart size on the AccountStats * fix: added AccountDetails loading state * feat: added migration banner * fix: fixed tests
This commit is contained in:
parent
d0ce3eea45
commit
cfd7fb3073
@ -14,11 +14,15 @@ jest.mock('hooks/useHealthComputer', () =>
|
||||
jest.mock('components/Account/AccountBalancesTable', () => jest.fn(() => null))
|
||||
|
||||
const mockedUseCurrentAccount = useCurrentAccount as jest.Mock
|
||||
const mockedAccount = { id: '1', deposits: [], lends: [], debts: [], vaults: [] }
|
||||
jest.mock('hooks/useAccountId', () => jest.fn(() => '1'))
|
||||
jest.mock('hooks/useAccounts', () => jest.fn(() => [mockedAccount]))
|
||||
|
||||
describe('<AccountDetails />', () => {
|
||||
beforeAll(() => {
|
||||
useStore.setState({
|
||||
address: 'walletAddress',
|
||||
accounts: [mockedAccount],
|
||||
})
|
||||
})
|
||||
|
||||
@ -27,7 +31,7 @@ describe('<AccountDetails />', () => {
|
||||
})
|
||||
|
||||
it('renders account details WHEN account is selected', () => {
|
||||
mockedUseCurrentAccount.mockReturnValue({ id: 1 })
|
||||
mockedUseCurrentAccount.mockReturnValue(mockedAccount)
|
||||
render(<AccountDetails />)
|
||||
|
||||
const container = screen.queryByTestId('account-details')
|
||||
|
@ -13,6 +13,7 @@ const data: LendingMarketTableData = {
|
||||
marketLiquidityRate: 0.017,
|
||||
marketLiquidationThreshold: 0.61,
|
||||
marketMaxLtv: 0.59,
|
||||
borrowEnabled: true,
|
||||
}
|
||||
|
||||
jest.mock('hooks/useDisplayCurrencyPrice', () => () => {
|
||||
|
@ -28,6 +28,8 @@ const mockedDepositedVault: DepositedVault = {
|
||||
values: {
|
||||
primary: BN_ZERO,
|
||||
secondary: BN_ZERO,
|
||||
unlocked: BN_ZERO,
|
||||
unlocking: BN_ZERO,
|
||||
},
|
||||
cap: {
|
||||
denom: 'mock',
|
||||
|
36
src/components/Account/AccountDetails/Skeleton.tsx
Normal file
36
src/components/Account/AccountDetails/Skeleton.tsx
Normal file
@ -0,0 +1,36 @@
|
||||
import { HealthGauge } from 'components/HealthGauge'
|
||||
import Loading from 'components/Loading'
|
||||
import Text from 'components/Text'
|
||||
|
||||
export default function Skeleton() {
|
||||
return (
|
||||
<div className='absolute flex items-start w-16 gap-4 right-4 top-6 opacity-90'>
|
||||
<div className='relative flex flex-wrap w-16 border min-w-16 group rounded-base border-white/20'>
|
||||
<div className='flex flex-wrap justify-center w-full py-4'>
|
||||
<HealthGauge health={0} />
|
||||
<Text size='2xs' className='mb-0.5 mt-1 w-full text-center text-white/50'>
|
||||
Account Health
|
||||
</Text>
|
||||
</div>
|
||||
<div className='flex flex-wrap justify-center 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>
|
||||
<Loading className='w-10 h-3 mt-1' />
|
||||
</div>
|
||||
<div className='flex flex-wrap justify-center 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>
|
||||
<Loading className='w-10 h-3 mt-1' />
|
||||
</div>
|
||||
<div className='flex flex-wrap justify-center w-full py-4 border-t border-white/20'>
|
||||
<Text size='2xs' className='mb-0.5 w-full text-center text-white/50'>
|
||||
APR
|
||||
</Text>
|
||||
<Loading className='w-10 h-3 mt-1' />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
@ -1,9 +1,11 @@
|
||||
import classNames from 'classnames'
|
||||
import { useMemo } from 'react'
|
||||
import { useCallback, useMemo } from 'react'
|
||||
|
||||
import AccountBalancesTable from 'components/Account/AccountBalancesTable'
|
||||
import AccountComposition from 'components/Account/AccountComposition'
|
||||
import AccountDetailsLeverage from 'components/Account/AccountDetails/AccountDetailsLeverage'
|
||||
import Skeleton from 'components/Account/AccountDetails/Skeleton'
|
||||
import EscButton from 'components/Button/EscButton'
|
||||
import { glowElement } from 'components/Button/utils'
|
||||
import Card from 'components/Card'
|
||||
import DisplayCurrency from 'components/DisplayCurrency'
|
||||
@ -14,6 +16,8 @@ import Text from 'components/Text'
|
||||
import { DEFAULT_SETTINGS } from 'constants/defaultSettings'
|
||||
import { REDUCE_MOTION_KEY } from 'constants/localStore'
|
||||
import { ORACLE_DENOM } from 'constants/oracle'
|
||||
import useAccountId from 'hooks/useAccountId'
|
||||
import useAccounts from 'hooks/useAccounts'
|
||||
import useBorrowMarketAssetsTableData from 'hooks/useBorrowMarketAssetsTableData'
|
||||
import useCurrentAccount from 'hooks/useCurrentAccount'
|
||||
import useHealthComputer from 'hooks/useHealthComputer'
|
||||
@ -29,11 +33,15 @@ import {
|
||||
} from 'utils/accounts'
|
||||
|
||||
export default function AccountDetailsController() {
|
||||
const account = useCurrentAccount()
|
||||
const address = useStore((s) => s.address)
|
||||
const { isLoading } = useAccounts(address)
|
||||
const accountId = useAccountId()
|
||||
const account = useCurrentAccount()
|
||||
const focusComponent = useStore((s) => s.focusComponent)
|
||||
|
||||
if (!account || !address || focusComponent) return null
|
||||
if (!address || focusComponent) return null
|
||||
|
||||
if ((isLoading && accountId && !focusComponent) || !account) return <Skeleton />
|
||||
|
||||
return <AccountDetails account={account} />
|
||||
}
|
||||
@ -81,6 +89,19 @@ function AccountDetails(props: Props) {
|
||||
[account, borrowAssetsData, lendingAssetsData, prices],
|
||||
)
|
||||
|
||||
function AccountDetailsHeader() {
|
||||
const onClose = useCallback(() => useStore.setState({ accountDetailsExpanded: false }), [])
|
||||
|
||||
return (
|
||||
<div className='flex items-center justify-between w-full p-4 bg-white/10 '>
|
||||
<Text size='lg' className='flex items-center flex-grow font-semibold'>
|
||||
{`Credit Account ${account.id}`}
|
||||
</Text>
|
||||
<EscButton onClick={onClose} hideText className='w-6 h-6' />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
data-testid='account-details'
|
||||
@ -146,7 +167,7 @@ function AccountDetails(props: Props) {
|
||||
{glowElement(!reduceMotion)}
|
||||
</div>
|
||||
<div className='flex w-90 backdrop-blur-sticky'>
|
||||
<Card className='w-full bg-white/5' title={`Credit Account ${account.id}`}>
|
||||
<Card className='w-full bg-white/5' title={<AccountDetailsHeader />}>
|
||||
<AccountComposition account={account} />
|
||||
<Text className='w-full px-4 py-2 text-white bg-white/10'>Balances</Text>
|
||||
<AccountBalancesTable
|
||||
|
@ -1,146 +0,0 @@
|
||||
import classNames from 'classnames'
|
||||
import { useCallback, useEffect } from 'react'
|
||||
import { useLocation, useNavigate, useParams } from 'react-router-dom'
|
||||
|
||||
import AccountFundFullPage from 'components/Account/AccountFund/AccountFundFullPage'
|
||||
import AccountStats from 'components/Account/AccountStats'
|
||||
import Button from 'components/Button'
|
||||
import Card from 'components/Card'
|
||||
import { ArrowDownLine, ArrowUpLine, TrashBin } from 'components/Icons'
|
||||
import Radio from 'components/Radio'
|
||||
import SwitchAutoLend from 'components/Switch/SwitchAutoLend'
|
||||
import Text from 'components/Text'
|
||||
import useCurrentAccount from 'hooks/useCurrentAccount'
|
||||
import usePrices from 'hooks/usePrices'
|
||||
import useStore from 'store'
|
||||
import { calculateAccountBalanceValue } from 'utils/accounts'
|
||||
import { getPage, getRoute } from 'utils/route'
|
||||
|
||||
interface Props {
|
||||
accounts: Account[]
|
||||
setShowMenu: (show: boolean) => void
|
||||
}
|
||||
|
||||
const accountCardHeaderClasses = classNames(
|
||||
'flex w-full items-center justify-between bg-white/20 px-4 py-2.5 text-white/70',
|
||||
'border border-transparent border-b-white/20',
|
||||
'group-hover/account:bg-white/30',
|
||||
)
|
||||
|
||||
export default function AccountList(props: Props) {
|
||||
const { accounts, setShowMenu } = props
|
||||
const navigate = useNavigate()
|
||||
const { pathname } = useLocation()
|
||||
const { address } = useParams()
|
||||
const { data: prices } = usePrices()
|
||||
const account = useCurrentAccount()
|
||||
const accountId = account?.id
|
||||
|
||||
const deleteAccountHandler = useCallback(() => {
|
||||
if (!account) return
|
||||
useStore.setState({ accountDeleteModal: account })
|
||||
}, [account])
|
||||
|
||||
useEffect(() => {
|
||||
if (!accountId) return
|
||||
const element = document.getElementById(`account-${accountId}`)
|
||||
if (element) {
|
||||
element.scrollIntoView({ behavior: 'smooth' })
|
||||
}
|
||||
}, [accountId])
|
||||
|
||||
if (!accounts?.length) return null
|
||||
|
||||
return (
|
||||
<div className='flex flex-wrap w-full p-4'>
|
||||
{accounts.map((account) => {
|
||||
const positionBalance = calculateAccountBalanceValue(account, prices)
|
||||
const isActive = accountId === account.id
|
||||
|
||||
return (
|
||||
<div key={account.id} id={`account-${account.id}`} className='w-full pt-4'>
|
||||
<Card
|
||||
id={`account-${account.id}`}
|
||||
key={account.id}
|
||||
className={classNames('w-full', !isActive && 'group/account hover:cursor-pointer')}
|
||||
contentClassName='bg-white/10 group-hover/account:bg-white/20'
|
||||
onClick={() => {
|
||||
if (isActive) return
|
||||
useStore.setState({ accountDeleteModal: null })
|
||||
navigate(getRoute(getPage(pathname), address, account.id))
|
||||
}}
|
||||
title={
|
||||
<div className={accountCardHeaderClasses} role={!isActive ? 'button' : undefined}>
|
||||
<Text size='xs' className='flex flex-1'>
|
||||
Credit Account {account.id}
|
||||
</Text>
|
||||
<Radio active={isActive} className='group-hover/account:opacity-100' />
|
||||
</div>
|
||||
}
|
||||
>
|
||||
{isActive ? (
|
||||
<>
|
||||
<div className='w-full p-4 border border-transparent border-b-white/20'>
|
||||
<AccountStats account={account} />
|
||||
</div>
|
||||
<div className='grid grid-flow-row grid-cols-2 gap-4 p-4'>
|
||||
<Button
|
||||
className='w-full'
|
||||
text='Fund'
|
||||
color='tertiary'
|
||||
leftIcon={<ArrowUpLine />}
|
||||
onClick={() => {
|
||||
setShowMenu(false)
|
||||
if (positionBalance.isLessThanOrEqualTo(0)) {
|
||||
useStore.setState({
|
||||
focusComponent: {
|
||||
component: <AccountFundFullPage />,
|
||||
onClose: () => {
|
||||
useStore.setState({ getStartedModal: true })
|
||||
},
|
||||
},
|
||||
})
|
||||
return
|
||||
}
|
||||
useStore.setState({ fundAndWithdrawModal: 'fund' })
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
className='w-full'
|
||||
color='tertiary'
|
||||
leftIcon={<ArrowDownLine />}
|
||||
text='Withdraw'
|
||||
onClick={() => {
|
||||
setShowMenu(false)
|
||||
useStore.setState({ fundAndWithdrawModal: 'withdraw' })
|
||||
}}
|
||||
disabled={positionBalance.isLessThanOrEqualTo(0)}
|
||||
/>
|
||||
<Button
|
||||
className='w-full col-span-2'
|
||||
color='tertiary'
|
||||
leftIcon={<TrashBin />}
|
||||
text='Delete'
|
||||
onClick={() => {
|
||||
setShowMenu(false)
|
||||
deleteAccountHandler()
|
||||
}}
|
||||
/>
|
||||
<SwitchAutoLend
|
||||
className='col-span-2 pt-4 border border-transparent border-t-white/10'
|
||||
accountId={account.id}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<div className='w-full p-4'>
|
||||
<AccountStats account={account} />
|
||||
</div>
|
||||
)}
|
||||
</Card>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
)
|
||||
}
|
112
src/components/Account/AccountList/AccountStats.tsx
Normal file
112
src/components/Account/AccountList/AccountStats.tsx
Normal file
@ -0,0 +1,112 @@
|
||||
import { useCallback, useMemo } from 'react'
|
||||
|
||||
import AccountFundFullPage from 'components/Account/AccountFund/AccountFundFullPage'
|
||||
import Skeleton from 'components/Account/AccountList/Skeleton'
|
||||
import Button from 'components/Button'
|
||||
import { ArrowDownLine, ArrowUpLine, TrashBin } from 'components/Icons'
|
||||
import SwitchAutoLend from 'components/Switch/SwitchAutoLend'
|
||||
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 useStore from 'store'
|
||||
import { calculateAccountApr, calculateAccountBalanceValue } from 'utils/accounts'
|
||||
|
||||
interface Props {
|
||||
accountId: string
|
||||
isActive?: boolean
|
||||
setShowMenu?: (show: boolean) => void
|
||||
}
|
||||
|
||||
export default function AccountStats(props: Props) {
|
||||
const { accountId, isActive, setShowMenu } = props
|
||||
const { data: account } = useAccount(accountId)
|
||||
const { data: prices } = usePrices()
|
||||
const positionBalance = useMemo(
|
||||
() => (!account ? null : calculateAccountBalanceValue(account, prices)),
|
||||
[account, prices],
|
||||
)
|
||||
const { health } = useHealthComputer(account)
|
||||
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 apr = useMemo(
|
||||
() =>
|
||||
!account ? null : calculateAccountApr(account, borrowAssetsData, lendingAssetsData, prices),
|
||||
[account, borrowAssetsData, lendingAssetsData, prices],
|
||||
)
|
||||
|
||||
const deleteAccountHandler = useCallback(() => {
|
||||
if (!account) return
|
||||
useStore.setState({ accountDeleteModal: account })
|
||||
}, [account])
|
||||
|
||||
return (
|
||||
<div className='w-full p-4'>
|
||||
<Skeleton health={!account ? 0 : health} positionBalance={positionBalance} apr={apr} />
|
||||
{isActive && setShowMenu && (
|
||||
<div className='grid grid-flow-row grid-cols-2 gap-4 pt-4'>
|
||||
<Button
|
||||
className='w-full'
|
||||
text='Fund'
|
||||
color='tertiary'
|
||||
leftIcon={<ArrowUpLine />}
|
||||
disabled={!positionBalance}
|
||||
onClick={() => {
|
||||
setShowMenu(false)
|
||||
if (!positionBalance) return
|
||||
if (positionBalance.isLessThanOrEqualTo(0)) {
|
||||
useStore.setState({
|
||||
focusComponent: {
|
||||
component: <AccountFundFullPage />,
|
||||
onClose: () => {
|
||||
useStore.setState({ getStartedModal: true })
|
||||
},
|
||||
},
|
||||
})
|
||||
return
|
||||
}
|
||||
useStore.setState({ fundAndWithdrawModal: 'fund' })
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
className='w-full'
|
||||
color='tertiary'
|
||||
leftIcon={<ArrowDownLine />}
|
||||
text='Withdraw'
|
||||
onClick={() => {
|
||||
setShowMenu(false)
|
||||
useStore.setState({ fundAndWithdrawModal: 'withdraw' })
|
||||
}}
|
||||
disabled={!positionBalance || positionBalance.isLessThanOrEqualTo(0)}
|
||||
/>
|
||||
<Button
|
||||
className='w-full col-span-2'
|
||||
color='tertiary'
|
||||
leftIcon={<TrashBin />}
|
||||
text='Delete'
|
||||
disabled={!account}
|
||||
onClick={() => {
|
||||
setShowMenu(false)
|
||||
deleteAccountHandler()
|
||||
}}
|
||||
/>
|
||||
<SwitchAutoLend
|
||||
className='col-span-2 pt-4 border-t border-white/10'
|
||||
accountId={accountId}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
52
src/components/Account/AccountList/Skeleton.tsx
Normal file
52
src/components/Account/AccountList/Skeleton.tsx
Normal file
@ -0,0 +1,52 @@
|
||||
import HealthBar from 'components/Account/HealthBar'
|
||||
import DisplayCurrency from 'components/DisplayCurrency'
|
||||
import { FormattedNumber } from 'components/FormattedNumber'
|
||||
import { ArrowChartLineUp, Heart } from 'components/Icons'
|
||||
import Loading from 'components/Loading'
|
||||
import Text from 'components/Text'
|
||||
import { ORACLE_DENOM } from 'constants/oracle'
|
||||
import { BNCoin } from 'types/classes/BNCoin'
|
||||
|
||||
interface Props {
|
||||
health: number
|
||||
positionBalance: BigNumber | null
|
||||
apr: BigNumber | null
|
||||
}
|
||||
|
||||
export default function Skeleton(props: Props) {
|
||||
const { positionBalance, apr, health } = props
|
||||
return (
|
||||
<div className='flex flex-wrap w-full'>
|
||||
{positionBalance ? (
|
||||
<DisplayCurrency
|
||||
coin={BNCoin.fromDenomAndBigNumber(ORACLE_DENOM, positionBalance)}
|
||||
className='w-full text-xl'
|
||||
/>
|
||||
) : (
|
||||
<Loading className='h-6 mt-1 w-30' />
|
||||
)}
|
||||
|
||||
<div className='flex items-center justify-between w-full mt-1'>
|
||||
<div className='flex items-center'>
|
||||
<ArrowChartLineUp className='w-4 mr-1' />
|
||||
{apr ? (
|
||||
<FormattedNumber
|
||||
className='text-xs text-white/70'
|
||||
amount={apr.toNumber()}
|
||||
options={{ prefix: 'APR: ', suffix: '%', minDecimals: 2, maxDecimals: 2 }}
|
||||
/>
|
||||
) : (
|
||||
<Loading className='h-4 w-18' />
|
||||
)}
|
||||
</div>
|
||||
<div className='flex items-center gap-1'>
|
||||
<Heart className='w-4' />
|
||||
<Text size='xs' className='w-auto mr-1 text-white/70'>
|
||||
Health
|
||||
</Text>
|
||||
<HealthBar health={health} className='w-[92px] h-0.5' hasLabel />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
75
src/components/Account/AccountList/index.tsx
Normal file
75
src/components/Account/AccountList/index.tsx
Normal file
@ -0,0 +1,75 @@
|
||||
import classNames from 'classnames'
|
||||
import { useEffect } from 'react'
|
||||
import { useLocation, useNavigate, useParams } from 'react-router-dom'
|
||||
|
||||
import AccountStats from 'components/Account/AccountList/AccountStats'
|
||||
import Card from 'components/Card'
|
||||
import Radio from 'components/Radio'
|
||||
import Text from 'components/Text'
|
||||
import useAccountId from 'hooks/useAccountId'
|
||||
import useAccountIds from 'hooks/useAccountIds'
|
||||
import useStore from 'store'
|
||||
import { getPage, getRoute } from 'utils/route'
|
||||
|
||||
interface Props {
|
||||
setShowMenu: (show: boolean) => void
|
||||
}
|
||||
|
||||
const accountCardHeaderClasses = classNames(
|
||||
'flex w-full items-center justify-between bg-white/20 px-4 py-2.5 text-white/70',
|
||||
'border border-transparent border-b-white/20',
|
||||
'group-hover/account:bg-white/30',
|
||||
)
|
||||
|
||||
export default function AccountList(props: Props) {
|
||||
const { setShowMenu } = props
|
||||
const navigate = useNavigate()
|
||||
const { pathname } = useLocation()
|
||||
const currentAccountId = useAccountId()
|
||||
const { address } = useParams()
|
||||
const { data: accountIds } = useAccountIds(address)
|
||||
|
||||
useEffect(() => {
|
||||
if (!currentAccountId) return
|
||||
const element = document.getElementById(`account-${currentAccountId}`)
|
||||
if (element) {
|
||||
element.scrollIntoView({ behavior: 'smooth' })
|
||||
}
|
||||
}, [currentAccountId])
|
||||
|
||||
if (!accountIds?.length) return null
|
||||
|
||||
return (
|
||||
<div className='flex flex-wrap w-full p-4'>
|
||||
{accountIds.map((accountId) => {
|
||||
const isActive = currentAccountId === accountId
|
||||
|
||||
return (
|
||||
<div key={accountId} id={`account-${accountId}`} className='w-full pt-4'>
|
||||
<Card
|
||||
id={`account-${accountId}`}
|
||||
key={accountId}
|
||||
className={classNames('w-full', !isActive && 'group/account hover:cursor-pointer')}
|
||||
contentClassName='bg-white/10 group-hover/account:bg-white/20'
|
||||
onClick={() => {
|
||||
if (isActive) return
|
||||
useStore.setState({ accountDeleteModal: null })
|
||||
navigate(getRoute(getPage(pathname), address, accountId))
|
||||
}}
|
||||
title={
|
||||
<div className={accountCardHeaderClasses} role={!isActive ? 'button' : undefined}>
|
||||
<Text size='xs' className='flex flex-1'>
|
||||
Credit Account {accountId}
|
||||
</Text>
|
||||
<Radio active={isActive} className='group-hover/account:opacity-100' />
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<AccountStats accountId={accountId} isActive={isActive} setShowMenu={setShowMenu} />
|
||||
</Card>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
)
|
||||
}
|
@ -2,15 +2,15 @@ import { Suspense } from 'react'
|
||||
|
||||
import AccountMenuContent from 'components/Account/AccountMenuContent'
|
||||
import Loading from 'components/Loading'
|
||||
import useAccounts from 'hooks/useAccounts'
|
||||
import useAccountIds from 'hooks/useAccountIds'
|
||||
import useStore from 'store'
|
||||
|
||||
function Content() {
|
||||
const address = useStore((s) => s.address)
|
||||
const { data: accounts, isLoading } = useAccounts(address)
|
||||
const { data: accountIds, isLoading } = useAccountIds(address)
|
||||
if (isLoading) return <Loading className='h-8 w-35' />
|
||||
if (!accounts) return null
|
||||
return <AccountMenuContent accounts={accounts} />
|
||||
if (!accountIds) return null
|
||||
return <AccountMenuContent />
|
||||
}
|
||||
|
||||
export default function AccountMenu() {
|
||||
|
@ -6,7 +6,6 @@ import AccountCreateFirst from 'components/Account/AccountCreateFirst'
|
||||
import AccountFund from 'components/Account/AccountFund/AccountFundFullPage'
|
||||
import AccountList from 'components/Account/AccountList'
|
||||
import Button from 'components/Button'
|
||||
import { CircularProgress } from 'components/CircularProgress'
|
||||
import { Account, Plus, PlusCircled } from 'components/Icons'
|
||||
import Overlay from 'components/Overlay'
|
||||
import Text from 'components/Text'
|
||||
@ -14,6 +13,7 @@ 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 useAccountIds from 'hooks/useAccountIds'
|
||||
import useAutoLend from 'hooks/useAutoLend'
|
||||
import useCurrentWalletBalance from 'hooks/useCurrentWalletBalance'
|
||||
import useLocalStorage from 'hooks/useLocalStorage'
|
||||
@ -27,14 +27,11 @@ import { getPage, getRoute } from 'utils/route'
|
||||
const menuClasses = 'absolute isolate flex w-full flex-wrap scrollbar-hide'
|
||||
const ACCOUNT_MENU_BUTTON_ID = 'account-menu-button'
|
||||
|
||||
interface Props {
|
||||
accounts: Account[]
|
||||
}
|
||||
|
||||
export default function AccountMenuContent(props: Props) {
|
||||
export default function AccountMenuContent() {
|
||||
const navigate = useNavigate()
|
||||
const { pathname } = useLocation()
|
||||
const { address } = useParams()
|
||||
const { data: accountIds } = useAccountIds(address)
|
||||
const accountId = useAccountId()
|
||||
|
||||
const createAccount = useStore((s) => s.createAccount)
|
||||
@ -45,13 +42,9 @@ export default function AccountMenuContent(props: Props) {
|
||||
const [lendAssets] = useLocalStorage<boolean>(LEND_ASSETS_KEY, DEFAULT_SETTINGS.lendAssets)
|
||||
const { enableAutoLendAccountId } = useAutoLend()
|
||||
|
||||
const hasCreditAccounts = !!props.accounts.length
|
||||
const hasCreditAccounts = !!accountIds.length
|
||||
const isAccountSelected = isNumber(accountId)
|
||||
|
||||
const selectedAccountDetails = props.accounts.find((account) => account.id === accountId)
|
||||
|
||||
const isLoadingAccount = isAccountSelected && selectedAccountDetails?.id !== accountId
|
||||
|
||||
const checkHasFunds = useCallback(() => {
|
||||
return (
|
||||
transactionFeeCoinBalance &&
|
||||
@ -149,14 +142,7 @@ export default function AccountMenuContent(props: Props) {
|
||||
'top-[54px] h-[calc(100%-54px)] items-start',
|
||||
)}
|
||||
>
|
||||
{isAccountSelected && isLoadingAccount && (
|
||||
<div className='flex items-center justify-center w-full h-full p-4'>
|
||||
<CircularProgress size={40} />
|
||||
</div>
|
||||
)}
|
||||
{hasCreditAccounts && !isLoadingAccount && (
|
||||
<AccountList accounts={props.accounts} setShowMenu={setShowMenu} />
|
||||
)}
|
||||
{hasCreditAccounts && <AccountList setShowMenu={setShowMenu} />}
|
||||
</div>
|
||||
</Overlay>
|
||||
</div>
|
||||
|
@ -1,68 +0,0 @@
|
||||
import { useMemo } from 'react'
|
||||
|
||||
import HealthBar from 'components/Account/HealthBar'
|
||||
import DisplayCurrency from 'components/DisplayCurrency'
|
||||
import { FormattedNumber } from 'components/FormattedNumber'
|
||||
import { ArrowChartLineUp, Heart } from 'components/Icons'
|
||||
import Text from 'components/Text'
|
||||
import { ORACLE_DENOM } from 'constants/oracle'
|
||||
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, calculateAccountBalanceValue } from 'utils/accounts'
|
||||
|
||||
interface Props {
|
||||
account: Account
|
||||
}
|
||||
|
||||
export default function AccountStats(props: Props) {
|
||||
const { data: prices } = usePrices()
|
||||
const positionBalance = useMemo(
|
||||
() => calculateAccountBalanceValue(props.account, prices),
|
||||
[props.account, prices],
|
||||
)
|
||||
const { health } = useHealthComputer(props.account)
|
||||
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 apr = useMemo(
|
||||
() => calculateAccountApr(props.account, borrowAssetsData, lendingAssetsData, prices),
|
||||
[props.account, borrowAssetsData, lendingAssetsData, prices],
|
||||
)
|
||||
return (
|
||||
<div className='flex flex-wrap w-full'>
|
||||
<DisplayCurrency
|
||||
coin={BNCoin.fromDenomAndBigNumber(ORACLE_DENOM, positionBalance)}
|
||||
className='w-full text-xl'
|
||||
/>
|
||||
<div className='flex items-center justify-between w-full mt-1'>
|
||||
<div className='flex items-center'>
|
||||
<ArrowChartLineUp className='w-4 mr-1' />
|
||||
<FormattedNumber
|
||||
className='text-xs text-white/70'
|
||||
amount={apr.toNumber()}
|
||||
options={{ prefix: 'APR: ', suffix: '%', minDecimals: 2, maxDecimals: 2 }}
|
||||
/>
|
||||
</div>
|
||||
<div className='flex items-center gap-1'>
|
||||
<Heart className='w-3' />
|
||||
<Text size='xs' className='w-auto mr-1 text-white/70'>
|
||||
Health
|
||||
</Text>
|
||||
<HealthBar health={health} className='w-[92px] h-0.5' hasLabel />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
@ -6,6 +6,8 @@ import Text from 'components/Text'
|
||||
|
||||
interface Props {
|
||||
enableKeyPress?: boolean
|
||||
hideText?: boolean
|
||||
className?: string
|
||||
onClick: () => void
|
||||
}
|
||||
|
||||
@ -34,10 +36,10 @@ export default function EscButton(props: Props) {
|
||||
leftIcon={<Cross />}
|
||||
iconClassName='w-3'
|
||||
color='tertiary'
|
||||
className='h-3 w-13'
|
||||
className={props.className ? props.className : 'h-3 w-13'}
|
||||
size='xs'
|
||||
>
|
||||
<Text size='2xs'>ESC</Text>
|
||||
{!props.hideText && <Text size='2xs'>ESC</Text>}
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
|
@ -38,8 +38,10 @@ export const HealthGauge = ({ diameter = 40, health = 0, updatedHealth = 0 }: Pr
|
||||
[color, updatedColor, isUpdated, isIncrease],
|
||||
)
|
||||
|
||||
const tooltipContent = health === 0 ? 'loading...' : isUpdated ? updatedLabel : label
|
||||
|
||||
return (
|
||||
<Tooltip type='info' content={isUpdated ? updatedLabel : label}>
|
||||
<Tooltip type='info' content={tooltipContent}>
|
||||
<div
|
||||
className={classNames(
|
||||
'relative grid place-items-center',
|
||||
|
43
src/components/MigrationBanner.tsx
Normal file
43
src/components/MigrationBanner.tsx
Normal file
@ -0,0 +1,43 @@
|
||||
import Card from 'components/Card'
|
||||
import { Cross, ExclamationMarkCircled } from 'components/Icons'
|
||||
import Text from 'components/Text'
|
||||
import { TextLink } from 'components/TextLink'
|
||||
import { DEFAULT_SETTINGS } from 'constants/defaultSettings'
|
||||
import { MIGRATION_BANNER_KEY } from 'constants/localStore'
|
||||
import useLocalStorage from 'hooks/useLocalStorage'
|
||||
|
||||
export default function MigrationBanner() {
|
||||
const [migrationBanner, setMigrationBanner] = useLocalStorage<boolean>(
|
||||
MIGRATION_BANNER_KEY,
|
||||
DEFAULT_SETTINGS.migrationBanner,
|
||||
)
|
||||
|
||||
if (!migrationBanner) return null
|
||||
return (
|
||||
<Card
|
||||
className='relative w-full bg-info-bg/20 text-info'
|
||||
contentClassName='flex w-full gap-2 px-4 py-3 items-center'
|
||||
>
|
||||
<div className='flex w-6 h-6 p-1 text-white rounded bg-info'>
|
||||
<ExclamationMarkCircled />
|
||||
</div>
|
||||
<Text className='flex flex-grow' size='sm'>
|
||||
If you have funds on{' '}
|
||||
<TextLink
|
||||
href='https://osmosis.marsprotocol.io'
|
||||
externalLink
|
||||
className='mx-1 text-white no-underline hover:underline'
|
||||
>
|
||||
Mars v1,
|
||||
</TextLink>
|
||||
please withdraw them and deposit them on Mars v2 to migrate.
|
||||
</Text>
|
||||
<div
|
||||
className='absolute right-0 flex items-center h-full px-4 w-11 opacity-80 hover:cursor-pointer hover:opacity-100'
|
||||
onClick={() => setMigrationBanner(false)}
|
||||
>
|
||||
<Cross className='w-3 h-3' />
|
||||
</div>
|
||||
</Card>
|
||||
)
|
||||
}
|
@ -3,22 +3,21 @@ 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
|
||||
accountId: string
|
||||
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>
|
||||
<Card className='p-4 bg-white/5'>
|
||||
<div className='flex items-center justify-between'>
|
||||
<Text>Credit account {props.accountId}</Text>
|
||||
<Text size='xs' className='text-white/60'>
|
||||
{props.isCurrent && '(current)'}
|
||||
</Text>
|
||||
|
@ -4,8 +4,6 @@ import { ReactNode } from 'react'
|
||||
import { TooltipArrow } from 'components/Icons'
|
||||
import Text from 'components/Text'
|
||||
|
||||
import { TooltipType } from '.'
|
||||
|
||||
interface Props {
|
||||
content: ReactNode | string
|
||||
type: TooltipType
|
||||
|
@ -18,8 +18,6 @@ interface Props {
|
||||
underline?: boolean
|
||||
}
|
||||
|
||||
export type TooltipType = 'info' | 'warning' | 'error'
|
||||
|
||||
export const Tooltip = (props: Props) => {
|
||||
const [reduceMotion] = useLocalStorage<boolean>(REDUCE_MOTION_KEY, DEFAULT_SETTINGS.reduceMotion)
|
||||
|
||||
|
@ -12,6 +12,7 @@ import { Check, Copy, ExternalLink, Osmo } from 'components/Icons'
|
||||
import Overlay from 'components/Overlay'
|
||||
import Text from 'components/Text'
|
||||
import RecentTransactions from 'components/Wallet/RecentTransactions'
|
||||
import WalletSelect from 'components/Wallet/WalletSelect'
|
||||
import { CHAINS } from 'constants/chains'
|
||||
import { ENV } from 'constants/env'
|
||||
import { BN_ZERO } from 'constants/math'
|
||||
@ -25,8 +26,6 @@ import { getBaseAsset, getEnabledMarketAssets } from 'utils/assets'
|
||||
import { truncate } from 'utils/formatters'
|
||||
import { getPage, getRoute } from 'utils/route'
|
||||
|
||||
import WalletSelect from './WalletSelect'
|
||||
|
||||
export default function WalletConnectedButton() {
|
||||
// ---------------
|
||||
// EXTERNAL HOOKS
|
||||
|
@ -8,4 +8,5 @@ export const DEFAULT_SETTINGS: Settings = {
|
||||
displayCurrency: ORACLE_DENOM,
|
||||
slippage: 0.02,
|
||||
tutorial: true,
|
||||
migrationBanner: true,
|
||||
}
|
||||
|
@ -8,3 +8,4 @@ export const SLIPPAGE_KEY = 'slippage'
|
||||
export const TERMS_OF_SERVICE_KEY = 'termsOfService'
|
||||
export const TUTORIAL_KEY = 'tutorial'
|
||||
export const TRANSACTIONS_KEY = 'transactions'
|
||||
export const MIGRATION_BANNER_KEY = 'migrationBanner'
|
||||
|
@ -84,7 +84,7 @@ export default function useDepositVault(props: Props): {
|
||||
amount: 'account_balance',
|
||||
},
|
||||
}))
|
||||
}, [isAutoLend, props.borrowings, props.deposits, props.reclaims])
|
||||
}, [isAutoLend, props.vault.denoms.primary, props.vault.denoms.secondary])
|
||||
|
||||
const actions = useMemo(() => {
|
||||
return [
|
||||
|
@ -1,6 +1,9 @@
|
||||
import { useCallback, useEffect, useState } from 'react'
|
||||
|
||||
import { DEFAULT_SETTINGS } from 'constants/defaultSettings'
|
||||
import { SLIPPAGE_KEY } from 'constants/localStore'
|
||||
import { BN_ZERO } from 'constants/math'
|
||||
import useLocalStorage from 'hooks/useLocalStorage'
|
||||
import usePrices from 'hooks/usePrices'
|
||||
import {
|
||||
addCoins,
|
||||
@ -15,10 +18,6 @@ import { cloneAccount } from 'utils/accounts'
|
||||
import { byDenom } from 'utils/array'
|
||||
import { getValueFromBNCoins } from 'utils/helpers'
|
||||
|
||||
import { DEFAULT_SETTINGS } from '../../constants/defaultSettings'
|
||||
import { SLIPPAGE_KEY } from '../../constants/localStore'
|
||||
import useLocalStorage from '../useLocalStorage'
|
||||
|
||||
export interface VaultValue {
|
||||
address: string
|
||||
value: BigNumber
|
||||
|
@ -1,5 +1,6 @@
|
||||
import BorrowIntro from 'components/Borrow/BorrowIntro'
|
||||
import BorrowTable from 'components/Borrow/BorrowTable'
|
||||
import MigrationBanner from 'components/MigrationBanner'
|
||||
import useBorrowMarketAssetsTableData from 'hooks/useBorrowMarketAssetsTableData'
|
||||
|
||||
export default function BorrowPage() {
|
||||
@ -7,6 +8,7 @@ export default function BorrowPage() {
|
||||
|
||||
return (
|
||||
<div className='flex flex-wrap w-full gap-6'>
|
||||
<MigrationBanner />
|
||||
<BorrowIntro />
|
||||
<BorrowTable data={accountBorrowedAssets} title='Borrowed Assets' />
|
||||
<BorrowTable data={availableAssets} title='Available to borrow' />
|
||||
|
@ -1,10 +1,12 @@
|
||||
import FarmIntro from 'components/Earn/Farm/FarmIntro'
|
||||
import { AvailableVaults, DepositedVaults } from 'components/Earn/Farm/Vaults'
|
||||
import Tab from 'components/Earn/Tab'
|
||||
import MigrationBanner from 'components/MigrationBanner'
|
||||
|
||||
export default function FarmPage() {
|
||||
return (
|
||||
<div className='flex flex-wrap w-full gap-6'>
|
||||
<MigrationBanner />
|
||||
<Tab isFarm />
|
||||
<FarmIntro />
|
||||
<DepositedVaults />
|
||||
|
@ -1,12 +1,14 @@
|
||||
import LendIntro from 'components/Earn/Lend/LendIntro'
|
||||
import LendingMarketsTable from 'components/Earn/Lend/LendingMarketsTable'
|
||||
import Tab from 'components/Earn/Tab'
|
||||
import MigrationBanner from 'components/MigrationBanner'
|
||||
import useLendingMarketAssetsTableData from 'hooks/useLendingMarketAssetsTableData'
|
||||
|
||||
export default function LendPage() {
|
||||
const { accountLentAssets, availableAssets } = useLendingMarketAssetsTableData()
|
||||
return (
|
||||
<div className='flex flex-wrap w-full gap-6'>
|
||||
<MigrationBanner />
|
||||
<Tab />
|
||||
<LendIntro />
|
||||
<LendingMarketsTable data={accountLentAssets} title='Lent Assets' />
|
||||
|
@ -1,6 +1,6 @@
|
||||
import React from 'react'
|
||||
import { useNavigate, useParams } from 'react-router-dom'
|
||||
|
||||
import MigrationBanner from 'components/MigrationBanner'
|
||||
import Balances from 'components/Portfolio/Account/Balances'
|
||||
import BreadCrumbs from 'components/Portfolio/Account/BreadCrumbs'
|
||||
import Summary from 'components/Portfolio/Account/Summary'
|
||||
@ -19,6 +19,7 @@ export default function PortfolioAccountPage() {
|
||||
|
||||
return (
|
||||
<div className='flex flex-wrap w-full gap-6'>
|
||||
<MigrationBanner />
|
||||
<BreadCrumbs accountId={accountId} />
|
||||
<Summary accountId={accountId} />
|
||||
<Balances accountId={accountId} />
|
||||
|
@ -1,9 +1,11 @@
|
||||
import MigrationBanner from 'components/MigrationBanner'
|
||||
import AccountOverview from 'components/Portfolio/Overview'
|
||||
import PortfolioIntro from 'components/Portfolio/PortfolioIntro'
|
||||
|
||||
export default function PortfolioPage() {
|
||||
return (
|
||||
<div className='flex flex-wrap w-full gap-6'>
|
||||
<MigrationBanner />
|
||||
<PortfolioIntro />
|
||||
<AccountOverview />
|
||||
</div>
|
||||
|
@ -1,5 +1,6 @@
|
||||
import { useState } from 'react'
|
||||
|
||||
import MigrationBanner from 'components/MigrationBanner'
|
||||
import AccountDetailsCard from 'components/Trade/AccountDetailsCard'
|
||||
import TradeChart from 'components/Trade/TradeChart'
|
||||
import TradeModule from 'components/Trade/TradeModule'
|
||||
@ -11,16 +12,19 @@ export default function TradePage() {
|
||||
const [sellAsset, setSellAsset] = useState(enabledMarketAssets[1])
|
||||
|
||||
return (
|
||||
<div className='grid h-full w-full grid-cols-[346px_auto] gap-4'>
|
||||
<TradeModule
|
||||
buyAsset={buyAsset}
|
||||
sellAsset={sellAsset}
|
||||
onChangeBuyAsset={setBuyAsset}
|
||||
onChangeSellAsset={setSellAsset}
|
||||
/>
|
||||
<TradeChart buyAsset={buyAsset} sellAsset={sellAsset} />
|
||||
<div />
|
||||
<AccountDetailsCard />
|
||||
<div className='flex flex-col w-full h-full gap-4'>
|
||||
<MigrationBanner />
|
||||
<div className='grid h-full w-full grid-cols-[346px_auto] gap-4'>
|
||||
<TradeModule
|
||||
buyAsset={buyAsset}
|
||||
sellAsset={sellAsset}
|
||||
onChangeBuyAsset={setBuyAsset}
|
||||
onChangeSellAsset={setSellAsset}
|
||||
/>
|
||||
<TradeChart buyAsset={buyAsset} sellAsset={sellAsset} />
|
||||
<div />
|
||||
<AccountDetailsCard />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
@ -6,12 +6,13 @@ import AccountDetails from 'components/Account/AccountDetails'
|
||||
import Background from 'components/Background'
|
||||
import Footer from 'components/Footer'
|
||||
import DesktopHeader from 'components/Header/DesktopHeader'
|
||||
import MigrationBanner from 'components/MigrationBanner'
|
||||
import ModalsContainer from 'components/Modals/ModalsContainer'
|
||||
import PageMetadata from 'components/PageMetadata'
|
||||
import Toaster from 'components/Toaster'
|
||||
import { DEFAULT_SETTINGS } from 'constants/defaultSettings'
|
||||
import { REDUCE_MOTION_KEY } from 'constants/localStore'
|
||||
import useCurrentAccount from 'hooks/useCurrentAccount'
|
||||
import useAccountId from 'hooks/useAccountId'
|
||||
import useLocalStorage from 'hooks/useLocalStorage'
|
||||
import useStore from 'store'
|
||||
|
||||
@ -44,7 +45,7 @@ export default function Layout({ children }: { children: React.ReactNode }) {
|
||||
const [reduceMotion] = useLocalStorage<boolean>(REDUCE_MOTION_KEY, DEFAULT_SETTINGS.reduceMotion)
|
||||
const accountDetailsExpanded = useStore((s) => s.accountDetailsExpanded)
|
||||
const isFullWidth = location.pathname.includes('trade') || location.pathname === '/'
|
||||
const account = useCurrentAccount()
|
||||
const accountId = useAccountId()
|
||||
|
||||
return (
|
||||
<>
|
||||
@ -57,7 +58,7 @@ export default function Layout({ children }: { children: React.ReactNode }) {
|
||||
'lg:mt-[73px]',
|
||||
'min-h-screen gap-6 px-4 py-6 w-full relative',
|
||||
'flex',
|
||||
isFullWidth && account && (accountDetailsExpanded ? 'pr-110.5' : 'pr-24'),
|
||||
isFullWidth && accountId && (accountDetailsExpanded ? 'pr-110.5' : 'pr-24'),
|
||||
!reduceMotion && isFullWidth && 'transition-all duration-300',
|
||||
'justify-center',
|
||||
focusComponent && 'items-center',
|
||||
|
1
src/types/interfaces/store/settings.d.ts
vendored
1
src/types/interfaces/store/settings.d.ts
vendored
@ -5,4 +5,5 @@ interface Settings {
|
||||
lendAssets: boolean
|
||||
slippage: number
|
||||
tutorial: boolean
|
||||
migrationBanner: boolean
|
||||
}
|
||||
|
1
src/types/interfaces/tooltip.d.ts
vendored
Normal file
1
src/types/interfaces/tooltip.d.ts
vendored
Normal file
@ -0,0 +1 @@
|
||||
type TooltipType = 'info' | 'warning' | 'error'
|
@ -245,6 +245,7 @@ module.exports = {
|
||||
4.5: '18px',
|
||||
13: '52px',
|
||||
15: '60px',
|
||||
18: '72px',
|
||||
30: '120px',
|
||||
35: '140px',
|
||||
50: '200px',
|
||||
|
Loading…
Reference in New Issue
Block a user