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 { render } from '@testing-library/react'
|
||||||
|
|
||||||
import { CircularProgress } from 'components/CircularProgress'
|
import { CircularProgress } from 'components/CircularProgress'
|
||||||
import useStore from 'store'
|
import { REDUCE_MOTION_KEY } from 'constants/localStore'
|
||||||
|
|
||||||
describe('<CircularProgress />', () => {
|
describe('<CircularProgress />', () => {
|
||||||
afterAll(() => {
|
afterAll(() => {
|
||||||
useStore.clearState()
|
localStorage.removeItem(REDUCE_MOTION_KEY)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should render', () => {
|
it('should render', () => {
|
||||||
@ -15,7 +15,7 @@ describe('<CircularProgress />', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('should render `...` when animations disabled', () => {
|
it('should render `...` when animations disabled', () => {
|
||||||
useStore.setState({ enableAnimations: false })
|
localStorage.setItem(REDUCE_MOTION_KEY, 'true')
|
||||||
|
|
||||||
const { getByText } = render(<CircularProgress />)
|
const { getByText } = render(<CircularProgress />)
|
||||||
const threeDots = getByText('...')
|
const threeDots = getByText('...')
|
||||||
@ -24,7 +24,7 @@ describe('<CircularProgress />', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('should render the component with animation classes when animations enabled', () => {
|
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 { container } = render(<CircularProgress />)
|
||||||
const progressWithAnimations = container.querySelector('.animate-progress')
|
const progressWithAnimations = container.querySelector('.animate-progress')
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { render } from '@testing-library/react'
|
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 { ASSETS } from 'constants/assets'
|
||||||
import { BN } from 'utils/helpers'
|
import { BN } from 'utils/helpers'
|
||||||
|
|
@ -1,13 +1,13 @@
|
|||||||
import { render } from '@testing-library/react'
|
import { render } from '@testing-library/react'
|
||||||
|
|
||||||
import Modal from 'components/Modal'
|
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 { TESTNET_VAULTS_META_DATA } from 'constants/vaults'
|
||||||
import useStore from 'store'
|
import useStore from 'store'
|
||||||
import { BN } from 'utils/helpers'
|
import { BN } from 'utils/helpers'
|
||||||
|
|
||||||
jest.mock('components/Modal')
|
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 = {
|
const mockedDepositedVault: DepositedVault = {
|
||||||
...TESTNET_VAULTS_META_DATA[0],
|
...TESTNET_VAULTS_META_DATA[0],
|
||||||
@ -36,31 +36,26 @@ const mockedDepositedVault: DepositedVault = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
describe('<UnlockModal />', () => {
|
describe('<UnlockModal />', () => {
|
||||||
beforeAll(() => {
|
afterAll(() => {
|
||||||
useStore.setState({ unlockModal: null })
|
useStore.clearState()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should render', () => {
|
it('should render', () => {
|
||||||
const { container } = render(<UnlockModal />)
|
const { container } = render(<UnlockModal />)
|
||||||
expect(mockedModal).toHaveBeenCalledTimes(1)
|
|
||||||
expect(container).toBeInTheDocument()
|
expect(container).toBeInTheDocument()
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('should set open attribute correctly', () => {
|
describe('should set content correctly', () => {
|
||||||
it('should set open = false when no modal is present in store', () => {
|
it('should have no content when no modal is present in store', () => {
|
||||||
|
useStore.setState({ unlockModal: null })
|
||||||
render(<UnlockModal />)
|
render(<UnlockModal />)
|
||||||
expect(mockedModal).toHaveBeenCalledWith(
|
expect(mockedModal).toHaveBeenCalledTimes(0)
|
||||||
expect.objectContaining({ open: false }),
|
|
||||||
expect.anything(),
|
|
||||||
)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
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 } })
|
useStore.setState({ unlockModal: { vault: mockedDepositedVault } })
|
||||||
render(<UnlockModal />)
|
render(<UnlockModal />)
|
||||||
expect(mockedModal).toHaveBeenLastCalledWith(
|
expect(mockedModal).toHaveBeenCalledTimes(1)
|
||||||
expect.objectContaining({ open: true }),
|
|
||||||
expect.anything(),
|
|
||||||
)
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@ -26,6 +26,7 @@ module.exports = {
|
|||||||
|
|
||||||
// Handle module aliases
|
// Handle module aliases
|
||||||
'^app/(.*)$': '<rootDir>/src/app/$1',
|
'^app/(.*)$': '<rootDir>/src/app/$1',
|
||||||
|
'^api/(.*)$': '<rootDir>/src/api/$1',
|
||||||
'^components/(.*)$': '<rootDir>/src/components/$1',
|
'^components/(.*)$': '<rootDir>/src/components/$1',
|
||||||
'^constants/(.*)$': '<rootDir>/src/constants/$1',
|
'^constants/(.*)$': '<rootDir>/src/constants/$1',
|
||||||
'^fonts/(.*)$': '<rootDir>/src/fonts/$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 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[]> {
|
export default async function getMarketBorrowings(): Promise<BorrowAsset[]> {
|
||||||
const liquidities = await getMarketLiquidities()
|
const liquidities = await getMarketLiquidities()
|
||||||
@ -20,7 +20,7 @@ export default async function getMarketBorrowings(): Promise<BorrowAsset[]> {
|
|||||||
borrowRate: market.borrowRate ?? 0,
|
borrowRate: market.borrowRate ?? 0,
|
||||||
liquidity: {
|
liquidity: {
|
||||||
amount: BN(amount),
|
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 getPrice from 'api/prices/getPrice'
|
||||||
|
import { BN } from 'utils/helpers'
|
||||||
|
|
||||||
const MARS_MAINNET_DENOM = 'ibc/573FCD90FACEE750F55A8864EF7D38265F07E5A9273FA0E8DAFD39951332B580'
|
const MARS_MAINNET_DENOM = 'ibc/573FCD90FACEE750F55A8864EF7D38265F07E5A9273FA0E8DAFD39951332B580'
|
||||||
const MARS_OSMO_POOL_URL = 'https://lcd-osmosis.blockapsis.com/osmosis/gamm/v1beta1/pools/907'
|
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 assetOut = findPoolAssetByTokenDenom(poolAssets, 'uosmo') as PoolAsset
|
||||||
|
|
||||||
const numerator = BN(assetIn.token.amount).div(assetIn.weight)
|
const numerator = BN(assetIn.token.amount).dividedBy(assetIn.weight)
|
||||||
const denominator = BN(assetOut.token.amount).div(assetOut.weight)
|
const denominator = BN(assetOut.token.amount).dividedBy(assetOut.weight)
|
||||||
|
|
||||||
return numerator.dividedBy(denominator)
|
return numerator.dividedBy(denominator)
|
||||||
}
|
}
|
||||||
|
@ -17,7 +17,7 @@ export default async function getVaultConfigs(
|
|||||||
lpTokenOut: lpDenom,
|
lpTokenOut: lpDenom,
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
.times(1 - slippage)
|
.multipliedBy(1 - slippage)
|
||||||
.integerValue()
|
.integerValue()
|
||||||
} catch (ex) {
|
} catch (ex) {
|
||||||
throw ex
|
throw ex
|
||||||
|
@ -14,18 +14,23 @@ import { FormattedNumber } from 'components/FormattedNumber'
|
|||||||
import { SortAsc, SortDesc, SortNone } from 'components/Icons'
|
import { SortAsc, SortDesc, SortNone } from 'components/Icons'
|
||||||
import Text from 'components/Text'
|
import Text from 'components/Text'
|
||||||
import { ASSETS } from 'constants/assets'
|
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 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 {
|
interface Props {
|
||||||
data: Account
|
data: Account
|
||||||
}
|
}
|
||||||
|
|
||||||
export const AccountBalancesTable = (props: Props) => {
|
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 { data: prices } = usePrices()
|
||||||
|
|
||||||
const [sorting, setSorting] = React.useState<SortingState>([])
|
const [sorting, setSorting] = React.useState<SortingState>([])
|
||||||
@ -104,13 +109,12 @@ export const AccountBalancesTable = (props: Props) => {
|
|||||||
return (
|
return (
|
||||||
<FormattedNumber
|
<FormattedNumber
|
||||||
className='text-right text-xs'
|
className='text-right text-xs'
|
||||||
amount={BN(
|
amount={demagnify(
|
||||||
demagnify(
|
row.original.amount,
|
||||||
row.original.amount,
|
ASSETS.find((asset) => asset.denom === row.original.denom) ?? ASSETS[0],
|
||||||
ASSETS.find((asset) => asset.denom === row.original.denom) ?? ASSETS[0],
|
|
||||||
),
|
|
||||||
)}
|
)}
|
||||||
options={{ maxDecimals: 4 }}
|
options={{ maxDecimals: 4 }}
|
||||||
|
animate
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
@ -123,8 +127,9 @@ export const AccountBalancesTable = (props: Props) => {
|
|||||||
return (
|
return (
|
||||||
<FormattedNumber
|
<FormattedNumber
|
||||||
className='text-xs'
|
className='text-xs'
|
||||||
amount={BN(row.original.apy)}
|
amount={row.original.apy}
|
||||||
options={{ maxDecimals: 2, minDecimals: 2, suffix: '%' }}
|
options={{ maxDecimals: 2, minDecimals: 2, suffix: '%' }}
|
||||||
|
animate
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
@ -146,7 +151,7 @@ export const AccountBalancesTable = (props: Props) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<table className='w-full'>
|
<table className='w-full'>
|
||||||
<thead className='border-b border-b-white/5'>
|
<thead className='border-b border-white/5'>
|
||||||
{table.getHeaderGroups().map((headerGroup) => (
|
{table.getHeaderGroups().map((headerGroup) => (
|
||||||
<tr key={headerGroup.id}>
|
<tr key={headerGroup.id}>
|
||||||
{headerGroup.headers.map((header, index) => {
|
{headerGroup.headers.map((header, index) => {
|
||||||
|
@ -89,12 +89,13 @@ function Item(props: ItemProps) {
|
|||||||
{props.title}
|
{props.title}
|
||||||
</Text>
|
</Text>
|
||||||
</div>
|
</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 ? (
|
{props.isPercentage ? (
|
||||||
<FormattedNumber
|
<FormattedNumber
|
||||||
amount={props.current}
|
amount={props.current.toNumber()}
|
||||||
options={{ suffix: '%', minDecimals: 2, maxDecimals: 2 }}
|
options={{ suffix: '%', minDecimals: 2, maxDecimals: 2 }}
|
||||||
className='text-sm'
|
className='text-sm'
|
||||||
|
animate
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<DisplayCurrency
|
<DisplayCurrency
|
||||||
@ -109,9 +110,10 @@ function Item(props: ItemProps) {
|
|||||||
</span>
|
</span>
|
||||||
{props.isPercentage ? (
|
{props.isPercentage ? (
|
||||||
<FormattedNumber
|
<FormattedNumber
|
||||||
amount={props.change}
|
amount={props.change.toNumber()}
|
||||||
options={{ suffix: '%', minDecimals: 2, maxDecimals: 2 }}
|
options={{ suffix: '%', minDecimals: 2, maxDecimals: 2 }}
|
||||||
className={classNames('text-sm', increase ? 'text-profit' : 'text-loss')}
|
className={classNames('text-sm', increase ? 'text-profit' : 'text-loss')}
|
||||||
|
animate
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<DisplayCurrency
|
<DisplayCurrency
|
||||||
|
@ -2,7 +2,9 @@ import classNames from 'classnames'
|
|||||||
|
|
||||||
import { Heart } from 'components/Icons'
|
import { Heart } from 'components/Icons'
|
||||||
import Text from 'components/Text'
|
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 {
|
interface Props {
|
||||||
health: number
|
health: number
|
||||||
@ -11,7 +13,7 @@ interface Props {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default function AccountHealth(props: 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
|
const healthBarWidth = (props.health / 100) * 53
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -33,7 +35,7 @@ export default function AccountHealth(props: Props) {
|
|||||||
rx='2'
|
rx='2'
|
||||||
fill='url(#bar)'
|
fill='url(#bar)'
|
||||||
style={{
|
style={{
|
||||||
transition: enableAnimations ? 'width 1s ease' : 'none',
|
transition: reduceMotion ? 'none' : 'width 1s ease',
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<defs>
|
<defs>
|
||||||
|
@ -11,20 +11,21 @@ import {
|
|||||||
|
|
||||||
import { FormattedNumber } from 'components/FormattedNumber'
|
import { FormattedNumber } from 'components/FormattedNumber'
|
||||||
import Text from 'components/Text'
|
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 { formatValue } from 'utils/formatters'
|
||||||
import { BN } from 'utils/helpers'
|
import { BN } from 'utils/helpers'
|
||||||
|
|
||||||
export const RiskChart = ({ data }: RiskChartProps) => {
|
export const RiskChart = ({ data }: RiskChartProps) => {
|
||||||
const enableAnimations = useStore((s) => s.enableAnimations)
|
const [reduceMotion] = useLocalStorage<boolean>(REDUCE_MOTION_KEY, DEFAULT_SETTINGS.reduceMotion)
|
||||||
const accountStats = null
|
|
||||||
const currentRisk = BN(0)
|
const currentRisk = BN(0)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='flex w-full flex-wrap overflow-hidden py-2'>
|
<div className='flex w-full flex-wrap overflow-hidden py-2'>
|
||||||
<FormattedNumber
|
<FormattedNumber
|
||||||
className='px-3 pb-2 text-lg'
|
className='px-3 pb-2 text-lg'
|
||||||
amount={currentRisk.times(100)}
|
amount={currentRisk.multipliedBy(100).toNumber()}
|
||||||
options={{
|
options={{
|
||||||
maxDecimals: 0,
|
maxDecimals: 0,
|
||||||
minDecimals: 0,
|
minDecimals: 0,
|
||||||
@ -92,7 +93,7 @@ export const RiskChart = ({ data }: RiskChartProps) => {
|
|||||||
dataKey='risk'
|
dataKey='risk'
|
||||||
stroke='#FFFFFF'
|
stroke='#FFFFFF'
|
||||||
fill='url(#chartGradient)'
|
fill='url(#chartGradient)'
|
||||||
isAnimationActive={enableAnimations}
|
isAnimationActive={!reduceMotion}
|
||||||
/>
|
/>
|
||||||
</AreaChart>
|
</AreaChart>
|
||||||
</ResponsiveContainer>
|
</ResponsiveContainer>
|
||||||
|
@ -13,8 +13,9 @@ export default function AmountAndValue(props: Props) {
|
|||||||
<TitleAndSubCell
|
<TitleAndSubCell
|
||||||
title={
|
title={
|
||||||
<FormattedNumber
|
<FormattedNumber
|
||||||
amount={props.amount}
|
amount={props.amount.toNumber()}
|
||||||
options={{ decimals: props.asset.decimals, abbreviated: true }}
|
options={{ decimals: props.asset.decimals, abbreviated: true }}
|
||||||
|
animate
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
sub={
|
sub={
|
||||||
|
@ -1,9 +1,11 @@
|
|||||||
import classNames from 'classnames'
|
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() {
|
export default function Background() {
|
||||||
const enableAnimations = useStore((s) => s.enableAnimations)
|
const [reduceMotion] = useLocalStorage<boolean>(REDUCE_MOTION_KEY, DEFAULT_SETTINGS.reduceMotion)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='background pointer-events-none fixed inset-0 h-full w-full overflow-hidden bg-body'>
|
<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]',
|
'left-[-10vw] top-[-10vw]',
|
||||||
'bg-orb-primary blur-orb-primary ',
|
'bg-orb-primary blur-orb-primary ',
|
||||||
'translate-x-0 translate-y-0 rounded-full opacity-20',
|
'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
|
<div
|
||||||
@ -28,7 +30,7 @@ export default function Background() {
|
|||||||
'bottom-[-10vw] right-[-8vw]',
|
'bottom-[-10vw] right-[-8vw]',
|
||||||
'bg-orb-secondary blur-orb-secondary',
|
'bg-orb-secondary blur-orb-secondary',
|
||||||
'translate-x-0 translate-y-0 rounded-full opacity-30',
|
'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
|
<div
|
||||||
@ -40,7 +42,7 @@ export default function Background() {
|
|||||||
'right-[-4vw] top-[-10vw]',
|
'right-[-4vw] top-[-10vw]',
|
||||||
'bg-orb-tertiary blur-orb-tertiary ',
|
'bg-orb-tertiary blur-orb-tertiary ',
|
||||||
'translate-x-0 translate-y-0 rounded-full opacity-20',
|
'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>
|
</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 {
|
import { ColumnDef, Row, Table } from '@tanstack/react-table'
|
||||||
ColumnDef,
|
import { useCallback, useMemo } from 'react'
|
||||||
flexRender,
|
|
||||||
getCoreRowModel,
|
|
||||||
getSortedRowModel,
|
|
||||||
SortingState,
|
|
||||||
useReactTable,
|
|
||||||
} from '@tanstack/react-table'
|
|
||||||
import classNames from 'classnames'
|
|
||||||
import React from 'react'
|
|
||||||
|
|
||||||
import AmountAndValue from 'components/AmountAndValue'
|
import AmountAndValue from 'components/AmountAndValue'
|
||||||
import AssetExpanded from 'components/Borrow/AssetExpanded'
|
import AssetImage from 'components/AssetImage'
|
||||||
import { AssetRow } from 'components/Borrow/AssetRow'
|
import BorrowActionButtons from 'components/Borrow/BorrowActionButtons'
|
||||||
import { ChevronDown, SortAsc, SortDesc, SortNone } from 'components/Icons'
|
import { FormattedNumber } from 'components/FormattedNumber'
|
||||||
|
import { ChevronDown, ChevronUp } from 'components/Icons'
|
||||||
import Loading from 'components/Loading'
|
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 TitleAndSubCell from 'components/TitleAndSubCell'
|
||||||
import { getEnabledMarketAssets } from 'utils/assets'
|
import { getEnabledMarketAssets } from 'utils/assets'
|
||||||
import { formatPercent } from 'utils/formatters'
|
import { BN } from 'utils/helpers'
|
||||||
import AssetImage from 'components/AssetImage'
|
|
||||||
|
|
||||||
type Props = {
|
interface Props {
|
||||||
data: BorrowAsset[] | BorrowAssetActive[]
|
title: string
|
||||||
|
data: BorrowMarketTableData[]
|
||||||
}
|
}
|
||||||
|
|
||||||
export const BorrowTable = (props: Props) => {
|
export default function BorrowTable(props: Props) {
|
||||||
const [sorting, setSorting] = React.useState<SortingState>([])
|
const { title, data } = props
|
||||||
|
const shouldShowAccountBorrowed = !!data[0]?.debt
|
||||||
const marketAssets = getEnabledMarketAssets()
|
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',
|
header: 'Asset',
|
||||||
id: 'symbol',
|
id: 'symbol',
|
||||||
cell: ({ row }) => {
|
cell: ({ row }) => {
|
||||||
const asset = marketAssets.find((asset) => asset.denom === row.original.denom)
|
const asset = row.original.asset
|
||||||
|
|
||||||
if (!asset) return null
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='flex flex-1 items-center gap-3'>
|
<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',
|
accessorKey: 'borrowRate',
|
||||||
header: 'Borrow Rate',
|
header: 'Borrow Rate',
|
||||||
@ -59,136 +86,44 @@ export const BorrowTable = (props: Props) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Text className='justify-end' size='sm'>
|
<FormattedNumber
|
||||||
{formatPercent(row.original.borrowRate, 2)}
|
className='justify-end text-xs'
|
||||||
</Text>
|
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',
|
accessorKey: 'liquidity',
|
||||||
header: 'Liquidity Available',
|
header: 'Liquidity Available',
|
||||||
cell: ({ row }) => {
|
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 (!asset) return null
|
||||||
|
|
||||||
if (row.original.liquidity === null) {
|
if (liquidity === null) {
|
||||||
return <Loading />
|
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,
|
enableSorting: false,
|
||||||
header: 'Manage',
|
header: 'Manage',
|
||||||
width: 150,
|
|
||||||
cell: ({ row }) => (
|
cell: ({ row }) => (
|
||||||
<div className='flex items-center justify-end'>
|
<div className='flex items-center justify-end'>
|
||||||
<div className={classNames('w-4', row.getIsExpanded() && 'rotate-180')}>
|
<div className='w-4'>{row.getIsExpanded() ? <ChevronUp /> : <ChevronDown />}</div>
|
||||||
<ChevronDown />
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
[marketAssets, props.data],
|
[shouldShowAccountBorrowed, marketAssets],
|
||||||
)
|
)
|
||||||
|
|
||||||
const table = useReactTable({
|
return <AssetListTable title={title} rowRenderer={rowRenderer} columns={columns} data={data} />
|
||||||
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>
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
@ -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 { FormattedNumber } from 'components/FormattedNumber'
|
||||||
import Text from 'components/Text'
|
import Text from 'components/Text'
|
||||||
import { Tooltip } from 'components/Tooltip'
|
import { Tooltip } from 'components/Tooltip'
|
||||||
import useStore from 'store'
|
import { DEFAULT_SETTINGS } from 'constants/defaultSettings'
|
||||||
import { BN } from 'utils/helpers'
|
import { REDUCE_MOTION_KEY } from 'constants/localStore'
|
||||||
|
import useLocalStorage from 'hooks/useLocalStorage'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
balance: number
|
balance: number
|
||||||
@ -30,8 +31,7 @@ export const BorrowCapacity = ({
|
|||||||
hideValues,
|
hideValues,
|
||||||
decimals = 2,
|
decimals = 2,
|
||||||
}: Props) => {
|
}: Props) => {
|
||||||
const enableAnimations = useStore((s) => s.enableAnimations)
|
const [reduceMotion] = useLocalStorage<boolean>(REDUCE_MOTION_KEY, DEFAULT_SETTINGS.reduceMotion)
|
||||||
|
|
||||||
const [percentOfMaxRound, setPercentOfMaxRound] = useState(0)
|
const [percentOfMaxRound, setPercentOfMaxRound] = useState(0)
|
||||||
const [percentOfMaxRange, setPercentOfMaxRange] = useState(0)
|
const [percentOfMaxRange, setPercentOfMaxRange] = useState(0)
|
||||||
const [limitPercentOfMax, setLimitPercentOfMax] = useState(0)
|
const [limitPercentOfMax, setLimitPercentOfMax] = useState(0)
|
||||||
@ -64,12 +64,12 @@ export const BorrowCapacity = ({
|
|||||||
{!hideValues && (
|
{!hideValues && (
|
||||||
<div
|
<div
|
||||||
className={classNames(
|
className={classNames(
|
||||||
enableAnimations && 'duration-800 transition-[opcity] delay-[1600ms]',
|
!reduceMotion && 'duration-800 transition-[opcity] delay-[1600ms]',
|
||||||
'text-3xs-caps',
|
'text-3xs-caps',
|
||||||
limitPercentOfMax ? 'opacity-50' : 'opacity-0',
|
limitPercentOfMax ? 'opacity-50' : 'opacity-0',
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<FormattedNumber animate amount={BN(limit)} />
|
<FormattedNumber animate amount={limit} />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@ -83,7 +83,7 @@ export const BorrowCapacity = ({
|
|||||||
<div
|
<div
|
||||||
className={classNames(
|
className={classNames(
|
||||||
'absolute left-0 h-full max-w-full rounded-l-3xl bg-body-dark',
|
'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={{
|
style={{
|
||||||
right: `${limitPercentOfMax ? 100 - limitPercentOfMax : 100}%`,
|
right: `${limitPercentOfMax ? 100 - limitPercentOfMax : 100}%`,
|
||||||
@ -94,7 +94,7 @@ export const BorrowCapacity = ({
|
|||||||
<div
|
<div
|
||||||
className={classNames(
|
className={classNames(
|
||||||
'h-full rounded-lg',
|
'h-full rounded-lg',
|
||||||
enableAnimations && 'transition-[width] duration-1000 ease-linear',
|
!reduceMotion && 'transition-[width] duration-1000 ease-linear',
|
||||||
)}
|
)}
|
||||||
style={{
|
style={{
|
||||||
width: `${percentOfMaxRange || 0.02}%`,
|
width: `${percentOfMaxRange || 0.02}%`,
|
||||||
@ -107,7 +107,7 @@ export const BorrowCapacity = ({
|
|||||||
<div
|
<div
|
||||||
className={classNames(
|
className={classNames(
|
||||||
'absolute bottom-0 h-[120%] w-[1px] bg-white',
|
'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}%` }}
|
style={{ left: `${limitPercentOfMax || 0}%` }}
|
||||||
/>
|
/>
|
||||||
@ -115,7 +115,7 @@ export const BorrowCapacity = ({
|
|||||||
<span
|
<span
|
||||||
className={classNames(
|
className={classNames(
|
||||||
'absolute top-1/2 mt-[1px] w-full -translate-y-1/2 text-center text-2xs-caps',
|
'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 && (
|
{max !== 0 && (
|
||||||
@ -127,7 +127,7 @@ export const BorrowCapacity = ({
|
|||||||
maxDecimals: decimals,
|
maxDecimals: decimals,
|
||||||
suffix: '%',
|
suffix: '%',
|
||||||
}}
|
}}
|
||||||
amount={BN(percentOfMaxRound)}
|
amount={percentOfMaxRound}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</span>
|
</span>
|
||||||
@ -139,9 +139,9 @@ export const BorrowCapacity = ({
|
|||||||
</Tooltip>
|
</Tooltip>
|
||||||
{!hideValues && (
|
{!hideValues && (
|
||||||
<div className='mt-2 flex opacity-50 text-3xs-caps'>
|
<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>
|
<span className='mr-1'>of</span>
|
||||||
<FormattedNumber animate amount={BN(max)} />
|
<FormattedNumber animate amount={max} />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
@ -55,6 +55,7 @@ export const buttonVariantClasses = {
|
|||||||
solid: 'rounded-sm text-white shadow-button justify-center group',
|
solid: 'rounded-sm text-white shadow-button justify-center group',
|
||||||
transparent: 'rounded-sm bg-transparent p-0 transition duration-200 ease-in',
|
transparent: 'rounded-sm bg-transparent p-0 transition duration-200 ease-in',
|
||||||
round: 'rounded-full p-0',
|
round: 'rounded-full p-0',
|
||||||
|
rounded: 'rounded-2xl',
|
||||||
}
|
}
|
||||||
|
|
||||||
export const circularProgressSize = {
|
export const circularProgressSize = {
|
||||||
|
@ -16,7 +16,9 @@ import {
|
|||||||
import { glowElement } from 'components/Button/utils'
|
import { glowElement } from 'components/Button/utils'
|
||||||
import { CircularProgress } from 'components/CircularProgress'
|
import { CircularProgress } from 'components/CircularProgress'
|
||||||
import { ChevronDown } from 'components/Icons'
|
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 {
|
interface Props {
|
||||||
children?: string | ReactNode
|
children?: string | ReactNode
|
||||||
@ -27,7 +29,7 @@ interface Props {
|
|||||||
showProgressIndicator?: boolean
|
showProgressIndicator?: boolean
|
||||||
size?: 'xs' | 'sm' | 'md' | 'lg'
|
size?: 'xs' | 'sm' | 'md' | 'lg'
|
||||||
text?: string | ReactNode
|
text?: string | ReactNode
|
||||||
variant?: 'solid' | 'transparent' | 'round'
|
variant?: 'solid' | 'transparent' | 'round' | 'rounded'
|
||||||
onClick?: (e: React.MouseEvent<HTMLButtonElement>) => void
|
onClick?: (e: React.MouseEvent<HTMLButtonElement>) => void
|
||||||
leftIcon?: ReactElement
|
leftIcon?: ReactElement
|
||||||
rightIcon?: ReactElement
|
rightIcon?: ReactElement
|
||||||
@ -36,6 +38,7 @@ interface Props {
|
|||||||
hasFocus?: boolean
|
hasFocus?: boolean
|
||||||
dataTestId?: string
|
dataTestId?: string
|
||||||
tabIndex?: number
|
tabIndex?: number
|
||||||
|
textClassNames?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
const Button = React.forwardRef(function Button(
|
const Button = React.forwardRef(function Button(
|
||||||
@ -57,13 +60,14 @@ const Button = React.forwardRef(function Button(
|
|||||||
hasFocus,
|
hasFocus,
|
||||||
dataTestId,
|
dataTestId,
|
||||||
tabIndex = 0,
|
tabIndex = 0,
|
||||||
|
textClassNames,
|
||||||
}: Props,
|
}: Props,
|
||||||
ref,
|
ref,
|
||||||
) {
|
) {
|
||||||
const enableAnimations = useStore((s) => s.enableAnimations)
|
const [reduceMotion] = useLocalStorage<boolean>(REDUCE_MOTION_KEY, DEFAULT_SETTINGS.reduceMotion)
|
||||||
const isDisabled = disabled || showProgressIndicator
|
const isDisabled = disabled || showProgressIndicator
|
||||||
const shouldShowText = text && !children
|
const shouldShowText = text && !children
|
||||||
const shouldShowGlowElement = variant === 'solid' && !isDisabled
|
const shouldShowGlowElement = variant === 'solid' && !isDisabled && !reduceMotion
|
||||||
|
|
||||||
const buttonClassNames = useMemo(() => {
|
const buttonClassNames = useMemo(() => {
|
||||||
const buttonClasses = [
|
const buttonClasses = [
|
||||||
@ -82,7 +86,7 @@ const Button = React.forwardRef(function Button(
|
|||||||
'relative z-1 flex items-center',
|
'relative z-1 flex items-center',
|
||||||
'cursor-pointer appearance-none break-normal outline-none',
|
'cursor-pointer appearance-none break-normal outline-none',
|
||||||
'text-white transition-all',
|
'text-white transition-all',
|
||||||
enableAnimations && 'transition-color',
|
!reduceMotion && 'transition-color',
|
||||||
buttonClasses,
|
buttonClasses,
|
||||||
buttonVariantClasses[variant],
|
buttonVariantClasses[variant],
|
||||||
variant === 'solid' && color === 'tertiary' && buttonBorderClasses,
|
variant === 'solid' && color === 'tertiary' && buttonBorderClasses,
|
||||||
@ -91,7 +95,7 @@ const Button = React.forwardRef(function Button(
|
|||||||
hasFocus && focusClasses[color],
|
hasFocus && focusClasses[color],
|
||||||
className,
|
className,
|
||||||
)
|
)
|
||||||
}, [className, color, enableAnimations, hasFocus, isDisabled, size, variant])
|
}, [className, color, reduceMotion, hasFocus, isDisabled, size, variant])
|
||||||
|
|
||||||
const [leftIconClassNames, rightIconClassNames] = useMemo(() => {
|
const [leftIconClassNames, rightIconClassNames] = useMemo(() => {
|
||||||
const hasContent = !!(text || children)
|
const hasContent = !!(text || children)
|
||||||
@ -116,7 +120,7 @@ const Button = React.forwardRef(function Button(
|
|||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
{leftIcon && <span className={classNames(leftIconClassNames)}>{leftIcon}</span>}
|
{leftIcon && <span className={classNames(leftIconClassNames)}>{leftIcon}</span>}
|
||||||
{shouldShowText && <span>{text}</span>}
|
{shouldShowText && <span className={textClassNames}>{text}</span>}
|
||||||
{children && children}
|
{children && children}
|
||||||
{rightIcon && <span className={classNames(rightIconClassNames)}>{rightIcon}</span>}
|
{rightIcon && <span className={classNames(rightIconClassNames)}>{rightIcon}</span>}
|
||||||
{hasSubmenu && (
|
{hasSubmenu && (
|
||||||
@ -126,7 +130,7 @@ const Button = React.forwardRef(function Button(
|
|||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
{shouldShowGlowElement && glowElement(enableAnimations)}
|
{shouldShowGlowElement && glowElement(!reduceMotion)}
|
||||||
</button>
|
</button>
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
@ -17,7 +17,7 @@ export default function Card(props: Props) {
|
|||||||
id={props.id}
|
id={props.id}
|
||||||
className={classNames(
|
className={classNames(
|
||||||
props.className,
|
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',
|
'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 classNames from 'classnames'
|
||||||
|
|
||||||
import Text from 'components/Text'
|
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 {
|
interface Props {
|
||||||
color?: string
|
color?: string
|
||||||
@ -10,15 +12,14 @@ interface Props {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const CircularProgress = ({ color = '#FFFFFF', size = 20, className }: 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 borderWidth = `${size / 10}px`
|
||||||
const borderColor = `${color} transparent transparent transparent`
|
const borderColor = `${color} transparent transparent transparent`
|
||||||
const loaderClasses = classNames('inline-block relative', className)
|
const loaderClasses = classNames('inline-block relative', className)
|
||||||
const elementClasses =
|
const elementClasses =
|
||||||
'block absolute w-4/5 h-4/5 m-[10%] rounded-full animate-progress border-solid'
|
'block absolute w-4/5 h-4/5 m-[10%] rounded-full animate-progress border-solid'
|
||||||
|
|
||||||
if (!enableAnimations)
|
if (reduceMotion)
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={classNames('flex items-center', loaderClasses)}
|
className={classNames('flex items-center', loaderClasses)}
|
||||||
|
@ -1,7 +1,12 @@
|
|||||||
|
import { useMemo } from 'react'
|
||||||
|
|
||||||
import { FormattedNumber } from 'components/FormattedNumber'
|
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 usePrices from 'hooks/usePrices'
|
||||||
import useStore from 'store'
|
|
||||||
import { BNCoin } from 'types/classes/BNCoin'
|
import { BNCoin } from 'types/classes/BNCoin'
|
||||||
|
import { getDisplayCurrencies } from 'utils/assets'
|
||||||
import { convertToDisplayAmount } from 'utils/formatters'
|
import { convertToDisplayAmount } from 'utils/formatters'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
@ -11,22 +16,33 @@ interface Props {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default function DisplayCurrency(props: 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 { data: prices } = usePrices()
|
||||||
|
|
||||||
|
const displayCurrencyAsset = useMemo(
|
||||||
|
() =>
|
||||||
|
displayCurrencies.find((asset) => asset.denom === displayCurrency) ?? displayCurrencies[0],
|
||||||
|
[displayCurrency, displayCurrencies],
|
||||||
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<FormattedNumber
|
<FormattedNumber
|
||||||
className={props.className}
|
className={props.className}
|
||||||
amount={convertToDisplayAmount(props.coin, displayCurrency, prices)}
|
amount={convertToDisplayAmount(props.coin, displayCurrency, prices).toNumber()}
|
||||||
options={{
|
options={{
|
||||||
minDecimals: 0,
|
minDecimals: 0,
|
||||||
maxDecimals: 2,
|
maxDecimals: 2,
|
||||||
abbreviated: true,
|
abbreviated: true,
|
||||||
prefix: `${props.isApproximation ? '~ ' : ''}${
|
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 { Suspense } from 'react'
|
||||||
|
|
||||||
import Card from 'components/Card'
|
import Card from 'components/Card'
|
||||||
import VaultCard from 'components/Earn/vault/VaultCard'
|
import VaultCard from 'components/Earn/Farm/VaultCard'
|
||||||
import useVaults from 'hooks/useVaults'
|
import useVaults from 'hooks/useVaults'
|
||||||
|
|
||||||
function Content() {
|
function Content() {
|
@ -1,5 +1,5 @@
|
|||||||
import Button from 'components/Button'
|
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 Text from 'components/Text'
|
||||||
import TitleAndSubCell from 'components/TitleAndSubCell'
|
import TitleAndSubCell from 'components/TitleAndSubCell'
|
||||||
import useCurrentAccount from 'hooks/useCurrentAccount'
|
import useCurrentAccount from 'hooks/useCurrentAccount'
|
@ -12,9 +12,10 @@ import React from 'react'
|
|||||||
|
|
||||||
import Button from 'components/Button'
|
import Button from 'components/Button'
|
||||||
import DisplayCurrency from 'components/DisplayCurrency'
|
import DisplayCurrency from 'components/DisplayCurrency'
|
||||||
import VaultExpanded from 'components/Earn/vault/VaultExpanded'
|
import VaultExpanded from 'components/Earn/Farm/VaultExpanded'
|
||||||
import VaultLogo from 'components/Earn/vault/VaultLogo'
|
import VaultLogo from 'components/Earn/Farm/VaultLogo'
|
||||||
import { VaultRow } from 'components/Earn/vault/VaultRow'
|
import { VaultRow } from 'components/Earn/Farm/VaultRow'
|
||||||
|
import { FormattedNumber } from 'components/FormattedNumber'
|
||||||
import { ChevronDown, SortAsc, SortDesc, SortNone } from 'components/Icons'
|
import { ChevronDown, SortAsc, SortDesc, SortNone } from 'components/Icons'
|
||||||
import Loading from 'components/Loading'
|
import Loading from 'components/Loading'
|
||||||
import Text from 'components/Text'
|
import Text from 'components/Text'
|
||||||
@ -24,7 +25,7 @@ import useStore from 'store'
|
|||||||
import { BNCoin } from 'types/classes/BNCoin'
|
import { BNCoin } from 'types/classes/BNCoin'
|
||||||
import { VaultStatus } from 'types/enums/vault'
|
import { VaultStatus } from 'types/enums/vault'
|
||||||
import { getAssetByDenom } from 'utils/assets'
|
import { getAssetByDenom } from 'utils/assets'
|
||||||
import { convertPercentage, formatPercent, formatValue, produceCountdown } from 'utils/formatters'
|
import { produceCountdown } from 'utils/formatters'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
data: Vault[] | DepositedVault[]
|
data: Vault[] | DepositedVault[]
|
||||||
@ -116,9 +117,15 @@ export const VaultTable = (props: Props) => {
|
|||||||
accessorKey: 'apy',
|
accessorKey: 'apy',
|
||||||
header: 'APY',
|
header: 'APY',
|
||||||
cell: ({ row }) => {
|
cell: ({ row }) => {
|
||||||
if (row.original.apy === null) return <Loading />
|
const vault = row.original as DepositedVault
|
||||||
|
if (vault.apy === null) return <Loading />
|
||||||
return (
|
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',
|
accessorKey: 'tvl',
|
||||||
header: 'TVL',
|
header: 'TVL',
|
||||||
cell: ({ row }) => {
|
cell: ({ row }) => {
|
||||||
|
const vault = row.original as DepositedVault
|
||||||
if (props.isLoading) return <Loading />
|
if (props.isLoading) return <Loading />
|
||||||
const coin = new BNCoin({
|
const coin = new BNCoin({
|
||||||
denom: row.original.cap.denom,
|
denom: vault.cap.denom,
|
||||||
amount: row.original.cap.used.toString(),
|
amount: vault.cap.used.toString(),
|
||||||
})
|
})
|
||||||
|
|
||||||
return <DisplayCurrency coin={coin} className='text-xs' />
|
return <DisplayCurrency coin={coin} className='text-xs' />
|
||||||
@ -139,24 +147,33 @@ export const VaultTable = (props: Props) => {
|
|||||||
accessorKey: 'cap',
|
accessorKey: 'cap',
|
||||||
header: 'Depo. Cap',
|
header: 'Depo. Cap',
|
||||||
cell: ({ row }) => {
|
cell: ({ row }) => {
|
||||||
|
const vault = row.original as DepositedVault
|
||||||
if (props.isLoading) return <Loading />
|
if (props.isLoading) return <Loading />
|
||||||
|
const percent = vault.cap.used
|
||||||
|
.dividedBy(vault.cap.max.multipliedBy(VAULT_DEPOSIT_BUFFER))
|
||||||
|
.multipliedBy(100)
|
||||||
|
.integerValue()
|
||||||
|
|
||||||
const percent = convertPercentage(
|
const decimals = getAssetByDenom(vault.cap.denom)?.decimals ?? 6
|
||||||
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
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TitleAndSubCell
|
<TitleAndSubCell
|
||||||
title={formatValue(row.original.cap.max.integerValue().toNumber(), {
|
title={
|
||||||
abbreviated: true,
|
<FormattedNumber
|
||||||
decimals,
|
amount={vault.cap.max.toNumber()}
|
||||||
})}
|
options={{ minDecimals: 2, abbreviated: true, decimals }}
|
||||||
sub={`${percent}% Filled`}
|
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'
|
return 'Deposit'
|
||||||
},
|
},
|
||||||
cell: ({ row }) => {
|
cell: ({ row }) => {
|
||||||
|
const vault = row.original as DepositedVault
|
||||||
function enterVaultHandler() {
|
function enterVaultHandler() {
|
||||||
useStore.setState({
|
useStore.setState({
|
||||||
vaultModal: {
|
vaultModal: {
|
||||||
vault: row.original,
|
vault,
|
||||||
selectedBorrowDenoms: [row.original.denoms.secondary],
|
selectedBorrowDenoms: [vault.denoms.secondary],
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
if (props.isLoading) return <Loading />
|
if (props.isLoading) return <Loading />
|
||||||
const vault = row.original as DepositedVault
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='flex items-center justify-end'>
|
<div className='flex items-center justify-end'>
|
||||||
{vault.status ? (
|
{vault.status ? (
|
@ -2,8 +2,8 @@ import { Suspense, useMemo } from 'react'
|
|||||||
import { useParams } from 'react-router-dom'
|
import { useParams } from 'react-router-dom'
|
||||||
|
|
||||||
import Card from 'components/Card'
|
import Card from 'components/Card'
|
||||||
import { VaultTable } from 'components/Earn/vault/VaultTable'
|
import { VaultTable } from 'components/Earn/Farm/VaultTable'
|
||||||
import VaultUnlockBanner from 'components/Earn/vault/VaultUnlockBanner'
|
import VaultUnlockBanner from 'components/Earn/Farm/VaultUnlockBanner'
|
||||||
import { IS_TESTNET } from 'constants/env'
|
import { IS_TESTNET } from 'constants/env'
|
||||||
import { TESTNET_VAULTS_META_DATA, VAULTS_META_DATA } from 'constants/vaults'
|
import { TESTNET_VAULTS_META_DATA, VAULTS_META_DATA } from 'constants/vaults'
|
||||||
import useDepositedVaults from 'hooks/useDepositedVaults'
|
import useDepositedVaults from 'hooks/useDepositedVaults'
|
@ -1,25 +1,25 @@
|
|||||||
import { useCallback } from 'react'
|
import { useCallback } from 'react'
|
||||||
|
|
||||||
|
import { ACCOUNT_MENU_BUTTON_ID } from 'components/Account/AccountMenuContent'
|
||||||
import Button from 'components/Button'
|
import Button from 'components/Button'
|
||||||
import { ArrowDownLine, ArrowUpLine, Enter } from 'components/Icons'
|
import { ArrowDownLine, ArrowUpLine, Enter } from 'components/Icons'
|
||||||
import Text from 'components/Text'
|
import Text from 'components/Text'
|
||||||
import { Tooltip } from 'components/Tooltip'
|
import { Tooltip } from 'components/Tooltip'
|
||||||
import ConditionalWrapper from 'hocs/ConditionalWrapper'
|
import ConditionalWrapper from 'hocs/ConditionalWrapper'
|
||||||
import useAlertDialog from 'hooks/useAlertDialog'
|
import useAlertDialog from 'hooks/useAlertDialog'
|
||||||
|
import useAutoLendEnabledAccountIds from 'hooks/useAutoLendEnabledAccountIds'
|
||||||
import useCurrentAccountDeposits from 'hooks/useCurrentAccountDeposits'
|
import useCurrentAccountDeposits from 'hooks/useCurrentAccountDeposits'
|
||||||
import useLendAndReclaimModal from 'hooks/useLendAndReclaimModal'
|
import useLendAndReclaimModal from 'hooks/useLendAndReclaimModal'
|
||||||
import { byDenom } from 'utils/array'
|
import { byDenom } from 'utils/array'
|
||||||
import useAutoLendEnabledAccountIds from 'hooks/useAutoLendEnabledAccountIds'
|
|
||||||
import { ACCOUNT_MENU_BUTTON_ID } from 'components/Account/AccountMenuContent'
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
data: LendingMarketTableData
|
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'
|
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 { asset, accountLentValue: accountLendValue } = props.data
|
||||||
const accountDeposits = useCurrentAccountDeposits()
|
const accountDeposits = useCurrentAccountDeposits()
|
||||||
const { openLend, openReclaim } = useLendAndReclaimModal()
|
const { openLend, openReclaim } = useLendAndReclaimModal()
|
||||||
@ -90,5 +90,3 @@ function LendingActionButtons(props: Props) {
|
|||||||
</div>
|
</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 { ColumnDef, Row, Table } from '@tanstack/react-table'
|
||||||
import Image from 'next/image'
|
import { useCallback, useMemo } from 'react'
|
||||||
import { useMemo } from 'react'
|
|
||||||
|
|
||||||
|
import AssetImage from 'components/AssetImage'
|
||||||
import LendingActionButtons from 'components/Earn/Lend/LendingActionButtons'
|
import LendingActionButtons from 'components/Earn/Lend/LendingActionButtons'
|
||||||
import LendingDetails from 'components/Earn/Lend/LendingDetails'
|
|
||||||
import { FormattedNumber } from 'components/FormattedNumber'
|
import { FormattedNumber } from 'components/FormattedNumber'
|
||||||
import { ChevronDown, ChevronUp } from 'components/Icons'
|
import { ChevronDown, ChevronUp } from 'components/Icons'
|
||||||
import AssetListTable from 'components/MarketAssetTable'
|
import AssetListTable from 'components/MarketAssetTable'
|
||||||
import MarketAssetTableRow from 'components/MarketAssetTable/MarketAssetTableRow'
|
import MarketAssetTableRow from 'components/MarketAssetTable/MarketAssetTableRow'
|
||||||
import Text from 'components/Text'
|
import MarketDetails from 'components/MarketAssetTable/MarketDetails'
|
||||||
import TitleAndSubCell from 'components/TitleAndSubCell'
|
import TitleAndSubCell from 'components/TitleAndSubCell'
|
||||||
import useDisplayCurrencyPrice from 'hooks/useDisplayCurrencyPrice'
|
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 {
|
interface Props {
|
||||||
title: string
|
title: string
|
||||||
data: LendingMarketTableData[]
|
data: LendingMarketTableData[]
|
||||||
}
|
}
|
||||||
|
|
||||||
function LendingMarketsTable(props: Props) {
|
export default function LendingMarketsTable(props: Props) {
|
||||||
const { title, data } = props
|
const { title, data } = props
|
||||||
const { symbol: displayCurrencySymbol } = useDisplayCurrencyPrice()
|
const { symbol: displayCurrencySymbol } = useDisplayCurrencyPrice()
|
||||||
const shouldShowAccountDeposit = !!data[0]?.accountLentValue
|
const shouldShowAccountDeposit = !!data[0]?.accountLentValue
|
||||||
|
|
||||||
const rowRenderer = (row: Row<LendingMarketTableData>, table: Table<LendingMarketTableData>) => {
|
const rowRenderer = useCallback(
|
||||||
return (
|
(row: Row<LendingMarketTableData>, table: Table<LendingMarketTableData>) => {
|
||||||
<MarketAssetTableRow
|
return (
|
||||||
key={`lend-asset-${row.id}`}
|
<MarketAssetTableRow
|
||||||
isExpanded={row.getIsExpanded()}
|
key={`lend-asset-${row.id}`}
|
||||||
resetExpanded={table.resetExpanded}
|
isExpanded={row.getIsExpanded()}
|
||||||
rowData={row}
|
resetExpanded={table.resetExpanded}
|
||||||
expandedActionButtons={<LendingActionButtons data={row.original} />}
|
rowData={row}
|
||||||
expandedDetails={<LendingDetails data={row.original} />}
|
expandedActionButtons={<LendingActionButtons data={row.original} />}
|
||||||
/>
|
expandedDetails={<MarketDetails data={row.original} />}
|
||||||
)
|
/>
|
||||||
}
|
)
|
||||||
|
},
|
||||||
|
[],
|
||||||
|
)
|
||||||
|
|
||||||
const columns = useMemo<ColumnDef<LendingMarketTableData>[]>(
|
const columns = useMemo<ColumnDef<LendingMarketTableData>[]>(
|
||||||
() => [
|
() => [
|
||||||
@ -47,7 +50,7 @@ function LendingMarketsTable(props: Props) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='flex flex-1 items-center gap-3'>
|
<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
|
<TitleAndSubCell
|
||||||
title={asset.symbol}
|
title={asset.symbol}
|
||||||
sub={asset.name}
|
sub={asset.name}
|
||||||
@ -68,8 +71,8 @@ function LendingMarketsTable(props: Props) {
|
|||||||
return (
|
return (
|
||||||
<FormattedNumber
|
<FormattedNumber
|
||||||
className='text-xs'
|
className='text-xs'
|
||||||
animate={true}
|
animate
|
||||||
amount={accountDepositValue}
|
amount={accountDepositValue.toNumber()}
|
||||||
options={{ suffix: ` ${displayCurrencySymbol}` }}
|
options={{ suffix: ` ${displayCurrencySymbol}` }}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
@ -81,8 +84,14 @@ function LendingMarketsTable(props: Props) {
|
|||||||
accessorKey: 'marketLiquidityRate',
|
accessorKey: 'marketLiquidityRate',
|
||||||
header: 'APR',
|
header: 'APR',
|
||||||
cell: ({ row }) => {
|
cell: ({ row }) => {
|
||||||
const apr = convertLiquidityRateToAPR(row.original.marketLiquidityRate)
|
return (
|
||||||
return <Text size='xs'>{apr.toFixed(2)}%</Text>
|
<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),
|
demagnify(marketDepositAmount, asset),
|
||||||
)
|
)
|
||||||
|
|
||||||
const [formattedRemainingCap, formattedDepositCap] = [remainingCap, marketDepositCap].map(
|
|
||||||
(value) =>
|
|
||||||
formatValue(value.toNumber(), {
|
|
||||||
decimals: asset.decimals,
|
|
||||||
abbreviated: true,
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TitleAndSubCell
|
<TitleAndSubCell
|
||||||
className='text-xs'
|
className='text-xs'
|
||||||
title={formattedDepositCap}
|
title={
|
||||||
sub={`${formattedRemainingCap} left`}
|
<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',
|
header: 'Manage',
|
||||||
cell: ({ row }) => (
|
cell: ({ row }) => (
|
||||||
<div className='flex items-center justify-end'>
|
<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>
|
</div>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
@ -127,5 +140,3 @@ function LendingMarketsTable(props: Props) {
|
|||||||
|
|
||||||
return <AssetListTable title={title} rowRenderer={rowRenderer} columns={columns} data={data} />
|
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 React, { useEffect, useRef } from 'react'
|
||||||
import { animated, useSpring } from 'react-spring'
|
import { animated, useSpring } from 'react-spring'
|
||||||
|
|
||||||
import useStore from 'store'
|
import { DEFAULT_SETTINGS } from 'constants/defaultSettings'
|
||||||
import { FormatOptions, formatValue } from 'utils/formatters'
|
import { REDUCE_MOTION_KEY } from 'constants/localStore'
|
||||||
import { BN } from 'utils/helpers'
|
import useLocalStorage from 'hooks/useLocalStorage'
|
||||||
|
import { formatValue } from 'utils/formatters'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
amount: BigNumber
|
amount: number
|
||||||
options?: FormatOptions
|
options?: FormatOptions
|
||||||
className?: string
|
className?: string
|
||||||
animate?: boolean
|
animate?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export const FormattedNumber = React.memo((props: Props) => {
|
export const FormattedNumber = React.memo(
|
||||||
const enableAnimations = useStore((s) => s.enableAnimations)
|
(props: Props) => {
|
||||||
const prevAmountRef = useRef<BigNumber>(BN(0))
|
const [reduceMotion] = useLocalStorage<boolean>(
|
||||||
|
REDUCE_MOTION_KEY,
|
||||||
|
DEFAULT_SETTINGS.reduceMotion,
|
||||||
|
)
|
||||||
|
const prevAmountRef = useRef<number>(0)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!prevAmountRef.current.eq(props.amount)) prevAmountRef.current = props.amount
|
if (prevAmountRef.current !== props.amount) prevAmountRef.current = props.amount
|
||||||
}, [props.amount])
|
}, [props.amount])
|
||||||
|
|
||||||
const springAmount = useSpring({
|
const springAmount = useSpring({
|
||||||
number: props.amount.toNumber(),
|
number: props.amount,
|
||||||
from: { number: prevAmountRef.current.toNumber() },
|
from: { number: prevAmountRef.current },
|
||||||
config: { duration: 1000 },
|
config: { duration: 1000 },
|
||||||
})
|
})
|
||||||
|
|
||||||
return (prevAmountRef.current.eq(props.amount) && props.amount.isZero()) ||
|
if (
|
||||||
!props.animate ||
|
(prevAmountRef.current === props.amount && props.amount === 0) ||
|
||||||
!enableAnimations ? (
|
!props.animate ||
|
||||||
<span className={classNames('number', props.className)}>
|
reduceMotion
|
||||||
{formatValue(props.amount.toString(), {
|
)
|
||||||
minDecimals: props.options?.minDecimals,
|
return (
|
||||||
maxDecimals: props.options?.maxDecimals,
|
<span className={classNames('number', props.className)}>
|
||||||
thousandSeparator: props.options?.thousandSeparator,
|
{formatValue(props.amount.toString(), props.options)}
|
||||||
prefix: props.options?.prefix,
|
</span>
|
||||||
suffix: props.options?.suffix,
|
)
|
||||||
rounded: props.options?.rounded,
|
|
||||||
abbreviated: props.options?.abbreviated,
|
return (
|
||||||
decimals: props.options?.decimals,
|
<animated.span className={classNames('number', props.className)}>
|
||||||
})}
|
{springAmount.number.to((num) => formatValue(num, props.options))}
|
||||||
</span>
|
</animated.span>
|
||||||
) : (
|
)
|
||||||
<animated.span className={classNames('number', props.className)}>
|
},
|
||||||
{springAmount.number.to((num) =>
|
(prevProps, nextProps) => prevProps.amount === nextProps.amount,
|
||||||
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>
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
FormattedNumber.displayName = 'FormattedNumber'
|
FormattedNumber.displayName = 'FormattedNumber'
|
||||||
|
@ -1,10 +1,11 @@
|
|||||||
import classNames from 'classnames'
|
import classNames from 'classnames'
|
||||||
import { ReactElement, ReactNode } from 'react'
|
import { ReactElement, ReactNode } from 'react'
|
||||||
|
|
||||||
import { Tooltip } from 'components/Tooltip'
|
|
||||||
import useStore from 'store'
|
|
||||||
import { FormattedNumber } from 'components/FormattedNumber'
|
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 {
|
interface Props {
|
||||||
tooltip: string | ReactNode
|
tooltip: string | ReactNode
|
||||||
@ -27,7 +28,7 @@ export const Gauge = ({
|
|||||||
icon,
|
icon,
|
||||||
labelClassName,
|
labelClassName,
|
||||||
}: Props) => {
|
}: Props) => {
|
||||||
const enableAnimations = useStore((s) => s.enableAnimations)
|
const [reduceMotion] = useLocalStorage<boolean>(REDUCE_MOTION_KEY, DEFAULT_SETTINGS.reduceMotion)
|
||||||
const radius = 16
|
const radius = 16
|
||||||
const percentageValue = percentage > 100 ? 100 : percentage < 0 ? 0 : percentage
|
const percentageValue = percentage > 100 ? 100 : percentage < 0 ? 0 : percentage
|
||||||
const circlePercent = 100 - percentageValue
|
const circlePercent = 100 - percentageValue
|
||||||
@ -75,7 +76,7 @@ export const Gauge = ({
|
|||||||
strokeDasharray='100'
|
strokeDasharray='100'
|
||||||
pathLength='100'
|
pathLength='100'
|
||||||
style={{
|
style={{
|
||||||
transition: enableAnimations ? 'stroke-dashoffset 1s ease' : 'none',
|
transition: reduceMotion ? 'none' : 'stroke-dashoffset 1s ease',
|
||||||
}}
|
}}
|
||||||
shapeRendering='geometricPrecision'
|
shapeRendering='geometricPrecision'
|
||||||
strokeLinecap='round'
|
strokeLinecap='round'
|
||||||
@ -88,7 +89,7 @@ export const Gauge = ({
|
|||||||
)}
|
)}
|
||||||
<FormattedNumber
|
<FormattedNumber
|
||||||
className={classNames(labelClassName, 'text-2xs')}
|
className={classNames(labelClassName, 'text-2xs')}
|
||||||
amount={BN(Math.round(percentage))}
|
amount={Math.round(percentage)}
|
||||||
options={{ maxDecimals: 0, minDecimals: 0 }}
|
options={{ maxDecimals: 0, minDecimals: 0 }}
|
||||||
animate
|
animate
|
||||||
/>
|
/>
|
||||||
|
@ -3,7 +3,7 @@ import classNames from 'classnames'
|
|||||||
import AccountMenu from 'components/Account/AccountMenu'
|
import AccountMenu from 'components/Account/AccountMenu'
|
||||||
import DesktopNavigation from 'components/Navigation/DesktopNavigation'
|
import DesktopNavigation from 'components/Navigation/DesktopNavigation'
|
||||||
import Settings from 'components/Settings'
|
import Settings from 'components/Settings'
|
||||||
import Wallet from 'components/Wallet/Wallet'
|
import Wallet from 'components/Wallet'
|
||||||
import useStore from 'store'
|
import useStore from 'store'
|
||||||
|
|
||||||
export const menuTree: { page: Page; label: string }[] = [
|
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 Account } from 'components/Icons/Account.svg'
|
||||||
export { default as AccountArrowDown } from 'components/Icons/AccountArrowDown.svg'
|
export { default as AccountArrowDown } from 'components/Icons/AccountArrowDown.svg'
|
||||||
export { default as ArrowChartLineUp } from 'components/Icons/ArrowChartLineUp.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 ArrowCircledTopRight } from 'components/Icons/ArrowCircledTopRight.svg'
|
||||||
export { default as ArrowDownLine } from 'components/Icons/ArrowDownLine.svg'
|
export { default as ArrowDownLine } from 'components/Icons/ArrowDownLine.svg'
|
||||||
export { default as ArrowRight } from 'components/Icons/ArrowRight.svg'
|
export { default as ArrowRight } from 'components/Icons/ArrowRight.svg'
|
||||||
|
@ -16,12 +16,12 @@ interface Props {
|
|||||||
|
|
||||||
export const LabelValuePair = ({ label, value, className }: Props) => (
|
export const LabelValuePair = ({ label, value, className }: Props) => (
|
||||||
<div className={classNames('flex w-full', className)}>
|
<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}
|
{label}
|
||||||
</Text>
|
</Text>
|
||||||
<Text size='xs' className='text-white/60'>
|
<Text size='xs' className='text-white/60'>
|
||||||
{value.format === 'number' ? (
|
{value.format === 'number' ? (
|
||||||
<FormattedNumber animate {...value} amount={BN(value.amount)} />
|
<FormattedNumber animate {...value} amount={BN(value.amount).toNumber()} />
|
||||||
) : (
|
) : (
|
||||||
value.amount || ''
|
value.amount || ''
|
||||||
)}
|
)}
|
||||||
|
@ -48,7 +48,7 @@ function AssetListTableRow<TData>(props: Props<TData>) {
|
|||||||
key={props.rowData.id}
|
key={props.rowData.id}
|
||||||
className={classNames(
|
className={classNames(
|
||||||
'cursor-pointer transition-colors',
|
'cursor-pointer transition-colors',
|
||||||
'border-b border-white border-opacity-10',
|
|
||||||
props.rowData.getIsExpanded() ? 'bg-black/20' : 'bg-white/0 hover:bg-white/5',
|
props.rowData.getIsExpanded() ? 'bg-black/20' : 'bg-white/0 hover:bg-white/5',
|
||||||
)}
|
)}
|
||||||
onClick={() => {
|
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 classNames from 'classnames'
|
||||||
import { ReactNode, useEffect, useRef } from 'react'
|
import { ReactNode, useEffect, useRef } from 'react'
|
||||||
|
|
||||||
|
import EscButton from 'components/Button/EscButton'
|
||||||
import Card from 'components/Card'
|
import Card from 'components/Card'
|
||||||
|
|
||||||
import EscButton from './Button/EscButton'
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
header: string | ReactNode
|
header: string | ReactNode
|
||||||
headerClassName?: string
|
headerClassName?: string
|
||||||
@ -14,12 +13,11 @@ interface Props {
|
|||||||
className?: string
|
className?: string
|
||||||
contentClassName?: string
|
contentClassName?: string
|
||||||
modalClassName?: string
|
modalClassName?: string
|
||||||
open: boolean
|
|
||||||
onClose: () => void
|
onClose: () => void
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function Modal(props: Props) {
|
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'
|
const modalClassName = props.modalClassName ?? 'max-w-modal'
|
||||||
|
|
||||||
function onClose() {
|
function onClose() {
|
||||||
@ -28,19 +26,17 @@ export default function Modal(props: Props) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (props.open) {
|
ref.current?.showModal()
|
||||||
ref.current?.showModal()
|
document.body.classList.add('h-screen', 'overflow-hidden')
|
||||||
} else {
|
}, [])
|
||||||
ref.current?.close()
|
|
||||||
}
|
|
||||||
}, [props.open])
|
|
||||||
|
|
||||||
// close dialog on unmount
|
// close dialog on unmount
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const dialog = ref.current
|
const dialog = ref.current
|
||||||
return () => {
|
return () => {
|
||||||
dialog.removeAttribute('open')
|
dialog?.removeAttribute('open')
|
||||||
dialog.close()
|
dialog?.close()
|
||||||
|
document.body.classList.remove('h-screen', 'overflow-hidden')
|
||||||
}
|
}
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
@ -51,13 +47,14 @@ export default function Modal(props: Props) {
|
|||||||
className={classNames(
|
className={classNames(
|
||||||
`w-screen border-none bg-transparent text-white`,
|
`w-screen border-none bg-transparent text-white`,
|
||||||
'focus-visible:outline-none',
|
'focus-visible:outline-none',
|
||||||
|
'max-h-full scrollbar-hide',
|
||||||
'backdrop:bg-black/50 backdrop:backdrop-blur-sm',
|
'backdrop:bg-black/50 backdrop:backdrop-blur-sm',
|
||||||
modalClassName,
|
modalClassName,
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<Card
|
<Card
|
||||||
className={classNames(
|
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,
|
props.className,
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
@ -65,7 +62,9 @@ export default function Modal(props: Props) {
|
|||||||
{props.header}
|
{props.header}
|
||||||
{!props.hideCloseBtn && <EscButton onClick={props.onClose} />}
|
{!props.hideCloseBtn && <EscButton onClick={props.onClose} />}
|
||||||
</div>
|
</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}
|
{props.children ? props.children : props.content}
|
||||||
</div>
|
</div>
|
||||||
</Card>
|
</Card>
|
||||||
|
@ -6,12 +6,12 @@ import {
|
|||||||
SortingState,
|
SortingState,
|
||||||
useReactTable,
|
useReactTable,
|
||||||
} from '@tanstack/react-table'
|
} from '@tanstack/react-table'
|
||||||
import { useEffect, useMemo, useState } from 'react'
|
|
||||||
import classNames from 'classnames'
|
import classNames from 'classnames'
|
||||||
|
import { useEffect, useMemo, useState } from 'react'
|
||||||
|
|
||||||
import { SortAsc, SortDesc, SortNone } from 'components/Icons'
|
import { SortAsc, SortDesc, SortNone } from 'components/Icons'
|
||||||
import Text from 'components/Text'
|
|
||||||
import useAddVaultAssetTableColumns from 'components/Modals/AddVaultAssets/useAddVaultAssetTableColumns'
|
import useAddVaultAssetTableColumns from 'components/Modals/AddVaultAssets/useAddVaultAssetTableColumns'
|
||||||
|
import Text from 'components/Text'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
assets: BorrowAsset[]
|
assets: BorrowAsset[]
|
||||||
@ -61,7 +61,7 @@ export default function AddVaultAssetTable(props: Props) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<table className='w-full'>
|
<table className='w-full'>
|
||||||
<thead className='border-b border-b-white/5'>
|
<thead className='border-b border-white/5'>
|
||||||
{table.getHeaderGroups().map((headerGroup) => (
|
{table.getHeaderGroups().map((headerGroup) => (
|
||||||
<tr key={headerGroup.id}>
|
<tr key={headerGroup.id}>
|
||||||
{headerGroup.headers.map((header, index) => {
|
{headerGroup.headers.map((header, index) => {
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
import { useCallback, useState } from 'react'
|
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 { CircularProgress } from 'components/CircularProgress'
|
||||||
|
import Modal from 'components/Modal'
|
||||||
import AddVaultAssetsModalContent from 'components/Modals/AddVaultAssets/AddVaultBorrowAssetsModalContent'
|
import AddVaultAssetsModalContent from 'components/Modals/AddVaultAssets/AddVaultBorrowAssetsModalContent'
|
||||||
|
import Text from 'components/Text'
|
||||||
|
import useStore from 'store'
|
||||||
|
|
||||||
export default function AddVaultBorrowAssetsModal() {
|
export default function AddVaultBorrowAssetsModal() {
|
||||||
const modal = useStore((s) => s.addVaultBorrowingsModal)
|
const modal = useStore((s) => s.addVaultBorrowingsModal)
|
||||||
@ -24,9 +24,9 @@ export default function AddVaultBorrowAssetsModal() {
|
|||||||
|
|
||||||
const showContent = modal && vaultModal?.vault
|
const showContent = modal && vaultModal?.vault
|
||||||
|
|
||||||
|
if (!showContent) return null
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
open={!!(modal && showContent)}
|
|
||||||
header={<Text>Add Assets</Text>}
|
header={<Text>Add Assets</Text>}
|
||||||
onClose={onClose}
|
onClose={onClose}
|
||||||
modalClassName='max-w-modal-xs'
|
modalClassName='max-w-modal-xs'
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
import { useCallback, useMemo, useState } from 'react'
|
import { useCallback, useMemo, useState } from 'react'
|
||||||
|
|
||||||
|
import AddVaultAssetTable from 'components/Modals/AddVaultAssets/AddVaultAssetTable'
|
||||||
import SearchBar from 'components/SearchBar'
|
import SearchBar from 'components/SearchBar'
|
||||||
import Text from 'components/Text'
|
import Text from 'components/Text'
|
||||||
import useMarketBorrowings from 'hooks/useMarketBorrowings'
|
import useMarketBorrowings from 'hooks/useMarketBorrowings'
|
||||||
import AddVaultAssetTable from 'components/Modals/AddVaultAssets/AddVaultAssetTable'
|
|
||||||
import useStore from 'store'
|
import useStore from 'store'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
@ -75,7 +75,7 @@ export default function AddVaultAssetsModalContent(props: Props) {
|
|||||||
|
|
||||||
return (
|
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
|
<SearchBar
|
||||||
value={searchString}
|
value={searchString}
|
||||||
placeholder={`Search for e.g. "ETH" or "Ethereum"`}
|
placeholder={`Search for e.g. "ETH" or "Ethereum"`}
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
import Button from 'components/Button'
|
import Button from 'components/Button'
|
||||||
import { ExclamationMarkCircled } from 'components/Icons'
|
import { ExclamationMarkCircled } from 'components/Icons'
|
||||||
import Modal from 'components/Modal'
|
import Modal from 'components/Modal'
|
||||||
|
import { NoIcon, YesIcon } from 'components/Modals/AlertDialog/ButtonIcons'
|
||||||
import Text from 'components/Text'
|
import Text from 'components/Text'
|
||||||
import useAlertDialog from 'hooks/useAlertDialog'
|
import useAlertDialog from 'hooks/useAlertDialog'
|
||||||
import { NoIcon, YesIcon } from 'components/Modals/AlertDialog/ButtonIcons'
|
|
||||||
|
|
||||||
function AlertDialogController() {
|
export default function AlertDialogController() {
|
||||||
const { config, close } = useAlertDialog()
|
const { config, close } = useAlertDialog()
|
||||||
|
|
||||||
if (!config) return null
|
if (!config) return null
|
||||||
@ -28,14 +28,13 @@ function AlertDialog(props: Props) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
open
|
|
||||||
onClose={props.close}
|
onClose={props.close}
|
||||||
header={
|
header={
|
||||||
<div className='grid h-12 w-12 place-items-center rounded-sm bg-white/5'>
|
<div className='grid h-12 w-12 place-items-center rounded-sm bg-white/5'>
|
||||||
{icon ?? <ExclamationMarkCircled width={18} />}
|
{icon ?? <ExclamationMarkCircled width={18} />}
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
modalClassName='w-[577px]'
|
modalClassName='max-w-modal-sm'
|
||||||
headerClassName='p-8'
|
headerClassName='p-8'
|
||||||
contentClassName='px-8 pb-8'
|
contentClassName='px-8 pb-8'
|
||||||
hideCloseBtn
|
hideCloseBtn
|
||||||
@ -62,5 +61,3 @@ function AlertDialog(props: Props) {
|
|||||||
</Modal>
|
</Modal>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default AlertDialogController
|
|
||||||
|
@ -15,7 +15,6 @@ import { BN } from 'utils/helpers'
|
|||||||
interface Props {
|
interface Props {
|
||||||
asset: Asset
|
asset: Asset
|
||||||
title: string
|
title: string
|
||||||
isOpen: boolean
|
|
||||||
coinBalances: Coin[]
|
coinBalances: Coin[]
|
||||||
contentHeader?: JSX.Element
|
contentHeader?: JSX.Element
|
||||||
actionButtonText: string
|
actionButtonText: string
|
||||||
@ -30,7 +29,6 @@ export default function AssetAmountSelectActionModal(props: Props) {
|
|||||||
const {
|
const {
|
||||||
asset,
|
asset,
|
||||||
title,
|
title,
|
||||||
isOpen,
|
|
||||||
coinBalances,
|
coinBalances,
|
||||||
contentHeader = null,
|
contentHeader = null,
|
||||||
actionButtonText,
|
actionButtonText,
|
||||||
@ -52,12 +50,11 @@ export default function AssetAmountSelectActionModal(props: Props) {
|
|||||||
)
|
)
|
||||||
|
|
||||||
const handleActionClick = useCallback(() => {
|
const handleActionClick = useCallback(() => {
|
||||||
onAction(amount, amount.eq(maxAmount))
|
onAction(amount, amount.isEqualTo(maxAmount))
|
||||||
}, [amount, maxAmount, onAction])
|
}, [amount, maxAmount, onAction])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
open={isOpen}
|
|
||||||
onClose={onClose}
|
onClose={onClose}
|
||||||
header={
|
header={
|
||||||
<span className='flex items-center gap-4 px-4'>
|
<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]'
|
contentClassName='flex flex-col min-h-[400px]'
|
||||||
>
|
>
|
||||||
{contentHeader}
|
{contentHeader}
|
||||||
<div className='flex flex-grow items-start gap-6 p-6'>
|
<div className='flex flex-1 items-start gap-6 p-6'>
|
||||||
<Card
|
<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'
|
contentClassName='gap-6 flex flex-col justify-between h-full'
|
||||||
>
|
>
|
||||||
<TokenInputWithSlider
|
<TokenInputWithSlider
|
@ -7,7 +7,7 @@ import Card from 'components/Card'
|
|||||||
import Divider from 'components/Divider'
|
import Divider from 'components/Divider'
|
||||||
import { ArrowRight } from 'components/Icons'
|
import { ArrowRight } from 'components/Icons'
|
||||||
import Modal from 'components/Modal'
|
import Modal from 'components/Modal'
|
||||||
import Select from 'components/Select/Select'
|
import Select from 'components/Select'
|
||||||
import Text from 'components/Text'
|
import Text from 'components/Text'
|
||||||
import TitleAndSubCell from 'components/TitleAndSubCell'
|
import TitleAndSubCell from 'components/TitleAndSubCell'
|
||||||
import TokenInputWithSlider from 'components/TokenInputWithSlider'
|
import TokenInputWithSlider from 'components/TokenInputWithSlider'
|
||||||
@ -21,8 +21,7 @@ import { formatPercent, formatValue } from 'utils/formatters'
|
|||||||
import { BN } from 'utils/helpers'
|
import { BN } from 'utils/helpers'
|
||||||
|
|
||||||
function getDebtAmount(modal: BorrowModal | null) {
|
function getDebtAmount(modal: BorrowModal | null) {
|
||||||
if (!(modal?.marketData as BorrowAssetActive)?.debt) return '0'
|
return BN((modal?.marketData as BorrowMarketTableData)?.debt ?? 0).toString()
|
||||||
return BN((modal?.marketData as BorrowAssetActive).debt).toString()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function getAssetLogo(modal: BorrowModal | null) {
|
function getAssetLogo(modal: BorrowModal | null) {
|
||||||
@ -119,9 +118,9 @@ export default function BorrowModal() {
|
|||||||
})
|
})
|
||||||
}, [amount, modal?.asset, currentAccount, isRepay])
|
}, [amount, modal?.asset, currentAccount, isRepay])
|
||||||
|
|
||||||
|
if (!modal) return null
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
open={!!modal}
|
|
||||||
onClose={onClose}
|
onClose={onClose}
|
||||||
header={
|
header={
|
||||||
<span className='flex items-center gap-4 px-4'>
|
<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'
|
headerClassName='gradient-header pl-2 pr-2.5 py-2.5 border-b-white/5 border-b'
|
||||||
contentClassName='flex flex-col'
|
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
|
<TitleAndSubCell
|
||||||
title={formatPercent(modal?.marketData.borrowRate || '0')}
|
title={formatPercent(modal?.marketData.borrowRate || '0')}
|
||||||
sub={'Borrow rate'}
|
sub={'Borrow rate'}
|
||||||
@ -153,9 +152,9 @@ export default function BorrowModal() {
|
|||||||
sub={'Liquidity available'}
|
sub={'Liquidity available'}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className='flex flex-grow items-start gap-6 p-6'>
|
<div className='flex flex-1 items-start gap-6 p-6'>
|
||||||
<Card
|
<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]'
|
contentClassName='gap-6 flex flex-col justify-between h-full min-h-[380px]'
|
||||||
>
|
>
|
||||||
<div className='flex w-full flex-wrap'>
|
<div className='flex w-full flex-wrap'>
|
||||||
@ -176,6 +175,7 @@ export default function BorrowModal() {
|
|||||||
options={accountOptions ?? []}
|
options={accountOptions ?? []}
|
||||||
title='Accounts'
|
title='Accounts'
|
||||||
defaultValue={selectedAccount?.id}
|
defaultValue={selectedAccount?.id}
|
||||||
|
containerClassName='w-full'
|
||||||
onChange={(account) => {
|
onChange={(account) => {
|
||||||
accounts && setSelectedAccount(accounts?.find((a) => a.id === account))
|
accounts && setSelectedAccount(accounts?.find((a) => a.id === account))
|
||||||
}}
|
}}
|
@ -83,9 +83,9 @@ export default function FundWithdrawModalContent(props: Props) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='flex flex-grow items-start gap-6 p-6'>
|
<div className='flex flex-1 items-start gap-6 p-6'>
|
||||||
<Card
|
<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'
|
contentClassName='gap-6 flex flex-col justify-between h-full'
|
||||||
>
|
>
|
||||||
<TokenInputWithSlider
|
<TokenInputWithSlider
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
|
import { CircularProgress } from 'components/CircularProgress'
|
||||||
import Modal from 'components/Modal'
|
import Modal from 'components/Modal'
|
||||||
|
import FundWithdrawModalContent from 'components/Modals/FundWithdraw/FundAndWithdrawModalContent'
|
||||||
import Text from 'components/Text'
|
import Text from 'components/Text'
|
||||||
import useCurrentAccount from 'hooks/useCurrentAccount'
|
import useCurrentAccount from 'hooks/useCurrentAccount'
|
||||||
import useStore from 'store'
|
import useStore from 'store'
|
||||||
import { CircularProgress } from 'components/CircularProgress'
|
|
||||||
import FundWithdrawModalContent from 'components/Modals/FundWithdraw/FundAndWithdrawModalContent'
|
|
||||||
|
|
||||||
export default function FundAndWithdrawModal() {
|
export default function FundAndWithdrawModal() {
|
||||||
const currentAccount = useCurrentAccount()
|
const currentAccount = useCurrentAccount()
|
||||||
@ -14,9 +14,9 @@ export default function FundAndWithdrawModal() {
|
|||||||
useStore.setState({ fundAndWithdrawModal: null })
|
useStore.setState({ fundAndWithdrawModal: null })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!modal) return null
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
open={!!modal}
|
|
||||||
onClose={onClose}
|
onClose={onClose}
|
||||||
header={
|
header={
|
||||||
<span className='flex items-center gap-4 px-4'>
|
<span className='flex items-center gap-4 px-4'>
|
@ -1,6 +1,6 @@
|
|||||||
import DisplayCurrency from 'components/DisplayCurrency'
|
import DisplayCurrency from 'components/DisplayCurrency'
|
||||||
import TitleAndSubCell from 'components/TitleAndSubCell'
|
|
||||||
import { FormattedNumber } from 'components/FormattedNumber'
|
import { FormattedNumber } from 'components/FormattedNumber'
|
||||||
|
import TitleAndSubCell from 'components/TitleAndSubCell'
|
||||||
import useAssetIncentivesApy from 'hooks/useAssetIncentiveApy'
|
import useAssetIncentivesApy from 'hooks/useAssetIncentiveApy'
|
||||||
import useCurrentWalletBalance from 'hooks/useCurrentWalletBalance'
|
import useCurrentWalletBalance from 'hooks/useCurrentWalletBalance'
|
||||||
import { BNCoin } from 'types/classes/BNCoin'
|
import { BNCoin } from 'types/classes/BNCoin'
|
||||||
@ -15,17 +15,18 @@ function DetailsHeader({ data }: Props) {
|
|||||||
const balanceInWallet = useCurrentWalletBalance(asset.denom)
|
const balanceInWallet = useCurrentWalletBalance(asset.denom)
|
||||||
|
|
||||||
return (
|
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 && (
|
{assetApy && (
|
||||||
<>
|
<>
|
||||||
<TitleAndSubCell
|
<TitleAndSubCell
|
||||||
title={
|
title={
|
||||||
<>
|
<>
|
||||||
<FormattedNumber amount={assetApy} options={{ suffix: '%' }} />
|
<FormattedNumber amount={assetApy.toNumber()} options={{ suffix: '%' }} animate />
|
||||||
<FormattedNumber
|
<FormattedNumber
|
||||||
className='ml-2 text-xs'
|
className='ml-2 text-xs'
|
||||||
amount={assetApy.div(365)}
|
amount={assetApy.dividedBy(365).toNumber()}
|
||||||
options={{ suffix: '%/day' }}
|
options={{ suffix: '%/day' }}
|
||||||
|
animate
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
|
@ -66,7 +66,6 @@ function LendAndReclaimModal({ currentAccount, config }: Props) {
|
|||||||
return (
|
return (
|
||||||
<AssetAmountSelectActionModal
|
<AssetAmountSelectActionModal
|
||||||
asset={asset}
|
asset={asset}
|
||||||
isOpen={true}
|
|
||||||
contentHeader={<DetailsHeader data={data} />}
|
contentHeader={<DetailsHeader data={data} />}
|
||||||
coinBalances={coinBalances}
|
coinBalances={coinBalances}
|
||||||
actionButtonText={actionText}
|
actionButtonText={actionText}
|
||||||
|
@ -4,21 +4,23 @@ import {
|
|||||||
BorrowModal,
|
BorrowModal,
|
||||||
FundAndWithdrawModal,
|
FundAndWithdrawModal,
|
||||||
LendAndReclaimModalController,
|
LendAndReclaimModalController,
|
||||||
|
SettingsModal,
|
||||||
UnlockModal,
|
UnlockModal,
|
||||||
VaultModal,
|
VaultModal,
|
||||||
WithdrawFromVaults,
|
WithdrawFromVaultsModal,
|
||||||
} from 'components/Modals'
|
} from 'components/Modals'
|
||||||
|
|
||||||
export default function ModalsContainer() {
|
export default function ModalsContainer() {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<VaultModal />
|
<AddVaultBorrowAssetsModal />
|
||||||
<BorrowModal />
|
<BorrowModal />
|
||||||
<FundAndWithdrawModal />
|
<FundAndWithdrawModal />
|
||||||
<AddVaultBorrowAssetsModal />
|
|
||||||
<UnlockModal />
|
|
||||||
<LendAndReclaimModalController />
|
<LendAndReclaimModalController />
|
||||||
<WithdrawFromVaults />
|
<SettingsModal />
|
||||||
|
<UnlockModal />
|
||||||
|
<VaultModal />
|
||||||
|
<WithdrawFromVaultsModal />
|
||||||
<AlertDialogController />
|
<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 { useParams } from 'react-router-dom'
|
||||||
|
|
||||||
import Button from 'components/Button'
|
import Button from 'components/Button'
|
||||||
|
import { NoIcon, YesIcon } from 'components/Modals/AlertDialog/ButtonIcons'
|
||||||
import Text from 'components/Text'
|
import Text from 'components/Text'
|
||||||
import useStore from 'store'
|
import useStore from 'store'
|
||||||
import { hardcodedFee } from 'utils/constants'
|
import { hardcodedFee } from 'utils/constants'
|
||||||
import { NoIcon, YesIcon } from 'components/Modals/AlertDialog/ButtonIcons'
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
depositedVault: DepositedVault
|
depositedVault: DepositedVault
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
import { CircularProgress } from 'components/CircularProgress'
|
|
||||||
import { LockUnlocked } from 'components/Icons'
|
import { LockUnlocked } from 'components/Icons'
|
||||||
import Modal from 'components/Modal'
|
import Modal from 'components/Modal'
|
||||||
import UnlockModalContent from 'components/Modals/Unlock/UnlockModalContent'
|
import UnlockModalContent from 'components/Modals/Unlock/UnlockModalContent'
|
||||||
@ -11,9 +10,9 @@ export default function UnlockModal() {
|
|||||||
useStore.setState({ unlockModal: null })
|
useStore.setState({ unlockModal: null })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!modal) return null
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
open={!!modal}
|
|
||||||
onClose={onClose}
|
onClose={onClose}
|
||||||
header={
|
header={
|
||||||
<div className='grid h-12 w-12 place-items-center rounded-sm bg-white/5'>
|
<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'
|
contentClassName='px-8 pb-8'
|
||||||
hideCloseBtn
|
hideCloseBtn
|
||||||
>
|
>
|
||||||
{modal ? (
|
<UnlockModalContent depositedVault={modal.vault} onClose={onClose} />
|
||||||
<UnlockModalContent depositedVault={modal.vault} onClose={onClose} />
|
|
||||||
) : (
|
|
||||||
<CircularProgress />
|
|
||||||
)}
|
|
||||||
</Modal>
|
</Modal>
|
||||||
)
|
)
|
||||||
}
|
}
|
@ -1,24 +1,23 @@
|
|||||||
import { useEffect, useMemo, useState } from 'react'
|
|
||||||
import BigNumber from 'bignumber.js'
|
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 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 DisplayCurrency from 'components/DisplayCurrency'
|
||||||
import usePrice from 'hooks/usePrice'
|
import Divider from 'components/Divider'
|
||||||
import { BNCoin } from 'types/classes/BNCoin'
|
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 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 {
|
export interface VaultBorrowingsProps {
|
||||||
account: Account
|
account: Account
|
||||||
@ -49,11 +48,11 @@ export default function VaultBorrowings(props: VaultBorrowingsProps) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
const primaryValue = useMemo(
|
const primaryValue = useMemo(
|
||||||
() => props.primaryAmount.times(primaryPrice),
|
() => props.primaryAmount.multipliedBy(primaryPrice),
|
||||||
[props.primaryAmount, primaryPrice],
|
[props.primaryAmount, primaryPrice],
|
||||||
)
|
)
|
||||||
const secondaryValue = useMemo(
|
const secondaryValue = useMemo(
|
||||||
() => props.secondaryAmount.times(secondaryPrice),
|
() => props.secondaryAmount.multipliedBy(secondaryPrice),
|
||||||
[props.secondaryAmount, secondaryPrice],
|
[props.secondaryAmount, secondaryPrice],
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -62,7 +61,7 @@ export default function VaultBorrowings(props: VaultBorrowingsProps) {
|
|||||||
const price = prices.find((price) => price.denom === curr.denom)?.amount
|
const price = prices.find((price) => price.denom === curr.denom)?.amount
|
||||||
if (!price) return prev
|
if (!price) return prev
|
||||||
|
|
||||||
return prev.plus(curr.amount.times(price))
|
return prev.plus(curr.amount.multipliedBy(price))
|
||||||
}, BN(0) as BigNumber)
|
}, BN(0) as BigNumber)
|
||||||
}, [props.borrowings, prices])
|
}, [props.borrowings, prices])
|
||||||
|
|
||||||
@ -113,7 +112,7 @@ export default function VaultBorrowings(props: VaultBorrowingsProps) {
|
|||||||
new BNCoin({
|
new BNCoin({
|
||||||
denom,
|
denom,
|
||||||
amount: (
|
amount: (
|
||||||
maxAmount.plus(currentAmount).times(value).div(100).decimalPlaces(0) || BN(0)
|
maxAmount.plus(currentAmount).multipliedBy(value).dividedBy(100).decimalPlaces(0) || BN(0)
|
||||||
).toString(),
|
).toString(),
|
||||||
}),
|
}),
|
||||||
]
|
]
|
||||||
@ -164,7 +163,7 @@ export default function VaultBorrowings(props: VaultBorrowingsProps) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
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) => {
|
{props.borrowings.map((coin) => {
|
||||||
const asset = getAssetByDenom(coin.denom)
|
const asset = getAssetByDenom(coin.denom)
|
||||||
const maxAmount = maxAmounts.find((maxAmount) => maxAmount.denom === coin.denom)?.amount
|
const maxAmount = maxAmounts.find((maxAmount) => maxAmount.denom === coin.denom)?.amount
|
||||||
|
@ -1,12 +1,11 @@
|
|||||||
import BigNumber from 'bignumber.js'
|
|
||||||
import { useMemo } from 'react'
|
import { useMemo } from 'react'
|
||||||
|
|
||||||
import DisplayCurrency from 'components/DisplayCurrency'
|
import DisplayCurrency from 'components/DisplayCurrency'
|
||||||
import usePrices from 'hooks/usePrices'
|
import usePrices from 'hooks/usePrices'
|
||||||
import useStore from 'store'
|
import useStore from 'store'
|
||||||
|
import { BNCoin } from 'types/classes/BNCoin'
|
||||||
import { formatAmountWithSymbol } from 'utils/formatters'
|
import { formatAmountWithSymbol } from 'utils/formatters'
|
||||||
import { BN } from 'utils/helpers'
|
import { BN } from 'utils/helpers'
|
||||||
import { BNCoin } from 'types/classes/BNCoin'
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
borrowings: BNCoin[]
|
borrowings: BNCoin[]
|
||||||
@ -22,7 +21,7 @@ export default function VaultDepositSubTitle(props: Props) {
|
|||||||
props.borrowings.map((coin) => {
|
props.borrowings.map((coin) => {
|
||||||
const price = prices.find((p) => p.denom === coin.denom)?.amount
|
const price = prices.find((p) => p.denom === coin.denom)?.amount
|
||||||
if (!price || coin.amount.isZero()) return
|
if (!price || coin.amount.isZero()) return
|
||||||
borrowingValue = borrowingValue.plus(coin.amount.times(price))
|
borrowingValue = borrowingValue.plus(coin.amount.multipliedBy(price))
|
||||||
texts.push(
|
texts.push(
|
||||||
formatAmountWithSymbol({
|
formatAmountWithSymbol({
|
||||||
denom: coin.denom,
|
denom: coin.denom,
|
||||||
|
@ -4,17 +4,17 @@ import { useMemo, useState } from 'react'
|
|||||||
import Button from 'components/Button'
|
import Button from 'components/Button'
|
||||||
import DisplayCurrency from 'components/DisplayCurrency'
|
import DisplayCurrency from 'components/DisplayCurrency'
|
||||||
import Divider from 'components/Divider'
|
import Divider from 'components/Divider'
|
||||||
|
import { Gauge } from 'components/Gauge'
|
||||||
import { ArrowRight, ExclamationMarkCircled } from 'components/Icons'
|
import { ArrowRight, ExclamationMarkCircled } from 'components/Icons'
|
||||||
import Slider from 'components/Slider'
|
import Slider from 'components/Slider'
|
||||||
import Switch from 'components/Switch'
|
import Switch from 'components/Switch'
|
||||||
import Text from 'components/Text'
|
import Text from 'components/Text'
|
||||||
import TokenInput from 'components/TokenInput'
|
import TokenInput from 'components/TokenInput'
|
||||||
import usePrice from 'hooks/usePrice'
|
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 useStore from 'store'
|
||||||
import { BNCoin } from 'types/classes/BNCoin'
|
import { BNCoin } from 'types/classes/BNCoin'
|
||||||
|
import { getAmount } from 'utils/accounts'
|
||||||
|
import { BN } from 'utils/helpers'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
primaryAmount: BigNumber
|
primaryAmount: BigNumber
|
||||||
@ -38,11 +38,11 @@ export default function VaultDeposit(props: Props) {
|
|||||||
const secondaryPrice = usePrice(props.secondaryAsset.denom)
|
const secondaryPrice = usePrice(props.secondaryAsset.denom)
|
||||||
|
|
||||||
const primaryValue = useMemo(
|
const primaryValue = useMemo(
|
||||||
() => props.primaryAmount.times(primaryPrice),
|
() => props.primaryAmount.multipliedBy(primaryPrice),
|
||||||
[props.primaryAmount, primaryPrice],
|
[props.primaryAmount, primaryPrice],
|
||||||
)
|
)
|
||||||
const secondaryValue = useMemo(
|
const secondaryValue = useMemo(
|
||||||
() => props.secondaryAmount.times(secondaryPrice),
|
() => props.secondaryAmount.multipliedBy(secondaryPrice),
|
||||||
[props.secondaryAmount, secondaryPrice],
|
[props.secondaryAmount, secondaryPrice],
|
||||||
)
|
)
|
||||||
const totalValue = useMemo(
|
const totalValue = useMemo(
|
||||||
@ -51,7 +51,7 @@ export default function VaultDeposit(props: Props) {
|
|||||||
)
|
)
|
||||||
|
|
||||||
const primaryValuePercentage = useMemo(() => {
|
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
|
return isNaN(value) ? 50 : value
|
||||||
}, [primaryValue, totalValue])
|
}, [primaryValue, totalValue])
|
||||||
const secondaryValuePercentage = useMemo(
|
const secondaryValuePercentage = useMemo(
|
||||||
@ -63,8 +63,8 @@ export default function VaultDeposit(props: Props) {
|
|||||||
() =>
|
() =>
|
||||||
BN(
|
BN(
|
||||||
Math.min(
|
Math.min(
|
||||||
availablePrimaryAmount.times(primaryPrice).toNumber(),
|
availablePrimaryAmount.multipliedBy(primaryPrice).toNumber(),
|
||||||
availableSecondaryAmount.times(secondaryPrice).toNumber(),
|
availableSecondaryAmount.multipliedBy(secondaryPrice).toNumber(),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
[availablePrimaryAmount, primaryPrice, availableSecondaryAmount, secondaryPrice],
|
[availablePrimaryAmount, primaryPrice, availableSecondaryAmount, secondaryPrice],
|
||||||
@ -83,7 +83,8 @@ export default function VaultDeposit(props: Props) {
|
|||||||
)
|
)
|
||||||
|
|
||||||
const [percentage, setPercentage] = useState(
|
const [percentage, setPercentage] = useState(
|
||||||
primaryValue.dividedBy(maxAssetValueNonCustom).times(100).decimalPlaces(0).toNumber() || 0,
|
primaryValue.dividedBy(maxAssetValueNonCustom).multipliedBy(100).decimalPlaces(0).toNumber() ||
|
||||||
|
0,
|
||||||
)
|
)
|
||||||
const disableInput =
|
const disableInput =
|
||||||
(availablePrimaryAmount.isZero() || availableSecondaryAmount.isZero()) && !props.isCustomRatio
|
(availablePrimaryAmount.isZero() || availableSecondaryAmount.isZero()) && !props.isCustomRatio
|
||||||
@ -103,7 +104,7 @@ export default function VaultDeposit(props: Props) {
|
|||||||
amount = primaryMax
|
amount = primaryMax
|
||||||
}
|
}
|
||||||
props.onChangePrimaryAmount(amount)
|
props.onChangePrimaryAmount(amount)
|
||||||
setPercentage(amount.dividedBy(primaryMax).times(100).decimalPlaces(0).toNumber())
|
setPercentage(amount.dividedBy(primaryMax).multipliedBy(100).decimalPlaces(0).toNumber())
|
||||||
if (!props.isCustomRatio) {
|
if (!props.isCustomRatio) {
|
||||||
props.onChangeSecondaryAmount(secondaryMax.multipliedBy(amount.dividedBy(primaryMax)))
|
props.onChangeSecondaryAmount(secondaryMax.multipliedBy(amount.dividedBy(primaryMax)))
|
||||||
}
|
}
|
||||||
@ -114,7 +115,7 @@ export default function VaultDeposit(props: Props) {
|
|||||||
amount = secondaryMax
|
amount = secondaryMax
|
||||||
}
|
}
|
||||||
props.onChangeSecondaryAmount(amount)
|
props.onChangeSecondaryAmount(amount)
|
||||||
setPercentage(amount.dividedBy(secondaryMax).times(100).decimalPlaces(0).toNumber())
|
setPercentage(amount.dividedBy(secondaryMax).multipliedBy(100).decimalPlaces(0).toNumber())
|
||||||
if (!props.isCustomRatio) {
|
if (!props.isCustomRatio) {
|
||||||
props.onChangePrimaryAmount(primaryMax.multipliedBy(amount.dividedBy(secondaryMax)))
|
props.onChangePrimaryAmount(primaryMax.multipliedBy(amount.dividedBy(secondaryMax)))
|
||||||
}
|
}
|
||||||
@ -152,7 +153,7 @@ export default function VaultDeposit(props: Props) {
|
|||||||
strokeWidth={3}
|
strokeWidth={3}
|
||||||
/>
|
/>
|
||||||
</div>
|
</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
|
<TokenInput
|
||||||
onChange={onChangePrimaryDeposit}
|
onChange={onChangePrimaryDeposit}
|
||||||
amount={props.primaryAmount}
|
amount={props.primaryAmount}
|
||||||
|
@ -27,8 +27,8 @@ export default function VaultDepositSubTitle(props: Props) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
const positionValue = props.primaryAmount
|
const positionValue = props.primaryAmount
|
||||||
.times(primaryPrice)
|
.multipliedBy(primaryPrice)
|
||||||
.plus(props.secondaryAmount.times(secondaryPrice))
|
.plus(props.secondaryAmount.multipliedBy(secondaryPrice))
|
||||||
.toNumber()
|
.toNumber()
|
||||||
|
|
||||||
const showPrimaryText = !props.primaryAmount.isZero()
|
const showPrimaryText = !props.primaryAmount.isZero()
|
||||||
|
@ -3,14 +3,14 @@ import { useCallback, useMemo, useState } from 'react'
|
|||||||
|
|
||||||
import Accordion from 'components/Accordion'
|
import Accordion from 'components/Accordion'
|
||||||
import AccountSummary from 'components/Account/AccountSummary'
|
import AccountSummary from 'components/Account/AccountSummary'
|
||||||
import useIsOpenArray from 'hooks/useIsOpenArray'
|
import VaultBorrowings from 'components/Modals/Vault/VaultBorrowings'
|
||||||
import { BN } from 'utils/helpers'
|
|
||||||
import useUpdateAccount from 'hooks/useUpdateAccount'
|
|
||||||
import VaultBorrowingsSubTitle from 'components/Modals/Vault/VaultBorrowingsSubTitle'
|
import VaultBorrowingsSubTitle from 'components/Modals/Vault/VaultBorrowingsSubTitle'
|
||||||
import VaultDeposit from 'components/Modals/Vault/VaultDeposits'
|
import VaultDeposit from 'components/Modals/Vault/VaultDeposits'
|
||||||
import VaultBorrowings from 'components/Modals/Vault/VaultBorrowings'
|
|
||||||
import VaultDepositSubTitle from 'components/Modals/Vault/VaultDepositsSubTitle'
|
import VaultDepositSubTitle from 'components/Modals/Vault/VaultDepositsSubTitle'
|
||||||
|
import useIsOpenArray from 'hooks/useIsOpenArray'
|
||||||
|
import useUpdateAccount from 'hooks/useUpdateAccount'
|
||||||
import { BNCoin } from 'types/classes/BNCoin'
|
import { BNCoin } from 'types/classes/BNCoin'
|
||||||
|
import { BN } from 'utils/helpers'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
vault: Vault | DepositedVault
|
vault: Vault | DepositedVault
|
||||||
@ -82,7 +82,7 @@ export default function VaultModalContent(props: Props) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='flex flex-grow items-start gap-6 p-6'>
|
<div className='flex flex-1 items-start gap-6 p-6'>
|
||||||
<Accordion
|
<Accordion
|
||||||
className='h-[546px] overflow-y-scroll scrollbar-hide'
|
className='h-[546px] overflow-y-scroll scrollbar-hide'
|
||||||
items={[
|
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 Modal from 'components/Modal'
|
||||||
import VaultModalContent from 'components/Modals/Vault/VaultModalContent'
|
import VaultModalContent from 'components/Modals/Vault/VaultModalContent'
|
||||||
import Text from 'components/Text'
|
import Text from 'components/Text'
|
||||||
@ -38,7 +38,6 @@ function VaultModal(props: Props) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
open={true}
|
|
||||||
onClose={onClose}
|
onClose={onClose}
|
||||||
header={
|
header={
|
||||||
<span className='flex items-center gap-4 px-4'>
|
<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 Button from 'components/Button'
|
||||||
import { CircularProgress } from 'components/CircularProgress'
|
import { CircularProgress } from 'components/CircularProgress'
|
||||||
import DisplayCurrency from 'components/DisplayCurrency'
|
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 { FormattedNumber } from 'components/FormattedNumber'
|
||||||
import Modal from 'components/Modal'
|
import Modal from 'components/Modal'
|
||||||
import Text from 'components/Text'
|
import Text from 'components/Text'
|
||||||
@ -13,9 +13,8 @@ import { BNCoin } from 'types/classes/BNCoin'
|
|||||||
import { getAssetByDenom } from 'utils/assets'
|
import { getAssetByDenom } from 'utils/assets'
|
||||||
import { hardcodedFee } from 'utils/constants'
|
import { hardcodedFee } from 'utils/constants'
|
||||||
import { demagnify } from 'utils/formatters'
|
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 modal = useStore((s) => s.withdrawFromVaultsModal)
|
||||||
const { accountId } = useParams()
|
const { accountId } = useParams()
|
||||||
const [isConfirming, setIsConfirming] = useState(false)
|
const [isConfirming, setIsConfirming] = useState(false)
|
||||||
@ -38,9 +37,10 @@ export default function WithdrawFromVaults() {
|
|||||||
onClose()
|
onClose()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!modal) return null
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
open={!!modal}
|
|
||||||
onClose={onClose}
|
onClose={onClose}
|
||||||
header={
|
header={
|
||||||
<span className='flex items-center'>
|
<span className='flex items-center'>
|
||||||
@ -63,7 +63,7 @@ export default function WithdrawFromVaults() {
|
|||||||
return (
|
return (
|
||||||
<div className='flex items-center gap-4' key={vault.unlockId}>
|
<div className='flex items-center gap-4' key={vault.unlockId}>
|
||||||
<VaultLogo vault={vault} />
|
<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 className='w-full'>{vault.name}</Text>
|
||||||
<Text size='sm' className='w-full text-white/50'>
|
<Text size='sm' className='w-full text-white/50'>
|
||||||
Unlocked
|
Unlocked
|
||||||
@ -72,14 +72,16 @@ export default function WithdrawFromVaults() {
|
|||||||
<div className='flex flex-wrap'>
|
<div className='flex flex-wrap'>
|
||||||
<DisplayCurrency coin={coin} className='w-full text-right' />
|
<DisplayCurrency coin={coin} className='w-full text-right' />
|
||||||
<FormattedNumber
|
<FormattedNumber
|
||||||
amount={BN(demagnify(vault.amounts.primary, primaryAsset))}
|
amount={demagnify(vault.amounts.primary, primaryAsset)}
|
||||||
className='w-full text-right text-sm text-white/50'
|
className='w-full text-right text-sm text-white/50'
|
||||||
options={{ suffix: ` ${vault.symbols.primary}` }}
|
options={{ suffix: ` ${vault.symbols.primary}` }}
|
||||||
|
animate
|
||||||
/>
|
/>
|
||||||
<FormattedNumber
|
<FormattedNumber
|
||||||
amount={BN(demagnify(vault.amounts.secondary, secondaryAsset))}
|
amount={demagnify(vault.amounts.secondary, secondaryAsset)}
|
||||||
className='w-full text-right text-sm text-white/50'
|
className='w-full text-right text-sm text-white/50'
|
||||||
options={{ suffix: ` ${vault.symbols.secondary}` }}
|
options={{ suffix: ` ${vault.symbols.secondary}` }}
|
||||||
|
animate
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
@ -1,8 +1,9 @@
|
|||||||
export { default as AddVaultBorrowAssetsModal } from 'components/Modals/AddVaultAssets/AddVaultBorrowAssetsModal'
|
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 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 (
|
return (
|
||||||
<div className='flex flex-grow items-center'>
|
<div className='flex flex-1 items-center'>
|
||||||
<NavLink href={getRoute('trade', address, accountId)} isActive={false}>
|
<NavLink href={getRoute('trade', address, accountId)} isActive={false}>
|
||||||
<span className='block h-10 w-10'>
|
<span className='block h-10 w-10'>
|
||||||
<Logo />
|
<Logo />
|
||||||
|
@ -6,7 +6,7 @@ import { demagnify, formatValue, magnify } from 'utils/formatters'
|
|||||||
import { BN } from 'utils/helpers'
|
import { BN } from 'utils/helpers'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
asset: Asset
|
asset: Asset | PseudoAsset
|
||||||
amount: BigNumber
|
amount: BigNumber
|
||||||
min?: BigNumber
|
min?: BigNumber
|
||||||
max?: BigNumber
|
max?: BigNumber
|
||||||
@ -16,6 +16,7 @@ interface Props {
|
|||||||
allowNegative?: boolean
|
allowNegative?: boolean
|
||||||
style?: {}
|
style?: {}
|
||||||
disabled?: boolean
|
disabled?: boolean
|
||||||
|
placeholder?: string
|
||||||
onChange: (amount: BigNumber) => void
|
onChange: (amount: BigNumber) => void
|
||||||
onBlur?: () => void
|
onBlur?: () => void
|
||||||
onFocus?: () => void
|
onFocus?: () => void
|
||||||
@ -37,11 +38,11 @@ export default function NumberInput(props: Props) {
|
|||||||
formatValue(props.amount.toNumber(), {
|
formatValue(props.amount.toNumber(), {
|
||||||
decimals: props.asset.decimals,
|
decimals: props.asset.decimals,
|
||||||
minDecimals: 0,
|
minDecimals: 0,
|
||||||
maxDecimals: props.asset.decimals,
|
maxDecimals: props.maxDecimals,
|
||||||
thousandSeparator: false,
|
thousandSeparator: false,
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
}, [props.amount, props.asset])
|
}, [props.amount, props.asset, props.maxDecimals])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!props.onRef) return
|
if (!props.onRef) return
|
||||||
@ -157,7 +158,7 @@ export default function NumberInput(props: Props) {
|
|||||||
props.className,
|
props.className,
|
||||||
)}
|
)}
|
||||||
style={props.style}
|
style={props.style}
|
||||||
placeholder='0'
|
placeholder={props.placeholder ?? '0'}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import classNames from 'classnames'
|
import classNames from 'classnames'
|
||||||
import { ChangeEvent, forwardRef } from 'react'
|
import { ChangeEvent, LegacyRef } from 'react'
|
||||||
|
import React from 'react'
|
||||||
|
|
||||||
import { Search } from 'components/Icons'
|
import { Search } from 'components/Icons'
|
||||||
|
|
||||||
@ -10,7 +11,7 @@ interface Props {
|
|||||||
onChange: (value: string) => void
|
onChange: (value: string) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
const SearchBar = (props: Props) => {
|
const SearchBar = (props: Props, ref: LegacyRef<HTMLDivElement>) => {
|
||||||
function onChange(event: ChangeEvent<HTMLInputElement>) {
|
function onChange(event: ChangeEvent<HTMLInputElement>) {
|
||||||
props.onChange(event.target.value)
|
props.onChange(event.target.value)
|
||||||
}
|
}
|
||||||
@ -22,11 +23,12 @@ const SearchBar = (props: Props) => {
|
|||||||
'relative isolate max-w-full overflow-hidden rounded-sm',
|
'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',
|
'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' />
|
<Search width={14} height={14} className='mr-2.5 text-white' />
|
||||||
<input
|
<input
|
||||||
value={props.value}
|
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}
|
placeholder={props.placeholder}
|
||||||
onChange={(event) => onChange(event)}
|
onChange={(event) => onChange(event)}
|
||||||
autoFocus={props.autofocus}
|
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 classNames from 'classnames'
|
||||||
|
|
||||||
|
import AssetImage from 'components/AssetImage'
|
||||||
import DisplayCurrency from 'components/DisplayCurrency'
|
import DisplayCurrency from 'components/DisplayCurrency'
|
||||||
import { ChevronDown } from 'components/Icons'
|
import { ChevronDown, ChevronRight } from 'components/Icons'
|
||||||
import Text from 'components/Text'
|
import Text from 'components/Text'
|
||||||
import { ASSETS } from 'constants/assets'
|
import { ASSETS } from 'constants/assets'
|
||||||
import { formatValue } from 'utils/formatters'
|
|
||||||
import AssetImage from 'components/AssetImage'
|
|
||||||
import { BNCoin } from 'types/classes/BNCoin'
|
import { BNCoin } from 'types/classes/BNCoin'
|
||||||
|
import { formatValue } from 'utils/formatters'
|
||||||
|
|
||||||
interface Props extends Option {
|
interface Props extends SelectOption {
|
||||||
isSelected?: boolean
|
isSelected?: boolean
|
||||||
isDisplay?: boolean
|
isDisplay?: boolean
|
||||||
isClicked?: boolean
|
isClicked?: boolean
|
||||||
onClick?: (value: string) => void
|
onClick?: (value: string) => void
|
||||||
|
displayClassName?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function Option(props: Props) {
|
export default function Option(props: Props) {
|
||||||
const isCoin = !!props.denom
|
const isCoin = !!props.denom
|
||||||
|
|
||||||
|
function handleOnClick(value: string | undefined) {
|
||||||
|
if (!props.onClick || !value) return
|
||||||
|
props.onClick(value)
|
||||||
|
}
|
||||||
|
|
||||||
if (isCoin) {
|
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'
|
const balance = props.amount ?? '0'
|
||||||
|
|
||||||
if (props.isDisplay) {
|
if (props.isDisplay) {
|
||||||
return (
|
return (
|
||||||
<div
|
<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} />
|
<AssetImage asset={asset} size={20} />
|
||||||
<span>{asset.symbol}</span>
|
<span className='flex'>{asset.symbol}</span>
|
||||||
<span
|
<ChevronRight
|
||||||
className={classNames(
|
className={classNames(
|
||||||
'inline-block w-2.5 transition-transform',
|
'block h-3 w-1.5 transition-transform',
|
||||||
props.isClicked ? 'rotate-0' : '-rotate-90',
|
props.isClicked ? 'rotate-90' : 'rotate-0',
|
||||||
)}
|
)}
|
||||||
>
|
/>
|
||||||
<ChevronDown />
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -50,7 +58,7 @@ export default function Option(props: Props) {
|
|||||||
'hover:cursor-pointer hover:bg-white/20',
|
'hover:cursor-pointer hover:bg-white/20',
|
||||||
!props.isSelected ? 'bg-white/10' : 'pointer-events-none',
|
!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'>
|
<div className='row-span-2 flex h-full items-center justify-center'>
|
||||||
<AssetImage asset={asset} size={32} />
|
<AssetImage asset={asset} size={32} />
|
||||||
@ -74,15 +82,12 @@ export default function Option(props: Props) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const label = props.label
|
|
||||||
if (props.isDisplay) {
|
if (props.isDisplay) {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={classNames(
|
className={classNames('flex w-full items-center justify-between p-3 hover:cursor-pointer')}
|
||||||
'flex w-full items-center justify-between bg-white/10 p-3 hover:cursor-pointer',
|
|
||||||
)}
|
|
||||||
>
|
>
|
||||||
<span>{label}</span>
|
<span className='flex flex-1'>{props.label}</span>
|
||||||
<span
|
<span
|
||||||
className={classNames(
|
className={classNames(
|
||||||
'inline-block w-2.5 transition-transform',
|
'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',
|
'block p-3 hover:cursor-pointer hover:bg-white/20',
|
||||||
props.isSelected && 'bg-white/10',
|
props.isSelected && 'bg-white/10',
|
||||||
)}
|
)}
|
||||||
onClick={() => props?.onClick && props.onClick(props.value)}
|
onClick={() => handleOnClick(props.value)}
|
||||||
>
|
>
|
||||||
{label}
|
{props.label}
|
||||||
</div>
|
</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 Button from 'components/Button'
|
||||||
import { Gear } from 'components/Icons'
|
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 useStore from 'store'
|
||||||
import { getDisplayCurrencies } from 'utils/assets'
|
|
||||||
|
|
||||||
export default function Settings() {
|
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 (
|
return (
|
||||||
<div className='relative'>
|
<Button
|
||||||
<Button
|
variant='solid'
|
||||||
variant='solid'
|
color='tertiary'
|
||||||
color='tertiary'
|
leftIcon={<Gear />}
|
||||||
leftIcon={<Gear />}
|
onClick={() => useStore.setState({ settingsModal: true })}
|
||||||
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>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -195,7 +195,7 @@ function Track(props: TrackProps) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
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 z-1 h-3 bg-martian-red ' style={{ width: `${percentage}%` }} />
|
||||||
<div className='absolute h-3 w-full bg-white/20' />
|
<div className='absolute h-3 w-full bg-white/20' />
|
||||||
</div>
|
</div>
|
||||||
|
@ -3,7 +3,7 @@ import classNames from 'classnames'
|
|||||||
interface Props {
|
interface Props {
|
||||||
name: string
|
name: string
|
||||||
checked: boolean
|
checked: boolean
|
||||||
onChange: () => void
|
onChange: (value: boolean) => void
|
||||||
className?: string
|
className?: string
|
||||||
disabled?: boolean
|
disabled?: boolean
|
||||||
}
|
}
|
||||||
@ -23,7 +23,7 @@ export default function Switch(props: Props) {
|
|||||||
name={props.name}
|
name={props.name}
|
||||||
className={classNames('peer hidden')}
|
className={classNames('peer hidden')}
|
||||||
checked={props.checked}
|
checked={props.checked}
|
||||||
onChange={props.onChange}
|
onChange={() => props.onChange(!props.checked)}
|
||||||
/>
|
/>
|
||||||
<label
|
<label
|
||||||
htmlFor={props.name}
|
htmlFor={props.name}
|
||||||
|
@ -1,15 +1,16 @@
|
|||||||
import classNames from 'classnames'
|
import classNames from 'classnames'
|
||||||
import { toast as createToast, Slide, ToastContainer } from 'react-toastify'
|
import { toast as createToast, Slide, ToastContainer } from 'react-toastify'
|
||||||
import { useNavigate } from 'react-router-dom'
|
|
||||||
import { mutate } from 'swr'
|
import { mutate } from 'swr'
|
||||||
|
|
||||||
import Button from 'components/Button'
|
|
||||||
import { CheckCircled, Cross, CrossCircled } from 'components/Icons'
|
import { CheckCircled, Cross, CrossCircled } from 'components/Icons'
|
||||||
import Text from 'components/Text'
|
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'
|
import useStore from 'store'
|
||||||
|
|
||||||
export default function Toaster() {
|
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)
|
const toast = useStore((s) => s.toast)
|
||||||
|
|
||||||
if (toast) {
|
if (toast) {
|
||||||
@ -66,7 +67,7 @@ export default function Toaster() {
|
|||||||
position='top-right'
|
position='top-right'
|
||||||
newestOnTop
|
newestOnTop
|
||||||
closeOnClick
|
closeOnClick
|
||||||
transition={enableAnimations ? Slide : undefined}
|
transition={reduceMotion ? undefined : Slide}
|
||||||
className='p-0'
|
className='p-0'
|
||||||
toastClassName='top-[73px] z-20 m-0 mb-4 flex w-full bg-transparent 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'
|
bodyClassName='p-0 m-0 w-full flex'
|
||||||
|
@ -1,20 +1,19 @@
|
|||||||
import BigNumber from 'bignumber.js'
|
import BigNumber from 'bignumber.js'
|
||||||
import classNames from 'classnames'
|
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 DisplayCurrency from 'components/DisplayCurrency'
|
||||||
|
import { FormattedNumber } from 'components/FormattedNumber'
|
||||||
|
import { ExclamationMarkTriangle, TrashBin } from 'components/Icons'
|
||||||
import NumberInput from 'components/NumberInput'
|
import NumberInput from 'components/NumberInput'
|
||||||
import Select from 'components/Select/Select'
|
import Select from 'components/Select'
|
||||||
import Text from 'components/Text'
|
import Text from 'components/Text'
|
||||||
|
import { Tooltip } from 'components/Tooltip'
|
||||||
import { ASSETS } from 'constants/assets'
|
import { ASSETS } from 'constants/assets'
|
||||||
import useStore from 'store'
|
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 { BNCoin } from 'types/classes/BNCoin'
|
||||||
|
import { BN } from 'utils/helpers'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
amount: BigNumber
|
amount: BigNumber
|
||||||
@ -65,7 +64,8 @@ export default function TokenInput(props: Props) {
|
|||||||
defaultValue={props.asset.denom}
|
defaultValue={props.asset.denom}
|
||||||
onChange={onChangeAsset}
|
onChange={onChangeAsset}
|
||||||
title={props.accountId ? `Account ${props.accountId}` : 'Your Wallet'}
|
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'>
|
<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}
|
onChange={props.onChange}
|
||||||
amount={props.amount}
|
amount={props.amount}
|
||||||
max={props.max}
|
max={props.max}
|
||||||
className='border-none p-3'
|
className='flex-1 border-none p-3'
|
||||||
/>
|
/>
|
||||||
{props.onDelete && (
|
{props.onDelete && (
|
||||||
<div role='button' className='grid items-center pr-2' onClick={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>
|
</Text>
|
||||||
<FormattedNumber
|
<FormattedNumber
|
||||||
className='mr-1 text-xs text-white/50'
|
className='mr-1 text-xs text-white/50'
|
||||||
amount={props.max}
|
amount={props.max.toNumber()}
|
||||||
options={{ decimals: props.asset.decimals }}
|
options={{ decimals: props.asset.decimals }}
|
||||||
|
animate
|
||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
dataTestId='token-input-max-button'
|
dataTestId='token-input-max-button'
|
||||||
|
@ -24,7 +24,7 @@ export default function TokenInputWithSlider(props: Props) {
|
|||||||
const [percentage, setPercentage] = useState(0)
|
const [percentage, setPercentage] = useState(0)
|
||||||
|
|
||||||
function onChangeSlider(percentage: number) {
|
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)
|
setPercentage(percentage)
|
||||||
setAmount(newAmount)
|
setAmount(newAmount)
|
||||||
props.onChange(newAmount)
|
props.onChange(newAmount)
|
||||||
@ -32,7 +32,7 @@ export default function TokenInputWithSlider(props: Props) {
|
|||||||
|
|
||||||
function onChangeAmount(newAmount: BigNumber) {
|
function onChangeAmount(newAmount: BigNumber) {
|
||||||
setAmount(newAmount)
|
setAmount(newAmount)
|
||||||
setPercentage(BN(newAmount).div(props.max).times(100).toNumber())
|
setPercentage(BN(newAmount).dividedBy(props.max).multipliedBy(100).toNumber())
|
||||||
props.onChange(newAmount)
|
props.onChange(newAmount)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3,8 +3,10 @@ import classNames from 'classnames'
|
|||||||
import { ReactNode } from 'react'
|
import { ReactNode } from 'react'
|
||||||
|
|
||||||
import { Questionmark } from 'components/Icons'
|
import { Questionmark } from 'components/Icons'
|
||||||
import useStore from 'store'
|
|
||||||
import TooltipContent from 'components/Tooltip/TooltipContent'
|
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 {
|
interface Props {
|
||||||
content: ReactNode | string
|
content: ReactNode | string
|
||||||
@ -19,7 +21,7 @@ interface Props {
|
|||||||
export type TooltipType = 'info' | 'warning' | 'error'
|
export type TooltipType = 'info' | 'warning' | 'error'
|
||||||
|
|
||||||
export const Tooltip = (props: Props) => {
|
export const Tooltip = (props: Props) => {
|
||||||
const enableAnimations = useStore((s) => s.enableAnimations)
|
const [reduceMotion] = useLocalStorage<boolean>(REDUCE_MOTION_KEY, DEFAULT_SETTINGS.reduceMotion)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Tippy
|
<Tippy
|
||||||
@ -34,7 +36,7 @@ export const Tooltip = (props: Props) => {
|
|||||||
className={classNames(
|
className={classNames(
|
||||||
props.underline &&
|
props.underline &&
|
||||||
'border-b-1 cursor-pointer border border-x-0 border-t-0 border-dashed border-white/50 hover:border-transparent',
|
'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,
|
props.className,
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
|
@ -14,6 +14,7 @@ export default function AssetButton(props: Props) {
|
|||||||
color='tertiary'
|
color='tertiary'
|
||||||
variant='transparent'
|
variant='transparent'
|
||||||
className='w-full border border-white/20'
|
className='w-full border border-white/20'
|
||||||
|
textClassNames='flex flex-1'
|
||||||
size='md'
|
size='md'
|
||||||
hasSubmenu
|
hasSubmenu
|
||||||
{...props}
|
{...props}
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
import { useCallback, useMemo, useState } from 'react'
|
import { useCallback, useState } from 'react'
|
||||||
|
|
||||||
import { SwapIcon } from 'components/Icons'
|
import { SwapIcon } from 'components/Icons'
|
||||||
import Text from 'components/Text'
|
import Text from 'components/Text'
|
||||||
import { ASSETS } from 'constants/assets'
|
|
||||||
import AssetButton from 'components/Trade/TradeModule/AssetSelector/AssetButton'
|
import AssetButton from 'components/Trade/TradeModule/AssetSelector/AssetButton'
|
||||||
import AssetOverlay, { OverlayState } from 'components/Trade/TradeModule/AssetSelector/AssetOverlay'
|
import AssetOverlay, { OverlayState } from 'components/Trade/TradeModule/AssetSelector/AssetOverlay'
|
||||||
|
import { ASSETS } from 'constants/assets'
|
||||||
|
|
||||||
export default function AssetSelector() {
|
export default function AssetSelector() {
|
||||||
const [overlayState, setOverlayState] = useState<OverlayState>('closed')
|
const [overlayState, setOverlayState] = useState<OverlayState>('closed')
|
||||||
@ -38,11 +38,6 @@ export default function AssetSelector() {
|
|||||||
[setOverlayState],
|
[setOverlayState],
|
||||||
)
|
)
|
||||||
|
|
||||||
const buyAssets = useMemo(
|
|
||||||
() => ASSETS.filter((asset) => asset.denom !== sellAsset.denom),
|
|
||||||
[sellAsset],
|
|
||||||
)
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='grid-rows-auto relative grid grid-cols-[1fr_min-content_1fr] gap-y-2 bg-white/5 p-3'>
|
<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>
|
<Text size='sm'>Buy</Text>
|
@ -1,29 +1,9 @@
|
|||||||
import { useState } from 'react'
|
|
||||||
import { useParams } from 'react-router-dom'
|
|
||||||
import classNames from 'classnames'
|
import classNames from 'classnames'
|
||||||
|
import { useState } from 'react'
|
||||||
|
|
||||||
import Loading from 'components/Loading'
|
|
||||||
import Text from 'components/Text'
|
|
||||||
import Divider from 'components/Divider'
|
import Divider from 'components/Divider'
|
||||||
import RangeInput from 'components/RangeInput'
|
import RangeInput from 'components/RangeInput'
|
||||||
import AssetSelector from 'components/Trade/TradeModule/AssetSelector/AssetSelector'
|
import AssetSelector from 'components/Trade/TradeModule/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' />
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function TradeModule() {
|
export default function TradeModule() {
|
||||||
const [value, setValue] = useState(0)
|
const [value, setValue] = useState(0)
|
||||||
@ -38,10 +18,13 @@ export default function TradeModule() {
|
|||||||
>
|
>
|
||||||
<AssetSelector />
|
<AssetSelector />
|
||||||
<Divider />
|
<Divider />
|
||||||
|
<RangeInput
|
||||||
<div className='p-4'>
|
max={4000}
|
||||||
<RangeInput max={4000} marginThreshold={2222} value={value} onChange={setValue} />
|
marginThreshold={2222}
|
||||||
</div>
|
value={value}
|
||||||
|
onChange={setValue}
|
||||||
|
wrapperClassName='p-4'
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -17,10 +17,10 @@ import Overlay from 'components/Overlay'
|
|||||||
import Text from 'components/Text'
|
import Text from 'components/Text'
|
||||||
import { IS_TESTNET } from 'constants/env'
|
import { IS_TESTNET } from 'constants/env'
|
||||||
import useToggle from 'hooks/useToggle'
|
import useToggle from 'hooks/useToggle'
|
||||||
|
import useWalletBalances from 'hooks/useWalletBalances'
|
||||||
import useStore from 'store'
|
import useStore from 'store'
|
||||||
import { getBaseAsset, getEnabledMarketAssets } from 'utils/assets'
|
import { getBaseAsset, getEnabledMarketAssets } from 'utils/assets'
|
||||||
import { formatValue, truncate } from 'utils/formatters'
|
import { formatValue, truncate } from 'utils/formatters'
|
||||||
import useWalletBalances from 'hooks/useWalletBalances'
|
|
||||||
import { BN } from 'utils/helpers'
|
import { BN } from 'utils/helpers'
|
||||||
|
|
||||||
export default function ConnectedButton() {
|
export default function ConnectedButton() {
|
||||||
@ -64,7 +64,7 @@ export default function ConnectedButton() {
|
|||||||
if (!walletBalances || walletBalances.length === 0) return
|
if (!walletBalances || walletBalances.length === 0) return
|
||||||
const newAmount = BigNumber(
|
const newAmount = BigNumber(
|
||||||
walletBalances?.find((coin: Coin) => coin.denom === baseAsset.denom)?.amount ?? 0,
|
walletBalances?.find((coin: Coin) => coin.denom === baseAsset.denom)?.amount ?? 0,
|
||||||
).div(10 ** baseAsset.decimals)
|
).dividedBy(10 ** baseAsset.decimals)
|
||||||
|
|
||||||
if (walletAmount.isEqualTo(newAmount)) return
|
if (walletAmount.isEqualTo(newAmount)) return
|
||||||
setWalletAmount(newAmount)
|
setWalletAmount(newAmount)
|
||||||
@ -120,7 +120,7 @@ export default function ConnectedButton() {
|
|||||||
<FormattedNumber
|
<FormattedNumber
|
||||||
animate
|
animate
|
||||||
className='flex items-end text-2xl '
|
className='flex items-end text-2xl '
|
||||||
amount={walletAmount}
|
amount={walletAmount.toNumber()}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</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',
|
'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:
|
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',
|
'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',
|
modalCloseButton: 'inline-block',
|
||||||
walletList: 'w-full',
|
walletList: 'w-full',
|
||||||
wallet:
|
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 DISPLAY_CURRENCY_KEY = 'displayCurrency'
|
||||||
export const ENABLE_ANIMATIONS_KEY = 'enableAnimations'
|
export const REDUCE_MOTION_KEY = 'reduceMotion'
|
||||||
export const FAVORITE_ASSETS = 'favoriteAssets'
|
export const FAVORITE_ASSETS = 'favoriteAssets'
|
||||||
|
export const LEND_ASSETS_KEY = 'lendAssets'
|
||||||
export const AUTO_LEND_ENABLED_ACCOUNT_IDS_KEY = 'autoLendEnabledAccountIds'
|
export const AUTO_LEND_ENABLED_ACCOUNT_IDS_KEY = 'autoLendEnabledAccountIds'
|
||||||
|
export const SLIPPAGE_KEY = 'slippage'
|
||||||
|
@ -1,18 +1,20 @@
|
|||||||
import debounce from 'debounce-promise'
|
import debounce from 'debounce-promise'
|
||||||
import { useMemo, useState } from 'react'
|
import { useMemo, useState } from 'react'
|
||||||
|
|
||||||
import { hardcodedFee } from 'utils/constants'
|
|
||||||
import getMinLpToReceive from 'api/vaults/getMinLpToReceive'
|
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 usePrices from 'hooks/usePrices'
|
||||||
import useStore from 'store'
|
|
||||||
import { BNCoin } from 'types/classes/BNCoin'
|
import { BNCoin } from 'types/classes/BNCoin'
|
||||||
import { Action } from 'types/generated/mars-credit-manager/MarsCreditManager.types'
|
import { Action } from 'types/generated/mars-credit-manager/MarsCreditManager.types'
|
||||||
|
import { hardcodedFee } from 'utils/constants'
|
||||||
|
import { BN } from 'utils/helpers'
|
||||||
import {
|
import {
|
||||||
getEnterVaultActions,
|
getEnterVaultActions,
|
||||||
getVaultDepositCoinsAndValue,
|
getVaultDepositCoinsAndValue,
|
||||||
getVaultSwapActions,
|
getVaultSwapActions,
|
||||||
} from 'utils/vaults'
|
} from 'utils/vaults'
|
||||||
import { BN } from 'utils/helpers'
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
vault: Vault
|
vault: Vault
|
||||||
@ -22,7 +24,7 @@ interface Props {
|
|||||||
export default function useDepositVault(props: Props): { actions: Action[]; fee: StdFee } {
|
export default function useDepositVault(props: Props): { actions: Action[]; fee: StdFee } {
|
||||||
const [minLpToReceive, setMinLpToReceive] = useState<BigNumber>(BN(0))
|
const [minLpToReceive, setMinLpToReceive] = useState<BigNumber>(BN(0))
|
||||||
const { data: prices } = usePrices()
|
const { data: prices } = usePrices()
|
||||||
const slippage = useStore((s) => s.slippage)
|
const [slippage] = useLocalStorage<number>(SLIPPAGE_KEY, DEFAULT_SETTINGS.slippage)
|
||||||
|
|
||||||
const borrowings: BNCoin[] = useMemo(
|
const borrowings: BNCoin[] = useMemo(
|
||||||
() => props.borrowings.filter((borrowing) => borrowing.amount.gt(0)),
|
() => props.borrowings.filter((borrowing) => borrowing.amount.gt(0)),
|
||||||
@ -60,7 +62,7 @@ export default function useDepositVault(props: Props): { actions: Action[]; fee:
|
|||||||
slippage,
|
slippage,
|
||||||
)
|
)
|
||||||
|
|
||||||
if (!lpAmount || lpAmount.eq(minLpToReceive)) return
|
if (!lpAmount || lpAmount.isEqualTo(minLpToReceive)) return
|
||||||
setMinLpToReceive(lpAmount)
|
setMinLpToReceive(lpAmount)
|
||||||
}, [
|
}, [
|
||||||
primaryCoin,
|
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 { ASSETS } from 'constants/assets'
|
||||||
import { BN } from 'utils/helpers'
|
import { DEFAULT_SETTINGS } from 'constants/defaultSettings'
|
||||||
import { byDenom } from 'utils/array'
|
import { DISPLAY_CURRENCY_KEY } from 'constants/localStore'
|
||||||
|
import useLocalStorage from 'hooks/useLocalStorage'
|
||||||
import usePrices from 'hooks/usePrices'
|
import usePrices from 'hooks/usePrices'
|
||||||
|
import { byDenom } from 'utils/array'
|
||||||
|
import { getDisplayCurrencies } from 'utils/assets'
|
||||||
|
import { BN } from 'utils/helpers'
|
||||||
|
|
||||||
function useDisplayCurrencyPrice() {
|
function useDisplayCurrencyPrice() {
|
||||||
const { data: prices } = usePrices()
|
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(
|
const getConversionRate = useCallback(
|
||||||
(denom: string) => {
|
(denom: string) => {
|
||||||
const assetPrice = prices.find(byDenom(denom))
|
const assetPrice = prices.find(byDenom(denom))
|
||||||
const displayCurrencyPrice = prices.find(byDenom(displayCurrency.denom))
|
const displayCurrencyPrice = prices.find(byDenom(displayCurrency))
|
||||||
|
|
||||||
if (assetPrice && displayCurrencyPrice) {
|
if (assetPrice && displayCurrencyPrice) {
|
||||||
return BN(assetPrice.amount).div(displayCurrencyPrice.amount)
|
return BN(assetPrice.amount).dividedBy(displayCurrencyPrice.amount)
|
||||||
} else {
|
} else {
|
||||||
throw 'Given denom or display currency price has not found'
|
throw 'Given denom or display currency price has not found'
|
||||||
}
|
}
|
||||||
@ -32,7 +46,7 @@ function useDisplayCurrencyPrice() {
|
|||||||
return {
|
return {
|
||||||
getConversionRate,
|
getConversionRate,
|
||||||
convertAmount,
|
convertAmount,
|
||||||
symbol: displayCurrency.symbol,
|
symbol: displayCurrencyAsset?.symbol,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,11 +1,13 @@
|
|||||||
import { AvailableBorrowings } from 'components/Borrow/Borrowings'
|
import BorrowTable from 'components/Borrow/BorrowTable'
|
||||||
import { ActiveBorrowings } from 'components/Borrow/Borrowings'
|
import useBorrowMarketAssetsTableData from 'hooks/useBorrowMarketAssetsTableData'
|
||||||
|
|
||||||
export default function BorrowPage() {
|
export default function BorrowPage() {
|
||||||
|
const { accountBorrowedAssets, availableAssets } = useBorrowMarketAssetsTableData()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<ActiveBorrowings />
|
<BorrowTable data={accountBorrowedAssets} title='Borrowed Assets' />
|
||||||
<AvailableBorrowings />
|
<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 Tab from 'components/Earn/Tab'
|
||||||
import { AvailableVaults, DepositedVaults } from 'components/Earn/vault/Vaults'
|
|
||||||
|
|
||||||
export default function FarmPage() {
|
export default function FarmPage() {
|
||||||
return (
|
return (
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import AccountOverview from 'components/Portfolio/AccountOverview'
|
import AccountOverview from 'components/Account/AccountOverview'
|
||||||
|
|
||||||
export default function PortfolioPage() {
|
export default function PortfolioPage() {
|
||||||
return <AccountOverview />
|
return <AccountOverview />
|
||||||
|
@ -4,7 +4,7 @@ export default function Document() {
|
|||||||
return (
|
return (
|
||||||
<Html className='m-0 p-0' lang='en'>
|
<Html className='m-0 p-0' lang='en'>
|
||||||
<Head />
|
<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 />
|
<Main />
|
||||||
<NextScript />
|
<NextScript />
|
||||||
</body>
|
</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