overall cleanup and refactoring + SettingsModal (#286)
This commit is contained in:
parent
ffe86a440c
commit
df6d7a3ba2
@ -1,11 +1,11 @@
|
||||
import { render } from '@testing-library/react'
|
||||
|
||||
import { CircularProgress } from 'components/CircularProgress'
|
||||
import useStore from 'store'
|
||||
import { REDUCE_MOTION_KEY } from 'constants/localStore'
|
||||
|
||||
describe('<CircularProgress />', () => {
|
||||
afterAll(() => {
|
||||
useStore.clearState()
|
||||
localStorage.removeItem(REDUCE_MOTION_KEY)
|
||||
})
|
||||
|
||||
it('should render', () => {
|
||||
@ -15,7 +15,7 @@ describe('<CircularProgress />', () => {
|
||||
})
|
||||
|
||||
it('should render `...` when animations disabled', () => {
|
||||
useStore.setState({ enableAnimations: false })
|
||||
localStorage.setItem(REDUCE_MOTION_KEY, 'true')
|
||||
|
||||
const { getByText } = render(<CircularProgress />)
|
||||
const threeDots = getByText('...')
|
||||
@ -24,7 +24,7 @@ describe('<CircularProgress />', () => {
|
||||
})
|
||||
|
||||
it('should render the component with animation classes when animations enabled', () => {
|
||||
useStore.setState({ enableAnimations: true })
|
||||
localStorage.setItem(REDUCE_MOTION_KEY, 'false')
|
||||
|
||||
const { container } = render(<CircularProgress />)
|
||||
const progressWithAnimations = container.querySelector('.animate-progress')
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { render } from '@testing-library/react'
|
||||
|
||||
import LendingDetails from 'components/Earn/Lend/LendingDetails'
|
||||
import LendingDetails from 'components/MarketAssetTable/MarketDetails'
|
||||
import { ASSETS } from 'constants/assets'
|
||||
import { BN } from 'utils/helpers'
|
||||
|
@ -1,13 +1,13 @@
|
||||
import { render } from '@testing-library/react'
|
||||
|
||||
import Modal from 'components/Modal'
|
||||
import UnlockModal from 'components/Modals/Unlock/UnlockModal'
|
||||
import UnlockModal from 'components/Modals/Unlock'
|
||||
import { TESTNET_VAULTS_META_DATA } from 'constants/vaults'
|
||||
import useStore from 'store'
|
||||
import { BN } from 'utils/helpers'
|
||||
|
||||
jest.mock('components/Modal')
|
||||
const mockedModal = jest.mocked(Modal).mockImplementation(() => <div>Modal</div>)
|
||||
const mockedModal = jest.mocked(Modal).mockImplementation(() => <div>Mock</div>)
|
||||
|
||||
const mockedDepositedVault: DepositedVault = {
|
||||
...TESTNET_VAULTS_META_DATA[0],
|
||||
@ -36,31 +36,26 @@ const mockedDepositedVault: DepositedVault = {
|
||||
}
|
||||
|
||||
describe('<UnlockModal />', () => {
|
||||
beforeAll(() => {
|
||||
useStore.setState({ unlockModal: null })
|
||||
afterAll(() => {
|
||||
useStore.clearState()
|
||||
})
|
||||
|
||||
it('should render', () => {
|
||||
const { container } = render(<UnlockModal />)
|
||||
expect(mockedModal).toHaveBeenCalledTimes(1)
|
||||
expect(container).toBeInTheDocument()
|
||||
})
|
||||
|
||||
describe('should set open attribute correctly', () => {
|
||||
it('should set open = false when no modal is present in store', () => {
|
||||
describe('should set content correctly', () => {
|
||||
it('should have no content when no modal is present in store', () => {
|
||||
useStore.setState({ unlockModal: null })
|
||||
render(<UnlockModal />)
|
||||
expect(mockedModal).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ open: false }),
|
||||
expect.anything(),
|
||||
)
|
||||
expect(mockedModal).toHaveBeenCalledTimes(0)
|
||||
})
|
||||
|
||||
it('should set open = true when no modal is present in store', () => {
|
||||
it('should have content when modal is present in store', () => {
|
||||
useStore.setState({ unlockModal: { vault: mockedDepositedVault } })
|
||||
render(<UnlockModal />)
|
||||
expect(mockedModal).toHaveBeenLastCalledWith(
|
||||
expect.objectContaining({ open: true }),
|
||||
expect.anything(),
|
||||
)
|
||||
expect(mockedModal).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
@ -26,6 +26,7 @@ module.exports = {
|
||||
|
||||
// Handle module aliases
|
||||
'^app/(.*)$': '<rootDir>/src/app/$1',
|
||||
'^api/(.*)$': '<rootDir>/src/api/$1',
|
||||
'^components/(.*)$': '<rootDir>/src/components/$1',
|
||||
'^constants/(.*)$': '<rootDir>/src/constants/$1',
|
||||
'^fonts/(.*)$': '<rootDir>/src/fonts/$1',
|
||||
|
@ -1,8 +1,8 @@
|
||||
import { BN } from 'utils/helpers'
|
||||
import getPrices from 'api/prices/getPrices'
|
||||
import getMarkets from 'api/markets/getMarkets'
|
||||
import { getEnabledMarketAssets } from 'utils/assets'
|
||||
import getMarketLiquidities from 'api/markets/getMarketLiquidities'
|
||||
import getMarkets from 'api/markets/getMarkets'
|
||||
import getPrices from 'api/prices/getPrices'
|
||||
import { getEnabledMarketAssets } from 'utils/assets'
|
||||
import { BN } from 'utils/helpers'
|
||||
|
||||
export default async function getMarketBorrowings(): Promise<BorrowAsset[]> {
|
||||
const liquidities = await getMarketLiquidities()
|
||||
@ -20,7 +20,7 @@ export default async function getMarketBorrowings(): Promise<BorrowAsset[]> {
|
||||
borrowRate: market.borrowRate ?? 0,
|
||||
liquidity: {
|
||||
amount: BN(amount),
|
||||
value: BN(amount).times(price),
|
||||
value: BN(amount).multipliedBy(price),
|
||||
},
|
||||
}
|
||||
})
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { BN } from 'utils/helpers'
|
||||
import getPrice from 'api/prices/getPrice'
|
||||
import { BN } from 'utils/helpers'
|
||||
|
||||
const MARS_MAINNET_DENOM = 'ibc/573FCD90FACEE750F55A8864EF7D38265F07E5A9273FA0E8DAFD39951332B580'
|
||||
const MARS_OSMO_POOL_URL = 'https://lcd-osmosis.blockapsis.com/osmosis/gamm/v1beta1/pools/907'
|
||||
@ -36,8 +36,8 @@ const calculateSpotPrice = (poolAssets: PoolAsset[]) => {
|
||||
|
||||
const assetOut = findPoolAssetByTokenDenom(poolAssets, 'uosmo') as PoolAsset
|
||||
|
||||
const numerator = BN(assetIn.token.amount).div(assetIn.weight)
|
||||
const denominator = BN(assetOut.token.amount).div(assetOut.weight)
|
||||
const numerator = BN(assetIn.token.amount).dividedBy(assetIn.weight)
|
||||
const denominator = BN(assetOut.token.amount).dividedBy(assetOut.weight)
|
||||
|
||||
return numerator.dividedBy(denominator)
|
||||
}
|
||||
|
@ -17,7 +17,7 @@ export default async function getVaultConfigs(
|
||||
lpTokenOut: lpDenom,
|
||||
}),
|
||||
)
|
||||
.times(1 - slippage)
|
||||
.multipliedBy(1 - slippage)
|
||||
.integerValue()
|
||||
} catch (ex) {
|
||||
throw ex
|
||||
|
@ -14,18 +14,23 @@ import { FormattedNumber } from 'components/FormattedNumber'
|
||||
import { SortAsc, SortDesc, SortNone } from 'components/Icons'
|
||||
import Text from 'components/Text'
|
||||
import { ASSETS } from 'constants/assets'
|
||||
import useStore from 'store'
|
||||
import { convertToDisplayAmount, demagnify } from 'utils/formatters'
|
||||
import { BN } from 'utils/helpers'
|
||||
import { BNCoin } from 'types/classes/BNCoin'
|
||||
import usePrices from 'hooks/usePrices'
|
||||
import useStore from 'store'
|
||||
import { BNCoin } from 'types/classes/BNCoin'
|
||||
import { convertToDisplayAmount, demagnify } from 'utils/formatters'
|
||||
import { DISPLAY_CURRENCY_KEY } from 'constants/localStore'
|
||||
import { DEFAULT_SETTINGS } from 'constants/defaultSettings'
|
||||
import useLocalStorage from 'hooks/useLocalStorage'
|
||||
|
||||
interface Props {
|
||||
data: Account
|
||||
}
|
||||
|
||||
export const AccountBalancesTable = (props: Props) => {
|
||||
const displayCurrency = useStore((s) => s.displayCurrency)
|
||||
const [displayCurrency] = useLocalStorage<string>(
|
||||
DISPLAY_CURRENCY_KEY,
|
||||
DEFAULT_SETTINGS.displayCurrency,
|
||||
)
|
||||
const { data: prices } = usePrices()
|
||||
|
||||
const [sorting, setSorting] = React.useState<SortingState>([])
|
||||
@ -104,13 +109,12 @@ export const AccountBalancesTable = (props: Props) => {
|
||||
return (
|
||||
<FormattedNumber
|
||||
className='text-right text-xs'
|
||||
amount={BN(
|
||||
demagnify(
|
||||
row.original.amount,
|
||||
ASSETS.find((asset) => asset.denom === row.original.denom) ?? ASSETS[0],
|
||||
),
|
||||
amount={demagnify(
|
||||
row.original.amount,
|
||||
ASSETS.find((asset) => asset.denom === row.original.denom) ?? ASSETS[0],
|
||||
)}
|
||||
options={{ maxDecimals: 4 }}
|
||||
animate
|
||||
/>
|
||||
)
|
||||
},
|
||||
@ -123,8 +127,9 @@ export const AccountBalancesTable = (props: Props) => {
|
||||
return (
|
||||
<FormattedNumber
|
||||
className='text-xs'
|
||||
amount={BN(row.original.apy)}
|
||||
amount={row.original.apy}
|
||||
options={{ maxDecimals: 2, minDecimals: 2, suffix: '%' }}
|
||||
animate
|
||||
/>
|
||||
)
|
||||
},
|
||||
@ -146,7 +151,7 @@ export const AccountBalancesTable = (props: Props) => {
|
||||
|
||||
return (
|
||||
<table className='w-full'>
|
||||
<thead className='border-b border-b-white/5'>
|
||||
<thead className='border-b border-white/5'>
|
||||
{table.getHeaderGroups().map((headerGroup) => (
|
||||
<tr key={headerGroup.id}>
|
||||
{headerGroup.headers.map((header, index) => {
|
||||
|
@ -89,12 +89,13 @@ function Item(props: ItemProps) {
|
||||
{props.title}
|
||||
</Text>
|
||||
</div>
|
||||
<div className='flex flex-grow items-center justify-end gap-2'>
|
||||
<div className='flex flex-1 items-center justify-end gap-2'>
|
||||
{props.isPercentage ? (
|
||||
<FormattedNumber
|
||||
amount={props.current}
|
||||
amount={props.current.toNumber()}
|
||||
options={{ suffix: '%', minDecimals: 2, maxDecimals: 2 }}
|
||||
className='text-sm'
|
||||
animate
|
||||
/>
|
||||
) : (
|
||||
<DisplayCurrency
|
||||
@ -109,9 +110,10 @@ function Item(props: ItemProps) {
|
||||
</span>
|
||||
{props.isPercentage ? (
|
||||
<FormattedNumber
|
||||
amount={props.change}
|
||||
amount={props.change.toNumber()}
|
||||
options={{ suffix: '%', minDecimals: 2, maxDecimals: 2 }}
|
||||
className={classNames('text-sm', increase ? 'text-profit' : 'text-loss')}
|
||||
animate
|
||||
/>
|
||||
) : (
|
||||
<DisplayCurrency
|
||||
|
@ -2,7 +2,9 @@ import classNames from 'classnames'
|
||||
|
||||
import { Heart } from 'components/Icons'
|
||||
import Text from 'components/Text'
|
||||
import useStore from 'store'
|
||||
import { DEFAULT_SETTINGS } from 'constants/defaultSettings'
|
||||
import { REDUCE_MOTION_KEY } from 'constants/localStore'
|
||||
import useLocalStorage from 'hooks/useLocalStorage'
|
||||
|
||||
interface Props {
|
||||
health: number
|
||||
@ -11,7 +13,7 @@ interface Props {
|
||||
}
|
||||
|
||||
export default function AccountHealth(props: Props) {
|
||||
const enableAnimations = useStore((s) => s.enableAnimations)
|
||||
const [reduceMotion] = useLocalStorage<boolean>(REDUCE_MOTION_KEY, DEFAULT_SETTINGS.reduceMotion)
|
||||
const healthBarWidth = (props.health / 100) * 53
|
||||
|
||||
return (
|
||||
@ -33,7 +35,7 @@ export default function AccountHealth(props: Props) {
|
||||
rx='2'
|
||||
fill='url(#bar)'
|
||||
style={{
|
||||
transition: enableAnimations ? 'width 1s ease' : 'none',
|
||||
transition: reduceMotion ? 'none' : 'width 1s ease',
|
||||
}}
|
||||
/>
|
||||
<defs>
|
||||
|
@ -11,20 +11,21 @@ import {
|
||||
|
||||
import { FormattedNumber } from 'components/FormattedNumber'
|
||||
import Text from 'components/Text'
|
||||
import useStore from 'store'
|
||||
import { DEFAULT_SETTINGS } from 'constants/defaultSettings'
|
||||
import { REDUCE_MOTION_KEY } from 'constants/localStore'
|
||||
import useLocalStorage from 'hooks/useLocalStorage'
|
||||
import { formatValue } from 'utils/formatters'
|
||||
import { BN } from 'utils/helpers'
|
||||
|
||||
export const RiskChart = ({ data }: RiskChartProps) => {
|
||||
const enableAnimations = useStore((s) => s.enableAnimations)
|
||||
const accountStats = null
|
||||
const [reduceMotion] = useLocalStorage<boolean>(REDUCE_MOTION_KEY, DEFAULT_SETTINGS.reduceMotion)
|
||||
const currentRisk = BN(0)
|
||||
|
||||
return (
|
||||
<div className='flex w-full flex-wrap overflow-hidden py-2'>
|
||||
<FormattedNumber
|
||||
className='px-3 pb-2 text-lg'
|
||||
amount={currentRisk.times(100)}
|
||||
amount={currentRisk.multipliedBy(100).toNumber()}
|
||||
options={{
|
||||
maxDecimals: 0,
|
||||
minDecimals: 0,
|
||||
@ -92,7 +93,7 @@ export const RiskChart = ({ data }: RiskChartProps) => {
|
||||
dataKey='risk'
|
||||
stroke='#FFFFFF'
|
||||
fill='url(#chartGradient)'
|
||||
isAnimationActive={enableAnimations}
|
||||
isAnimationActive={!reduceMotion}
|
||||
/>
|
||||
</AreaChart>
|
||||
</ResponsiveContainer>
|
||||
|
@ -13,8 +13,9 @@ export default function AmountAndValue(props: Props) {
|
||||
<TitleAndSubCell
|
||||
title={
|
||||
<FormattedNumber
|
||||
amount={props.amount}
|
||||
amount={props.amount.toNumber()}
|
||||
options={{ decimals: props.asset.decimals, abbreviated: true }}
|
||||
animate
|
||||
/>
|
||||
}
|
||||
sub={
|
||||
|
@ -1,9 +1,11 @@
|
||||
import classNames from 'classnames'
|
||||
|
||||
import useStore from 'store'
|
||||
import { DEFAULT_SETTINGS } from 'constants/defaultSettings'
|
||||
import { REDUCE_MOTION_KEY } from 'constants/localStore'
|
||||
import useLocalStorage from 'hooks/useLocalStorage'
|
||||
|
||||
export default function Background() {
|
||||
const enableAnimations = useStore((s) => s.enableAnimations)
|
||||
const [reduceMotion] = useLocalStorage<boolean>(REDUCE_MOTION_KEY, DEFAULT_SETTINGS.reduceMotion)
|
||||
|
||||
return (
|
||||
<div className='background pointer-events-none fixed inset-0 h-full w-full overflow-hidden bg-body'>
|
||||
@ -16,7 +18,7 @@ export default function Background() {
|
||||
'left-[-10vw] top-[-10vw]',
|
||||
'bg-orb-primary blur-orb-primary ',
|
||||
'translate-x-0 translate-y-0 rounded-full opacity-20',
|
||||
enableAnimations && 'animate-[float_120s_ease-in-out_infinite_2s]',
|
||||
!reduceMotion && 'animate-[float_120s_ease-in-out_infinite_2s]',
|
||||
)}
|
||||
/>
|
||||
<div
|
||||
@ -28,7 +30,7 @@ export default function Background() {
|
||||
'bottom-[-10vw] right-[-8vw]',
|
||||
'bg-orb-secondary blur-orb-secondary',
|
||||
'translate-x-0 translate-y-0 rounded-full opacity-30',
|
||||
enableAnimations && 'animate-[float_150s_ease-in-out_infinite_1s]',
|
||||
!reduceMotion && 'animate-[float_150s_ease-in-out_infinite_1s]',
|
||||
)}
|
||||
/>
|
||||
<div
|
||||
@ -40,7 +42,7 @@ export default function Background() {
|
||||
'right-[-4vw] top-[-10vw]',
|
||||
'bg-orb-tertiary blur-orb-tertiary ',
|
||||
'translate-x-0 translate-y-0 rounded-full opacity-20',
|
||||
enableAnimations && 'animate-[float_180s_ease-in_infinite]',
|
||||
!reduceMotion && 'animate-[float_180s_ease-in_infinite]',
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
|
@ -1,56 +0,0 @@
|
||||
import { Row } from '@tanstack/react-table'
|
||||
|
||||
import Button from 'components/Button'
|
||||
import useStore from 'store'
|
||||
import { getEnabledMarketAssets } from 'utils/assets'
|
||||
|
||||
type AssetRowProps = {
|
||||
row: Row<BorrowAsset | BorrowAssetActive>
|
||||
onBorrowClick: () => void
|
||||
onRepayClick: () => void
|
||||
resetExpanded: (defaultState?: boolean | undefined) => void
|
||||
}
|
||||
|
||||
export default function AssetExpanded(props: AssetRowProps) {
|
||||
const marketAssets = getEnabledMarketAssets()
|
||||
const asset = marketAssets.find((asset) => asset.denom === props.row.original.denom)
|
||||
let isActive: boolean = false
|
||||
|
||||
if (!asset) return null
|
||||
|
||||
function borrowHandler() {
|
||||
if (!asset) return null
|
||||
useStore.setState({ borrowModal: { asset: asset, marketData: props.row.original } })
|
||||
}
|
||||
|
||||
function repayHandler() {
|
||||
if (!asset) return null
|
||||
useStore.setState({
|
||||
borrowModal: { asset: asset, marketData: props.row.original, isRepay: true },
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<tr
|
||||
key={props.row.id}
|
||||
className='cursor-pointer bg-black/20 transition-colors'
|
||||
onClick={(e) => {
|
||||
e.preventDefault()
|
||||
const isExpanded = props.row.getIsExpanded()
|
||||
props.resetExpanded()
|
||||
!isExpanded && props.row.toggleExpanded()
|
||||
}}
|
||||
>
|
||||
<td colSpan={isActive ? 5 : 4}>
|
||||
<div className='flex justify-end gap-4 p-4'>
|
||||
<Button
|
||||
onClick={borrowHandler}
|
||||
color='primary'
|
||||
text={isActive ? 'Borrow more' : 'Borrow'}
|
||||
/>
|
||||
{isActive && <Button color='primary' text='Repay' onClick={repayHandler} />}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
)
|
||||
}
|
@ -1,40 +0,0 @@
|
||||
import { flexRender, Row } from '@tanstack/react-table'
|
||||
import classNames from 'classnames'
|
||||
|
||||
import { getEnabledMarketAssets } from 'utils/assets'
|
||||
|
||||
type AssetRowProps = {
|
||||
row: Row<BorrowAsset | BorrowAssetActive>
|
||||
resetExpanded: (defaultState?: boolean | undefined) => void
|
||||
}
|
||||
|
||||
export const AssetRow = (props: AssetRowProps) => {
|
||||
const marketAssets = getEnabledMarketAssets()
|
||||
const asset = marketAssets.find((asset) => asset.denom === props.row.original.denom)
|
||||
|
||||
if (!asset) return null
|
||||
|
||||
return (
|
||||
<tr
|
||||
key={props.row.id}
|
||||
className={classNames(
|
||||
'cursor-pointer transition-colors',
|
||||
props.row.getIsExpanded() ? ' bg-black/20' : 'bg-white/0 hover:bg-white/5',
|
||||
)}
|
||||
onClick={(e) => {
|
||||
e.preventDefault()
|
||||
const isExpanded = props.row.getIsExpanded()
|
||||
props.resetExpanded()
|
||||
!isExpanded && props.row.toggleExpanded()
|
||||
}}
|
||||
>
|
||||
{props.row.getVisibleCells().map((cell, index) => {
|
||||
return (
|
||||
<td key={cell.id} className={'p-4 text-right'}>
|
||||
{flexRender(cell.column.columnDef.cell, cell.getContext())}
|
||||
</td>
|
||||
)
|
||||
})}
|
||||
</tr>
|
||||
)
|
||||
}
|
41
src/components/Borrow/BorrowActionButtons.tsx
Normal file
41
src/components/Borrow/BorrowActionButtons.tsx
Normal file
@ -0,0 +1,41 @@
|
||||
import { useCallback } from 'react'
|
||||
|
||||
import Button from 'components/Button'
|
||||
import { Plus } from 'components/Icons'
|
||||
import useStore from 'store'
|
||||
import { getEnabledMarketAssets } from 'utils/assets'
|
||||
|
||||
interface Props {
|
||||
data: BorrowMarketTableData
|
||||
}
|
||||
|
||||
export default function BorrowActionButtons(props: Props) {
|
||||
const { asset, debt } = props.data
|
||||
const marketAssets = getEnabledMarketAssets()
|
||||
const currentAsset = marketAssets.find((a) => a.denom === asset.denom)
|
||||
|
||||
const borrowHandler = useCallback(() => {
|
||||
if (!currentAsset) return null
|
||||
useStore.setState({ borrowModal: { asset: currentAsset, marketData: props.data } })
|
||||
}, [currentAsset, props.data])
|
||||
|
||||
const repayHandler = useCallback(() => {
|
||||
if (!currentAsset) return null
|
||||
useStore.setState({
|
||||
borrowModal: { asset: currentAsset, marketData: props.data, isRepay: true },
|
||||
})
|
||||
}, [currentAsset, props.data])
|
||||
|
||||
return (
|
||||
<div className='flex flex-row space-x-2'>
|
||||
<Button
|
||||
leftIcon={debt ? <Plus /> : undefined}
|
||||
onClick={borrowHandler}
|
||||
color='secondary'
|
||||
text={debt ? 'Borrow more' : 'Borrow'}
|
||||
className='min-w-40 text-center'
|
||||
/>
|
||||
{debt && <Button color='secondary' text='Repay' onClick={repayHandler} />}
|
||||
</div>
|
||||
)
|
||||
}
|
@ -1,42 +1,53 @@
|
||||
import {
|
||||
ColumnDef,
|
||||
flexRender,
|
||||
getCoreRowModel,
|
||||
getSortedRowModel,
|
||||
SortingState,
|
||||
useReactTable,
|
||||
} from '@tanstack/react-table'
|
||||
import classNames from 'classnames'
|
||||
import React from 'react'
|
||||
import { ColumnDef, Row, Table } from '@tanstack/react-table'
|
||||
import { useCallback, useMemo } from 'react'
|
||||
|
||||
import AmountAndValue from 'components/AmountAndValue'
|
||||
import AssetExpanded from 'components/Borrow/AssetExpanded'
|
||||
import { AssetRow } from 'components/Borrow/AssetRow'
|
||||
import { ChevronDown, SortAsc, SortDesc, SortNone } from 'components/Icons'
|
||||
import AssetImage from 'components/AssetImage'
|
||||
import BorrowActionButtons from 'components/Borrow/BorrowActionButtons'
|
||||
import { FormattedNumber } from 'components/FormattedNumber'
|
||||
import { ChevronDown, ChevronUp } from 'components/Icons'
|
||||
import Loading from 'components/Loading'
|
||||
import Text from 'components/Text'
|
||||
import AssetListTable from 'components/MarketAssetTable'
|
||||
import MarketAssetTableRow from 'components/MarketAssetTable/MarketAssetTableRow'
|
||||
import MarketDetails from 'components/MarketAssetTable/MarketDetails'
|
||||
import TitleAndSubCell from 'components/TitleAndSubCell'
|
||||
import { getEnabledMarketAssets } from 'utils/assets'
|
||||
import { formatPercent } from 'utils/formatters'
|
||||
import AssetImage from 'components/AssetImage'
|
||||
import { BN } from 'utils/helpers'
|
||||
|
||||
type Props = {
|
||||
data: BorrowAsset[] | BorrowAssetActive[]
|
||||
interface Props {
|
||||
title: string
|
||||
data: BorrowMarketTableData[]
|
||||
}
|
||||
|
||||
export const BorrowTable = (props: Props) => {
|
||||
const [sorting, setSorting] = React.useState<SortingState>([])
|
||||
export default function BorrowTable(props: Props) {
|
||||
const { title, data } = props
|
||||
const shouldShowAccountBorrowed = !!data[0]?.debt
|
||||
const marketAssets = getEnabledMarketAssets()
|
||||
|
||||
const columns = React.useMemo<ColumnDef<BorrowAsset | BorrowAssetActive>[]>(
|
||||
const rowRenderer = useCallback(
|
||||
(row: Row<BorrowMarketTableData>, table: Table<BorrowMarketTableData>) => {
|
||||
return (
|
||||
<MarketAssetTableRow
|
||||
key={`borrow-asset-${row.id}`}
|
||||
isExpanded={row.getIsExpanded()}
|
||||
resetExpanded={table.resetExpanded}
|
||||
rowData={row}
|
||||
expandedActionButtons={<BorrowActionButtons data={row.original} />}
|
||||
expandedDetails={<MarketDetails data={row.original} />}
|
||||
/>
|
||||
)
|
||||
},
|
||||
[],
|
||||
)
|
||||
|
||||
const columns = useMemo<ColumnDef<BorrowMarketTableData>[]>(
|
||||
() => [
|
||||
{
|
||||
accessorKey: 'asset.name',
|
||||
header: 'Asset',
|
||||
id: 'symbol',
|
||||
cell: ({ row }) => {
|
||||
const asset = marketAssets.find((asset) => asset.denom === row.original.denom)
|
||||
|
||||
if (!asset) return null
|
||||
const asset = row.original.asset
|
||||
|
||||
return (
|
||||
<div className='flex flex-1 items-center gap-3'>
|
||||
@ -50,6 +61,22 @@ export const BorrowTable = (props: Props) => {
|
||||
)
|
||||
},
|
||||
},
|
||||
...(shouldShowAccountBorrowed
|
||||
? [
|
||||
{
|
||||
accessorKey: 'debt',
|
||||
header: 'Borrowed',
|
||||
cell: (info: any) => {
|
||||
const borrowAsset = info.row.original as BorrowMarketTableData
|
||||
const asset = marketAssets.find((asset) => asset.denom === borrowAsset.asset.denom)
|
||||
|
||||
if (!asset) return null
|
||||
|
||||
return <AmountAndValue asset={asset} amount={borrowAsset?.debt ?? BN(0)} />
|
||||
},
|
||||
},
|
||||
]
|
||||
: []),
|
||||
{
|
||||
accessorKey: 'borrowRate',
|
||||
header: 'Borrow Rate',
|
||||
@ -59,136 +86,44 @@ export const BorrowTable = (props: Props) => {
|
||||
}
|
||||
|
||||
return (
|
||||
<Text className='justify-end' size='sm'>
|
||||
{formatPercent(row.original.borrowRate, 2)}
|
||||
</Text>
|
||||
<FormattedNumber
|
||||
className='justify-end text-xs'
|
||||
amount={row.original.borrowRate * 100}
|
||||
options={{ minDecimals: 2, maxDecimals: 2, suffix: '%' }}
|
||||
animate
|
||||
/>
|
||||
)
|
||||
},
|
||||
},
|
||||
...((props.data[0] as BorrowAssetActive)?.debt
|
||||
? [
|
||||
{
|
||||
accessorKey: 'debt',
|
||||
header: 'Borrowed',
|
||||
cell: (info: any) => {
|
||||
const borrowAsset = info.row.original as BorrowAssetActive
|
||||
const asset = marketAssets.find((asset) => asset.denom === borrowAsset.denom)
|
||||
|
||||
if (!asset) return null
|
||||
|
||||
return <AmountAndValue asset={asset} amount={borrowAsset.debt} />
|
||||
},
|
||||
},
|
||||
]
|
||||
: []),
|
||||
{
|
||||
accessorKey: 'liquidity',
|
||||
header: 'Liquidity Available',
|
||||
cell: ({ row }) => {
|
||||
const asset = marketAssets.find((asset) => asset.denom === row.original.denom)
|
||||
const { liquidity, asset: borrowAsset } = row.original
|
||||
const asset = marketAssets.find((asset) => asset.denom === borrowAsset.denom)
|
||||
|
||||
if (!asset) return null
|
||||
|
||||
if (row.original.liquidity === null) {
|
||||
if (liquidity === null) {
|
||||
return <Loading />
|
||||
}
|
||||
|
||||
return <AmountAndValue asset={asset} amount={row.original.liquidity.amount} />
|
||||
return <AmountAndValue asset={asset} amount={liquidity.amount ?? BN(0)} />
|
||||
},
|
||||
},
|
||||
{
|
||||
accessorKey: 'status',
|
||||
accessorKey: 'manage',
|
||||
enableSorting: false,
|
||||
header: 'Manage',
|
||||
width: 150,
|
||||
cell: ({ row }) => (
|
||||
<div className='flex items-center justify-end'>
|
||||
<div className={classNames('w-4', row.getIsExpanded() && 'rotate-180')}>
|
||||
<ChevronDown />
|
||||
</div>
|
||||
<div className='w-4'>{row.getIsExpanded() ? <ChevronUp /> : <ChevronDown />}</div>
|
||||
</div>
|
||||
),
|
||||
},
|
||||
],
|
||||
[marketAssets, props.data],
|
||||
[shouldShowAccountBorrowed, marketAssets],
|
||||
)
|
||||
|
||||
const table = useReactTable({
|
||||
data: props.data,
|
||||
columns,
|
||||
state: {
|
||||
sorting,
|
||||
},
|
||||
onSortingChange: setSorting,
|
||||
getCoreRowModel: getCoreRowModel(),
|
||||
getSortedRowModel: getSortedRowModel(),
|
||||
})
|
||||
|
||||
return (
|
||||
<table className='w-full'>
|
||||
<thead className='bg-black/20'>
|
||||
{table.getHeaderGroups().map((headerGroup) => (
|
||||
<tr key={headerGroup.id}>
|
||||
{headerGroup.headers.map((header, index) => {
|
||||
return (
|
||||
<th
|
||||
key={header.id}
|
||||
onClick={header.column.getToggleSortingHandler()}
|
||||
className={classNames(
|
||||
'px-4 py-3',
|
||||
header.column.getCanSort() && 'cursor-pointer',
|
||||
header.id === 'symbol' ? 'text-left' : 'text-right',
|
||||
)}
|
||||
>
|
||||
<div
|
||||
className={classNames(
|
||||
'flex',
|
||||
header.id === 'symbol' ? 'justify-start' : 'justify-end',
|
||||
'align-center',
|
||||
)}
|
||||
>
|
||||
<span className='h-6 w-6 text-white'>
|
||||
{header.column.getCanSort()
|
||||
? {
|
||||
asc: <SortAsc />,
|
||||
desc: <SortDesc />,
|
||||
false: <SortNone />,
|
||||
}[header.column.getIsSorted() as string] ?? null
|
||||
: null}
|
||||
</span>
|
||||
<Text
|
||||
tag='span'
|
||||
size='sm'
|
||||
className='flex items-center font-normal text-white/40'
|
||||
>
|
||||
{flexRender(header.column.columnDef.header, header.getContext())}
|
||||
</Text>
|
||||
</div>
|
||||
</th>
|
||||
)
|
||||
})}
|
||||
</tr>
|
||||
))}
|
||||
</thead>
|
||||
<tbody>
|
||||
{table.getRowModel().rows.map((row) => {
|
||||
if (row.getIsExpanded()) {
|
||||
return (
|
||||
<React.Fragment key={`${row.id}_subrow`}>
|
||||
<AssetRow key={`${row.id}_asset`} row={row} resetExpanded={table.resetExpanded} />
|
||||
<AssetExpanded
|
||||
key={`${row.id}_expanded`}
|
||||
row={row}
|
||||
onBorrowClick={() => {}}
|
||||
onRepayClick={() => {}}
|
||||
resetExpanded={table.resetExpanded}
|
||||
/>
|
||||
</React.Fragment>
|
||||
)
|
||||
}
|
||||
return <AssetRow key={row.index} row={row} resetExpanded={table.resetExpanded} />
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
)
|
||||
return <AssetListTable title={title} rowRenderer={rowRenderer} columns={columns} data={data} />
|
||||
}
|
||||
|
@ -1,90 +0,0 @@
|
||||
import { Suspense, useMemo } from 'react'
|
||||
import { useParams } from 'react-router-dom'
|
||||
|
||||
import Card from 'components/Card'
|
||||
import { getEnabledMarketAssets } from 'utils/assets'
|
||||
import { BorrowTable } from 'components/Borrow/BorrowTable'
|
||||
import useAccountDebts from 'hooks/useAccountDebts'
|
||||
import useMarketBorrowings from 'hooks/useMarketBorrowings'
|
||||
|
||||
interface Props {
|
||||
type: 'active' | 'available'
|
||||
}
|
||||
|
||||
function Content(props: Props) {
|
||||
const { accountId } = useParams()
|
||||
const { data: debtData } = useAccountDebts(accountId)
|
||||
const { data: borrowData } = useMarketBorrowings()
|
||||
|
||||
const marketAssets = getEnabledMarketAssets()
|
||||
|
||||
const { available, active } = useMemo(
|
||||
() =>
|
||||
marketAssets.reduce(
|
||||
(prev: { available: BorrowAsset[]; active: BorrowAssetActive[] }, curr) => {
|
||||
const borrow = borrowData.find((borrow) => borrow.denom === curr.denom)
|
||||
if (borrow) {
|
||||
const debt = debtData?.find((debt) => debt.denom === curr.denom)
|
||||
if (debt) {
|
||||
prev.active.push({
|
||||
...borrow,
|
||||
debt: debt.amount,
|
||||
})
|
||||
} else {
|
||||
prev.available.push(borrow)
|
||||
}
|
||||
}
|
||||
return prev
|
||||
},
|
||||
{ available: [], active: [] },
|
||||
),
|
||||
[marketAssets, borrowData, debtData],
|
||||
)
|
||||
|
||||
const assets = props.type === 'active' ? active : available
|
||||
|
||||
if (!assets.length) return null
|
||||
|
||||
if (props.type === 'active') {
|
||||
return (
|
||||
<Card
|
||||
className='mb-4 h-fit w-full bg-white/5'
|
||||
title={props.type === 'active' ? 'Borrowings' : 'Available to borrow'}
|
||||
>
|
||||
<BorrowTable data={assets} />
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
|
||||
return <BorrowTable data={assets} />
|
||||
}
|
||||
|
||||
function Fallback() {
|
||||
const marketAssets = getEnabledMarketAssets()
|
||||
|
||||
const available: BorrowAsset[] = marketAssets.reduce((prev: BorrowAsset[], curr) => {
|
||||
prev.push({ ...curr, borrowRate: null, liquidity: null })
|
||||
|
||||
return prev
|
||||
}, [])
|
||||
|
||||
return <BorrowTable data={available} />
|
||||
}
|
||||
|
||||
export function AvailableBorrowings() {
|
||||
return (
|
||||
<Card className='h-fit w-full bg-white/5' title={'Available to borrow'}>
|
||||
<Suspense fallback={<Fallback />}>
|
||||
<Content type='available' />
|
||||
</Suspense>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
|
||||
export function ActiveBorrowings() {
|
||||
return (
|
||||
<Suspense fallback={null}>
|
||||
<Content type='active' />
|
||||
</Suspense>
|
||||
)
|
||||
}
|
@ -4,8 +4,9 @@ import { useEffect, useState } from 'react'
|
||||
import { FormattedNumber } from 'components/FormattedNumber'
|
||||
import Text from 'components/Text'
|
||||
import { Tooltip } from 'components/Tooltip'
|
||||
import useStore from 'store'
|
||||
import { BN } from 'utils/helpers'
|
||||
import { DEFAULT_SETTINGS } from 'constants/defaultSettings'
|
||||
import { REDUCE_MOTION_KEY } from 'constants/localStore'
|
||||
import useLocalStorage from 'hooks/useLocalStorage'
|
||||
|
||||
interface Props {
|
||||
balance: number
|
||||
@ -30,8 +31,7 @@ export const BorrowCapacity = ({
|
||||
hideValues,
|
||||
decimals = 2,
|
||||
}: Props) => {
|
||||
const enableAnimations = useStore((s) => s.enableAnimations)
|
||||
|
||||
const [reduceMotion] = useLocalStorage<boolean>(REDUCE_MOTION_KEY, DEFAULT_SETTINGS.reduceMotion)
|
||||
const [percentOfMaxRound, setPercentOfMaxRound] = useState(0)
|
||||
const [percentOfMaxRange, setPercentOfMaxRange] = useState(0)
|
||||
const [limitPercentOfMax, setLimitPercentOfMax] = useState(0)
|
||||
@ -64,12 +64,12 @@ export const BorrowCapacity = ({
|
||||
{!hideValues && (
|
||||
<div
|
||||
className={classNames(
|
||||
enableAnimations && 'duration-800 transition-[opcity] delay-[1600ms]',
|
||||
!reduceMotion && 'duration-800 transition-[opcity] delay-[1600ms]',
|
||||
'text-3xs-caps',
|
||||
limitPercentOfMax ? 'opacity-50' : 'opacity-0',
|
||||
)}
|
||||
>
|
||||
<FormattedNumber animate amount={BN(limit)} />
|
||||
<FormattedNumber animate amount={limit} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
@ -83,7 +83,7 @@ export const BorrowCapacity = ({
|
||||
<div
|
||||
className={classNames(
|
||||
'absolute left-0 h-full max-w-full rounded-l-3xl bg-body-dark',
|
||||
enableAnimations && 'transition-[right] duration-1000 ease-linear',
|
||||
!reduceMotion && 'transition-[right] duration-1000 ease-linear',
|
||||
)}
|
||||
style={{
|
||||
right: `${limitPercentOfMax ? 100 - limitPercentOfMax : 100}%`,
|
||||
@ -94,7 +94,7 @@ export const BorrowCapacity = ({
|
||||
<div
|
||||
className={classNames(
|
||||
'h-full rounded-lg',
|
||||
enableAnimations && 'transition-[width] duration-1000 ease-linear',
|
||||
!reduceMotion && 'transition-[width] duration-1000 ease-linear',
|
||||
)}
|
||||
style={{
|
||||
width: `${percentOfMaxRange || 0.02}%`,
|
||||
@ -107,7 +107,7 @@ export const BorrowCapacity = ({
|
||||
<div
|
||||
className={classNames(
|
||||
'absolute bottom-0 h-[120%] w-[1px] bg-white',
|
||||
enableAnimations && 'transition-[left] duration-1000 ease-linear',
|
||||
!reduceMotion && 'transition-[left] duration-1000 ease-linear',
|
||||
)}
|
||||
style={{ left: `${limitPercentOfMax || 0}%` }}
|
||||
/>
|
||||
@ -115,7 +115,7 @@ export const BorrowCapacity = ({
|
||||
<span
|
||||
className={classNames(
|
||||
'absolute top-1/2 mt-[1px] w-full -translate-y-1/2 text-center text-2xs-caps',
|
||||
enableAnimations && 'animate-fadein opacity-0',
|
||||
!reduceMotion && 'animate-fadein opacity-0',
|
||||
)}
|
||||
>
|
||||
{max !== 0 && (
|
||||
@ -127,7 +127,7 @@ export const BorrowCapacity = ({
|
||||
maxDecimals: decimals,
|
||||
suffix: '%',
|
||||
}}
|
||||
amount={BN(percentOfMaxRound)}
|
||||
amount={percentOfMaxRound}
|
||||
/>
|
||||
)}
|
||||
</span>
|
||||
@ -139,9 +139,9 @@ export const BorrowCapacity = ({
|
||||
</Tooltip>
|
||||
{!hideValues && (
|
||||
<div className='mt-2 flex opacity-50 text-3xs-caps'>
|
||||
<FormattedNumber animate amount={BN(balance)} className='mr-1' />
|
||||
<FormattedNumber animate amount={balance} className='mr-1' />
|
||||
<span className='mr-1'>of</span>
|
||||
<FormattedNumber animate amount={BN(max)} />
|
||||
<FormattedNumber animate amount={max} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
@ -55,6 +55,7 @@ export const buttonVariantClasses = {
|
||||
solid: 'rounded-sm text-white shadow-button justify-center group',
|
||||
transparent: 'rounded-sm bg-transparent p-0 transition duration-200 ease-in',
|
||||
round: 'rounded-full p-0',
|
||||
rounded: 'rounded-2xl',
|
||||
}
|
||||
|
||||
export const circularProgressSize = {
|
||||
|
@ -16,7 +16,9 @@ import {
|
||||
import { glowElement } from 'components/Button/utils'
|
||||
import { CircularProgress } from 'components/CircularProgress'
|
||||
import { ChevronDown } from 'components/Icons'
|
||||
import useStore from 'store'
|
||||
import { DEFAULT_SETTINGS } from 'constants/defaultSettings'
|
||||
import { REDUCE_MOTION_KEY } from 'constants/localStore'
|
||||
import useLocalStorage from 'hooks/useLocalStorage'
|
||||
|
||||
interface Props {
|
||||
children?: string | ReactNode
|
||||
@ -27,7 +29,7 @@ interface Props {
|
||||
showProgressIndicator?: boolean
|
||||
size?: 'xs' | 'sm' | 'md' | 'lg'
|
||||
text?: string | ReactNode
|
||||
variant?: 'solid' | 'transparent' | 'round'
|
||||
variant?: 'solid' | 'transparent' | 'round' | 'rounded'
|
||||
onClick?: (e: React.MouseEvent<HTMLButtonElement>) => void
|
||||
leftIcon?: ReactElement
|
||||
rightIcon?: ReactElement
|
||||
@ -36,6 +38,7 @@ interface Props {
|
||||
hasFocus?: boolean
|
||||
dataTestId?: string
|
||||
tabIndex?: number
|
||||
textClassNames?: string
|
||||
}
|
||||
|
||||
const Button = React.forwardRef(function Button(
|
||||
@ -57,13 +60,14 @@ const Button = React.forwardRef(function Button(
|
||||
hasFocus,
|
||||
dataTestId,
|
||||
tabIndex = 0,
|
||||
textClassNames,
|
||||
}: Props,
|
||||
ref,
|
||||
) {
|
||||
const enableAnimations = useStore((s) => s.enableAnimations)
|
||||
const [reduceMotion] = useLocalStorage<boolean>(REDUCE_MOTION_KEY, DEFAULT_SETTINGS.reduceMotion)
|
||||
const isDisabled = disabled || showProgressIndicator
|
||||
const shouldShowText = text && !children
|
||||
const shouldShowGlowElement = variant === 'solid' && !isDisabled
|
||||
const shouldShowGlowElement = variant === 'solid' && !isDisabled && !reduceMotion
|
||||
|
||||
const buttonClassNames = useMemo(() => {
|
||||
const buttonClasses = [
|
||||
@ -82,7 +86,7 @@ const Button = React.forwardRef(function Button(
|
||||
'relative z-1 flex items-center',
|
||||
'cursor-pointer appearance-none break-normal outline-none',
|
||||
'text-white transition-all',
|
||||
enableAnimations && 'transition-color',
|
||||
!reduceMotion && 'transition-color',
|
||||
buttonClasses,
|
||||
buttonVariantClasses[variant],
|
||||
variant === 'solid' && color === 'tertiary' && buttonBorderClasses,
|
||||
@ -91,7 +95,7 @@ const Button = React.forwardRef(function Button(
|
||||
hasFocus && focusClasses[color],
|
||||
className,
|
||||
)
|
||||
}, [className, color, enableAnimations, hasFocus, isDisabled, size, variant])
|
||||
}, [className, color, reduceMotion, hasFocus, isDisabled, size, variant])
|
||||
|
||||
const [leftIconClassNames, rightIconClassNames] = useMemo(() => {
|
||||
const hasContent = !!(text || children)
|
||||
@ -116,7 +120,7 @@ const Button = React.forwardRef(function Button(
|
||||
) : (
|
||||
<>
|
||||
{leftIcon && <span className={classNames(leftIconClassNames)}>{leftIcon}</span>}
|
||||
{shouldShowText && <span>{text}</span>}
|
||||
{shouldShowText && <span className={textClassNames}>{text}</span>}
|
||||
{children && children}
|
||||
{rightIcon && <span className={classNames(rightIconClassNames)}>{rightIcon}</span>}
|
||||
{hasSubmenu && (
|
||||
@ -126,7 +130,7 @@ const Button = React.forwardRef(function Button(
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
{shouldShowGlowElement && glowElement(enableAnimations)}
|
||||
{shouldShowGlowElement && glowElement(!reduceMotion)}
|
||||
</button>
|
||||
)
|
||||
})
|
||||
|
@ -17,7 +17,7 @@ export default function Card(props: Props) {
|
||||
id={props.id}
|
||||
className={classNames(
|
||||
props.className,
|
||||
'relative isolate max-w-full overflow-hidden rounded-base',
|
||||
'relative isolate max-w-full rounded-base',
|
||||
'before:content-[" "] before:absolute before:inset-0 before:-z-1 before:rounded-base before:p-[1px] before:border-glas',
|
||||
)}
|
||||
>
|
||||
|
@ -1,7 +1,9 @@
|
||||
import classNames from 'classnames'
|
||||
|
||||
import Text from 'components/Text'
|
||||
import useStore from 'store'
|
||||
import { DEFAULT_SETTINGS } from 'constants/defaultSettings'
|
||||
import { REDUCE_MOTION_KEY } from 'constants/localStore'
|
||||
import useLocalStorage from 'hooks/useLocalStorage'
|
||||
|
||||
interface Props {
|
||||
color?: string
|
||||
@ -10,15 +12,14 @@ interface Props {
|
||||
}
|
||||
|
||||
export const CircularProgress = ({ color = '#FFFFFF', size = 20, className }: Props) => {
|
||||
const enableAnimations = useStore((s) => s.enableAnimations)
|
||||
|
||||
const [reduceMotion] = useLocalStorage<boolean>(REDUCE_MOTION_KEY, DEFAULT_SETTINGS.reduceMotion)
|
||||
const borderWidth = `${size / 10}px`
|
||||
const borderColor = `${color} transparent transparent transparent`
|
||||
const loaderClasses = classNames('inline-block relative', className)
|
||||
const elementClasses =
|
||||
'block absolute w-4/5 h-4/5 m-[10%] rounded-full animate-progress border-solid'
|
||||
|
||||
if (!enableAnimations)
|
||||
if (reduceMotion)
|
||||
return (
|
||||
<div
|
||||
className={classNames('flex items-center', loaderClasses)}
|
||||
|
@ -1,7 +1,12 @@
|
||||
import { useMemo } from 'react'
|
||||
|
||||
import { FormattedNumber } from 'components/FormattedNumber'
|
||||
import { DEFAULT_SETTINGS } from 'constants/defaultSettings'
|
||||
import { DISPLAY_CURRENCY_KEY } from 'constants/localStore'
|
||||
import useLocalStorage from 'hooks/useLocalStorage'
|
||||
import usePrices from 'hooks/usePrices'
|
||||
import useStore from 'store'
|
||||
import { BNCoin } from 'types/classes/BNCoin'
|
||||
import { getDisplayCurrencies } from 'utils/assets'
|
||||
import { convertToDisplayAmount } from 'utils/formatters'
|
||||
|
||||
interface Props {
|
||||
@ -11,22 +16,33 @@ interface Props {
|
||||
}
|
||||
|
||||
export default function DisplayCurrency(props: Props) {
|
||||
const displayCurrency = useStore((s) => s.displayCurrency)
|
||||
const displayCurrencies = getDisplayCurrencies()
|
||||
const [displayCurrency] = useLocalStorage<string>(
|
||||
DISPLAY_CURRENCY_KEY,
|
||||
DEFAULT_SETTINGS.displayCurrency,
|
||||
)
|
||||
const { data: prices } = usePrices()
|
||||
|
||||
const displayCurrencyAsset = useMemo(
|
||||
() =>
|
||||
displayCurrencies.find((asset) => asset.denom === displayCurrency) ?? displayCurrencies[0],
|
||||
[displayCurrency, displayCurrencies],
|
||||
)
|
||||
|
||||
return (
|
||||
<FormattedNumber
|
||||
className={props.className}
|
||||
amount={convertToDisplayAmount(props.coin, displayCurrency, prices)}
|
||||
amount={convertToDisplayAmount(props.coin, displayCurrency, prices).toNumber()}
|
||||
options={{
|
||||
minDecimals: 0,
|
||||
maxDecimals: 2,
|
||||
abbreviated: true,
|
||||
prefix: `${props.isApproximation ? '~ ' : ''}${
|
||||
displayCurrency.prefix ? displayCurrency.prefix : ''
|
||||
displayCurrencyAsset.prefix ? displayCurrencyAsset.prefix : ''
|
||||
}`,
|
||||
suffix: displayCurrency.symbol ? ` ${displayCurrency.symbol}` : '',
|
||||
suffix: displayCurrencyAsset.symbol ? ` ${displayCurrencyAsset.symbol}` : '',
|
||||
}}
|
||||
animate
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { Suspense } from 'react'
|
||||
|
||||
import Card from 'components/Card'
|
||||
import VaultCard from 'components/Earn/vault/VaultCard'
|
||||
import VaultCard from 'components/Earn/Farm/VaultCard'
|
||||
import useVaults from 'hooks/useVaults'
|
||||
|
||||
function Content() {
|
@ -1,5 +1,5 @@
|
||||
import Button from 'components/Button'
|
||||
import VaultLogo from 'components/Earn/vault/VaultLogo'
|
||||
import VaultLogo from 'components/Earn/Farm/VaultLogo'
|
||||
import Text from 'components/Text'
|
||||
import TitleAndSubCell from 'components/TitleAndSubCell'
|
||||
import useCurrentAccount from 'hooks/useCurrentAccount'
|
@ -12,9 +12,10 @@ import React from 'react'
|
||||
|
||||
import Button from 'components/Button'
|
||||
import DisplayCurrency from 'components/DisplayCurrency'
|
||||
import VaultExpanded from 'components/Earn/vault/VaultExpanded'
|
||||
import VaultLogo from 'components/Earn/vault/VaultLogo'
|
||||
import { VaultRow } from 'components/Earn/vault/VaultRow'
|
||||
import VaultExpanded from 'components/Earn/Farm/VaultExpanded'
|
||||
import VaultLogo from 'components/Earn/Farm/VaultLogo'
|
||||
import { VaultRow } from 'components/Earn/Farm/VaultRow'
|
||||
import { FormattedNumber } from 'components/FormattedNumber'
|
||||
import { ChevronDown, SortAsc, SortDesc, SortNone } from 'components/Icons'
|
||||
import Loading from 'components/Loading'
|
||||
import Text from 'components/Text'
|
||||
@ -24,7 +25,7 @@ import useStore from 'store'
|
||||
import { BNCoin } from 'types/classes/BNCoin'
|
||||
import { VaultStatus } from 'types/enums/vault'
|
||||
import { getAssetByDenom } from 'utils/assets'
|
||||
import { convertPercentage, formatPercent, formatValue, produceCountdown } from 'utils/formatters'
|
||||
import { produceCountdown } from 'utils/formatters'
|
||||
|
||||
type Props = {
|
||||
data: Vault[] | DepositedVault[]
|
||||
@ -116,9 +117,15 @@ export const VaultTable = (props: Props) => {
|
||||
accessorKey: 'apy',
|
||||
header: 'APY',
|
||||
cell: ({ row }) => {
|
||||
if (row.original.apy === null) return <Loading />
|
||||
const vault = row.original as DepositedVault
|
||||
if (vault.apy === null) return <Loading />
|
||||
return (
|
||||
<Text size='xs'>{row.original.apy ? formatPercent(row.original.apy, 2) : '-'}</Text>
|
||||
<FormattedNumber
|
||||
amount={(vault.apy ?? 0) * 100}
|
||||
options={{ minDecimals: 2, maxDecimals: 2, suffix: '%' }}
|
||||
className='text-xs'
|
||||
animate
|
||||
/>
|
||||
)
|
||||
},
|
||||
},
|
||||
@ -126,10 +133,11 @@ export const VaultTable = (props: Props) => {
|
||||
accessorKey: 'tvl',
|
||||
header: 'TVL',
|
||||
cell: ({ row }) => {
|
||||
const vault = row.original as DepositedVault
|
||||
if (props.isLoading) return <Loading />
|
||||
const coin = new BNCoin({
|
||||
denom: row.original.cap.denom,
|
||||
amount: row.original.cap.used.toString(),
|
||||
denom: vault.cap.denom,
|
||||
amount: vault.cap.used.toString(),
|
||||
})
|
||||
|
||||
return <DisplayCurrency coin={coin} className='text-xs' />
|
||||
@ -139,24 +147,33 @@ export const VaultTable = (props: Props) => {
|
||||
accessorKey: 'cap',
|
||||
header: 'Depo. Cap',
|
||||
cell: ({ row }) => {
|
||||
const vault = row.original as DepositedVault
|
||||
if (props.isLoading) return <Loading />
|
||||
const percent = vault.cap.used
|
||||
.dividedBy(vault.cap.max.multipliedBy(VAULT_DEPOSIT_BUFFER))
|
||||
.multipliedBy(100)
|
||||
.integerValue()
|
||||
|
||||
const percent = convertPercentage(
|
||||
row.original.cap.used
|
||||
.div(row.original.cap.max.times(VAULT_DEPOSIT_BUFFER))
|
||||
.times(100)
|
||||
.integerValue()
|
||||
.toNumber(),
|
||||
)
|
||||
const decimals = getAssetByDenom(row.original.cap.denom)?.decimals ?? 6
|
||||
const decimals = getAssetByDenom(vault.cap.denom)?.decimals ?? 6
|
||||
|
||||
return (
|
||||
<TitleAndSubCell
|
||||
title={formatValue(row.original.cap.max.integerValue().toNumber(), {
|
||||
abbreviated: true,
|
||||
decimals,
|
||||
})}
|
||||
sub={`${percent}% Filled`}
|
||||
title={
|
||||
<FormattedNumber
|
||||
amount={vault.cap.max.toNumber()}
|
||||
options={{ minDecimals: 2, abbreviated: true, decimals }}
|
||||
className='text-xs'
|
||||
animate
|
||||
/>
|
||||
}
|
||||
sub={
|
||||
<FormattedNumber
|
||||
amount={percent.toNumber()}
|
||||
options={{ minDecimals: 2, maxDecimals: 2, suffix: '% Filled' }}
|
||||
className='text-xs'
|
||||
animate
|
||||
/>
|
||||
}
|
||||
/>
|
||||
)
|
||||
},
|
||||
@ -170,18 +187,17 @@ export const VaultTable = (props: Props) => {
|
||||
return 'Deposit'
|
||||
},
|
||||
cell: ({ row }) => {
|
||||
const vault = row.original as DepositedVault
|
||||
function enterVaultHandler() {
|
||||
useStore.setState({
|
||||
vaultModal: {
|
||||
vault: row.original,
|
||||
selectedBorrowDenoms: [row.original.denoms.secondary],
|
||||
vault,
|
||||
selectedBorrowDenoms: [vault.denoms.secondary],
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
if (props.isLoading) return <Loading />
|
||||
const vault = row.original as DepositedVault
|
||||
|
||||
return (
|
||||
<div className='flex items-center justify-end'>
|
||||
{vault.status ? (
|
@ -2,8 +2,8 @@ import { Suspense, useMemo } from 'react'
|
||||
import { useParams } from 'react-router-dom'
|
||||
|
||||
import Card from 'components/Card'
|
||||
import { VaultTable } from 'components/Earn/vault/VaultTable'
|
||||
import VaultUnlockBanner from 'components/Earn/vault/VaultUnlockBanner'
|
||||
import { VaultTable } from 'components/Earn/Farm/VaultTable'
|
||||
import VaultUnlockBanner from 'components/Earn/Farm/VaultUnlockBanner'
|
||||
import { IS_TESTNET } from 'constants/env'
|
||||
import { TESTNET_VAULTS_META_DATA, VAULTS_META_DATA } from 'constants/vaults'
|
||||
import useDepositedVaults from 'hooks/useDepositedVaults'
|
@ -1,25 +1,25 @@
|
||||
import { useCallback } from 'react'
|
||||
|
||||
import { ACCOUNT_MENU_BUTTON_ID } from 'components/Account/AccountMenuContent'
|
||||
import Button from 'components/Button'
|
||||
import { ArrowDownLine, ArrowUpLine, Enter } from 'components/Icons'
|
||||
import Text from 'components/Text'
|
||||
import { Tooltip } from 'components/Tooltip'
|
||||
import ConditionalWrapper from 'hocs/ConditionalWrapper'
|
||||
import useAlertDialog from 'hooks/useAlertDialog'
|
||||
import useAutoLendEnabledAccountIds from 'hooks/useAutoLendEnabledAccountIds'
|
||||
import useCurrentAccountDeposits from 'hooks/useCurrentAccountDeposits'
|
||||
import useLendAndReclaimModal from 'hooks/useLendAndReclaimModal'
|
||||
import { byDenom } from 'utils/array'
|
||||
import useAutoLendEnabledAccountIds from 'hooks/useAutoLendEnabledAccountIds'
|
||||
import { ACCOUNT_MENU_BUTTON_ID } from 'components/Account/AccountMenuContent'
|
||||
|
||||
interface Props {
|
||||
data: LendingMarketTableData
|
||||
}
|
||||
|
||||
const buttonClassnames = 'm-0 flex w-40 text-lg'
|
||||
const buttonClassnames = 'm-0 flex w-40'
|
||||
const iconClassnames = 'ml-0 mr-1 w-4 h-4'
|
||||
|
||||
function LendingActionButtons(props: Props) {
|
||||
export default function LendingActionButtons(props: Props) {
|
||||
const { asset, accountLentValue: accountLendValue } = props.data
|
||||
const accountDeposits = useCurrentAccountDeposits()
|
||||
const { openLend, openReclaim } = useLendAndReclaimModal()
|
||||
@ -90,5 +90,3 @@ function LendingActionButtons(props: Props) {
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default LendingActionButtons
|
||||
|
@ -1,62 +0,0 @@
|
||||
import TitleAndSubCell from 'components/TitleAndSubCell'
|
||||
import { formatPercent, formatValue } from 'utils/formatters'
|
||||
import useDisplayCurrencyPrice from 'hooks/useDisplayCurrencyPrice'
|
||||
|
||||
interface Props {
|
||||
data: LendingMarketTableData
|
||||
}
|
||||
|
||||
function LendingDetails({ data }: Props) {
|
||||
const {
|
||||
convertAmount,
|
||||
getConversionRate,
|
||||
symbol: displayCurrencySymbol,
|
||||
} = useDisplayCurrencyPrice()
|
||||
const {
|
||||
asset,
|
||||
marketMaxLtv,
|
||||
marketDepositAmount,
|
||||
marketLiquidityAmount,
|
||||
marketLiquidationThreshold,
|
||||
} = data
|
||||
const formattedTotalSuppliedValue = formatValue(
|
||||
convertAmount(asset, marketDepositAmount).toNumber(),
|
||||
{
|
||||
abbreviated: true,
|
||||
suffix: ` ${displayCurrencySymbol}`,
|
||||
},
|
||||
)
|
||||
const formattedPrice = formatValue(getConversionRate(asset.denom).toNumber(), {
|
||||
maxDecimals: 2,
|
||||
suffix: ` ${displayCurrencySymbol}`,
|
||||
})
|
||||
const totalBorrowed = marketDepositAmount.minus(marketLiquidityAmount)
|
||||
const utilizationRatePercent = formatPercent(
|
||||
totalBorrowed.dividedBy(marketDepositAmount).toNumber(),
|
||||
2,
|
||||
)
|
||||
|
||||
const details = [
|
||||
{ info: formattedTotalSuppliedValue, title: 'Total Supplied' },
|
||||
{ info: formatPercent(marketMaxLtv, 2), title: 'Max LTV' },
|
||||
{ info: formatPercent(marketLiquidationThreshold, 2), title: 'Liquidation Threshold' },
|
||||
{ info: formattedPrice, title: 'Oracle Price' },
|
||||
{ info: utilizationRatePercent, title: 'Utilization Rate' },
|
||||
]
|
||||
|
||||
return (
|
||||
<div className='flex flex-1 justify-center rounded-md bg-white bg-opacity-5'>
|
||||
{details.map((detail, index) => (
|
||||
<TitleAndSubCell
|
||||
key={index}
|
||||
className='text-md text-center'
|
||||
containerClassName='m-5 ml-10 mr-10 space-y-2'
|
||||
title={detail.info}
|
||||
sub={detail.title}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default LendingDetails
|
@ -1,40 +1,43 @@
|
||||
import { ColumnDef, Row, Table } from '@tanstack/react-table'
|
||||
import Image from 'next/image'
|
||||
import { useMemo } from 'react'
|
||||
import { useCallback, useMemo } from 'react'
|
||||
|
||||
import AssetImage from 'components/AssetImage'
|
||||
import LendingActionButtons from 'components/Earn/Lend/LendingActionButtons'
|
||||
import LendingDetails from 'components/Earn/Lend/LendingDetails'
|
||||
import { FormattedNumber } from 'components/FormattedNumber'
|
||||
import { ChevronDown, ChevronUp } from 'components/Icons'
|
||||
import AssetListTable from 'components/MarketAssetTable'
|
||||
import MarketAssetTableRow from 'components/MarketAssetTable/MarketAssetTableRow'
|
||||
import Text from 'components/Text'
|
||||
import MarketDetails from 'components/MarketAssetTable/MarketDetails'
|
||||
import TitleAndSubCell from 'components/TitleAndSubCell'
|
||||
import useDisplayCurrencyPrice from 'hooks/useDisplayCurrencyPrice'
|
||||
import { convertLiquidityRateToAPR, demagnify, formatValue } from 'utils/formatters'
|
||||
import { convertLiquidityRateToAPR, demagnify } from 'utils/formatters'
|
||||
import { BN } from 'utils/helpers'
|
||||
|
||||
interface Props {
|
||||
title: string
|
||||
data: LendingMarketTableData[]
|
||||
}
|
||||
|
||||
function LendingMarketsTable(props: Props) {
|
||||
export default function LendingMarketsTable(props: Props) {
|
||||
const { title, data } = props
|
||||
const { symbol: displayCurrencySymbol } = useDisplayCurrencyPrice()
|
||||
const shouldShowAccountDeposit = !!data[0]?.accountLentValue
|
||||
|
||||
const rowRenderer = (row: Row<LendingMarketTableData>, table: Table<LendingMarketTableData>) => {
|
||||
return (
|
||||
<MarketAssetTableRow
|
||||
key={`lend-asset-${row.id}`}
|
||||
isExpanded={row.getIsExpanded()}
|
||||
resetExpanded={table.resetExpanded}
|
||||
rowData={row}
|
||||
expandedActionButtons={<LendingActionButtons data={row.original} />}
|
||||
expandedDetails={<LendingDetails data={row.original} />}
|
||||
/>
|
||||
)
|
||||
}
|
||||
const rowRenderer = useCallback(
|
||||
(row: Row<LendingMarketTableData>, table: Table<LendingMarketTableData>) => {
|
||||
return (
|
||||
<MarketAssetTableRow
|
||||
key={`lend-asset-${row.id}`}
|
||||
isExpanded={row.getIsExpanded()}
|
||||
resetExpanded={table.resetExpanded}
|
||||
rowData={row}
|
||||
expandedActionButtons={<LendingActionButtons data={row.original} />}
|
||||
expandedDetails={<MarketDetails data={row.original} />}
|
||||
/>
|
||||
)
|
||||
},
|
||||
[],
|
||||
)
|
||||
|
||||
const columns = useMemo<ColumnDef<LendingMarketTableData>[]>(
|
||||
() => [
|
||||
@ -47,7 +50,7 @@ function LendingMarketsTable(props: Props) {
|
||||
|
||||
return (
|
||||
<div className='flex flex-1 items-center gap-3'>
|
||||
<Image src={asset.logo} alt={asset.symbol} width={32} height={32} />
|
||||
<AssetImage asset={asset} size={32} />
|
||||
<TitleAndSubCell
|
||||
title={asset.symbol}
|
||||
sub={asset.name}
|
||||
@ -68,8 +71,8 @@ function LendingMarketsTable(props: Props) {
|
||||
return (
|
||||
<FormattedNumber
|
||||
className='text-xs'
|
||||
animate={true}
|
||||
amount={accountDepositValue}
|
||||
animate
|
||||
amount={accountDepositValue.toNumber()}
|
||||
options={{ suffix: ` ${displayCurrencySymbol}` }}
|
||||
/>
|
||||
)
|
||||
@ -81,8 +84,14 @@ function LendingMarketsTable(props: Props) {
|
||||
accessorKey: 'marketLiquidityRate',
|
||||
header: 'APR',
|
||||
cell: ({ row }) => {
|
||||
const apr = convertLiquidityRateToAPR(row.original.marketLiquidityRate)
|
||||
return <Text size='xs'>{apr.toFixed(2)}%</Text>
|
||||
return (
|
||||
<FormattedNumber
|
||||
amount={convertLiquidityRateToAPR(row.original.marketLiquidityRate)}
|
||||
options={{ minDecimals: 2, maxDecimals: 2, suffix: '%' }}
|
||||
className='text-xs'
|
||||
animate
|
||||
/>
|
||||
)
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -94,19 +103,23 @@ function LendingMarketsTable(props: Props) {
|
||||
demagnify(marketDepositAmount, asset),
|
||||
)
|
||||
|
||||
const [formattedRemainingCap, formattedDepositCap] = [remainingCap, marketDepositCap].map(
|
||||
(value) =>
|
||||
formatValue(value.toNumber(), {
|
||||
decimals: asset.decimals,
|
||||
abbreviated: true,
|
||||
}),
|
||||
)
|
||||
|
||||
return (
|
||||
<TitleAndSubCell
|
||||
className='text-xs'
|
||||
title={formattedDepositCap}
|
||||
sub={`${formattedRemainingCap} left`}
|
||||
title={
|
||||
<FormattedNumber
|
||||
amount={marketDepositCap.toNumber()}
|
||||
options={{ abbreviated: true, decimals: asset.decimals }}
|
||||
animate
|
||||
/>
|
||||
}
|
||||
sub={
|
||||
<FormattedNumber
|
||||
amount={remainingCap.toNumber()}
|
||||
options={{ abbreviated: true, decimals: asset.decimals, suffix: ` left` }}
|
||||
animate
|
||||
/>
|
||||
}
|
||||
/>
|
||||
)
|
||||
},
|
||||
@ -117,7 +130,7 @@ function LendingMarketsTable(props: Props) {
|
||||
header: 'Manage',
|
||||
cell: ({ row }) => (
|
||||
<div className='flex items-center justify-end'>
|
||||
<div className='w-4'>{row.getIsExpanded() ? <ChevronDown /> : <ChevronUp />}</div>
|
||||
<div className='w-4'>{row.getIsExpanded() ? <ChevronUp /> : <ChevronDown />}</div>
|
||||
</div>
|
||||
),
|
||||
},
|
||||
@ -127,5 +140,3 @@ function LendingMarketsTable(props: Props) {
|
||||
|
||||
return <AssetListTable title={title} rowRenderer={rowRenderer} columns={columns} data={data} />
|
||||
}
|
||||
|
||||
export default LendingMarketsTable
|
||||
|
@ -2,62 +2,54 @@ import classNames from 'classnames'
|
||||
import React, { useEffect, useRef } from 'react'
|
||||
import { animated, useSpring } from 'react-spring'
|
||||
|
||||
import useStore from 'store'
|
||||
import { FormatOptions, formatValue } from 'utils/formatters'
|
||||
import { BN } from 'utils/helpers'
|
||||
import { DEFAULT_SETTINGS } from 'constants/defaultSettings'
|
||||
import { REDUCE_MOTION_KEY } from 'constants/localStore'
|
||||
import useLocalStorage from 'hooks/useLocalStorage'
|
||||
import { formatValue } from 'utils/formatters'
|
||||
|
||||
interface Props {
|
||||
amount: BigNumber
|
||||
amount: number
|
||||
options?: FormatOptions
|
||||
className?: string
|
||||
animate?: boolean
|
||||
}
|
||||
|
||||
export const FormattedNumber = React.memo((props: Props) => {
|
||||
const enableAnimations = useStore((s) => s.enableAnimations)
|
||||
const prevAmountRef = useRef<BigNumber>(BN(0))
|
||||
export const FormattedNumber = React.memo(
|
||||
(props: Props) => {
|
||||
const [reduceMotion] = useLocalStorage<boolean>(
|
||||
REDUCE_MOTION_KEY,
|
||||
DEFAULT_SETTINGS.reduceMotion,
|
||||
)
|
||||
const prevAmountRef = useRef<number>(0)
|
||||
|
||||
useEffect(() => {
|
||||
if (!prevAmountRef.current.eq(props.amount)) prevAmountRef.current = props.amount
|
||||
}, [props.amount])
|
||||
useEffect(() => {
|
||||
if (prevAmountRef.current !== props.amount) prevAmountRef.current = props.amount
|
||||
}, [props.amount])
|
||||
|
||||
const springAmount = useSpring({
|
||||
number: props.amount.toNumber(),
|
||||
from: { number: prevAmountRef.current.toNumber() },
|
||||
config: { duration: 1000 },
|
||||
})
|
||||
const springAmount = useSpring({
|
||||
number: props.amount,
|
||||
from: { number: prevAmountRef.current },
|
||||
config: { duration: 1000 },
|
||||
})
|
||||
|
||||
return (prevAmountRef.current.eq(props.amount) && props.amount.isZero()) ||
|
||||
!props.animate ||
|
||||
!enableAnimations ? (
|
||||
<span className={classNames('number', props.className)}>
|
||||
{formatValue(props.amount.toString(), {
|
||||
minDecimals: props.options?.minDecimals,
|
||||
maxDecimals: props.options?.maxDecimals,
|
||||
thousandSeparator: props.options?.thousandSeparator,
|
||||
prefix: props.options?.prefix,
|
||||
suffix: props.options?.suffix,
|
||||
rounded: props.options?.rounded,
|
||||
abbreviated: props.options?.abbreviated,
|
||||
decimals: props.options?.decimals,
|
||||
})}
|
||||
</span>
|
||||
) : (
|
||||
<animated.span className={classNames('number', props.className)}>
|
||||
{springAmount.number.to((num) =>
|
||||
formatValue(num, {
|
||||
minDecimals: props.options?.minDecimals,
|
||||
maxDecimals: props.options?.maxDecimals,
|
||||
thousandSeparator: props.options?.thousandSeparator,
|
||||
prefix: props.options?.prefix,
|
||||
suffix: props.options?.suffix,
|
||||
rounded: props.options?.rounded,
|
||||
abbreviated: props.options?.abbreviated,
|
||||
decimals: props.options?.decimals,
|
||||
}),
|
||||
)}
|
||||
</animated.span>
|
||||
)
|
||||
})
|
||||
if (
|
||||
(prevAmountRef.current === props.amount && props.amount === 0) ||
|
||||
!props.animate ||
|
||||
reduceMotion
|
||||
)
|
||||
return (
|
||||
<span className={classNames('number', props.className)}>
|
||||
{formatValue(props.amount.toString(), props.options)}
|
||||
</span>
|
||||
)
|
||||
|
||||
return (
|
||||
<animated.span className={classNames('number', props.className)}>
|
||||
{springAmount.number.to((num) => formatValue(num, props.options))}
|
||||
</animated.span>
|
||||
)
|
||||
},
|
||||
(prevProps, nextProps) => prevProps.amount === nextProps.amount,
|
||||
)
|
||||
|
||||
FormattedNumber.displayName = 'FormattedNumber'
|
||||
|
@ -1,10 +1,11 @@
|
||||
import classNames from 'classnames'
|
||||
import { ReactElement, ReactNode } from 'react'
|
||||
|
||||
import { Tooltip } from 'components/Tooltip'
|
||||
import useStore from 'store'
|
||||
import { FormattedNumber } from 'components/FormattedNumber'
|
||||
import { BN } from 'utils/helpers'
|
||||
import { Tooltip } from 'components/Tooltip'
|
||||
import { DEFAULT_SETTINGS } from 'constants/defaultSettings'
|
||||
import { REDUCE_MOTION_KEY } from 'constants/localStore'
|
||||
import useLocalStorage from 'hooks/useLocalStorage'
|
||||
|
||||
interface Props {
|
||||
tooltip: string | ReactNode
|
||||
@ -27,7 +28,7 @@ export const Gauge = ({
|
||||
icon,
|
||||
labelClassName,
|
||||
}: Props) => {
|
||||
const enableAnimations = useStore((s) => s.enableAnimations)
|
||||
const [reduceMotion] = useLocalStorage<boolean>(REDUCE_MOTION_KEY, DEFAULT_SETTINGS.reduceMotion)
|
||||
const radius = 16
|
||||
const percentageValue = percentage > 100 ? 100 : percentage < 0 ? 0 : percentage
|
||||
const circlePercent = 100 - percentageValue
|
||||
@ -75,7 +76,7 @@ export const Gauge = ({
|
||||
strokeDasharray='100'
|
||||
pathLength='100'
|
||||
style={{
|
||||
transition: enableAnimations ? 'stroke-dashoffset 1s ease' : 'none',
|
||||
transition: reduceMotion ? 'none' : 'stroke-dashoffset 1s ease',
|
||||
}}
|
||||
shapeRendering='geometricPrecision'
|
||||
strokeLinecap='round'
|
||||
@ -88,7 +89,7 @@ export const Gauge = ({
|
||||
)}
|
||||
<FormattedNumber
|
||||
className={classNames(labelClassName, 'text-2xs')}
|
||||
amount={BN(Math.round(percentage))}
|
||||
amount={Math.round(percentage)}
|
||||
options={{ maxDecimals: 0, minDecimals: 0 }}
|
||||
animate
|
||||
/>
|
||||
|
@ -3,7 +3,7 @@ import classNames from 'classnames'
|
||||
import AccountMenu from 'components/Account/AccountMenu'
|
||||
import DesktopNavigation from 'components/Navigation/DesktopNavigation'
|
||||
import Settings from 'components/Settings'
|
||||
import Wallet from 'components/Wallet/Wallet'
|
||||
import Wallet from 'components/Wallet'
|
||||
import useStore from 'store'
|
||||
|
||||
export const menuTree: { page: Page; label: string }[] = [
|
||||
|
8
src/components/Icons/ArrowCircle.svg
Normal file
8
src/components/Icons/ArrowCircle.svg
Normal file
@ -0,0 +1,8 @@
|
||||
<svg viewBox="0 0 21 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
d="M20 8C20 8 17.995 5.26822 16.3662 3.63824C14.7373 2.00827 12.4864 1 10 1C5.02944 1 1 5.02944 1 10C1 14.9706 5.02944 19 10 19C14.1031 19 17.5649 16.2543 18.6482 12.5M20 8V2M20 8H14"
|
||||
stroke="currentColor"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
</svg>
|
After Width: | Height: | Size: 362 B |
@ -2,6 +2,7 @@
|
||||
export { default as Account } from 'components/Icons/Account.svg'
|
||||
export { default as AccountArrowDown } from 'components/Icons/AccountArrowDown.svg'
|
||||
export { default as ArrowChartLineUp } from 'components/Icons/ArrowChartLineUp.svg'
|
||||
export { default as ArrowCircle } from 'components/Icons/ArrowCircle.svg'
|
||||
export { default as ArrowCircledTopRight } from 'components/Icons/ArrowCircledTopRight.svg'
|
||||
export { default as ArrowDownLine } from 'components/Icons/ArrowDownLine.svg'
|
||||
export { default as ArrowRight } from 'components/Icons/ArrowRight.svg'
|
||||
|
@ -16,12 +16,12 @@ interface Props {
|
||||
|
||||
export const LabelValuePair = ({ label, value, className }: Props) => (
|
||||
<div className={classNames('flex w-full', className)}>
|
||||
<Text size='xs' className='flex-grow text-white/60'>
|
||||
<Text size='xs' className='flex-1 text-white/60'>
|
||||
{label}
|
||||
</Text>
|
||||
<Text size='xs' className='text-white/60'>
|
||||
{value.format === 'number' ? (
|
||||
<FormattedNumber animate {...value} amount={BN(value.amount)} />
|
||||
<FormattedNumber animate {...value} amount={BN(value.amount).toNumber()} />
|
||||
) : (
|
||||
value.amount || ''
|
||||
)}
|
||||
|
@ -48,7 +48,7 @@ function AssetListTableRow<TData>(props: Props<TData>) {
|
||||
key={props.rowData.id}
|
||||
className={classNames(
|
||||
'cursor-pointer transition-colors',
|
||||
'border-b border-white border-opacity-10',
|
||||
|
||||
props.rowData.getIsExpanded() ? 'bg-black/20' : 'bg-white/0 hover:bg-white/5',
|
||||
)}
|
||||
onClick={() => {
|
||||
|
93
src/components/MarketAssetTable/MarketDetails.tsx
Normal file
93
src/components/MarketAssetTable/MarketDetails.tsx
Normal file
@ -0,0 +1,93 @@
|
||||
import { useMemo } from 'react'
|
||||
|
||||
import { FormattedNumber } from 'components/FormattedNumber'
|
||||
import TitleAndSubCell from 'components/TitleAndSubCell'
|
||||
import useDisplayCurrencyPrice from 'hooks/useDisplayCurrencyPrice'
|
||||
|
||||
interface Props {
|
||||
data: BorrowMarketTableData | LendingMarketTableData
|
||||
}
|
||||
|
||||
interface Detail {
|
||||
amount: number
|
||||
options: FormatOptions
|
||||
title: string
|
||||
}
|
||||
|
||||
export default function MarketDetails({ data }: Props) {
|
||||
const {
|
||||
convertAmount,
|
||||
getConversionRate,
|
||||
symbol: displayCurrencySymbol,
|
||||
} = useDisplayCurrencyPrice()
|
||||
const {
|
||||
asset,
|
||||
marketMaxLtv,
|
||||
marketDepositAmount,
|
||||
marketLiquidityAmount,
|
||||
marketLiquidationThreshold,
|
||||
} = data
|
||||
|
||||
const totalBorrowed = marketDepositAmount.minus(marketLiquidityAmount)
|
||||
|
||||
const details: Detail[] = useMemo(
|
||||
() => [
|
||||
{
|
||||
amount: convertAmount(asset, marketDepositAmount).toNumber(),
|
||||
options: { abbreviated: true, suffix: ` ${displayCurrencySymbol}` },
|
||||
title: 'Total Supplied',
|
||||
},
|
||||
{
|
||||
amount: marketMaxLtv * 100,
|
||||
options: { minDecimals: 2, maxDecimals: 2, suffix: '%' },
|
||||
title: 'Max LTV',
|
||||
},
|
||||
{
|
||||
amount: marketLiquidationThreshold * 100,
|
||||
options: { minDecimals: 2, maxDecimals: 2, suffix: '%' },
|
||||
title: 'Liquidation Threshold',
|
||||
},
|
||||
{
|
||||
amount: getConversionRate(asset.denom).toNumber(),
|
||||
options: { minDecimals: 2, maxDecimals: 2, suffix: ` ${displayCurrencySymbol}` },
|
||||
title: 'Oracle Price',
|
||||
},
|
||||
{
|
||||
amount: totalBorrowed.dividedBy(marketDepositAmount).multipliedBy(100).toNumber(),
|
||||
options: { minDecimals: 2, maxDecimals: 2, suffix: '%' },
|
||||
title: 'Utilization Rate',
|
||||
},
|
||||
],
|
||||
[
|
||||
asset,
|
||||
marketDepositAmount,
|
||||
marketLiquidationThreshold,
|
||||
marketMaxLtv,
|
||||
displayCurrencySymbol,
|
||||
convertAmount,
|
||||
getConversionRate,
|
||||
totalBorrowed,
|
||||
],
|
||||
)
|
||||
|
||||
return (
|
||||
<div className='flex flex-1 justify-center rounded-md bg-white bg-opacity-5'>
|
||||
{details.map((detail, index) => (
|
||||
<TitleAndSubCell
|
||||
key={index}
|
||||
className='text-center'
|
||||
containerClassName='m-5 ml-10 mr-10 space-y-1'
|
||||
title={
|
||||
<FormattedNumber
|
||||
className='text-center text-xs'
|
||||
amount={detail.amount}
|
||||
options={detail.options}
|
||||
animate
|
||||
/>
|
||||
}
|
||||
sub={detail.title}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
@ -1,10 +1,9 @@
|
||||
import classNames from 'classnames'
|
||||
import { ReactNode, useEffect, useRef } from 'react'
|
||||
|
||||
import EscButton from 'components/Button/EscButton'
|
||||
import Card from 'components/Card'
|
||||
|
||||
import EscButton from './Button/EscButton'
|
||||
|
||||
interface Props {
|
||||
header: string | ReactNode
|
||||
headerClassName?: string
|
||||
@ -14,12 +13,11 @@ interface Props {
|
||||
className?: string
|
||||
contentClassName?: string
|
||||
modalClassName?: string
|
||||
open: boolean
|
||||
onClose: () => void
|
||||
}
|
||||
|
||||
export default function Modal(props: Props) {
|
||||
const ref: any = useRef(null)
|
||||
const ref: React.RefObject<HTMLDialogElement> = useRef(null)
|
||||
const modalClassName = props.modalClassName ?? 'max-w-modal'
|
||||
|
||||
function onClose() {
|
||||
@ -28,19 +26,17 @@ export default function Modal(props: Props) {
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (props.open) {
|
||||
ref.current?.showModal()
|
||||
} else {
|
||||
ref.current?.close()
|
||||
}
|
||||
}, [props.open])
|
||||
ref.current?.showModal()
|
||||
document.body.classList.add('h-screen', 'overflow-hidden')
|
||||
}, [])
|
||||
|
||||
// close dialog on unmount
|
||||
useEffect(() => {
|
||||
const dialog = ref.current
|
||||
return () => {
|
||||
dialog.removeAttribute('open')
|
||||
dialog.close()
|
||||
dialog?.removeAttribute('open')
|
||||
dialog?.close()
|
||||
document.body.classList.remove('h-screen', 'overflow-hidden')
|
||||
}
|
||||
}, [])
|
||||
|
||||
@ -51,13 +47,14 @@ export default function Modal(props: Props) {
|
||||
className={classNames(
|
||||
`w-screen border-none bg-transparent text-white`,
|
||||
'focus-visible:outline-none',
|
||||
'max-h-full scrollbar-hide',
|
||||
'backdrop:bg-black/50 backdrop:backdrop-blur-sm',
|
||||
modalClassName,
|
||||
)}
|
||||
>
|
||||
<Card
|
||||
className={classNames(
|
||||
'relative flex max-w-full flex-grow bg-white/5 backdrop-blur-3xl',
|
||||
'relative flex max-w-full flex-1 bg-white/5 backdrop-blur-3xl',
|
||||
props.className,
|
||||
)}
|
||||
>
|
||||
@ -65,7 +62,9 @@ export default function Modal(props: Props) {
|
||||
{props.header}
|
||||
{!props.hideCloseBtn && <EscButton onClick={props.onClose} />}
|
||||
</div>
|
||||
<div className={classNames(props.contentClassName, 'flex-grow')}>
|
||||
<div
|
||||
className={classNames(props.contentClassName, 'flex-1 overflow-y-scroll scrollbar-hide')}
|
||||
>
|
||||
{props.children ? props.children : props.content}
|
||||
</div>
|
||||
</Card>
|
||||
|
@ -6,12 +6,12 @@ import {
|
||||
SortingState,
|
||||
useReactTable,
|
||||
} from '@tanstack/react-table'
|
||||
import { useEffect, useMemo, useState } from 'react'
|
||||
import classNames from 'classnames'
|
||||
import { useEffect, useMemo, useState } from 'react'
|
||||
|
||||
import { SortAsc, SortDesc, SortNone } from 'components/Icons'
|
||||
import Text from 'components/Text'
|
||||
import useAddVaultAssetTableColumns from 'components/Modals/AddVaultAssets/useAddVaultAssetTableColumns'
|
||||
import Text from 'components/Text'
|
||||
|
||||
interface Props {
|
||||
assets: BorrowAsset[]
|
||||
@ -61,7 +61,7 @@ export default function AddVaultAssetTable(props: Props) {
|
||||
|
||||
return (
|
||||
<table className='w-full'>
|
||||
<thead className='border-b border-b-white/5'>
|
||||
<thead className='border-b border-white/5'>
|
||||
{table.getHeaderGroups().map((headerGroup) => (
|
||||
<tr key={headerGroup.id}>
|
||||
{headerGroup.headers.map((header, index) => {
|
||||
|
@ -1,10 +1,10 @@
|
||||
import { useCallback, useState } from 'react'
|
||||
|
||||
import Modal from 'components/Modal'
|
||||
import useStore from 'store'
|
||||
import Text from 'components/Text'
|
||||
import { CircularProgress } from 'components/CircularProgress'
|
||||
import Modal from 'components/Modal'
|
||||
import AddVaultAssetsModalContent from 'components/Modals/AddVaultAssets/AddVaultBorrowAssetsModalContent'
|
||||
import Text from 'components/Text'
|
||||
import useStore from 'store'
|
||||
|
||||
export default function AddVaultBorrowAssetsModal() {
|
||||
const modal = useStore((s) => s.addVaultBorrowingsModal)
|
||||
@ -24,9 +24,9 @@ export default function AddVaultBorrowAssetsModal() {
|
||||
|
||||
const showContent = modal && vaultModal?.vault
|
||||
|
||||
if (!showContent) return null
|
||||
return (
|
||||
<Modal
|
||||
open={!!(modal && showContent)}
|
||||
header={<Text>Add Assets</Text>}
|
||||
onClose={onClose}
|
||||
modalClassName='max-w-modal-xs'
|
||||
|
@ -1,9 +1,9 @@
|
||||
import { useCallback, useMemo, useState } from 'react'
|
||||
|
||||
import AddVaultAssetTable from 'components/Modals/AddVaultAssets/AddVaultAssetTable'
|
||||
import SearchBar from 'components/SearchBar'
|
||||
import Text from 'components/Text'
|
||||
import useMarketBorrowings from 'hooks/useMarketBorrowings'
|
||||
import AddVaultAssetTable from 'components/Modals/AddVaultAssets/AddVaultAssetTable'
|
||||
import useStore from 'store'
|
||||
|
||||
interface Props {
|
||||
@ -75,7 +75,7 @@ export default function AddVaultAssetsModalContent(props: Props) {
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className='border-b border-b-white/5 bg-white/10 px-4 py-3'>
|
||||
<div className='border-b border-white/5 bg-white/10 px-4 py-3'>
|
||||
<SearchBar
|
||||
value={searchString}
|
||||
placeholder={`Search for e.g. "ETH" or "Ethereum"`}
|
||||
|
@ -1,11 +1,11 @@
|
||||
import Button from 'components/Button'
|
||||
import { ExclamationMarkCircled } from 'components/Icons'
|
||||
import Modal from 'components/Modal'
|
||||
import { NoIcon, YesIcon } from 'components/Modals/AlertDialog/ButtonIcons'
|
||||
import Text from 'components/Text'
|
||||
import useAlertDialog from 'hooks/useAlertDialog'
|
||||
import { NoIcon, YesIcon } from 'components/Modals/AlertDialog/ButtonIcons'
|
||||
|
||||
function AlertDialogController() {
|
||||
export default function AlertDialogController() {
|
||||
const { config, close } = useAlertDialog()
|
||||
|
||||
if (!config) return null
|
||||
@ -28,14 +28,13 @@ function AlertDialog(props: Props) {
|
||||
|
||||
return (
|
||||
<Modal
|
||||
open
|
||||
onClose={props.close}
|
||||
header={
|
||||
<div className='grid h-12 w-12 place-items-center rounded-sm bg-white/5'>
|
||||
{icon ?? <ExclamationMarkCircled width={18} />}
|
||||
</div>
|
||||
}
|
||||
modalClassName='w-[577px]'
|
||||
modalClassName='max-w-modal-sm'
|
||||
headerClassName='p-8'
|
||||
contentClassName='px-8 pb-8'
|
||||
hideCloseBtn
|
||||
@ -62,5 +61,3 @@ function AlertDialog(props: Props) {
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
|
||||
export default AlertDialogController
|
||||
|
@ -15,7 +15,6 @@ import { BN } from 'utils/helpers'
|
||||
interface Props {
|
||||
asset: Asset
|
||||
title: string
|
||||
isOpen: boolean
|
||||
coinBalances: Coin[]
|
||||
contentHeader?: JSX.Element
|
||||
actionButtonText: string
|
||||
@ -30,7 +29,6 @@ export default function AssetAmountSelectActionModal(props: Props) {
|
||||
const {
|
||||
asset,
|
||||
title,
|
||||
isOpen,
|
||||
coinBalances,
|
||||
contentHeader = null,
|
||||
actionButtonText,
|
||||
@ -52,12 +50,11 @@ export default function AssetAmountSelectActionModal(props: Props) {
|
||||
)
|
||||
|
||||
const handleActionClick = useCallback(() => {
|
||||
onAction(amount, amount.eq(maxAmount))
|
||||
onAction(amount, amount.isEqualTo(maxAmount))
|
||||
}, [amount, maxAmount, onAction])
|
||||
|
||||
return (
|
||||
<Modal
|
||||
open={isOpen}
|
||||
onClose={onClose}
|
||||
header={
|
||||
<span className='flex items-center gap-4 px-4'>
|
||||
@ -69,9 +66,9 @@ export default function AssetAmountSelectActionModal(props: Props) {
|
||||
contentClassName='flex flex-col min-h-[400px]'
|
||||
>
|
||||
{contentHeader}
|
||||
<div className='flex flex-grow items-start gap-6 p-6'>
|
||||
<div className='flex flex-1 items-start gap-6 p-6'>
|
||||
<Card
|
||||
className='flex flex-grow bg-white/5 p-4'
|
||||
className='flex flex-1 bg-white/5 p-4'
|
||||
contentClassName='gap-6 flex flex-col justify-between h-full'
|
||||
>
|
||||
<TokenInputWithSlider
|
@ -7,7 +7,7 @@ import Card from 'components/Card'
|
||||
import Divider from 'components/Divider'
|
||||
import { ArrowRight } from 'components/Icons'
|
||||
import Modal from 'components/Modal'
|
||||
import Select from 'components/Select/Select'
|
||||
import Select from 'components/Select'
|
||||
import Text from 'components/Text'
|
||||
import TitleAndSubCell from 'components/TitleAndSubCell'
|
||||
import TokenInputWithSlider from 'components/TokenInputWithSlider'
|
||||
@ -21,8 +21,7 @@ import { formatPercent, formatValue } from 'utils/formatters'
|
||||
import { BN } from 'utils/helpers'
|
||||
|
||||
function getDebtAmount(modal: BorrowModal | null) {
|
||||
if (!(modal?.marketData as BorrowAssetActive)?.debt) return '0'
|
||||
return BN((modal?.marketData as BorrowAssetActive).debt).toString()
|
||||
return BN((modal?.marketData as BorrowMarketTableData)?.debt ?? 0).toString()
|
||||
}
|
||||
|
||||
function getAssetLogo(modal: BorrowModal | null) {
|
||||
@ -119,9 +118,9 @@ export default function BorrowModal() {
|
||||
})
|
||||
}, [amount, modal?.asset, currentAccount, isRepay])
|
||||
|
||||
if (!modal) return null
|
||||
return (
|
||||
<Modal
|
||||
open={!!modal}
|
||||
onClose={onClose}
|
||||
header={
|
||||
<span className='flex items-center gap-4 px-4'>
|
||||
@ -134,7 +133,7 @@ export default function BorrowModal() {
|
||||
headerClassName='gradient-header pl-2 pr-2.5 py-2.5 border-b-white/5 border-b'
|
||||
contentClassName='flex flex-col'
|
||||
>
|
||||
<div className='flex gap-3 border-b border-b-white/5 px-6 py-4 gradient-header'>
|
||||
<div className='flex gap-3 border-b border-white/5 px-6 py-4 gradient-header'>
|
||||
<TitleAndSubCell
|
||||
title={formatPercent(modal?.marketData.borrowRate || '0')}
|
||||
sub={'Borrow rate'}
|
||||
@ -153,9 +152,9 @@ export default function BorrowModal() {
|
||||
sub={'Liquidity available'}
|
||||
/>
|
||||
</div>
|
||||
<div className='flex flex-grow items-start gap-6 p-6'>
|
||||
<div className='flex flex-1 items-start gap-6 p-6'>
|
||||
<Card
|
||||
className='flex flex-grow bg-white/5 p-4'
|
||||
className='flex flex-1 bg-white/5 p-4'
|
||||
contentClassName='gap-6 flex flex-col justify-between h-full min-h-[380px]'
|
||||
>
|
||||
<div className='flex w-full flex-wrap'>
|
||||
@ -176,6 +175,7 @@ export default function BorrowModal() {
|
||||
options={accountOptions ?? []}
|
||||
title='Accounts'
|
||||
defaultValue={selectedAccount?.id}
|
||||
containerClassName='w-full'
|
||||
onChange={(account) => {
|
||||
accounts && setSelectedAccount(accounts?.find((a) => a.id === account))
|
||||
}}
|
@ -83,9 +83,9 @@ export default function FundWithdrawModalContent(props: Props) {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='flex flex-grow items-start gap-6 p-6'>
|
||||
<div className='flex flex-1 items-start gap-6 p-6'>
|
||||
<Card
|
||||
className='flex flex-grow bg-white/5 p-4'
|
||||
className='flex flex-1 bg-white/5 p-4'
|
||||
contentClassName='gap-6 flex flex-col justify-between h-full'
|
||||
>
|
||||
<TokenInputWithSlider
|
||||
|
@ -1,9 +1,9 @@
|
||||
import { CircularProgress } from 'components/CircularProgress'
|
||||
import Modal from 'components/Modal'
|
||||
import FundWithdrawModalContent from 'components/Modals/FundWithdraw/FundAndWithdrawModalContent'
|
||||
import Text from 'components/Text'
|
||||
import useCurrentAccount from 'hooks/useCurrentAccount'
|
||||
import useStore from 'store'
|
||||
import { CircularProgress } from 'components/CircularProgress'
|
||||
import FundWithdrawModalContent from 'components/Modals/FundWithdraw/FundAndWithdrawModalContent'
|
||||
|
||||
export default function FundAndWithdrawModal() {
|
||||
const currentAccount = useCurrentAccount()
|
||||
@ -14,9 +14,9 @@ export default function FundAndWithdrawModal() {
|
||||
useStore.setState({ fundAndWithdrawModal: null })
|
||||
}
|
||||
|
||||
if (!modal) return null
|
||||
return (
|
||||
<Modal
|
||||
open={!!modal}
|
||||
onClose={onClose}
|
||||
header={
|
||||
<span className='flex items-center gap-4 px-4'>
|
@ -1,6 +1,6 @@
|
||||
import DisplayCurrency from 'components/DisplayCurrency'
|
||||
import TitleAndSubCell from 'components/TitleAndSubCell'
|
||||
import { FormattedNumber } from 'components/FormattedNumber'
|
||||
import TitleAndSubCell from 'components/TitleAndSubCell'
|
||||
import useAssetIncentivesApy from 'hooks/useAssetIncentiveApy'
|
||||
import useCurrentWalletBalance from 'hooks/useCurrentWalletBalance'
|
||||
import { BNCoin } from 'types/classes/BNCoin'
|
||||
@ -15,17 +15,18 @@ function DetailsHeader({ data }: Props) {
|
||||
const balanceInWallet = useCurrentWalletBalance(asset.denom)
|
||||
|
||||
return (
|
||||
<div className='flex gap-6 border-b border-b-white/5 px-6 py-4 gradient-header'>
|
||||
<div className='flex gap-6 border-b border-white/5 px-6 py-4 gradient-header'>
|
||||
{assetApy && (
|
||||
<>
|
||||
<TitleAndSubCell
|
||||
title={
|
||||
<>
|
||||
<FormattedNumber amount={assetApy} options={{ suffix: '%' }} />
|
||||
<FormattedNumber amount={assetApy.toNumber()} options={{ suffix: '%' }} animate />
|
||||
<FormattedNumber
|
||||
className='ml-2 text-xs'
|
||||
amount={assetApy.div(365)}
|
||||
amount={assetApy.dividedBy(365).toNumber()}
|
||||
options={{ suffix: '%/day' }}
|
||||
animate
|
||||
/>
|
||||
</>
|
||||
}
|
||||
|
@ -66,7 +66,6 @@ function LendAndReclaimModal({ currentAccount, config }: Props) {
|
||||
return (
|
||||
<AssetAmountSelectActionModal
|
||||
asset={asset}
|
||||
isOpen={true}
|
||||
contentHeader={<DetailsHeader data={data} />}
|
||||
coinBalances={coinBalances}
|
||||
actionButtonText={actionText}
|
||||
|
@ -4,21 +4,23 @@ import {
|
||||
BorrowModal,
|
||||
FundAndWithdrawModal,
|
||||
LendAndReclaimModalController,
|
||||
SettingsModal,
|
||||
UnlockModal,
|
||||
VaultModal,
|
||||
WithdrawFromVaults,
|
||||
WithdrawFromVaultsModal,
|
||||
} from 'components/Modals'
|
||||
|
||||
export default function ModalsContainer() {
|
||||
return (
|
||||
<>
|
||||
<VaultModal />
|
||||
<AddVaultBorrowAssetsModal />
|
||||
<BorrowModal />
|
||||
<FundAndWithdrawModal />
|
||||
<AddVaultBorrowAssetsModal />
|
||||
<UnlockModal />
|
||||
<LendAndReclaimModalController />
|
||||
<WithdrawFromVaults />
|
||||
<SettingsModal />
|
||||
<UnlockModal />
|
||||
<VaultModal />
|
||||
<WithdrawFromVaultsModal />
|
||||
<AlertDialogController />
|
||||
</>
|
||||
)
|
||||
|
32
src/components/Modals/Settings/SettingsOptions.tsx
Normal file
32
src/components/Modals/Settings/SettingsOptions.tsx
Normal file
@ -0,0 +1,32 @@
|
||||
import classNames from 'classnames'
|
||||
import { ReactNode } from 'react'
|
||||
|
||||
import Text from 'components/Text'
|
||||
|
||||
interface Props {
|
||||
label: string
|
||||
decsription: string
|
||||
className?: string
|
||||
children: ReactNode | ReactNode[]
|
||||
}
|
||||
|
||||
export default function SettingsOptions(props: Props) {
|
||||
return (
|
||||
<div
|
||||
className={classNames(
|
||||
'mb-6 flex w-full items-start justify-between border-b border-white/5',
|
||||
props.className,
|
||||
)}
|
||||
>
|
||||
<div className='flex w-120 flex-wrap'>
|
||||
<Text size='lg' className='mb-2 w-full'>
|
||||
{props.label}
|
||||
</Text>
|
||||
<Text size='xs' className='text-white/50'>
|
||||
{props.decsription}
|
||||
</Text>
|
||||
</div>
|
||||
<div className='flex w-60 flex-wrap justify-end'>{props.children}</div>
|
||||
</div>
|
||||
)
|
||||
}
|
42
src/components/Modals/Settings/SettingsSwitch.tsx
Normal file
42
src/components/Modals/Settings/SettingsSwitch.tsx
Normal file
@ -0,0 +1,42 @@
|
||||
import classNames from 'classnames'
|
||||
|
||||
import Switch from 'components/Switch'
|
||||
import Text from 'components/Text'
|
||||
|
||||
interface Props {
|
||||
onChange: (value: boolean) => void
|
||||
name: string
|
||||
value: boolean
|
||||
label: string
|
||||
decsription: string
|
||||
className?: string
|
||||
withStatus?: boolean
|
||||
}
|
||||
|
||||
export default function SettingsSwitch(props: Props) {
|
||||
return (
|
||||
<div
|
||||
className={classNames(
|
||||
'mb-6 flex w-full items-start justify-between border-b border-white/5 pb-6',
|
||||
props.className,
|
||||
)}
|
||||
>
|
||||
<div className='flex w-100 flex-wrap'>
|
||||
<Text size='lg' className='mb-2 w-full'>
|
||||
{props.label}
|
||||
</Text>
|
||||
<Text size='xs' className='text-white/50'>
|
||||
{props.decsription}
|
||||
</Text>
|
||||
</div>
|
||||
<div className='flex w-60 flex-wrap justify-end'>
|
||||
<Switch name={props.name} checked={props.value} onChange={props.onChange} />
|
||||
{props.withStatus && (
|
||||
<Text size='sm' className='mt-2 w-full text-end'>
|
||||
{props.value ? 'ON' : 'OFF'}
|
||||
</Text>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
306
src/components/Modals/Settings/index.tsx
Normal file
306
src/components/Modals/Settings/index.tsx
Normal file
@ -0,0 +1,306 @@
|
||||
import classNames from 'classnames'
|
||||
import { useCallback, useMemo, useState } from 'react'
|
||||
|
||||
import AssetImage from 'components/AssetImage'
|
||||
import Button from 'components/Button'
|
||||
import { ArrowCircle, Enter } from 'components/Icons'
|
||||
import Modal from 'components/Modal'
|
||||
import SettingsOptions from 'components/Modals/Settings/SettingsOptions'
|
||||
import SettingsSwitch from 'components/Modals/Settings/SettingsSwitch'
|
||||
import NumberInput from 'components/NumberInput'
|
||||
import Select from 'components/Select'
|
||||
import Text from 'components/Text'
|
||||
import { DEFAULT_SETTINGS } from 'constants/defaultSettings'
|
||||
import {
|
||||
DISPLAY_CURRENCY_KEY,
|
||||
LEND_ASSETS_KEY,
|
||||
PREFERRED_ASSET_KEY,
|
||||
REDUCE_MOTION_KEY,
|
||||
SLIPPAGE_KEY,
|
||||
} from 'constants/localStore'
|
||||
import useAlertDialog from 'hooks/useAlertDialog'
|
||||
import useLocalStorage from 'hooks/useLocalStorage'
|
||||
import useStore from 'store'
|
||||
import { getAllAssets, getDisplayCurrencies } from 'utils/assets'
|
||||
import { BN } from 'utils/helpers'
|
||||
|
||||
const slippages = [0.02, 0.03]
|
||||
|
||||
export default function SettingsModal() {
|
||||
const modal = useStore((s) => s.settingsModal)
|
||||
const { open: showResetDialog } = useAlertDialog()
|
||||
const displayCurrencies = getDisplayCurrencies()
|
||||
const assets = getAllAssets()
|
||||
const [customSlippage, setCustomSlippage] = useState<number>(0)
|
||||
const [inputRef, setInputRef] = useState<React.RefObject<HTMLInputElement>>()
|
||||
const [isCustom, setIsCustom] = useState(false)
|
||||
const [displayCurrency, setDisplayCurrency] = useLocalStorage<string>(
|
||||
DISPLAY_CURRENCY_KEY,
|
||||
DEFAULT_SETTINGS.displayCurrency,
|
||||
)
|
||||
const [preferredAsset, setPreferredAsset] = useLocalStorage<string>(
|
||||
PREFERRED_ASSET_KEY,
|
||||
DEFAULT_SETTINGS.preferredAsset,
|
||||
)
|
||||
const [reduceMotion, setReduceMotion] = useLocalStorage<boolean>(
|
||||
REDUCE_MOTION_KEY,
|
||||
DEFAULT_SETTINGS.reduceMotion,
|
||||
)
|
||||
const [lendAssets, setLendAssets] = useLocalStorage<boolean>(
|
||||
LEND_ASSETS_KEY,
|
||||
DEFAULT_SETTINGS.lendAssets,
|
||||
)
|
||||
const [slippage, setSlippage] = useLocalStorage<number>(SLIPPAGE_KEY, DEFAULT_SETTINGS.slippage)
|
||||
|
||||
const displayCurrenciesOptions = useMemo(
|
||||
() =>
|
||||
displayCurrencies.map((asset, index) => ({
|
||||
label: (
|
||||
<div className='flex w-full gap-2' key={index}>
|
||||
<AssetImage asset={asset} size={16} />
|
||||
<Text size='sm' className='leading-4'>
|
||||
{asset.symbol}
|
||||
</Text>
|
||||
</div>
|
||||
),
|
||||
value: asset.denom,
|
||||
})),
|
||||
[displayCurrencies],
|
||||
)
|
||||
|
||||
const preferredAssetsOptions = useMemo(
|
||||
() =>
|
||||
assets.map((asset, index) => ({
|
||||
label: [
|
||||
<div className='flex w-full gap-2' key={index}>
|
||||
<AssetImage asset={asset} size={16} />
|
||||
<Text size='sm' className='leading-4'>
|
||||
{asset.symbol}
|
||||
</Text>
|
||||
</div>,
|
||||
],
|
||||
value: asset.denom,
|
||||
})),
|
||||
[assets],
|
||||
)
|
||||
|
||||
const handleReduceMotion = useCallback(
|
||||
(value: boolean) => {
|
||||
setReduceMotion(!value)
|
||||
},
|
||||
[setReduceMotion],
|
||||
)
|
||||
|
||||
const handleLendAssets = useCallback(
|
||||
(value: boolean) => {
|
||||
setLendAssets(value)
|
||||
},
|
||||
[setLendAssets],
|
||||
)
|
||||
|
||||
const handlePreferredAsset = useCallback(
|
||||
(value: string) => {
|
||||
setPreferredAsset(value)
|
||||
},
|
||||
[setPreferredAsset],
|
||||
)
|
||||
|
||||
const handleDisplayCurrency = useCallback(
|
||||
(value: string) => {
|
||||
setDisplayCurrency(value)
|
||||
},
|
||||
[setDisplayCurrency],
|
||||
)
|
||||
|
||||
const handleSlippageInputFocus = useCallback(() => {
|
||||
setIsCustom(true)
|
||||
}, [])
|
||||
|
||||
const handleSlippage = useCallback(
|
||||
(value: number) => {
|
||||
setSlippage(value)
|
||||
},
|
||||
[setSlippage],
|
||||
)
|
||||
|
||||
const handleSlippageInputBlur = useCallback(() => {
|
||||
setIsCustom(false)
|
||||
|
||||
if (!customSlippage) {
|
||||
setCustomSlippage(0)
|
||||
handleSlippage(slippages[0])
|
||||
return
|
||||
}
|
||||
|
||||
const value = Number(customSlippage || 0)
|
||||
if (slippages.includes(value)) {
|
||||
setCustomSlippage(0)
|
||||
handleSlippage(value)
|
||||
return
|
||||
}
|
||||
|
||||
handleSlippage(BN(customSlippage).toNumber())
|
||||
}, [customSlippage, handleSlippage])
|
||||
|
||||
const handleSlippageInput = useCallback(
|
||||
(value: BigNumber) => {
|
||||
if (!value.toString()) {
|
||||
return
|
||||
}
|
||||
setCustomSlippage(value.dividedBy(100).toNumber())
|
||||
handleSlippage(value.dividedBy(100).toNumber())
|
||||
},
|
||||
[handleSlippage],
|
||||
)
|
||||
|
||||
const handleResetSettings = useCallback(() => {
|
||||
handleDisplayCurrency(DEFAULT_SETTINGS.displayCurrency)
|
||||
handlePreferredAsset(DEFAULT_SETTINGS.preferredAsset)
|
||||
handleSlippage(DEFAULT_SETTINGS.slippage)
|
||||
handleReduceMotion(!DEFAULT_SETTINGS.reduceMotion)
|
||||
handleLendAssets(DEFAULT_SETTINGS.lendAssets)
|
||||
}, [
|
||||
handleDisplayCurrency,
|
||||
handleReduceMotion,
|
||||
handleLendAssets,
|
||||
handlePreferredAsset,
|
||||
handleSlippage,
|
||||
])
|
||||
|
||||
const showResetModal = useCallback(() => {
|
||||
showResetDialog({
|
||||
icon: (
|
||||
<div className='flex h-full w-full p-3'>
|
||||
<ArrowCircle />
|
||||
</div>
|
||||
),
|
||||
title: 'Are you sure you want to restore to default?',
|
||||
description:
|
||||
'Once you reset to default settings you can’t revert it, and will result in the permanent loss of your current settings',
|
||||
positiveButton: {
|
||||
text: 'Yes',
|
||||
icon: <Enter />,
|
||||
onClick: handleResetSettings,
|
||||
},
|
||||
})
|
||||
}, [showResetDialog, handleResetSettings])
|
||||
|
||||
const handleCloseModal = useCallback(() => {
|
||||
useStore.setState({ settingsModal: false })
|
||||
}, [])
|
||||
|
||||
if (!modal) return null
|
||||
|
||||
return (
|
||||
<Modal
|
||||
onClose={handleCloseModal}
|
||||
header={
|
||||
<span className='flex flex-wrap items-center'>
|
||||
<Text size='3xl' className='w-full pb-1'>
|
||||
Global Settings
|
||||
</Text>
|
||||
<Text size='sm' className='text-white/50'>
|
||||
Customise to match your workflow
|
||||
</Text>
|
||||
</span>
|
||||
}
|
||||
headerClassName='p-6'
|
||||
contentClassName='flex flex-wrap px-6 pb-6 pt-4'
|
||||
>
|
||||
<SettingsSwitch
|
||||
onChange={handleLendAssets}
|
||||
name='lendAssets'
|
||||
value={lendAssets}
|
||||
label='Lend assets in credit account'
|
||||
decsription='By turning this on you will automatically lend out all the assets you deposit into your credit account to earn yield.'
|
||||
withStatus
|
||||
/>
|
||||
<SettingsSwitch
|
||||
onChange={handleReduceMotion}
|
||||
name='reduceMotion'
|
||||
value={!reduceMotion}
|
||||
label='Reduce Motion'
|
||||
decsription='Turns off all animations inside the dApp. Turning animations off can increase the
|
||||
overall performance on lower-end hardware.'
|
||||
withStatus
|
||||
/>
|
||||
<SettingsOptions
|
||||
label='Preferred asset'
|
||||
decsription='By selecting a different asset you always have the trading pair or asset selector
|
||||
pre-filled with this asset.'
|
||||
className='pb-6'
|
||||
>
|
||||
<Select
|
||||
label='Global'
|
||||
options={preferredAssetsOptions}
|
||||
defaultValue={preferredAsset}
|
||||
onChange={handlePreferredAsset}
|
||||
className='relative w-60 rounded-base border border-white/10'
|
||||
containerClassName='justify-end mb-4'
|
||||
/>
|
||||
<Select
|
||||
label='Display Currency'
|
||||
options={displayCurrenciesOptions}
|
||||
defaultValue={displayCurrency}
|
||||
onChange={handleDisplayCurrency}
|
||||
className='relative w-60 rounded-base border border-white/10'
|
||||
containerClassName='justify-end'
|
||||
/>
|
||||
</SettingsOptions>
|
||||
<SettingsOptions
|
||||
label='Slippage tolerance'
|
||||
decsription='Some vaults require token swaps. The transaction will fail if the price of the swap asset changes unfavourably by more than this percentage'
|
||||
className='pb-21'
|
||||
>
|
||||
{slippages.map((value) => (
|
||||
<Button
|
||||
key={`slippage-${value}`}
|
||||
color='secondary'
|
||||
variant='rounded'
|
||||
onClick={() => {
|
||||
handleSlippage(value)
|
||||
}}
|
||||
className={classNames(
|
||||
'mr-3 text-[16px]',
|
||||
slippage === value && !isCustom && 'bg-white/10',
|
||||
)}
|
||||
text={`${value * 100}%`}
|
||||
/>
|
||||
))}
|
||||
<Button
|
||||
onClick={() => inputRef?.current?.focus()}
|
||||
color='secondary'
|
||||
variant='rounded'
|
||||
className={classNames('w-16', !slippages.includes(slippage) && 'bg-white/10')}
|
||||
>
|
||||
<NumberInput
|
||||
asset={{ decimals: 0, symbol: '%' }}
|
||||
onRef={setInputRef}
|
||||
onChange={handleSlippageInput}
|
||||
onBlur={handleSlippageInputBlur}
|
||||
onFocus={handleSlippageInputFocus}
|
||||
amount={BN(customSlippage).multipliedBy(100)}
|
||||
max={BN(10)}
|
||||
min={BN(0)}
|
||||
maxDecimals={1}
|
||||
maxLength={2}
|
||||
style={{ fontSize: 16 }}
|
||||
placeholder='...'
|
||||
className='!w-6'
|
||||
/>
|
||||
%
|
||||
</Button>
|
||||
</SettingsOptions>
|
||||
<div className='flex w-full justify-between'>
|
||||
<Button
|
||||
color='quaternary'
|
||||
variant='transparent'
|
||||
onClick={showResetModal}
|
||||
leftIcon={<ArrowCircle />}
|
||||
text='Reset to default settings'
|
||||
/>
|
||||
<Button text='Confirm' onClick={handleCloseModal} />
|
||||
</div>
|
||||
</Modal>
|
||||
)
|
||||
}
|
@ -2,10 +2,10 @@ import { useState } from 'react'
|
||||
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 useStore from 'store'
|
||||
import { hardcodedFee } from 'utils/constants'
|
||||
import { NoIcon, YesIcon } from 'components/Modals/AlertDialog/ButtonIcons'
|
||||
|
||||
interface Props {
|
||||
depositedVault: DepositedVault
|
||||
|
@ -1,4 +1,3 @@
|
||||
import { CircularProgress } from 'components/CircularProgress'
|
||||
import { LockUnlocked } from 'components/Icons'
|
||||
import Modal from 'components/Modal'
|
||||
import UnlockModalContent from 'components/Modals/Unlock/UnlockModalContent'
|
||||
@ -11,9 +10,9 @@ export default function UnlockModal() {
|
||||
useStore.setState({ unlockModal: null })
|
||||
}
|
||||
|
||||
if (!modal) return null
|
||||
return (
|
||||
<Modal
|
||||
open={!!modal}
|
||||
onClose={onClose}
|
||||
header={
|
||||
<div className='grid h-12 w-12 place-items-center rounded-sm bg-white/5'>
|
||||
@ -25,11 +24,7 @@ export default function UnlockModal() {
|
||||
contentClassName='px-8 pb-8'
|
||||
hideCloseBtn
|
||||
>
|
||||
{modal ? (
|
||||
<UnlockModalContent depositedVault={modal.vault} onClose={onClose} />
|
||||
) : (
|
||||
<CircularProgress />
|
||||
)}
|
||||
<UnlockModalContent depositedVault={modal.vault} onClose={onClose} />
|
||||
</Modal>
|
||||
)
|
||||
}
|
@ -1,24 +1,23 @@
|
||||
import { useEffect, useMemo, useState } from 'react'
|
||||
import BigNumber from 'bignumber.js'
|
||||
import React from 'react'
|
||||
import React, { useEffect, useMemo, useState } from 'react'
|
||||
|
||||
import { BN } from 'utils/helpers'
|
||||
import { findCoinByDenom, getAssetByDenom } from 'utils/assets'
|
||||
import Button from 'components/Button'
|
||||
import TokenInput from 'components/TokenInput'
|
||||
import Divider from 'components/Divider'
|
||||
import Text from 'components/Text'
|
||||
import { ArrowRight, ExclamationMarkCircled } from 'components/Icons'
|
||||
import { formatPercent } from 'utils/formatters'
|
||||
import Slider from 'components/Slider'
|
||||
import usePrices from 'hooks/usePrices'
|
||||
import useMarketAssets from 'hooks/useMarketAssets'
|
||||
import { calculateMaxBorrowAmounts } from 'utils/vaults'
|
||||
import useStore from 'store'
|
||||
import DisplayCurrency from 'components/DisplayCurrency'
|
||||
import usePrice from 'hooks/usePrice'
|
||||
import { BNCoin } from 'types/classes/BNCoin'
|
||||
import Divider from 'components/Divider'
|
||||
import { ArrowRight, ExclamationMarkCircled } from 'components/Icons'
|
||||
import Slider from 'components/Slider'
|
||||
import Text from 'components/Text'
|
||||
import TokenInput from 'components/TokenInput'
|
||||
import useDepositVault from 'hooks/broadcast/useDepositVault'
|
||||
import useMarketAssets from 'hooks/useMarketAssets'
|
||||
import usePrice from 'hooks/usePrice'
|
||||
import usePrices from 'hooks/usePrices'
|
||||
import useStore from 'store'
|
||||
import { BNCoin } from 'types/classes/BNCoin'
|
||||
import { findCoinByDenom, getAssetByDenom } from 'utils/assets'
|
||||
import { formatPercent } from 'utils/formatters'
|
||||
import { BN } from 'utils/helpers'
|
||||
import { calculateMaxBorrowAmounts } from 'utils/vaults'
|
||||
|
||||
export interface VaultBorrowingsProps {
|
||||
account: Account
|
||||
@ -49,11 +48,11 @@ export default function VaultBorrowings(props: VaultBorrowingsProps) {
|
||||
})
|
||||
|
||||
const primaryValue = useMemo(
|
||||
() => props.primaryAmount.times(primaryPrice),
|
||||
() => props.primaryAmount.multipliedBy(primaryPrice),
|
||||
[props.primaryAmount, primaryPrice],
|
||||
)
|
||||
const secondaryValue = useMemo(
|
||||
() => props.secondaryAmount.times(secondaryPrice),
|
||||
() => props.secondaryAmount.multipliedBy(secondaryPrice),
|
||||
[props.secondaryAmount, secondaryPrice],
|
||||
)
|
||||
|
||||
@ -62,7 +61,7 @@ export default function VaultBorrowings(props: VaultBorrowingsProps) {
|
||||
const price = prices.find((price) => price.denom === curr.denom)?.amount
|
||||
if (!price) return prev
|
||||
|
||||
return prev.plus(curr.amount.times(price))
|
||||
return prev.plus(curr.amount.multipliedBy(price))
|
||||
}, BN(0) as BigNumber)
|
||||
}, [props.borrowings, prices])
|
||||
|
||||
@ -113,7 +112,7 @@ export default function VaultBorrowings(props: VaultBorrowingsProps) {
|
||||
new BNCoin({
|
||||
denom,
|
||||
amount: (
|
||||
maxAmount.plus(currentAmount).times(value).div(100).decimalPlaces(0) || BN(0)
|
||||
maxAmount.plus(currentAmount).multipliedBy(value).dividedBy(100).decimalPlaces(0) || BN(0)
|
||||
).toString(),
|
||||
}),
|
||||
]
|
||||
@ -164,7 +163,7 @@ export default function VaultBorrowings(props: VaultBorrowingsProps) {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='flex flex-grow flex-col gap-4 p-4'>
|
||||
<div className='flex flex-1 flex-col gap-4 p-4'>
|
||||
{props.borrowings.map((coin) => {
|
||||
const asset = getAssetByDenom(coin.denom)
|
||||
const maxAmount = maxAmounts.find((maxAmount) => maxAmount.denom === coin.denom)?.amount
|
||||
|
@ -1,12 +1,11 @@
|
||||
import BigNumber from 'bignumber.js'
|
||||
import { useMemo } from 'react'
|
||||
|
||||
import DisplayCurrency from 'components/DisplayCurrency'
|
||||
import usePrices from 'hooks/usePrices'
|
||||
import useStore from 'store'
|
||||
import { BNCoin } from 'types/classes/BNCoin'
|
||||
import { formatAmountWithSymbol } from 'utils/formatters'
|
||||
import { BN } from 'utils/helpers'
|
||||
import { BNCoin } from 'types/classes/BNCoin'
|
||||
|
||||
interface Props {
|
||||
borrowings: BNCoin[]
|
||||
@ -22,7 +21,7 @@ export default function VaultDepositSubTitle(props: Props) {
|
||||
props.borrowings.map((coin) => {
|
||||
const price = prices.find((p) => p.denom === coin.denom)?.amount
|
||||
if (!price || coin.amount.isZero()) return
|
||||
borrowingValue = borrowingValue.plus(coin.amount.times(price))
|
||||
borrowingValue = borrowingValue.plus(coin.amount.multipliedBy(price))
|
||||
texts.push(
|
||||
formatAmountWithSymbol({
|
||||
denom: coin.denom,
|
||||
|
@ -4,17 +4,17 @@ import { useMemo, useState } from 'react'
|
||||
import Button from 'components/Button'
|
||||
import DisplayCurrency from 'components/DisplayCurrency'
|
||||
import Divider from 'components/Divider'
|
||||
import { Gauge } from 'components/Gauge'
|
||||
import { ArrowRight, ExclamationMarkCircled } from 'components/Icons'
|
||||
import Slider from 'components/Slider'
|
||||
import Switch from 'components/Switch'
|
||||
import Text from 'components/Text'
|
||||
import TokenInput from 'components/TokenInput'
|
||||
import usePrice from 'hooks/usePrice'
|
||||
import { getAmount } from 'utils/accounts'
|
||||
import { BN } from 'utils/helpers'
|
||||
import { Gauge } from 'components/Gauge'
|
||||
import useStore from 'store'
|
||||
import { BNCoin } from 'types/classes/BNCoin'
|
||||
import { getAmount } from 'utils/accounts'
|
||||
import { BN } from 'utils/helpers'
|
||||
|
||||
interface Props {
|
||||
primaryAmount: BigNumber
|
||||
@ -38,11 +38,11 @@ export default function VaultDeposit(props: Props) {
|
||||
const secondaryPrice = usePrice(props.secondaryAsset.denom)
|
||||
|
||||
const primaryValue = useMemo(
|
||||
() => props.primaryAmount.times(primaryPrice),
|
||||
() => props.primaryAmount.multipliedBy(primaryPrice),
|
||||
[props.primaryAmount, primaryPrice],
|
||||
)
|
||||
const secondaryValue = useMemo(
|
||||
() => props.secondaryAmount.times(secondaryPrice),
|
||||
() => props.secondaryAmount.multipliedBy(secondaryPrice),
|
||||
[props.secondaryAmount, secondaryPrice],
|
||||
)
|
||||
const totalValue = useMemo(
|
||||
@ -51,7 +51,7 @@ export default function VaultDeposit(props: Props) {
|
||||
)
|
||||
|
||||
const primaryValuePercentage = useMemo(() => {
|
||||
const value = primaryValue.div(totalValue).times(100).decimalPlaces(2).toNumber()
|
||||
const value = primaryValue.dividedBy(totalValue).multipliedBy(100).decimalPlaces(2).toNumber()
|
||||
return isNaN(value) ? 50 : value
|
||||
}, [primaryValue, totalValue])
|
||||
const secondaryValuePercentage = useMemo(
|
||||
@ -63,8 +63,8 @@ export default function VaultDeposit(props: Props) {
|
||||
() =>
|
||||
BN(
|
||||
Math.min(
|
||||
availablePrimaryAmount.times(primaryPrice).toNumber(),
|
||||
availableSecondaryAmount.times(secondaryPrice).toNumber(),
|
||||
availablePrimaryAmount.multipliedBy(primaryPrice).toNumber(),
|
||||
availableSecondaryAmount.multipliedBy(secondaryPrice).toNumber(),
|
||||
),
|
||||
),
|
||||
[availablePrimaryAmount, primaryPrice, availableSecondaryAmount, secondaryPrice],
|
||||
@ -83,7 +83,8 @@ export default function VaultDeposit(props: Props) {
|
||||
)
|
||||
|
||||
const [percentage, setPercentage] = useState(
|
||||
primaryValue.dividedBy(maxAssetValueNonCustom).times(100).decimalPlaces(0).toNumber() || 0,
|
||||
primaryValue.dividedBy(maxAssetValueNonCustom).multipliedBy(100).decimalPlaces(0).toNumber() ||
|
||||
0,
|
||||
)
|
||||
const disableInput =
|
||||
(availablePrimaryAmount.isZero() || availableSecondaryAmount.isZero()) && !props.isCustomRatio
|
||||
@ -103,7 +104,7 @@ export default function VaultDeposit(props: Props) {
|
||||
amount = primaryMax
|
||||
}
|
||||
props.onChangePrimaryAmount(amount)
|
||||
setPercentage(amount.dividedBy(primaryMax).times(100).decimalPlaces(0).toNumber())
|
||||
setPercentage(amount.dividedBy(primaryMax).multipliedBy(100).decimalPlaces(0).toNumber())
|
||||
if (!props.isCustomRatio) {
|
||||
props.onChangeSecondaryAmount(secondaryMax.multipliedBy(amount.dividedBy(primaryMax)))
|
||||
}
|
||||
@ -114,7 +115,7 @@ export default function VaultDeposit(props: Props) {
|
||||
amount = secondaryMax
|
||||
}
|
||||
props.onChangeSecondaryAmount(amount)
|
||||
setPercentage(amount.dividedBy(secondaryMax).times(100).decimalPlaces(0).toNumber())
|
||||
setPercentage(amount.dividedBy(secondaryMax).multipliedBy(100).decimalPlaces(0).toNumber())
|
||||
if (!props.isCustomRatio) {
|
||||
props.onChangePrimaryAmount(primaryMax.multipliedBy(amount.dividedBy(secondaryMax)))
|
||||
}
|
||||
@ -152,7 +153,7 @@ export default function VaultDeposit(props: Props) {
|
||||
strokeWidth={3}
|
||||
/>
|
||||
</div>
|
||||
<div className='flex h-full flex-grow flex-col justify-between gap-6'>
|
||||
<div className='flex h-full flex-1 flex-col justify-between gap-6'>
|
||||
<TokenInput
|
||||
onChange={onChangePrimaryDeposit}
|
||||
amount={props.primaryAmount}
|
||||
|
@ -27,8 +27,8 @@ export default function VaultDepositSubTitle(props: Props) {
|
||||
})
|
||||
|
||||
const positionValue = props.primaryAmount
|
||||
.times(primaryPrice)
|
||||
.plus(props.secondaryAmount.times(secondaryPrice))
|
||||
.multipliedBy(primaryPrice)
|
||||
.plus(props.secondaryAmount.multipliedBy(secondaryPrice))
|
||||
.toNumber()
|
||||
|
||||
const showPrimaryText = !props.primaryAmount.isZero()
|
||||
|
@ -3,14 +3,14 @@ import { useCallback, useMemo, useState } from 'react'
|
||||
|
||||
import Accordion from 'components/Accordion'
|
||||
import AccountSummary from 'components/Account/AccountSummary'
|
||||
import useIsOpenArray from 'hooks/useIsOpenArray'
|
||||
import { BN } from 'utils/helpers'
|
||||
import useUpdateAccount from 'hooks/useUpdateAccount'
|
||||
import VaultBorrowings from 'components/Modals/Vault/VaultBorrowings'
|
||||
import VaultBorrowingsSubTitle from 'components/Modals/Vault/VaultBorrowingsSubTitle'
|
||||
import VaultDeposit from 'components/Modals/Vault/VaultDeposits'
|
||||
import VaultBorrowings from 'components/Modals/Vault/VaultBorrowings'
|
||||
import VaultDepositSubTitle from 'components/Modals/Vault/VaultDepositsSubTitle'
|
||||
import useIsOpenArray from 'hooks/useIsOpenArray'
|
||||
import useUpdateAccount from 'hooks/useUpdateAccount'
|
||||
import { BNCoin } from 'types/classes/BNCoin'
|
||||
import { BN } from 'utils/helpers'
|
||||
|
||||
interface Props {
|
||||
vault: Vault | DepositedVault
|
||||
@ -82,7 +82,7 @@ export default function VaultModalContent(props: Props) {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='flex flex-grow items-start gap-6 p-6'>
|
||||
<div className='flex flex-1 items-start gap-6 p-6'>
|
||||
<Accordion
|
||||
className='h-[546px] overflow-y-scroll scrollbar-hide'
|
||||
items={[
|
||||
|
@ -1,4 +1,4 @@
|
||||
import VaultLogo from 'components/Earn/vault/VaultLogo'
|
||||
import VaultLogo from 'components/Earn/Farm/VaultLogo'
|
||||
import Modal from 'components/Modal'
|
||||
import VaultModalContent from 'components/Modals/Vault/VaultModalContent'
|
||||
import Text from 'components/Text'
|
||||
@ -38,7 +38,6 @@ function VaultModal(props: Props) {
|
||||
|
||||
return (
|
||||
<Modal
|
||||
open={true}
|
||||
onClose={onClose}
|
||||
header={
|
||||
<span className='flex items-center gap-4 px-4'>
|
||||
|
@ -4,7 +4,7 @@ import { useParams } from 'react-router-dom'
|
||||
import Button from 'components/Button'
|
||||
import { CircularProgress } from 'components/CircularProgress'
|
||||
import DisplayCurrency from 'components/DisplayCurrency'
|
||||
import VaultLogo from 'components/Earn/vault/VaultLogo'
|
||||
import VaultLogo from 'components/Earn/Farm/VaultLogo'
|
||||
import { FormattedNumber } from 'components/FormattedNumber'
|
||||
import Modal from 'components/Modal'
|
||||
import Text from 'components/Text'
|
||||
@ -13,9 +13,8 @@ import { BNCoin } from 'types/classes/BNCoin'
|
||||
import { getAssetByDenom } from 'utils/assets'
|
||||
import { hardcodedFee } from 'utils/constants'
|
||||
import { demagnify } from 'utils/formatters'
|
||||
import { BN } from 'utils/helpers'
|
||||
|
||||
export default function WithdrawFromVaults() {
|
||||
export default function WithdrawFromVaultsModal() {
|
||||
const modal = useStore((s) => s.withdrawFromVaultsModal)
|
||||
const { accountId } = useParams()
|
||||
const [isConfirming, setIsConfirming] = useState(false)
|
||||
@ -38,9 +37,10 @@ export default function WithdrawFromVaults() {
|
||||
onClose()
|
||||
}
|
||||
|
||||
if (!modal) return null
|
||||
|
||||
return (
|
||||
<Modal
|
||||
open={!!modal}
|
||||
onClose={onClose}
|
||||
header={
|
||||
<span className='flex items-center'>
|
||||
@ -63,7 +63,7 @@ export default function WithdrawFromVaults() {
|
||||
return (
|
||||
<div className='flex items-center gap-4' key={vault.unlockId}>
|
||||
<VaultLogo vault={vault} />
|
||||
<div className='flex flex-grow flex-wrap'>
|
||||
<div className='flex flex-1 flex-wrap'>
|
||||
<Text className='w-full'>{vault.name}</Text>
|
||||
<Text size='sm' className='w-full text-white/50'>
|
||||
Unlocked
|
||||
@ -72,14 +72,16 @@ export default function WithdrawFromVaults() {
|
||||
<div className='flex flex-wrap'>
|
||||
<DisplayCurrency coin={coin} className='w-full text-right' />
|
||||
<FormattedNumber
|
||||
amount={BN(demagnify(vault.amounts.primary, primaryAsset))}
|
||||
amount={demagnify(vault.amounts.primary, primaryAsset)}
|
||||
className='w-full text-right text-sm text-white/50'
|
||||
options={{ suffix: ` ${vault.symbols.primary}` }}
|
||||
animate
|
||||
/>
|
||||
<FormattedNumber
|
||||
amount={BN(demagnify(vault.amounts.secondary, secondaryAsset))}
|
||||
amount={demagnify(vault.amounts.secondary, secondaryAsset)}
|
||||
className='w-full text-right text-sm text-white/50'
|
||||
options={{ suffix: ` ${vault.symbols.secondary}` }}
|
||||
animate
|
||||
/>
|
||||
</div>
|
||||
</div>
|
@ -1,8 +1,9 @@
|
||||
export { default as AddVaultBorrowAssetsModal } from 'components/Modals/AddVaultAssets/AddVaultBorrowAssetsModal'
|
||||
export { default as BorrowModal } from 'components/Modals/Borrow/BorrowModal'
|
||||
export { default as FundAndWithdrawModal } from 'components/Modals/FundWithdraw/FundAndWithdrawModal'
|
||||
export { default as LendAndReclaimModalController } from 'components/Modals/LendAndReclaim'
|
||||
export { default as UnlockModal } from 'components/Modals/Unlock/UnlockModal'
|
||||
export { default as VaultModal } from 'components/Modals/Vault'
|
||||
export { default as WithdrawFromVaults } from 'components/Modals/WithdrawFromVaults/WithdrawFromVaults'
|
||||
export { default as AlertDialogController } from 'components/Modals/AlertDialog'
|
||||
export { default as BorrowModal } from 'components/Modals/BorrowModal'
|
||||
export { default as FundAndWithdrawModal } from 'components/Modals/FundWithdraw'
|
||||
export { default as LendAndReclaimModalController } from 'components/Modals/LendAndReclaim'
|
||||
export { default as SettingsModal } from 'components/Modals/Settings'
|
||||
export { default as UnlockModal } from 'components/Modals/Unlock'
|
||||
export { default as VaultModal } from 'components/Modals/Vault'
|
||||
export { default as WithdrawFromVaultsModal } from 'components/Modals/WithdrawFromVaultsModal'
|
||||
|
@ -13,7 +13,7 @@ export default function DesktopNavigation() {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='flex flex-grow items-center'>
|
||||
<div className='flex flex-1 items-center'>
|
||||
<NavLink href={getRoute('trade', address, accountId)} isActive={false}>
|
||||
<span className='block h-10 w-10'>
|
||||
<Logo />
|
||||
|
@ -6,7 +6,7 @@ import { demagnify, formatValue, magnify } from 'utils/formatters'
|
||||
import { BN } from 'utils/helpers'
|
||||
|
||||
interface Props {
|
||||
asset: Asset
|
||||
asset: Asset | PseudoAsset
|
||||
amount: BigNumber
|
||||
min?: BigNumber
|
||||
max?: BigNumber
|
||||
@ -16,6 +16,7 @@ interface Props {
|
||||
allowNegative?: boolean
|
||||
style?: {}
|
||||
disabled?: boolean
|
||||
placeholder?: string
|
||||
onChange: (amount: BigNumber) => void
|
||||
onBlur?: () => void
|
||||
onFocus?: () => void
|
||||
@ -37,11 +38,11 @@ export default function NumberInput(props: Props) {
|
||||
formatValue(props.amount.toNumber(), {
|
||||
decimals: props.asset.decimals,
|
||||
minDecimals: 0,
|
||||
maxDecimals: props.asset.decimals,
|
||||
maxDecimals: props.maxDecimals,
|
||||
thousandSeparator: false,
|
||||
}),
|
||||
)
|
||||
}, [props.amount, props.asset])
|
||||
}, [props.amount, props.asset, props.maxDecimals])
|
||||
|
||||
useEffect(() => {
|
||||
if (!props.onRef) return
|
||||
@ -157,7 +158,7 @@ export default function NumberInput(props: Props) {
|
||||
props.className,
|
||||
)}
|
||||
style={props.style}
|
||||
placeholder='0'
|
||||
placeholder={props.placeholder ?? '0'}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
import classNames from 'classnames'
|
||||
import { ChangeEvent, forwardRef } from 'react'
|
||||
import { ChangeEvent, LegacyRef } from 'react'
|
||||
import React from 'react'
|
||||
|
||||
import { Search } from 'components/Icons'
|
||||
|
||||
@ -10,7 +11,7 @@ interface Props {
|
||||
onChange: (value: string) => void
|
||||
}
|
||||
|
||||
const SearchBar = (props: Props) => {
|
||||
const SearchBar = (props: Props, ref: LegacyRef<HTMLDivElement>) => {
|
||||
function onChange(event: ChangeEvent<HTMLInputElement>) {
|
||||
props.onChange(event.target.value)
|
||||
}
|
||||
@ -22,11 +23,12 @@ const SearchBar = (props: Props) => {
|
||||
'relative isolate max-w-full overflow-hidden rounded-sm',
|
||||
'before:content-[" "] before:absolute before:inset-0 before:-z-1 before:rounded-sm before:p-[1px] before:border-glas',
|
||||
)}
|
||||
ref={ref}
|
||||
>
|
||||
<Search width={14} height={14} className='mr-2.5 text-white' />
|
||||
<input
|
||||
value={props.value}
|
||||
className='h-full w-full bg-transparent text-xs placeholder-white/30 outline-none'
|
||||
className='h-full w-full bg-transparent text-xs placeholder-white/50 outline-none'
|
||||
placeholder={props.placeholder}
|
||||
onChange={(event) => onChange(event)}
|
||||
autoFocus={props.autofocus}
|
||||
@ -35,4 +37,4 @@ const SearchBar = (props: Props) => {
|
||||
)
|
||||
}
|
||||
|
||||
export default forwardRef(SearchBar)
|
||||
export default React.forwardRef(SearchBar)
|
||||
|
@ -1,42 +1,50 @@
|
||||
import classNames from 'classnames'
|
||||
|
||||
import AssetImage from 'components/AssetImage'
|
||||
import DisplayCurrency from 'components/DisplayCurrency'
|
||||
import { ChevronDown } from 'components/Icons'
|
||||
import { ChevronDown, ChevronRight } from 'components/Icons'
|
||||
import Text from 'components/Text'
|
||||
import { ASSETS } from 'constants/assets'
|
||||
import { formatValue } from 'utils/formatters'
|
||||
import AssetImage from 'components/AssetImage'
|
||||
import { BNCoin } from 'types/classes/BNCoin'
|
||||
import { formatValue } from 'utils/formatters'
|
||||
|
||||
interface Props extends Option {
|
||||
interface Props extends SelectOption {
|
||||
isSelected?: boolean
|
||||
isDisplay?: boolean
|
||||
isClicked?: boolean
|
||||
onClick?: (value: string) => void
|
||||
displayClassName?: string
|
||||
}
|
||||
|
||||
export default function Option(props: Props) {
|
||||
const isCoin = !!props.denom
|
||||
|
||||
function handleOnClick(value: string | undefined) {
|
||||
if (!props.onClick || !value) return
|
||||
props.onClick(value)
|
||||
}
|
||||
|
||||
if (isCoin) {
|
||||
const asset = ASSETS.find((asset) => asset.denom === props.denom) || ASSETS[0]
|
||||
const asset = ASSETS.find((asset) => asset.denom === props.denom) ?? ASSETS[0]
|
||||
const balance = props.amount ?? '0'
|
||||
|
||||
if (props.isDisplay) {
|
||||
return (
|
||||
<div
|
||||
className={classNames('flex items-center gap-2 bg-white/10 p-3', 'hover:cursor-pointer')}
|
||||
className={classNames(
|
||||
'flex h-full w-auto items-center gap-2 bg-white/10 px-2',
|
||||
'hover:cursor-pointer',
|
||||
props.displayClassName,
|
||||
)}
|
||||
>
|
||||
<AssetImage asset={asset} size={20} />
|
||||
<span>{asset.symbol}</span>
|
||||
<span
|
||||
<span className='flex'>{asset.symbol}</span>
|
||||
<ChevronRight
|
||||
className={classNames(
|
||||
'inline-block w-2.5 transition-transform',
|
||||
props.isClicked ? 'rotate-0' : '-rotate-90',
|
||||
'block h-3 w-1.5 transition-transform',
|
||||
props.isClicked ? 'rotate-90' : 'rotate-0',
|
||||
)}
|
||||
>
|
||||
<ChevronDown />
|
||||
</span>
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@ -50,7 +58,7 @@ export default function Option(props: Props) {
|
||||
'hover:cursor-pointer hover:bg-white/20',
|
||||
!props.isSelected ? 'bg-white/10' : 'pointer-events-none',
|
||||
)}
|
||||
onClick={() => props?.onClick && props.onClick(asset.denom)}
|
||||
onClick={() => handleOnClick(asset.denom)}
|
||||
>
|
||||
<div className='row-span-2 flex h-full items-center justify-center'>
|
||||
<AssetImage asset={asset} size={32} />
|
||||
@ -74,15 +82,12 @@ export default function Option(props: Props) {
|
||||
)
|
||||
}
|
||||
|
||||
const label = props.label
|
||||
if (props.isDisplay) {
|
||||
return (
|
||||
<div
|
||||
className={classNames(
|
||||
'flex w-full items-center justify-between bg-white/10 p-3 hover:cursor-pointer',
|
||||
)}
|
||||
className={classNames('flex w-full items-center justify-between p-3 hover:cursor-pointer')}
|
||||
>
|
||||
<span>{label}</span>
|
||||
<span className='flex flex-1'>{props.label}</span>
|
||||
<span
|
||||
className={classNames(
|
||||
'inline-block w-2.5 transition-transform',
|
||||
@ -101,9 +106,9 @@ export default function Option(props: Props) {
|
||||
'block p-3 hover:cursor-pointer hover:bg-white/20',
|
||||
props.isSelected && 'bg-white/10',
|
||||
)}
|
||||
onClick={() => props?.onClick && props.onClick(props.value)}
|
||||
onClick={() => handleOnClick(props.value)}
|
||||
>
|
||||
{label}
|
||||
{props.label}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
@ -1,105 +0,0 @@
|
||||
import classNames from 'classnames'
|
||||
import { useEffect, useState } from 'react'
|
||||
|
||||
import { ChevronDown } from 'components/Icons'
|
||||
import Overlay from 'components/Overlay'
|
||||
import Option from 'components/Select/Option'
|
||||
import Text from 'components/Text'
|
||||
import useToggle from 'hooks/useToggle'
|
||||
|
||||
interface Props {
|
||||
options: Option[]
|
||||
defaultValue?: string
|
||||
onChange: (value: string) => void
|
||||
isParent?: boolean
|
||||
className?: string
|
||||
title?: string
|
||||
}
|
||||
|
||||
export default function Select(props: Props) {
|
||||
const [value, setValue] = useState(props.defaultValue)
|
||||
const selectedOption = value
|
||||
? props.options.find((option) => option?.value === value || option?.denom === value)
|
||||
: null
|
||||
|
||||
const [selected, setSelected] = useState<Option>(selectedOption)
|
||||
|
||||
const [showDropdown, setShowDropdown] = useToggle()
|
||||
function handleChange(optionValue: string) {
|
||||
setValue(optionValue)
|
||||
setSelected(
|
||||
props.options.find(
|
||||
(option) => option?.value === optionValue || option?.denom === optionValue,
|
||||
),
|
||||
)
|
||||
setShowDropdown(false)
|
||||
props.onChange(optionValue)
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (props.defaultValue && value === props.defaultValue && selected) return
|
||||
setValue(props.defaultValue)
|
||||
setSelected(
|
||||
props.options.find(
|
||||
(option) => option?.value === props.defaultValue || option?.denom === props.defaultValue,
|
||||
),
|
||||
)
|
||||
}, [value, props.defaultValue, props.options, selected])
|
||||
|
||||
return (
|
||||
<div
|
||||
data-testid='select-component'
|
||||
className={classNames(
|
||||
props.isParent && 'relative',
|
||||
'flex min-w-fit items-center gap-2',
|
||||
props.className,
|
||||
)}
|
||||
role='select'
|
||||
onClick={() => setShowDropdown(!showDropdown)}
|
||||
>
|
||||
{selected ? (
|
||||
<Option {...selected} isClicked={showDropdown} isDisplay />
|
||||
) : (
|
||||
<div
|
||||
className={classNames('flex items-center gap-2 bg-white/10 p-3', 'hover:cursor-pointer')}
|
||||
>
|
||||
<Text className='w-full opacity-50 hover:cursor-pointer'>Select</Text>
|
||||
<span
|
||||
className={classNames(
|
||||
'inline-block w-2.5 transition-transform',
|
||||
showDropdown ? 'rotate-0' : '-rotate-90',
|
||||
)}
|
||||
>
|
||||
<ChevronDown />
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
<Overlay
|
||||
show={showDropdown}
|
||||
className={classNames('left-0 top-[calc(100%+8px)] isolate w-full')}
|
||||
setShow={setShowDropdown}
|
||||
hasBackdropIsolation
|
||||
>
|
||||
<div className='relative isolate w-full overflow-hidden rounded-sm'>
|
||||
{props.title && (
|
||||
<Text size='lg' className='block bg-white/25 p-4 font-bold'>
|
||||
{props.title}
|
||||
</Text>
|
||||
)}
|
||||
{props.options.map((option: Option, index: number) => (
|
||||
<Option
|
||||
key={index}
|
||||
{...option}
|
||||
isSelected={
|
||||
option?.value
|
||||
? option?.value === selected?.value
|
||||
: option?.denom === selected?.denom
|
||||
}
|
||||
onClick={handleChange}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</Overlay>
|
||||
</div>
|
||||
)
|
||||
}
|
124
src/components/Select/index.tsx
Normal file
124
src/components/Select/index.tsx
Normal file
@ -0,0 +1,124 @@
|
||||
import classNames from 'classnames'
|
||||
import { useEffect, useState } from 'react'
|
||||
|
||||
import { ChevronDown } from 'components/Icons'
|
||||
import Overlay from 'components/Overlay'
|
||||
import Option from 'components/Select/Option'
|
||||
import Text from 'components/Text'
|
||||
import useToggle from 'hooks/useToggle'
|
||||
|
||||
interface Props {
|
||||
options: SelectOption[]
|
||||
defaultValue?: string
|
||||
onChange: (value: string) => void
|
||||
isParent?: boolean
|
||||
className?: string
|
||||
title?: string
|
||||
label?: string
|
||||
displayClassName?: string
|
||||
containerClassName?: string
|
||||
}
|
||||
|
||||
export default function Select(props: Props) {
|
||||
const [value, setValue] = useState(props.defaultValue)
|
||||
const selectedOption = value
|
||||
? props.options.find((option) => option?.value === value || option?.denom === value)
|
||||
: undefined
|
||||
const [selected, setSelected] = useState<SelectOption | undefined>(undefined)
|
||||
const [showDropdown, setShowDropdown] = useToggle()
|
||||
|
||||
function handleChange(optionValue: string) {
|
||||
setValue(optionValue)
|
||||
const option = props.options.find(
|
||||
(option) => option?.value === optionValue || option?.denom === optionValue,
|
||||
)
|
||||
if (!option) return
|
||||
setSelected(option)
|
||||
setShowDropdown(false)
|
||||
props.onChange(optionValue)
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (selectedOption && !selected) setSelected(selectedOption)
|
||||
|
||||
if (props.defaultValue && value === props.defaultValue) return
|
||||
setValue(props.defaultValue)
|
||||
const option = props.options.find(
|
||||
(option) => option?.value === props.defaultValue || option?.denom === props.defaultValue,
|
||||
)
|
||||
if (!option) return
|
||||
setSelected(option)
|
||||
}, [value, props.defaultValue, props.options, selected, selectedOption])
|
||||
|
||||
return (
|
||||
<div className={classNames('flex flex-col flex-wrap', props.containerClassName)}>
|
||||
{props.label && (
|
||||
<Text size='sm' className='mb-2 w-full'>
|
||||
{props.label}
|
||||
</Text>
|
||||
)}
|
||||
<div
|
||||
data-testid='select-component'
|
||||
className={classNames(
|
||||
props.isParent && 'relative',
|
||||
'flex min-w-fit items-center gap-2 bg-white/10',
|
||||
props.className,
|
||||
)}
|
||||
role='select'
|
||||
onClick={() => setShowDropdown(!showDropdown)}
|
||||
>
|
||||
{selected ? (
|
||||
<Option
|
||||
{...selected}
|
||||
isClicked={showDropdown}
|
||||
displayClassName={props.displayClassName}
|
||||
isDisplay
|
||||
/>
|
||||
) : (
|
||||
<div
|
||||
className={classNames(
|
||||
'flex items-center gap-2 bg-white/10 p-3',
|
||||
'hover:cursor-pointer',
|
||||
)}
|
||||
>
|
||||
<Text className='w-full opacity-50 hover:cursor-pointer'>Select</Text>
|
||||
<span
|
||||
className={classNames(
|
||||
'inline-block w-2.5 transition-transform',
|
||||
showDropdown ? 'rotate-0' : '-rotate-90',
|
||||
)}
|
||||
>
|
||||
<ChevronDown />
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
<Overlay
|
||||
show={showDropdown}
|
||||
className={classNames('left-0 top-[calc(100%+8px)] isolate w-full')}
|
||||
setShow={setShowDropdown}
|
||||
hasBackdropIsolation
|
||||
>
|
||||
<div className='relative isolate w-full overflow-hidden rounded-sm'>
|
||||
{props.title && (
|
||||
<Text size='lg' className='block bg-white/25 p-4 font-bold'>
|
||||
{props.title}
|
||||
</Text>
|
||||
)}
|
||||
{props.options.map((option: SelectOption, index: number) => (
|
||||
<Option
|
||||
key={index}
|
||||
{...option}
|
||||
isSelected={
|
||||
option?.value
|
||||
? option?.value === selected?.value
|
||||
: option?.denom === selected?.denom
|
||||
}
|
||||
onClick={handleChange}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</Overlay>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
@ -1,112 +1,14 @@
|
||||
import Button from 'components/Button'
|
||||
import { Gear } from 'components/Icons'
|
||||
import Overlay from 'components/Overlay'
|
||||
import Switch from 'components/Switch'
|
||||
import Text from 'components/Text'
|
||||
import { Tooltip } from 'components/Tooltip'
|
||||
import { ASSETS } from 'constants/assets'
|
||||
import { DISPLAY_CURRENCY_KEY, ENABLE_ANIMATIONS_KEY } from 'constants/localStore'
|
||||
import { useAnimations } from 'hooks/useAnimations'
|
||||
import useToggle from 'hooks/useToggle'
|
||||
import useStore from 'store'
|
||||
import { getDisplayCurrencies } from 'utils/assets'
|
||||
|
||||
export default function Settings() {
|
||||
useAnimations()
|
||||
|
||||
const [showMenu, setShowMenu] = useToggle()
|
||||
const enableAnimations = useStore((s) => s.enableAnimations)
|
||||
const displayCurrency = useStore((s) => s.displayCurrency)
|
||||
const displayCurrencies = getDisplayCurrencies()
|
||||
|
||||
const storageDisplayCurrency = localStorage.getItem(DISPLAY_CURRENCY_KEY)
|
||||
if (storageDisplayCurrency) {
|
||||
const storedDisplayCurrency = ASSETS.find(
|
||||
(asset) => asset.symbol === JSON.parse(storageDisplayCurrency).symbol,
|
||||
)
|
||||
if (storedDisplayCurrency && storedDisplayCurrency !== displayCurrency) {
|
||||
setDisplayCurrency(storedDisplayCurrency)
|
||||
}
|
||||
}
|
||||
|
||||
function handleReduceMotion() {
|
||||
useStore.setState({ enableAnimations: !enableAnimations })
|
||||
if (typeof window !== 'undefined')
|
||||
window.localStorage.setItem(ENABLE_ANIMATIONS_KEY, enableAnimations ? 'false' : 'true')
|
||||
}
|
||||
|
||||
function handleCurrencyChange(e: React.ChangeEvent<HTMLSelectElement>) {
|
||||
const displayCurrency = displayCurrencies.find((c) => c.symbol === e.target.value)
|
||||
if (!displayCurrency) return
|
||||
|
||||
setDisplayCurrency(displayCurrency)
|
||||
}
|
||||
|
||||
function setDisplayCurrency(displayCurrency: Asset) {
|
||||
useStore.setState({ displayCurrency: displayCurrency })
|
||||
localStorage.setItem(DISPLAY_CURRENCY_KEY, JSON.stringify(displayCurrency))
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='relative'>
|
||||
<Button
|
||||
variant='solid'
|
||||
color='tertiary'
|
||||
leftIcon={<Gear />}
|
||||
onClick={() => setShowMenu(!showMenu)}
|
||||
hasFocus={showMenu}
|
||||
/>
|
||||
<Overlay className='right-0 mt-2 w-[240px]' show={showMenu} setShow={setShowMenu}>
|
||||
<div className='flex w-full flex-wrap p-4'>
|
||||
<Text size='sm' uppercase={true} className='w-full pb-4'>
|
||||
Settings
|
||||
</Text>
|
||||
<div className='flex w-full'>
|
||||
<div className='flex flex-1'>
|
||||
<Text size='sm' className='mr-2'>
|
||||
Reduce Motion
|
||||
</Text>
|
||||
<Tooltip
|
||||
type='info'
|
||||
interactive
|
||||
content={
|
||||
<Text size='sm'>
|
||||
Turns off all animations inside the dApp. Turning animations off can increase
|
||||
the overall performance on lower-end hardware.
|
||||
</Text>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<Switch name='reduceMotion' checked={!enableAnimations} onChange={handleReduceMotion} />
|
||||
</div>
|
||||
<div className='mt-4 flex w-full flex-col'>
|
||||
<div className='flex'>
|
||||
<Text size='sm' className='mr-2'>
|
||||
Display Currency
|
||||
</Text>
|
||||
<Tooltip
|
||||
type='info'
|
||||
content={
|
||||
<Text size='sm'>
|
||||
Sets the denomination of values to a different currency. While OSMO is the
|
||||
currency the TWAP oracles return. All other values are fetched from liquidity
|
||||
pools.
|
||||
</Text>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<select
|
||||
value={displayCurrency.symbol}
|
||||
onChange={handleCurrencyChange}
|
||||
className='mt-2 w-full rounded-sm border border-white/20 bg-transparent p-1 text-sm'
|
||||
>
|
||||
{displayCurrencies.map((currency) => (
|
||||
<option key={currency.denom}>{currency.symbol}</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</Overlay>
|
||||
</div>
|
||||
<Button
|
||||
variant='solid'
|
||||
color='tertiary'
|
||||
leftIcon={<Gear />}
|
||||
onClick={() => useStore.setState({ settingsModal: true })}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
@ -195,7 +195,7 @@ function Track(props: TrackProps) {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='relative h-1 flex-grow overflow-hidden rounded-sm bg-transparent'>
|
||||
<div className='relative h-1 flex-1 overflow-hidden rounded-sm bg-transparent'>
|
||||
<div className='absolute z-1 h-3 bg-martian-red ' style={{ width: `${percentage}%` }} />
|
||||
<div className='absolute h-3 w-full bg-white/20' />
|
||||
</div>
|
||||
|
@ -3,7 +3,7 @@ import classNames from 'classnames'
|
||||
interface Props {
|
||||
name: string
|
||||
checked: boolean
|
||||
onChange: () => void
|
||||
onChange: (value: boolean) => void
|
||||
className?: string
|
||||
disabled?: boolean
|
||||
}
|
||||
@ -23,7 +23,7 @@ export default function Switch(props: Props) {
|
||||
name={props.name}
|
||||
className={classNames('peer hidden')}
|
||||
checked={props.checked}
|
||||
onChange={props.onChange}
|
||||
onChange={() => props.onChange(!props.checked)}
|
||||
/>
|
||||
<label
|
||||
htmlFor={props.name}
|
||||
|
@ -1,15 +1,16 @@
|
||||
import classNames from 'classnames'
|
||||
import { toast as createToast, Slide, ToastContainer } from 'react-toastify'
|
||||
import { useNavigate } from 'react-router-dom'
|
||||
import { mutate } from 'swr'
|
||||
|
||||
import Button from 'components/Button'
|
||||
import { CheckCircled, Cross, CrossCircled } from 'components/Icons'
|
||||
import Text from 'components/Text'
|
||||
import { DEFAULT_SETTINGS } from 'constants/defaultSettings'
|
||||
import { REDUCE_MOTION_KEY } from 'constants/localStore'
|
||||
import useLocalStorage from 'hooks/useLocalStorage'
|
||||
import useStore from 'store'
|
||||
|
||||
export default function Toaster() {
|
||||
const enableAnimations = useStore((s) => s.enableAnimations)
|
||||
const [reduceMotion] = useLocalStorage<boolean>(REDUCE_MOTION_KEY, DEFAULT_SETTINGS.reduceMotion)
|
||||
const toast = useStore((s) => s.toast)
|
||||
|
||||
if (toast) {
|
||||
@ -66,7 +67,7 @@ export default function Toaster() {
|
||||
position='top-right'
|
||||
newestOnTop
|
||||
closeOnClick
|
||||
transition={enableAnimations ? Slide : undefined}
|
||||
transition={reduceMotion ? undefined : Slide}
|
||||
className='p-0'
|
||||
toastClassName='top-[73px] z-20 m-0 mb-4 flex w-full bg-transparent p-0'
|
||||
bodyClassName='p-0 m-0 w-full flex'
|
||||
|
@ -1,20 +1,19 @@
|
||||
import BigNumber from 'bignumber.js'
|
||||
import classNames from 'classnames'
|
||||
import Image from 'next/image'
|
||||
|
||||
import AssetImage from 'components/AssetImage'
|
||||
import Button from 'components/Button'
|
||||
import DisplayCurrency from 'components/DisplayCurrency'
|
||||
import { FormattedNumber } from 'components/FormattedNumber'
|
||||
import { ExclamationMarkTriangle, TrashBin } from 'components/Icons'
|
||||
import NumberInput from 'components/NumberInput'
|
||||
import Select from 'components/Select/Select'
|
||||
import Select from 'components/Select'
|
||||
import Text from 'components/Text'
|
||||
import { Tooltip } from 'components/Tooltip'
|
||||
import { ASSETS } from 'constants/assets'
|
||||
import useStore from 'store'
|
||||
import { BN } from 'utils/helpers'
|
||||
import { FormattedNumber } from 'components/FormattedNumber'
|
||||
import Button from 'components/Button'
|
||||
import { ExclamationMarkTriangle, TrashBin } from 'components/Icons'
|
||||
import { Tooltip } from 'components/Tooltip'
|
||||
import AssetImage from 'components/AssetImage'
|
||||
import { BNCoin } from 'types/classes/BNCoin'
|
||||
import { BN } from 'utils/helpers'
|
||||
|
||||
interface Props {
|
||||
amount: BigNumber
|
||||
@ -65,7 +64,8 @@ export default function TokenInput(props: Props) {
|
||||
defaultValue={props.asset.denom}
|
||||
onChange={onChangeAsset}
|
||||
title={props.accountId ? `Account ${props.accountId}` : 'Your Wallet'}
|
||||
className='border-r border-white/20 bg-white/5'
|
||||
className='h-full border-r border-white/20 bg-white/5'
|
||||
displayClassName='rounded-l-sm'
|
||||
/>
|
||||
) : (
|
||||
<div className='flex min-w-fit items-center gap-2 border-r border-white/20 bg-white/5 p-3'>
|
||||
@ -80,7 +80,7 @@ export default function TokenInput(props: Props) {
|
||||
onChange={props.onChange}
|
||||
amount={props.amount}
|
||||
max={props.max}
|
||||
className='border-none p-3'
|
||||
className='flex-1 border-none p-3'
|
||||
/>
|
||||
{props.onDelete && (
|
||||
<div role='button' className='grid items-center pr-2' onClick={props.onDelete}>
|
||||
@ -109,8 +109,9 @@ export default function TokenInput(props: Props) {
|
||||
</Text>
|
||||
<FormattedNumber
|
||||
className='mr-1 text-xs text-white/50'
|
||||
amount={props.max}
|
||||
amount={props.max.toNumber()}
|
||||
options={{ decimals: props.asset.decimals }}
|
||||
animate
|
||||
/>
|
||||
<Button
|
||||
dataTestId='token-input-max-button'
|
||||
|
@ -24,7 +24,7 @@ export default function TokenInputWithSlider(props: Props) {
|
||||
const [percentage, setPercentage] = useState(0)
|
||||
|
||||
function onChangeSlider(percentage: number) {
|
||||
const newAmount = BN(percentage).div(100).times(props.max).integerValue()
|
||||
const newAmount = BN(percentage).dividedBy(100).multipliedBy(props.max).integerValue()
|
||||
setPercentage(percentage)
|
||||
setAmount(newAmount)
|
||||
props.onChange(newAmount)
|
||||
@ -32,7 +32,7 @@ export default function TokenInputWithSlider(props: Props) {
|
||||
|
||||
function onChangeAmount(newAmount: BigNumber) {
|
||||
setAmount(newAmount)
|
||||
setPercentage(BN(newAmount).div(props.max).times(100).toNumber())
|
||||
setPercentage(BN(newAmount).dividedBy(props.max).multipliedBy(100).toNumber())
|
||||
props.onChange(newAmount)
|
||||
}
|
||||
|
||||
|
@ -3,8 +3,10 @@ import classNames from 'classnames'
|
||||
import { ReactNode } from 'react'
|
||||
|
||||
import { Questionmark } from 'components/Icons'
|
||||
import useStore from 'store'
|
||||
import TooltipContent from 'components/Tooltip/TooltipContent'
|
||||
import { DEFAULT_SETTINGS } from 'constants/defaultSettings'
|
||||
import { REDUCE_MOTION_KEY } from 'constants/localStore'
|
||||
import useLocalStorage from 'hooks/useLocalStorage'
|
||||
|
||||
interface Props {
|
||||
content: ReactNode | string
|
||||
@ -19,7 +21,7 @@ interface Props {
|
||||
export type TooltipType = 'info' | 'warning' | 'error'
|
||||
|
||||
export const Tooltip = (props: Props) => {
|
||||
const enableAnimations = useStore((s) => s.enableAnimations)
|
||||
const [reduceMotion] = useLocalStorage<boolean>(REDUCE_MOTION_KEY, DEFAULT_SETTINGS.reduceMotion)
|
||||
|
||||
return (
|
||||
<Tippy
|
||||
@ -34,7 +36,7 @@ export const Tooltip = (props: Props) => {
|
||||
className={classNames(
|
||||
props.underline &&
|
||||
'border-b-1 cursor-pointer border border-x-0 border-t-0 border-dashed border-white/50 hover:border-transparent',
|
||||
enableAnimations && 'transition-all',
|
||||
!reduceMotion && 'transition-all',
|
||||
props.className,
|
||||
)}
|
||||
>
|
||||
|
@ -14,6 +14,7 @@ export default function AssetButton(props: Props) {
|
||||
color='tertiary'
|
||||
variant='transparent'
|
||||
className='w-full border border-white/20'
|
||||
textClassNames='flex flex-1'
|
||||
size='md'
|
||||
hasSubmenu
|
||||
{...props}
|
||||
|
@ -1,10 +1,10 @@
|
||||
import { useCallback, useMemo, useState } from 'react'
|
||||
import { useCallback, useState } from 'react'
|
||||
|
||||
import { SwapIcon } from 'components/Icons'
|
||||
import Text from 'components/Text'
|
||||
import { ASSETS } from 'constants/assets'
|
||||
import AssetButton from 'components/Trade/TradeModule/AssetSelector/AssetButton'
|
||||
import AssetOverlay, { OverlayState } from 'components/Trade/TradeModule/AssetSelector/AssetOverlay'
|
||||
import { ASSETS } from 'constants/assets'
|
||||
|
||||
export default function AssetSelector() {
|
||||
const [overlayState, setOverlayState] = useState<OverlayState>('closed')
|
||||
@ -38,11 +38,6 @@ export default function AssetSelector() {
|
||||
[setOverlayState],
|
||||
)
|
||||
|
||||
const buyAssets = useMemo(
|
||||
() => ASSETS.filter((asset) => asset.denom !== sellAsset.denom),
|
||||
[sellAsset],
|
||||
)
|
||||
|
||||
return (
|
||||
<div className='grid-rows-auto relative grid grid-cols-[1fr_min-content_1fr] gap-y-2 bg-white/5 p-3'>
|
||||
<Text size='sm'>Buy</Text>
|
@ -1,29 +1,9 @@
|
||||
import { useState } from 'react'
|
||||
import { useParams } from 'react-router-dom'
|
||||
import classNames from 'classnames'
|
||||
import { useState } from 'react'
|
||||
|
||||
import Loading from 'components/Loading'
|
||||
import Text from 'components/Text'
|
||||
import Divider from 'components/Divider'
|
||||
import RangeInput from 'components/RangeInput'
|
||||
import AssetSelector from 'components/Trade/TradeModule/AssetSelector/AssetSelector'
|
||||
|
||||
function Content() {
|
||||
const params = useParams()
|
||||
const address = params.address
|
||||
const currentAccount = params.accountId
|
||||
const hasAccount = !isNaN(Number(currentAccount))
|
||||
|
||||
if (!address) return <Text size='sm'>You need to be connected to trade</Text>
|
||||
|
||||
if (!hasAccount) return <Text size='sm'>Select an Account to trade</Text>
|
||||
|
||||
return <Text size='sm'>{`Trade with Account ${currentAccount}`}</Text>
|
||||
}
|
||||
|
||||
function Fallback() {
|
||||
return <Loading className='h-4 w-50' />
|
||||
}
|
||||
import AssetSelector from 'components/Trade/TradeModule/AssetSelector'
|
||||
|
||||
export default function TradeModule() {
|
||||
const [value, setValue] = useState(0)
|
||||
@ -38,10 +18,13 @@ export default function TradeModule() {
|
||||
>
|
||||
<AssetSelector />
|
||||
<Divider />
|
||||
|
||||
<div className='p-4'>
|
||||
<RangeInput max={4000} marginThreshold={2222} value={value} onChange={setValue} />
|
||||
</div>
|
||||
<RangeInput
|
||||
max={4000}
|
||||
marginThreshold={2222}
|
||||
value={value}
|
||||
onChange={setValue}
|
||||
wrapperClassName='p-4'
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
@ -17,10 +17,10 @@ import Overlay from 'components/Overlay'
|
||||
import Text from 'components/Text'
|
||||
import { IS_TESTNET } from 'constants/env'
|
||||
import useToggle from 'hooks/useToggle'
|
||||
import useWalletBalances from 'hooks/useWalletBalances'
|
||||
import useStore from 'store'
|
||||
import { getBaseAsset, getEnabledMarketAssets } from 'utils/assets'
|
||||
import { formatValue, truncate } from 'utils/formatters'
|
||||
import useWalletBalances from 'hooks/useWalletBalances'
|
||||
import { BN } from 'utils/helpers'
|
||||
|
||||
export default function ConnectedButton() {
|
||||
@ -64,7 +64,7 @@ export default function ConnectedButton() {
|
||||
if (!walletBalances || walletBalances.length === 0) return
|
||||
const newAmount = BigNumber(
|
||||
walletBalances?.find((coin: Coin) => coin.denom === baseAsset.denom)?.amount ?? 0,
|
||||
).div(10 ** baseAsset.decimals)
|
||||
).dividedBy(10 ** baseAsset.decimals)
|
||||
|
||||
if (walletAmount.isEqualTo(newAmount)) return
|
||||
setWalletAmount(newAmount)
|
||||
@ -120,7 +120,7 @@ export default function ConnectedButton() {
|
||||
<FormattedNumber
|
||||
animate
|
||||
className='flex items-end text-2xl '
|
||||
amount={walletAmount}
|
||||
amount={walletAmount.toNumber()}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -32,7 +32,7 @@ export const WalletConnectProvider: FC<Props> = ({ children }) => {
|
||||
'relative z-50 w-[460px] max-w-full rounded-base border border-white/20 bg-white/5 p-6 pb-4 backdrop-blur-3xl flex flex-wrap focus-visible:outline-none',
|
||||
modalOverlay:
|
||||
'fixed inset-0 bg-black/60 w-full h-full z-40 flex items-center justify-center cursor-pointer m-0 backdrop-blur-sm',
|
||||
modalHeader: 'text-lg text-white mb-4 flex-grow',
|
||||
modalHeader: 'text-lg text-white mb-4 flex-1',
|
||||
modalCloseButton: 'inline-block',
|
||||
walletList: 'w-full',
|
||||
wallet:
|
||||
|
9
src/constants/defaultSettings.ts
Normal file
9
src/constants/defaultSettings.ts
Normal file
@ -0,0 +1,9 @@
|
||||
import { ASSETS } from 'constants/assets'
|
||||
|
||||
export const DEFAULT_SETTINGS: Settings = {
|
||||
reduceMotion: false,
|
||||
lendAssets: false,
|
||||
preferredAsset: ASSETS[0].denom,
|
||||
displayCurrency: ASSETS[0].denom,
|
||||
slippage: 0.02,
|
||||
}
|
@ -1,4 +1,7 @@
|
||||
export const PREFERRED_ASSET_KEY = 'favouriteAsset'
|
||||
export const DISPLAY_CURRENCY_KEY = 'displayCurrency'
|
||||
export const ENABLE_ANIMATIONS_KEY = 'enableAnimations'
|
||||
export const REDUCE_MOTION_KEY = 'reduceMotion'
|
||||
export const FAVORITE_ASSETS = 'favoriteAssets'
|
||||
export const LEND_ASSETS_KEY = 'lendAssets'
|
||||
export const AUTO_LEND_ENABLED_ACCOUNT_IDS_KEY = 'autoLendEnabledAccountIds'
|
||||
export const SLIPPAGE_KEY = 'slippage'
|
||||
|
@ -1,18 +1,20 @@
|
||||
import debounce from 'debounce-promise'
|
||||
import { useMemo, useState } from 'react'
|
||||
|
||||
import { hardcodedFee } from 'utils/constants'
|
||||
import getMinLpToReceive from 'api/vaults/getMinLpToReceive'
|
||||
import { DEFAULT_SETTINGS } from 'constants/defaultSettings'
|
||||
import { SLIPPAGE_KEY } from 'constants/localStore'
|
||||
import useLocalStorage from 'hooks/useLocalStorage'
|
||||
import usePrices from 'hooks/usePrices'
|
||||
import useStore from 'store'
|
||||
import { BNCoin } from 'types/classes/BNCoin'
|
||||
import { Action } from 'types/generated/mars-credit-manager/MarsCreditManager.types'
|
||||
import { hardcodedFee } from 'utils/constants'
|
||||
import { BN } from 'utils/helpers'
|
||||
import {
|
||||
getEnterVaultActions,
|
||||
getVaultDepositCoinsAndValue,
|
||||
getVaultSwapActions,
|
||||
} from 'utils/vaults'
|
||||
import { BN } from 'utils/helpers'
|
||||
|
||||
interface Props {
|
||||
vault: Vault
|
||||
@ -22,7 +24,7 @@ interface Props {
|
||||
export default function useDepositVault(props: Props): { actions: Action[]; fee: StdFee } {
|
||||
const [minLpToReceive, setMinLpToReceive] = useState<BigNumber>(BN(0))
|
||||
const { data: prices } = usePrices()
|
||||
const slippage = useStore((s) => s.slippage)
|
||||
const [slippage] = useLocalStorage<number>(SLIPPAGE_KEY, DEFAULT_SETTINGS.slippage)
|
||||
|
||||
const borrowings: BNCoin[] = useMemo(
|
||||
() => props.borrowings.filter((borrowing) => borrowing.amount.gt(0)),
|
||||
@ -60,7 +62,7 @@ export default function useDepositVault(props: Props): { actions: Action[]; fee:
|
||||
slippage,
|
||||
)
|
||||
|
||||
if (!lpAmount || lpAmount.eq(minLpToReceive)) return
|
||||
if (!lpAmount || lpAmount.isEqualTo(minLpToReceive)) return
|
||||
setMinLpToReceive(lpAmount)
|
||||
}, [
|
||||
primaryCoin,
|
||||
|
@ -1,33 +0,0 @@
|
||||
import { useCallback, useEffect } from 'react'
|
||||
|
||||
import { ENABLE_ANIMATIONS_KEY } from 'constants/localStore'
|
||||
import useStore from 'store'
|
||||
|
||||
export const useAnimations = () => {
|
||||
const enableAnimations = useStore((s) => s.enableAnimations)
|
||||
const enableAnimationsLocalStorage =
|
||||
typeof window !== 'undefined' ? window.localStorage.getItem(ENABLE_ANIMATIONS_KEY) : false
|
||||
|
||||
const queryChangeHandler = useCallback(
|
||||
(event: MediaQueryListEvent) => {
|
||||
if (enableAnimationsLocalStorage) return
|
||||
useStore.setState({ enableAnimations: !event?.matches ?? true })
|
||||
},
|
||||
[enableAnimationsLocalStorage],
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
const mediaQuery: MediaQueryList = window.matchMedia('(prefers-reduced-motion: reduce)')
|
||||
const storeSetting = enableAnimationsLocalStorage === 'true'
|
||||
useStore.setState({ enableAnimations: storeSetting })
|
||||
|
||||
if (mediaQuery) {
|
||||
if (enableAnimationsLocalStorage) return
|
||||
useStore.setState({ enableAnimations: !mediaQuery.matches })
|
||||
mediaQuery.addEventListener('change', queryChangeHandler)
|
||||
return () => mediaQuery.removeEventListener('change', queryChangeHandler)
|
||||
}
|
||||
}, [enableAnimations, enableAnimationsLocalStorage, queryChangeHandler])
|
||||
|
||||
return enableAnimations
|
||||
}
|
51
src/hooks/useBorrowMarketAssetsTableData.ts
Normal file
51
src/hooks/useBorrowMarketAssetsTableData.ts
Normal file
@ -0,0 +1,51 @@
|
||||
import { useMemo } from 'react'
|
||||
|
||||
import useDepositEnabledMarkets from 'hooks/useDepositEnabledMarkets'
|
||||
import useMarketBorrowings from 'hooks/useMarketBorrowings'
|
||||
import useMarketDeposits from 'hooks/useMarketDeposits'
|
||||
import useMarketLiquidities from 'hooks/useMarketLiquidities'
|
||||
import { byDenom } from 'utils/array'
|
||||
import { getAssetByDenom } from 'utils/assets'
|
||||
import { BN } from 'utils/helpers'
|
||||
|
||||
import useCurrentAccountDebts from './useCurrentAccountDebts'
|
||||
|
||||
export default function useBorrowMarketAssetsTableData(): {
|
||||
accountBorrowedAssets: BorrowMarketTableData[]
|
||||
availableAssets: BorrowMarketTableData[]
|
||||
} {
|
||||
const markets = useDepositEnabledMarkets()
|
||||
const accountDebts = useCurrentAccountDebts()
|
||||
const { data: borrowData } = useMarketBorrowings()
|
||||
const { data: marketDeposits } = useMarketDeposits()
|
||||
const { data: marketLiquidities } = useMarketLiquidities()
|
||||
|
||||
return useMemo(() => {
|
||||
const accountBorrowedAssets: BorrowMarketTableData[] = [],
|
||||
availableAssets: BorrowMarketTableData[] = []
|
||||
|
||||
markets.forEach(({ denom, liquidityRate, liquidationThreshold, maxLtv }) => {
|
||||
const asset = getAssetByDenom(denom) as Asset
|
||||
const borrow = borrowData.find((borrow) => borrow.denom === denom)
|
||||
const marketDepositAmount = BN(marketDeposits.find(byDenom(denom))?.amount ?? 0)
|
||||
const marketLiquidityAmount = BN(marketLiquidities.find(byDenom(denom))?.amount ?? 0)
|
||||
|
||||
const debt = accountDebts?.find((debt) => debt.denom === denom)
|
||||
if (!borrow) return
|
||||
|
||||
const borrowMarketAsset: BorrowMarketTableData = {
|
||||
...borrow,
|
||||
asset,
|
||||
debt: debt?.amount,
|
||||
marketDepositAmount,
|
||||
marketLiquidityAmount,
|
||||
marketLiquidityRate: liquidityRate,
|
||||
marketLiquidationThreshold: liquidationThreshold,
|
||||
marketMaxLtv: maxLtv,
|
||||
}
|
||||
;(borrowMarketAsset.debt ? accountBorrowedAssets : availableAssets).push(borrowMarketAsset)
|
||||
})
|
||||
|
||||
return { accountBorrowedAssets, availableAssets }
|
||||
}, [accountDebts, borrowData, markets, marketDeposits, marketLiquidities])
|
||||
}
|
6
src/hooks/useCurrentAccountDebts.ts
Normal file
6
src/hooks/useCurrentAccountDebts.ts
Normal file
@ -0,0 +1,6 @@
|
||||
import useCurrentAccount from 'hooks/useCurrentAccount'
|
||||
|
||||
export default function useCurrentAccountDebts() {
|
||||
const account = useCurrentAccount()
|
||||
return account?.debts ?? []
|
||||
}
|
@ -1,21 +1,35 @@
|
||||
import { useCallback } from 'react'
|
||||
import { useCallback, useMemo } from 'react'
|
||||
|
||||
import useStore from 'store'
|
||||
import { BN } from 'utils/helpers'
|
||||
import { byDenom } from 'utils/array'
|
||||
import { ASSETS } from 'constants/assets'
|
||||
import { DEFAULT_SETTINGS } from 'constants/defaultSettings'
|
||||
import { DISPLAY_CURRENCY_KEY } from 'constants/localStore'
|
||||
import useLocalStorage from 'hooks/useLocalStorage'
|
||||
import usePrices from 'hooks/usePrices'
|
||||
import { byDenom } from 'utils/array'
|
||||
import { getDisplayCurrencies } from 'utils/assets'
|
||||
import { BN } from 'utils/helpers'
|
||||
|
||||
function useDisplayCurrencyPrice() {
|
||||
const { data: prices } = usePrices()
|
||||
const displayCurrency = useStore((s) => s.displayCurrency)
|
||||
const displayCurrencies = getDisplayCurrencies()
|
||||
const [displayCurrency] = useLocalStorage<string>(
|
||||
DISPLAY_CURRENCY_KEY,
|
||||
DEFAULT_SETTINGS.displayCurrency,
|
||||
)
|
||||
|
||||
const displayCurrencyAsset = useMemo(
|
||||
() =>
|
||||
displayCurrencies.find((asset) => asset.denom === displayCurrency) ?? displayCurrencies[0],
|
||||
[displayCurrency, displayCurrencies],
|
||||
)
|
||||
|
||||
const getConversionRate = useCallback(
|
||||
(denom: string) => {
|
||||
const assetPrice = prices.find(byDenom(denom))
|
||||
const displayCurrencyPrice = prices.find(byDenom(displayCurrency.denom))
|
||||
const displayCurrencyPrice = prices.find(byDenom(displayCurrency))
|
||||
|
||||
if (assetPrice && displayCurrencyPrice) {
|
||||
return BN(assetPrice.amount).div(displayCurrencyPrice.amount)
|
||||
return BN(assetPrice.amount).dividedBy(displayCurrencyPrice.amount)
|
||||
} else {
|
||||
throw 'Given denom or display currency price has not found'
|
||||
}
|
||||
@ -32,7 +46,7 @@ function useDisplayCurrencyPrice() {
|
||||
return {
|
||||
getConversionRate,
|
||||
convertAmount,
|
||||
symbol: displayCurrency.symbol,
|
||||
symbol: displayCurrencyAsset?.symbol,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,11 +1,13 @@
|
||||
import { AvailableBorrowings } from 'components/Borrow/Borrowings'
|
||||
import { ActiveBorrowings } from 'components/Borrow/Borrowings'
|
||||
import BorrowTable from 'components/Borrow/BorrowTable'
|
||||
import useBorrowMarketAssetsTableData from 'hooks/useBorrowMarketAssetsTableData'
|
||||
|
||||
export default function BorrowPage() {
|
||||
const { accountBorrowedAssets, availableAssets } = useBorrowMarketAssetsTableData()
|
||||
|
||||
return (
|
||||
<>
|
||||
<ActiveBorrowings />
|
||||
<AvailableBorrowings />
|
||||
<BorrowTable data={accountBorrowedAssets} title='Borrowed Assets' />
|
||||
<BorrowTable data={availableAssets} title='Available to borrow' />
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { AvailableVaults, DepositedVaults } from 'components/Earn/Farm/Vaults'
|
||||
import Tab from 'components/Earn/Tab'
|
||||
import { AvailableVaults, DepositedVaults } from 'components/Earn/vault/Vaults'
|
||||
|
||||
export default function FarmPage() {
|
||||
return (
|
||||
|
@ -1,4 +1,4 @@
|
||||
import AccountOverview from 'components/Portfolio/AccountOverview'
|
||||
import AccountOverview from 'components/Account/AccountOverview'
|
||||
|
||||
export default function PortfolioPage() {
|
||||
return <AccountOverview />
|
||||
|
@ -4,7 +4,7 @@ export default function Document() {
|
||||
return (
|
||||
<Html className='m-0 p-0' lang='en'>
|
||||
<Head />
|
||||
<body className='m-0 cursor-default bg-body p-0 font-sans text-white'>
|
||||
<body className='m-0 cursor-default bg-body p-0 font-sans text-white scrollbar-hide'>
|
||||
<Main />
|
||||
<NextScript />
|
||||
</body>
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user