update layout for modal, implement borrow tables (#105)
This commit is contained in:
parent
493ec7c44c
commit
cbb0700455
@ -1,4 +1,5 @@
|
||||
import Background from 'components/Background'
|
||||
import FetchPrices from 'components/FetchPrices'
|
||||
import { Modals } from 'components/Modals'
|
||||
import DesktopNavigation from 'components/Navigation/DesktopNavigation'
|
||||
import Toaster from 'components/Toaster'
|
||||
@ -19,6 +20,7 @@ export default function RootLayout({ children }: { children: React.ReactNode })
|
||||
</WalletConnectProvider>
|
||||
<Modals />
|
||||
<Toaster />
|
||||
<FetchPrices />
|
||||
<main className='relative flex lg:min-h-[calc(100vh-120px)]'>
|
||||
<div className='flex flex-grow flex-col flex-wrap'>{children}</div>
|
||||
</main>
|
||||
|
@ -1,5 +0,0 @@
|
||||
import Loading from 'components/Loading'
|
||||
|
||||
export default function page() {
|
||||
return <Loading />
|
||||
}
|
@ -17,10 +17,7 @@ export default async function page({ params }: { params: PageParams }) {
|
||||
if (debt) {
|
||||
prev.active.push({
|
||||
...borrow,
|
||||
debt: {
|
||||
amount: '100000',
|
||||
value: '12389478321',
|
||||
},
|
||||
debt: debt.amount,
|
||||
})
|
||||
} else {
|
||||
prev.available.push(borrow)
|
||||
|
@ -10,7 +10,6 @@ import { ArrowRightLine, ChevronDown, ChevronLeft } from 'components/Icons'
|
||||
import { LabelValuePair } from 'components/LabelValuePair'
|
||||
import { PositionsList } from 'components/PositionsList'
|
||||
import { useAccountStats } from 'hooks/data/useAccountStats'
|
||||
import { useBalances } from 'hooks/data/useBalances'
|
||||
import { convertFromGwei } from 'utils/formatters'
|
||||
import { createRiskData } from 'utils/risk'
|
||||
import useStore from 'store'
|
||||
@ -23,7 +22,6 @@ export const AccountDetails = () => {
|
||||
const marketAssets = getMarketAssets()
|
||||
const baseAsset = getBaseAsset()
|
||||
|
||||
const balances = useBalances()
|
||||
const accountStats = useAccountStats()
|
||||
|
||||
const [showManageMenu, setShowManageMenu] = useState(false)
|
||||
@ -118,7 +116,6 @@ export const AccountDetails = () => {
|
||||
/>
|
||||
</div>
|
||||
{riskData && <RiskChart data={riskData} />}
|
||||
<PositionsList title='Balances' data={balances} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
@ -10,7 +10,7 @@ import { Text } from 'components/Text'
|
||||
import { useAccountStats } from 'hooks/data/useAccountStats'
|
||||
import { useCreditAccounts } from 'hooks/queries/useCreditAccounts'
|
||||
import { getBaseAsset } from 'utils/assets'
|
||||
import { formatValue } from 'utils/formatters'
|
||||
import { formatLeverage, formatValue } from 'utils/formatters'
|
||||
|
||||
export const AccountStatus = () => {
|
||||
const baseAsset = getBaseAsset()
|
||||
@ -41,7 +41,7 @@ export const AccountStatus = () => {
|
||||
.dividedBy(10 ** baseAsset.decimals)
|
||||
.toNumber()}
|
||||
animate
|
||||
prefix='$: '
|
||||
options={{ prefix: '$: ' }}
|
||||
/>
|
||||
</Text>
|
||||
|
||||
@ -50,10 +50,9 @@ export const AccountStatus = () => {
|
||||
label='Lvg'
|
||||
tooltip={
|
||||
<Text size='sm'>
|
||||
Current Leverage:{' '}
|
||||
{formatValue(accountStats.currentLeverage, 0, 2, true, false, 'x')}
|
||||
Current Leverage: {formatLeverage(accountStats.currentLeverage)}
|
||||
<br />
|
||||
Max Leverage: {formatValue(accountStats.maxLeverage, 0, 2, true, false, 'x')}
|
||||
Max Leverage: {formatLeverage(accountStats.maxLeverage)}
|
||||
</Text>
|
||||
}
|
||||
/>
|
||||
@ -63,7 +62,8 @@ export const AccountStatus = () => {
|
||||
label='Risk'
|
||||
tooltip={
|
||||
<Text size='sm'>
|
||||
Current Risk: {formatValue(accountStats.risk * 100, 0, 2, true, false, '%')}
|
||||
Current Risk:{' '}
|
||||
{formatValue(accountStats.risk * 100, { minDecimals: 0, suffix: '%' })}
|
||||
</Text>
|
||||
}
|
||||
/>
|
||||
|
@ -13,7 +13,7 @@ export const ConfirmModal = () => {
|
||||
const deleteOpen = useStore((s) => s.deleteAccountModal)
|
||||
|
||||
return (
|
||||
<Modal open={createOpen || deleteOpen}>
|
||||
<Modal title='Confirm' open={createOpen || deleteOpen}>
|
||||
<div
|
||||
className={classNames(
|
||||
'relative flex h-[630px] w-full flex-wrap items-center justify-center p-6',
|
||||
|
@ -94,7 +94,7 @@ export const FundAccountModal = () => {
|
||||
const percentageValue = isNaN(amount) ? 0 : (amount * 100) / walletAmount
|
||||
|
||||
return (
|
||||
<Modal open={open} setOpen={setOpen}>
|
||||
<Modal title='Fund account' open={open} setOpen={setOpen}>
|
||||
<div className='flex min-h-[520px] w-full'>
|
||||
{balanceIsLoading && (
|
||||
<div className='absolute inset-0 z-40 grid place-items-center bg-black/50'>
|
||||
|
@ -25,11 +25,13 @@ export const RiskChart = ({ data }: RiskChartProps) => {
|
||||
<FormattedNumber
|
||||
className='px-3 pb-2 text-lg'
|
||||
amount={currentRisk * 100}
|
||||
maxDecimals={0}
|
||||
minDecimals={0}
|
||||
options={{
|
||||
maxDecimals: 0,
|
||||
minDecimals: 0,
|
||||
prefix: 'Risk score: ',
|
||||
suffix: '/100',
|
||||
}}
|
||||
animate
|
||||
prefix='Risk Score: '
|
||||
suffix='/100'
|
||||
/>
|
||||
<div className='-ml-6 h-[100px] w-[412px]'>
|
||||
<ResponsiveContainer width='100%' height='100%'>
|
||||
@ -77,7 +79,9 @@ export const RiskChart = ({ data }: RiskChartProps) => {
|
||||
return (
|
||||
<div className='max-w-[320px] rounded-lg px-4 py-2 shadow-tooltip gradient-tooltip '>
|
||||
<Text size='sm'>{moment(label).format('MM-DD-YYYY')}</Text>
|
||||
<Text size='sm'>Risk: {formatValue(risk, 0, 0, true, false, '%')}</Text>
|
||||
<Text size='sm'>
|
||||
Risk: {formatValue(risk, { minDecimals: 0, maxDecimals: 0, suffix: '%' })}
|
||||
</Text>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
@ -5,7 +5,7 @@ import React, { useEffect, useMemo, useState } from 'react'
|
||||
import { toast } from 'react-toastify'
|
||||
|
||||
import { BorrowCapacity } from 'components/BorrowCapacity'
|
||||
import { convertFromGwei, formatValue } from 'utils/formatters'
|
||||
import { convertFromGwei, formatLeverage, formatValue } from 'utils/formatters'
|
||||
import { getTokenDecimals, getTokenSymbol } from 'utils/tokens'
|
||||
import { CircularProgress } from 'components/CircularProgress'
|
||||
import { Button } from 'components/Button'
|
||||
@ -17,7 +17,6 @@ import { LabelValuePair } from 'components/LabelValuePair'
|
||||
import { Modal } from 'components/Modal'
|
||||
import { PositionsList } from 'components/PositionsList'
|
||||
import { useAccountStats } from 'hooks/data/useAccountStats'
|
||||
import { useBalances } from 'hooks/data/useBalances'
|
||||
import { useCalculateMaxWithdrawAmount } from 'hooks/data/useCalculateMaxWithdrawAmount'
|
||||
import { useWithdrawFunds } from 'hooks/mutations/useWithdrawFunds'
|
||||
import { useCreditAccountPositions } from 'hooks/queries/useCreditAccountPositions'
|
||||
@ -46,7 +45,6 @@ export const WithdrawModal = () => {
|
||||
// EXTERNAL HOOKS
|
||||
// ---------------
|
||||
const { data: tokenPrices } = useTokenPrices()
|
||||
const balances = useBalances()
|
||||
|
||||
const selectedTokenSymbol = getTokenSymbol(selectedToken, marketAssets)
|
||||
const selectedTokenDecimals = getTokenDecimals(selectedToken, marketAssets)
|
||||
@ -153,7 +151,7 @@ export const WithdrawModal = () => {
|
||||
}
|
||||
|
||||
return (
|
||||
<Modal open={open} setOpen={setOpen}>
|
||||
<Modal title='Withdraw' open={open} setOpen={setOpen}>
|
||||
<div className='flex min-h-[470px] w-full flex-wrap'>
|
||||
{isLoading && (
|
||||
<div className='absolute inset-0 z-40 grid place-items-center bg-black/50'>
|
||||
@ -205,7 +203,7 @@ export const WithdrawModal = () => {
|
||||
</div>
|
||||
</div>
|
||||
<Text size='xs' uppercase className='mb-2 text-white/60'>
|
||||
Available: {formatValue(maxWithdrawAmount, 0, 4, true, false, false, false, false)}
|
||||
Available: {formatValue(maxWithdrawAmount, { minDecimals: 0, maxDecimals: 4 })}
|
||||
</Text>
|
||||
<Slider
|
||||
className='mb-6'
|
||||
@ -265,7 +263,7 @@ export const WithdrawModal = () => {
|
||||
amount={BigNumber(accountStats.netWorth)
|
||||
.dividedBy(10 ** baseAsset.decimals)
|
||||
.toNumber()}
|
||||
prefix='$: '
|
||||
options={{ prefix: '$: ' }}
|
||||
animate
|
||||
/>
|
||||
</Text>
|
||||
@ -275,11 +273,9 @@ export const WithdrawModal = () => {
|
||||
label='Lvg'
|
||||
tooltip={
|
||||
<Text size='sm'>
|
||||
Current Leverage:{' '}
|
||||
{formatValue(accountStats.currentLeverage, 0, 2, true, false, 'x')}
|
||||
Current Leverage: {formatLeverage(accountStats.currentLeverage)}
|
||||
<br />
|
||||
Max Leverage:{' '}
|
||||
{formatValue(accountStats.maxLeverage, 0, 2, true, false, 'x')}
|
||||
Max Leverage: {formatLeverage(accountStats.maxLeverage)}
|
||||
</Text>
|
||||
}
|
||||
/>
|
||||
@ -288,7 +284,8 @@ export const WithdrawModal = () => {
|
||||
label='Risk'
|
||||
tooltip={
|
||||
<Text size='sm'>
|
||||
Current Risk: {formatValue(accountStats.risk * 100, 0, 2, true, false, '%')}
|
||||
Current Risk:{' '}
|
||||
{formatValue(accountStats.risk * 100, { minDecimals: 0, suffix: '%' })}
|
||||
</Text>
|
||||
}
|
||||
/>
|
||||
@ -331,7 +328,6 @@ export const WithdrawModal = () => {
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<PositionsList title='Balances' data={balances} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
27
src/components/AmountAndValue.tsx
Normal file
27
src/components/AmountAndValue.tsx
Normal file
@ -0,0 +1,27 @@
|
||||
import { FormattedNumber } from './FormattedNumber'
|
||||
import TitleAndSubCell from './TitleAndSubCell'
|
||||
|
||||
interface Props {
|
||||
asset: Asset
|
||||
amount: string
|
||||
}
|
||||
|
||||
export default function AmountAndValue(props: Props) {
|
||||
return (
|
||||
<TitleAndSubCell
|
||||
title={
|
||||
<FormattedNumber
|
||||
amount={props.amount}
|
||||
options={{ decimals: props.asset.decimals, abbreviated: true }}
|
||||
/>
|
||||
}
|
||||
sub={
|
||||
<FormattedNumber
|
||||
amount={props.amount}
|
||||
options={{ prefix: '$', abbreviated: true, decimals: props.asset.decimals }}
|
||||
/>
|
||||
}
|
||||
className='justify-end'
|
||||
/>
|
||||
)
|
||||
}
|
@ -1,11 +1,14 @@
|
||||
'use client'
|
||||
|
||||
import React from 'react'
|
||||
import { Row } from '@tanstack/react-table'
|
||||
|
||||
import { getMarketAssets } from 'utils/assets'
|
||||
import { Button } from 'components/Button'
|
||||
import useStore from 'store'
|
||||
|
||||
type AssetRowProps = {
|
||||
row: Row<BorrowAsset>
|
||||
row: Row<BorrowAsset | BorrowAssetActive>
|
||||
onBorrowClick: () => void
|
||||
onRepayClick: () => void
|
||||
resetExpanded: (defaultState?: boolean | undefined) => void
|
||||
@ -14,9 +17,22 @@ type AssetRowProps = {
|
||||
export default function AssetExpanded(props: AssetRowProps) {
|
||||
const marketAssets = getMarketAssets()
|
||||
const asset = marketAssets.find((asset) => asset.denom === props.row.original.denom)
|
||||
let isActive: boolean = false
|
||||
|
||||
if ((props.row.original as BorrowAssetActive)?.debt) {
|
||||
isActive = true
|
||||
}
|
||||
|
||||
if (!asset) return null
|
||||
|
||||
function borrowHandler() {
|
||||
useStore.setState({ borrowModal: true })
|
||||
}
|
||||
|
||||
function repayHandler() {
|
||||
useStore.setState({ repayModal: true })
|
||||
}
|
||||
|
||||
return (
|
||||
<tr
|
||||
key={props.row.id}
|
||||
@ -28,10 +44,14 @@ export default function AssetExpanded(props: AssetRowProps) {
|
||||
!isExpanded && props.row.toggleExpanded()
|
||||
}}
|
||||
>
|
||||
<td colSpan={4}>
|
||||
<td colSpan={isActive ? 5 : 4}>
|
||||
<div className='flex justify-end p-4'>
|
||||
<Button color='secondary' text='CLick me' onClick={() => {}} />
|
||||
<Button color='primary' text='CLick me' />
|
||||
<Button
|
||||
onClick={borrowHandler}
|
||||
color='primary'
|
||||
text={isActive ? 'Borrow more' : 'Borrow'}
|
||||
/>
|
||||
{isActive && <Button color='primary' text='Repay' />}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
@ -4,7 +4,7 @@ import { flexRender, Row } from '@tanstack/react-table'
|
||||
import { getMarketAssets } from 'utils/assets'
|
||||
|
||||
type AssetRowProps = {
|
||||
row: Row<BorrowAsset>
|
||||
row: Row<BorrowAsset | BorrowAssetActive>
|
||||
resetExpanded: (defaultState?: boolean | undefined) => void
|
||||
}
|
||||
|
||||
|
@ -15,6 +15,11 @@ import classNames from 'classnames'
|
||||
import { AssetRow } from 'components/Borrow/AssetRow'
|
||||
import { ChevronDown, ChevronUp } from 'components/Icons'
|
||||
import { getMarketAssets } from 'utils/assets'
|
||||
import { Text } from 'components/Text'
|
||||
import TitleAndSubCell from 'components/TitleAndSubCell'
|
||||
import { FormattedNumber } from 'components/FormattedNumber'
|
||||
import AmountAndValue from 'components/AmountAndValue'
|
||||
import { formatPercent } from 'utils/formatters'
|
||||
|
||||
import AssetExpanded from './AssetExpanded'
|
||||
|
||||
@ -26,7 +31,7 @@ export const BorrowTable = (props: Props) => {
|
||||
const [sorting, setSorting] = React.useState<SortingState>([])
|
||||
const marketAssets = getMarketAssets()
|
||||
|
||||
const columns = React.useMemo<ColumnDef<BorrowAsset>[]>(
|
||||
const columns = React.useMemo<ColumnDef<BorrowAsset | BorrowAssetActive>[]>(
|
||||
() => [
|
||||
{
|
||||
header: 'Asset',
|
||||
@ -37,12 +42,9 @@ export const BorrowTable = (props: Props) => {
|
||||
if (!asset) return null
|
||||
|
||||
return (
|
||||
<div className='flex flex-1 items-center'>
|
||||
<div className='flex flex-1 items-center gap-3'>
|
||||
<Image src={asset.logo} alt='token' width={32} height={32} />
|
||||
<div className='pl-2'>
|
||||
<div>{asset.symbol}</div>
|
||||
<div className='text-xs'>{asset.name}</div>
|
||||
</div>
|
||||
<TitleAndSubCell title={asset.symbol} sub={asset.name} />
|
||||
</div>
|
||||
)
|
||||
},
|
||||
@ -50,17 +52,38 @@ export const BorrowTable = (props: Props) => {
|
||||
{
|
||||
accessorKey: 'borrowRate',
|
||||
header: 'Borrow Rate',
|
||||
cell: ({ row }) => <div>{(Number(row.original.borrowRate) * 100).toFixed(2)}%</div>,
|
||||
cell: ({ row }) => (
|
||||
<Text className='justify-end' size='sm'>
|
||||
{formatPercent(row.original.borrowRate)}
|
||||
</Text>
|
||||
),
|
||||
},
|
||||
...((props.data[0] as BorrowAssetActive)?.debt
|
||||
? [
|
||||
{
|
||||
accessorKey: 'debt',
|
||||
header: 'Borrowed',
|
||||
cell: (info: any) => {
|
||||
const borrowAsset = info.row.original as BorrowAssetActive
|
||||
const asset = marketAssets.find((asset) => asset.denom === borrowAsset.denom)
|
||||
|
||||
if (!asset) return null
|
||||
|
||||
return <AmountAndValue asset={asset} amount={borrowAsset.debt} />
|
||||
},
|
||||
},
|
||||
]
|
||||
: []),
|
||||
{
|
||||
accessorKey: 'liquidity',
|
||||
header: 'Liquidity Available',
|
||||
cell: ({ row }) => (
|
||||
<div className='items-right flex flex-col'>
|
||||
<div className=''>{row.original.liquidity.amount}</div>
|
||||
<div className='text-xs opacity-60'>${row.original.liquidity.value}</div>
|
||||
</div>
|
||||
),
|
||||
cell: ({ row }) => {
|
||||
const asset = marketAssets.find((asset) => asset.denom === row.original.denom)
|
||||
|
||||
if (!asset) return null
|
||||
|
||||
return <AmountAndValue asset={asset} amount={row.original.liquidity.amount} />
|
||||
},
|
||||
},
|
||||
{
|
||||
accessorKey: 'status',
|
||||
@ -91,7 +114,7 @@ export const BorrowTable = (props: Props) => {
|
||||
|
||||
return (
|
||||
<table className='w-full'>
|
||||
<thead className='bg-white/5'>
|
||||
<thead className='bg-black/20'>
|
||||
{table.getHeaderGroups().map((headerGroup) => (
|
||||
<tr key={headerGroup.id}>
|
||||
{headerGroup.headers.map((header, index) => {
|
||||
@ -100,7 +123,7 @@ export const BorrowTable = (props: Props) => {
|
||||
key={header.id}
|
||||
onClick={header.column.getToggleSortingHandler()}
|
||||
className={classNames(
|
||||
'px-4 py-2',
|
||||
'px-4 py-3',
|
||||
header.column.getCanSort() && 'cursor-pointer',
|
||||
header.id === 'symbol' ? 'text-left' : 'text-right',
|
||||
)}
|
||||
@ -109,6 +132,7 @@ export const BorrowTable = (props: Props) => {
|
||||
className={classNames(
|
||||
'flex',
|
||||
header.id === 'symbol' ? 'justify-start' : 'justify-end',
|
||||
'align-center',
|
||||
)}
|
||||
>
|
||||
{header.column.getCanSort()
|
||||
@ -124,7 +148,9 @@ export const BorrowTable = (props: Props) => {
|
||||
),
|
||||
}[header.column.getIsSorted() as string] ?? null
|
||||
: null}
|
||||
<span>{flexRender(header.column.columnDef.header, header.getContext())}</span>
|
||||
<Text tag='span' size='sm' className='font-normal text-white/40'>
|
||||
{flexRender(header.column.columnDef.header, header.getContext())}
|
||||
</Text>
|
||||
</div>
|
||||
</th>
|
||||
)
|
||||
|
@ -121,11 +121,12 @@ export const BorrowCapacity = ({
|
||||
<FormattedNumber
|
||||
className='text-white'
|
||||
animate
|
||||
options={{
|
||||
minDecimals: decimals,
|
||||
maxDecimals: decimals,
|
||||
suffix: '%',
|
||||
}}
|
||||
amount={percentOfMaxRound}
|
||||
minDecimals={decimals}
|
||||
maxDecimals={decimals}
|
||||
suffix='%'
|
||||
abbreviated={false}
|
||||
/>
|
||||
)}
|
||||
</span>
|
||||
|
@ -1,314 +1,25 @@
|
||||
import { Dialog, Switch, Transition } from '@headlessui/react'
|
||||
import BigNumber from 'bignumber.js'
|
||||
import React, { useMemo, useState } from 'react'
|
||||
import { NumericFormat } from 'react-number-format'
|
||||
|
||||
import { Button } from 'components/Button'
|
||||
import { CircularProgress } from 'components/CircularProgress'
|
||||
import { ContainerSecondary } from 'components/ContainerSecondary'
|
||||
import { Gauge } from 'components/Gauge'
|
||||
import { PositionsList } from 'components/PositionsList'
|
||||
import { ProgressBar } from 'components/ProgressBar'
|
||||
import { Slider } from 'components/Slider'
|
||||
import { Text } from 'components/Text'
|
||||
import { Tooltip } from 'components/Tooltip'
|
||||
import { useAccountStats } from 'hooks/data/useAccountStats'
|
||||
import { useBalances } from 'hooks/data/useBalances'
|
||||
import { useCalculateMaxBorrowAmount } from 'hooks/data/useCalculateMaxBorrowAmount'
|
||||
import { useBorrowFunds } from 'hooks/mutations/useBorrowFunds'
|
||||
import { useAllBalances } from 'hooks/queries/useAllBalances'
|
||||
import { useMarkets } from 'hooks/queries/useMarkets'
|
||||
import { useTokenPrices } from 'hooks/queries/useTokenPrices'
|
||||
import useStore from 'store'
|
||||
import { getBaseAsset, getMarketAssets } from 'utils/assets'
|
||||
import { formatCurrency, formatValue } from 'utils/formatters'
|
||||
import { getTokenDecimals, getTokenSymbol } from 'utils/tokens'
|
||||
|
||||
type Props = {
|
||||
show: boolean
|
||||
onClose: () => void
|
||||
tokenDenom: string
|
||||
}
|
||||
import { Modal } from './Modal'
|
||||
import TitleAndSubCell from './TitleAndSubCell'
|
||||
|
||||
export const BorrowModal = ({ show, onClose, tokenDenom }: Props) => {
|
||||
const [amount, setAmount] = useState(0)
|
||||
const [isBorrowToCreditAccount, setIsBorrowToCreditAccount] = useState(false)
|
||||
export default function BorrowModal() {
|
||||
const open = useStore((s) => s.borrowModal)
|
||||
|
||||
const selectedAccount = useStore((s) => s.selectedAccount)
|
||||
const marketAssets = getMarketAssets()
|
||||
const baseAsset = getBaseAsset()
|
||||
|
||||
const balances = useBalances()
|
||||
|
||||
const { actions, borrowAmount } = useMemo(() => {
|
||||
const borrowAmount = BigNumber(amount)
|
||||
.times(10 ** getTokenDecimals(tokenDenom, marketAssets))
|
||||
.toNumber()
|
||||
|
||||
const withdrawAmount = isBorrowToCreditAccount ? 0 : borrowAmount
|
||||
|
||||
return {
|
||||
borrowAmount,
|
||||
withdrawAmount,
|
||||
actions: [
|
||||
{
|
||||
type: 'borrow',
|
||||
amount: borrowAmount,
|
||||
denom: tokenDenom,
|
||||
},
|
||||
{
|
||||
type: 'withdraw',
|
||||
amount: withdrawAmount,
|
||||
denom: tokenDenom,
|
||||
},
|
||||
] as AccountStatsAction[],
|
||||
}
|
||||
}, [amount, isBorrowToCreditAccount, tokenDenom, marketAssets])
|
||||
|
||||
const accountStats = useAccountStats(actions)
|
||||
|
||||
const tokenSymbol = getTokenSymbol(tokenDenom, marketAssets)
|
||||
|
||||
const { mutate, isLoading } = useBorrowFunds(borrowAmount, tokenDenom, !isBorrowToCreditAccount, {
|
||||
onSuccess: () => {
|
||||
onClose()
|
||||
useStore.setState({
|
||||
toast: { message: `${amount} ${tokenSymbol} successfully Borrowed` },
|
||||
})
|
||||
},
|
||||
})
|
||||
|
||||
const { data: tokenPrices } = useTokenPrices()
|
||||
const { data: balancesData } = useAllBalances()
|
||||
const { data: marketsData } = useMarkets()
|
||||
|
||||
const handleSubmit = () => {
|
||||
mutate()
|
||||
}
|
||||
|
||||
const walletAmount = useMemo(() => {
|
||||
return BigNumber(balancesData?.find((balance) => balance.denom === tokenDenom)?.amount ?? 0)
|
||||
.div(10 ** getTokenDecimals(tokenDenom, marketAssets))
|
||||
.toNumber()
|
||||
}, [balancesData, tokenDenom, marketAssets])
|
||||
|
||||
const tokenPrice = tokenPrices?.[tokenDenom] ?? 0
|
||||
const borrowRate = Number(marketsData?.[tokenDenom]?.borrow_rate)
|
||||
|
||||
const maxValue = useCalculateMaxBorrowAmount(tokenDenom, isBorrowToCreditAccount)
|
||||
|
||||
const percentageValue = useMemo(() => {
|
||||
if (isNaN(amount) || maxValue === 0) return 0
|
||||
|
||||
return (amount * 100) / maxValue
|
||||
}, [amount, maxValue])
|
||||
|
||||
const handleValueChange = (value: number) => {
|
||||
if (value > maxValue) {
|
||||
setAmount(maxValue)
|
||||
return
|
||||
}
|
||||
|
||||
setAmount(value)
|
||||
}
|
||||
|
||||
const handleSliderValueChange = (value: number[]) => {
|
||||
const decimal = value[0] / 100
|
||||
const tokenDecimals = getTokenDecimals(tokenDenom, marketAssets)
|
||||
// limit decimal precision based on token contract decimals
|
||||
const newAmount = Number((decimal * maxValue).toFixed(tokenDecimals))
|
||||
|
||||
setAmount(newAmount)
|
||||
}
|
||||
|
||||
const handleBorrowTargetChange = () => {
|
||||
setIsBorrowToCreditAccount((c) => !c)
|
||||
// reset amount due to max value calculations changing depending on borrow target
|
||||
setAmount(0)
|
||||
function setOpen(isOpen: boolean) {
|
||||
useStore.setState({ borrowModal: isOpen })
|
||||
}
|
||||
|
||||
return (
|
||||
<Transition appear show={show} as={React.Fragment}>
|
||||
<Dialog as='div' className='relative z-10' onClose={onClose}>
|
||||
<Transition.Child
|
||||
as={React.Fragment}
|
||||
enter='ease-out duration-300'
|
||||
enterFrom='opacity-0'
|
||||
enterTo='opacity-100'
|
||||
leave='ease-in duration-200'
|
||||
leaveFrom='opacity-100'
|
||||
leaveTo='opacity-0'
|
||||
>
|
||||
<div className='fixed inset-0 bg-black bg-opacity-80' />
|
||||
</Transition.Child>
|
||||
|
||||
<div className='fixed inset-0 overflow-y-auto'>
|
||||
<div className='flex min-h-full items-center justify-center p-4'>
|
||||
<Transition.Child
|
||||
as={React.Fragment}
|
||||
enter='ease-out duration-300'
|
||||
enterFrom='opacity-0 scale-95'
|
||||
enterTo='opacity-100 scale-100'
|
||||
leave='ease-in duration-200'
|
||||
leaveFrom='opacity-100 scale-100'
|
||||
leaveTo='opacity-0 scale-95'
|
||||
>
|
||||
<Dialog.Panel className='flex w-full max-w-3xl transform overflow-hidden rounded-2xl bg-[#585A74] align-middle shadow-xl transition-all'>
|
||||
{isLoading && (
|
||||
<div className='absolute inset-0 z-40 grid place-items-center bg-black/50'>
|
||||
<CircularProgress />
|
||||
</div>
|
||||
)}
|
||||
<div className='flex flex-1 flex-col p-4'>
|
||||
<Dialog.Title as='h3' className='mb-4 text-center font-medium'>
|
||||
Borrow {tokenSymbol}
|
||||
</Dialog.Title>
|
||||
<div className='mb-4 flex flex-col gap-2 text-sm'>
|
||||
<ContainerSecondary>
|
||||
<p className='mb-1'>
|
||||
In wallet: {walletAmount.toLocaleString()} {tokenSymbol}
|
||||
</p>
|
||||
<p className='mb-5'>Borrow Rate: {(borrowRate * 100).toFixed(2)}%</p>
|
||||
|
||||
<div className='mb-7'>
|
||||
<p className='mb-2 font-semibold uppercase tracking-widest'>Amount</p>
|
||||
<NumericFormat
|
||||
className='mb-2 h-[32px] w-full rounded-lg border border-black/50 bg-transparent px-2'
|
||||
value={amount}
|
||||
placeholder='0'
|
||||
allowNegative={false}
|
||||
onValueChange={(v) => handleValueChange(v.floatValue || 0)}
|
||||
suffix={` ${tokenSymbol}`}
|
||||
decimalScale={getTokenDecimals(tokenDenom, marketAssets)}
|
||||
/>
|
||||
<div className='flex justify-between text-xs tracking-widest'>
|
||||
<div>
|
||||
1 {tokenSymbol} = {formatCurrency(tokenPrice)}
|
||||
</div>
|
||||
<div>{formatCurrency(tokenPrice * amount)}</div>
|
||||
</div>
|
||||
</div>
|
||||
<Slider
|
||||
className='mb-6'
|
||||
value={percentageValue}
|
||||
onChange={handleSliderValueChange}
|
||||
onMaxClick={() => setAmount(maxValue)}
|
||||
/>
|
||||
</ContainerSecondary>
|
||||
<ContainerSecondary className='flex items-center justify-between'>
|
||||
<div className='flex'>
|
||||
Borrow to Credit Account{' '}
|
||||
<Tooltip
|
||||
className='ml-2'
|
||||
content={
|
||||
<>
|
||||
<Text size='sm' className='mb-2'>
|
||||
OFF = Borrow directly into your wallet by using your account Assets
|
||||
as collateral. The borrowed asset will become a liability in your
|
||||
account.
|
||||
</Text>
|
||||
<Text size='sm'>
|
||||
ON = Borrow into your Account. The borrowed asset will be available
|
||||
in the account as an Asset and appear also as a liability in your
|
||||
account.
|
||||
</Text>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<Switch
|
||||
checked={isBorrowToCreditAccount}
|
||||
onChange={handleBorrowTargetChange}
|
||||
className={`${
|
||||
isBorrowToCreditAccount ? 'bg-blue-600' : 'bg-gray-400'
|
||||
} relative inline-flex h-6 w-11 items-center rounded-full`}
|
||||
>
|
||||
<span
|
||||
className={`${
|
||||
isBorrowToCreditAccount ? 'translate-x-6' : 'translate-x-1'
|
||||
} inline-block h-4 w-4 transform rounded-full bg-white transition`}
|
||||
/>
|
||||
</Switch>
|
||||
</ContainerSecondary>
|
||||
</div>
|
||||
<Button
|
||||
className='mt-auto'
|
||||
onClick={handleSubmit}
|
||||
disabled={amount === 0 || !amount}
|
||||
>
|
||||
Borrow
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div className='flex w-1/2 flex-col justify-center bg-[#4A4C60] p-4'>
|
||||
<p className='text-bold mb-3 text-xs uppercase text-white/50'>About</p>
|
||||
<h4 className='mb-4 text-xl'>Account {selectedAccount}</h4>
|
||||
<div className='mb-2 rounded-md border border-white/20 p-3'>
|
||||
{accountStats && (
|
||||
<div className='flex items-center gap-x-3'>
|
||||
<p className='flex-1 text-xs'>
|
||||
{formatCurrency(
|
||||
BigNumber(accountStats.netWorth)
|
||||
.dividedBy(10 ** baseAsset.decimals)
|
||||
.toNumber(),
|
||||
)}
|
||||
</p>
|
||||
<Gauge
|
||||
value={accountStats.currentLeverage / accountStats.maxLeverage}
|
||||
label='Lvg'
|
||||
tooltip={
|
||||
<Text size='sm'>
|
||||
Current Leverage:{' '}
|
||||
{formatValue(accountStats.currentLeverage, 0, 2, true, false, 'x')}
|
||||
<br />
|
||||
Max Leverage:{' '}
|
||||
{formatValue(accountStats.maxLeverage, 0, 2, true, false, 'x')}
|
||||
</Text>
|
||||
}
|
||||
/>
|
||||
<Gauge
|
||||
value={accountStats.risk}
|
||||
label='Risk'
|
||||
tooltip={
|
||||
<Text size='sm'>
|
||||
Current Risk:{' '}
|
||||
{formatValue(accountStats.risk * 100, 0, 2, true, false, '%')}
|
||||
</Text>
|
||||
}
|
||||
/>
|
||||
<ProgressBar value={accountStats.health} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className='mb-2 rounded-md border border-white/20 p-3 text-sm'>
|
||||
<div className='mb-1 flex justify-between'>
|
||||
<div>Total Position:</div>
|
||||
<div className='font-semibold'>
|
||||
{formatCurrency(
|
||||
BigNumber(accountStats?.totalPosition ?? 0)
|
||||
.dividedBy(10 ** baseAsset.decimals)
|
||||
.toNumber(),
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className='flex justify-between'>
|
||||
<div>Total Liabilities:</div>
|
||||
<div className='font-semibold'>
|
||||
{formatCurrency(
|
||||
BigNumber(accountStats?.totalDebt ?? 0)
|
||||
.dividedBy(10 ** baseAsset.decimals)
|
||||
.toNumber(),
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<PositionsList title='Balances' data={balances} />
|
||||
</div>
|
||||
</Dialog.Panel>
|
||||
</Transition.Child>
|
||||
</div>
|
||||
</div>
|
||||
</Dialog>
|
||||
</Transition>
|
||||
<Modal open={open} setOpen={setOpen} title='Borrow OSMO'>
|
||||
<div className='flex gap-3'>
|
||||
<TitleAndSubCell title='10.00%' sub={'Borrow rate'} />
|
||||
<div className='h-100 w-[1px] bg-white/10'></div>
|
||||
<TitleAndSubCell title='$200' sub={'Borrowed'} />
|
||||
<div className='h-100 w-[1px] bg-white/10'></div>
|
||||
<TitleAndSubCell title='10.5M ($105M)' sub={'Liquidity available'} />
|
||||
</div>
|
||||
<div className='flex'></div>
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
|
@ -1,6 +1,8 @@
|
||||
import classNames from 'classnames'
|
||||
import { ReactNode } from 'react'
|
||||
|
||||
import { Text } from 'components/Text'
|
||||
|
||||
interface Props {
|
||||
title: string
|
||||
children: ReactNode
|
||||
@ -15,8 +17,10 @@ export const Card = (props: Props) => {
|
||||
'h-fit w-full max-w-full overflow-hidden rounded-md border-[1px] border-white/20',
|
||||
)}
|
||||
>
|
||||
<div className='bg-white/10 p-4 font-semibold'>{props.title}</div>
|
||||
<div className=''>{props.children}</div>
|
||||
<Text size='lg' className='bg-white/10 p-4 font-semibold'>
|
||||
{props.title}
|
||||
</Text>
|
||||
<div>{props.children}</div>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
|
15
src/components/FetchPrices.tsx
Normal file
15
src/components/FetchPrices.tsx
Normal file
@ -0,0 +1,15 @@
|
||||
'use client'
|
||||
|
||||
import useSWR from 'swr'
|
||||
|
||||
import useStore from 'store'
|
||||
import { getPrices } from 'utils/api'
|
||||
|
||||
export default function FetchPrices() {
|
||||
useSWR('prices', getPrices, {
|
||||
refreshInterval: 30000,
|
||||
onSuccess: (prices) => useStore.setState({ prices }),
|
||||
})
|
||||
|
||||
return null
|
||||
}
|
@ -5,64 +5,60 @@ import React, { useEffect, useRef } from 'react'
|
||||
import { animated, useSpring } from 'react-spring'
|
||||
|
||||
import useStore from 'store'
|
||||
import { formatValue } from 'utils/formatters'
|
||||
import { FormatOptions, formatValue } from 'utils/formatters'
|
||||
|
||||
export const FormattedNumber = React.memo(
|
||||
({
|
||||
amount,
|
||||
animate = false,
|
||||
className,
|
||||
minDecimals = 2,
|
||||
maxDecimals = 2,
|
||||
thousandSeparator = true,
|
||||
prefix = false,
|
||||
suffix = false,
|
||||
rounded = false,
|
||||
abbreviated = false,
|
||||
}: FormattedNumberProps) => {
|
||||
const enableAnimations = useStore((s) => s.enableAnimations)
|
||||
const prevAmountRef = useRef<number>(0)
|
||||
interface Props {
|
||||
amount: number | string
|
||||
options?: FormatOptions
|
||||
className?: string
|
||||
animate?: boolean
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (prevAmountRef.current !== Number(amount)) prevAmountRef.current = Number(amount)
|
||||
}, [amount])
|
||||
export const FormattedNumber = React.memo((props: Props) => {
|
||||
const enableAnimations = useStore((s) => s.enableAnimations)
|
||||
const prevAmountRef = useRef<number>(0)
|
||||
|
||||
const springAmount = useSpring({
|
||||
number: Number(amount),
|
||||
from: { number: prevAmountRef.current },
|
||||
config: { duration: 1000 },
|
||||
})
|
||||
useEffect(() => {
|
||||
if (prevAmountRef.current !== Number(props.amount)) prevAmountRef.current = Number(props.amount)
|
||||
}, [props.amount])
|
||||
|
||||
return (prevAmountRef.current === amount && amount === 0) || !animate || !enableAnimations ? (
|
||||
<span className={classNames('number', className)}>
|
||||
{formatValue(
|
||||
amount,
|
||||
minDecimals,
|
||||
maxDecimals,
|
||||
thousandSeparator,
|
||||
prefix,
|
||||
suffix,
|
||||
rounded,
|
||||
abbreviated,
|
||||
)}
|
||||
</span>
|
||||
) : (
|
||||
<animated.span className={classNames('number', className)}>
|
||||
{springAmount.number.to((num) =>
|
||||
formatValue(
|
||||
num,
|
||||
minDecimals,
|
||||
maxDecimals,
|
||||
thousandSeparator,
|
||||
prefix,
|
||||
suffix,
|
||||
rounded,
|
||||
abbreviated,
|
||||
),
|
||||
)}
|
||||
</animated.span>
|
||||
)
|
||||
},
|
||||
)
|
||||
const springAmount = useSpring({
|
||||
number: Number(props.amount),
|
||||
from: { number: prevAmountRef.current },
|
||||
config: { duration: 1000 },
|
||||
})
|
||||
|
||||
return (prevAmountRef.current === props.amount && props.amount === 0) ||
|
||||
!props.animate ||
|
||||
!enableAnimations ? (
|
||||
<span className={classNames('number', props.className)}>
|
||||
{formatValue(props.amount, {
|
||||
minDecimals: props.options?.minDecimals,
|
||||
maxDecimals: props.options?.maxDecimals,
|
||||
thousandSeparator: props.options?.thousandSeparator,
|
||||
prefix: props.options?.prefix,
|
||||
suffix: props.options?.suffix,
|
||||
rounded: props.options?.rounded,
|
||||
abbreviated: props.options?.abbreviated,
|
||||
decimals: props.options?.decimals,
|
||||
})}
|
||||
</span>
|
||||
) : (
|
||||
<animated.span className={classNames('number', props.className)}>
|
||||
{springAmount.number.to((num) =>
|
||||
formatValue(num, {
|
||||
minDecimals: props.options?.minDecimals,
|
||||
maxDecimals: props.options?.maxDecimals,
|
||||
thousandSeparator: props.options?.thousandSeparator,
|
||||
prefix: props.options?.prefix,
|
||||
suffix: props.options?.suffix,
|
||||
rounded: props.options?.rounded,
|
||||
abbreviated: props.options?.abbreviated,
|
||||
decimals: props.options?.decimals,
|
||||
}),
|
||||
)}
|
||||
</animated.span>
|
||||
)
|
||||
})
|
||||
|
||||
FormattedNumber.displayName = 'FormattedNumber'
|
||||
|
@ -2,9 +2,12 @@ import classNames from 'classnames'
|
||||
import { ReactNode } from 'react'
|
||||
|
||||
import { Close } from 'components/Icons'
|
||||
import { Card } from 'components/Card'
|
||||
import { Text } from 'components/Text'
|
||||
|
||||
import { Button } from './Button'
|
||||
|
||||
interface Props {
|
||||
title: string
|
||||
children?: ReactNode | string
|
||||
content?: ReactNode | string
|
||||
className?: string
|
||||
@ -12,31 +15,28 @@ interface Props {
|
||||
setOpen?: (open: boolean) => void
|
||||
}
|
||||
|
||||
export const Modal = ({ children, content, className, open, setOpen }: Props) => {
|
||||
export const Modal = (props: Props) => {
|
||||
const onClickAway = () => {
|
||||
if (setOpen) setOpen(false)
|
||||
if (props.setOpen) props.setOpen(false)
|
||||
}
|
||||
|
||||
return open ? (
|
||||
return props.open ? (
|
||||
<div className='fixed top-0 left-0 z-20 h-screen w-screen'>
|
||||
<div className='relative flex h-full w-full items-center justify-center'>
|
||||
<Card
|
||||
title='Modal'
|
||||
className={classNames('relative z-40 w-[790px] max-w-full p-0', className)}
|
||||
>
|
||||
{setOpen && (
|
||||
<span
|
||||
className='absolute top-4 right-4 z-50 w-[32px] text-white opacity-60 hover:cursor-pointer hover:opacity-100'
|
||||
onClick={onClickAway}
|
||||
role='button'
|
||||
>
|
||||
<Close />
|
||||
</span>
|
||||
<section
|
||||
className={classNames(
|
||||
'relative z-40 w-[790px] max-w-full rounded-md border-[1px] border-white/20 bg-white/5 p-6 backdrop-blur-3xl ',
|
||||
props.className,
|
||||
)}
|
||||
{children ? children : content}
|
||||
</Card>
|
||||
>
|
||||
<div className='flex justify-between pb-6'>
|
||||
<Text>{props.title}</Text>
|
||||
<Button onClick={onClickAway} text='X' color='tertiary' />
|
||||
</div>
|
||||
<div>{props.children ? props.children : props.content}</div>
|
||||
</section>
|
||||
<div
|
||||
className='fixed top-0 left-0 z-30 block h-full w-full bg-black/70 backdrop-blur hover:cursor-pointer'
|
||||
className='fixed top-0 left-0 z-30 block h-full w-full bg-black/50 hover:cursor-pointer'
|
||||
onClick={onClickAway}
|
||||
role='button'
|
||||
/>
|
||||
|
@ -3,10 +3,13 @@
|
||||
import { ConfirmModal } from 'components/Account/ConfirmModal'
|
||||
import { FundAccountModal } from 'components/Account/FundAccountModal'
|
||||
|
||||
import BorrowModal from './BorrowModal'
|
||||
|
||||
export const Modals = () => (
|
||||
<>
|
||||
<FundAccountModal />
|
||||
{/* <WithdrawModal /> */}
|
||||
<ConfirmModal />
|
||||
<BorrowModal />
|
||||
</>
|
||||
)
|
||||
|
@ -13,7 +13,6 @@ import { useRepayFunds } from 'hooks/mutations/useRepayFunds'
|
||||
import { useAllBalances } from 'hooks/queries/useAllBalances'
|
||||
import { useCreditAccountPositions } from 'hooks/queries/useCreditAccountPositions'
|
||||
import { useTokenPrices } from 'hooks/queries/useTokenPrices'
|
||||
import { formatCurrency } from 'utils/formatters'
|
||||
import { getTokenDecimals, getTokenSymbol } from 'utils/tokens'
|
||||
import { getMarketAssets } from 'utils/assets'
|
||||
import useStore from 'store'
|
||||
@ -33,6 +32,7 @@ export const RepayModal = ({ show, onClose, tokenDenom }: Props) => {
|
||||
const selectedAccount = useStore((s) => s.selectedAccount)
|
||||
const { data: positionsData } = useCreditAccountPositions(selectedAccount ?? '')
|
||||
const marketAssets = getMarketAssets()
|
||||
const formatCurrency = useStore((s) => s.formatCurrency)
|
||||
|
||||
const tokenSymbol = getTokenSymbol(tokenDenom, marketAssets)
|
||||
|
||||
@ -155,9 +155,11 @@ export const RepayModal = ({ show, onClose, tokenDenom }: Props) => {
|
||||
/>
|
||||
<div className='flex justify-between text-xs tracking-widest'>
|
||||
<div>
|
||||
1 {tokenSymbol} = {formatCurrency(tokenPrice)}
|
||||
1 {tokenSymbol} = {formatCurrency({ denom: tokenDenom, amount: '1' })}
|
||||
</div>
|
||||
<div>
|
||||
{formatCurrency({ denom: tokenDenom, amount: amount.toString() })}
|
||||
</div>
|
||||
<div>{formatCurrency(tokenPrice * amount)}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@ -29,6 +29,7 @@ export const Text = ({
|
||||
<HtmlElement
|
||||
className={classNames(
|
||||
className,
|
||||
'flex items-center',
|
||||
uppercase ? `text-${sizeClass}-caps` : `text-${sizeClass}`,
|
||||
monospace && 'number',
|
||||
)}
|
||||
|
20
src/components/TitleAndSubCell.tsx
Normal file
20
src/components/TitleAndSubCell.tsx
Normal file
@ -0,0 +1,20 @@
|
||||
import { Text } from 'components/Text'
|
||||
|
||||
interface Props {
|
||||
title: string | React.ReactNode
|
||||
sub: string | React.ReactNode
|
||||
className?: string
|
||||
}
|
||||
|
||||
export default function TitleAndSubCell(props: Props) {
|
||||
return (
|
||||
<div className='flex flex-col gap-[0.5]'>
|
||||
<Text className={props.className} size='sm'>
|
||||
{props.title}
|
||||
</Text>
|
||||
<Text size='sm' className={'text-white/50 ' + props.className}>
|
||||
{props.sub}
|
||||
</Text>
|
||||
</div>
|
||||
)
|
||||
}
|
@ -104,7 +104,7 @@ export default function ConnectedButton() {
|
||||
{isLoading ? (
|
||||
<CircularProgress size={12} />
|
||||
) : (
|
||||
`${formatValue(walletAmount, 2, 2, true, false, ` ${baseAsset.symbol}`)}`
|
||||
`${formatValue(walletAmount, { suffix: baseAsset.symbol })}`
|
||||
)}
|
||||
</div>
|
||||
</button>
|
||||
|
@ -1,36 +0,0 @@
|
||||
import { useEffect, useState } from 'react'
|
||||
|
||||
import { useCreditAccountPositions } from 'hooks/queries/useCreditAccountPositions'
|
||||
import { useMarkets } from 'hooks/queries/useMarkets'
|
||||
import { useTokenPrices } from 'hooks/queries/useTokenPrices'
|
||||
import { formatBalances } from 'utils/balances'
|
||||
import useStore from 'store'
|
||||
import { getMarketAssets } from 'utils/assets'
|
||||
|
||||
export const useBalances = () => {
|
||||
const [balanceData, setBalanceData] = useState<PositionsData[]>()
|
||||
|
||||
const { data: marketsData } = useMarkets()
|
||||
const { data: tokenPrices } = useTokenPrices()
|
||||
const selectedAccount = useStore((s) => s.selectedAccount)
|
||||
const marketAssets = getMarketAssets()
|
||||
|
||||
const { data: positionsData, isLoading: isLoadingPositions } = useCreditAccountPositions(
|
||||
selectedAccount ?? '',
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
const balances =
|
||||
positionsData?.coins && tokenPrices
|
||||
? formatBalances(positionsData.coins, tokenPrices, false, marketAssets)
|
||||
: []
|
||||
const debtBalances =
|
||||
positionsData?.debts && tokenPrices
|
||||
? formatBalances(positionsData.debts, tokenPrices, true, marketAssets, marketsData)
|
||||
: []
|
||||
|
||||
setBalanceData([...balances, ...debtBalances])
|
||||
}, [positionsData, marketsData, tokenPrices, marketAssets])
|
||||
|
||||
return balanceData
|
||||
}
|
@ -12,7 +12,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||
const account = await (await fetch(`${URL_API}/accounts/${accountId}`)).json()
|
||||
|
||||
if (account) {
|
||||
return res.status(200).json(account.debts)
|
||||
return res.status(200).json([{ denom: 'uosmo', amount: '123876' }])
|
||||
}
|
||||
|
||||
return res.status(404)
|
||||
|
@ -1,23 +1,52 @@
|
||||
import { Coin } from '@cosmjs/stargate'
|
||||
import BigNumber from 'bignumber.js'
|
||||
import { GetState, SetState } from 'zustand'
|
||||
|
||||
import { getMarketAssets } from 'utils/assets'
|
||||
import { formatValue } from 'utils/formatters'
|
||||
|
||||
export interface CommonSlice {
|
||||
borrowModal: boolean
|
||||
createAccountModal: boolean
|
||||
deleteAccountModal: boolean
|
||||
enableAnimations: boolean
|
||||
repayModal: boolean
|
||||
fundAccountModal: boolean
|
||||
prices: Coin[]
|
||||
isOpen: boolean
|
||||
selectedAccount: string | null
|
||||
withdrawModal: boolean
|
||||
formatCurrency: (coin: Coin) => string
|
||||
}
|
||||
|
||||
export function createCommonSlice(set: SetState<CommonSlice>, get: GetState<CommonSlice>) {
|
||||
return {
|
||||
borrowModal: false,
|
||||
createAccountModal: false,
|
||||
deleteAccountModal: false,
|
||||
repayModal: false,
|
||||
enableAnimations: true,
|
||||
fundAccountModal: false,
|
||||
prices: [],
|
||||
isOpen: true,
|
||||
selectedAccount: null,
|
||||
withdrawModal: false,
|
||||
formatCurrency: (coin: Coin) => {
|
||||
const price = get().prices.find((price) => price.denom === coin.denom)
|
||||
const marketAsset = getMarketAssets().find((asset) => asset.denom === coin.denom)
|
||||
|
||||
if (!price || !marketAsset) return ''
|
||||
|
||||
return formatValue(
|
||||
new BigNumber(coin.amount)
|
||||
.times(price.amount)
|
||||
.dividedBy(10 ** marketAsset.decimals)
|
||||
.toNumber(),
|
||||
{
|
||||
minDecimals: 0,
|
||||
prefix: '$',
|
||||
},
|
||||
)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
@ -66,20 +66,21 @@ a {
|
||||
}
|
||||
|
||||
/* ORBS */
|
||||
@mixin orbs($count) {
|
||||
@mixin orbs($count, $hue) {
|
||||
$text-shadow: ();
|
||||
@for $i from 0 through $count {
|
||||
$text-shadow: $text-shadow,
|
||||
(-0.5+ (random()) * 3) +
|
||||
em
|
||||
(-0.5+ (random()) * 3) +
|
||||
em
|
||||
7px
|
||||
hsla((random() * 50)+210, 100%, 45%, 0.3);
|
||||
(-0.5+ (random()) * 3) + em (-0.5+ (random()) * 3) + em 10px rgb(92, 5, 92);
|
||||
// hsla((random() * 50)+$hue, 100%, 45%);
|
||||
}
|
||||
text-shadow: $text-shadow;
|
||||
}
|
||||
|
||||
@mixin newOrbs($count, $color) {
|
||||
filter: blur(4px);
|
||||
background: radial-gradient(circle at center, rgba($color, 0.25) 0%, rgba($color, 0) 20%);
|
||||
}
|
||||
|
||||
.background {
|
||||
font-family: serif;
|
||||
font-size: 90px;
|
||||
@ -98,18 +99,27 @@ a {
|
||||
width: 3em;
|
||||
height: 3em;
|
||||
content: '.';
|
||||
color: transparent;
|
||||
mix-blend-mode: screen;
|
||||
}
|
||||
|
||||
&:after {
|
||||
top: 10%;
|
||||
left: 10%;
|
||||
@include newOrbs(1, rgb(177, 47, 37));
|
||||
}
|
||||
|
||||
&:before {
|
||||
@include orbs(15);
|
||||
top: 80%;
|
||||
left: 80%;
|
||||
@include newOrbs(1, rgb(83, 7, 129));
|
||||
animation-duration: 300s;
|
||||
animation-delay: -50s;
|
||||
// animation-delay: -50s;
|
||||
animation: 180s -15s move infinite ease-in-out alternate;
|
||||
}
|
||||
|
||||
&:after {
|
||||
@include orbs(25);
|
||||
// @include orbs(15, 260);
|
||||
animation-duration: 600s;
|
||||
animation: 180s 0s move infinite ease-in-out alternate;
|
||||
}
|
||||
|
5
src/types/interfaces/asset.d.ts
vendored
5
src/types/interfaces/asset.d.ts
vendored
@ -26,8 +26,5 @@ interface BorrowAsset {
|
||||
}
|
||||
|
||||
interface BorrowAssetActive extends BorrowAsset {
|
||||
debt: {
|
||||
amount: string
|
||||
value: string
|
||||
}
|
||||
debt: string
|
||||
}
|
||||
|
@ -1,43 +0,0 @@
|
||||
import { Coin } from '@cosmjs/stargate'
|
||||
|
||||
import { convertFromGwei, getTokenTotalUSDValue } from 'utils/formatters'
|
||||
import { getTokenSymbol } from 'utils/tokens'
|
||||
|
||||
export const formatBalances = (
|
||||
positionData: Coin[],
|
||||
tokenPrices: KeyValuePair,
|
||||
debt: boolean,
|
||||
marketAssets: Asset[],
|
||||
marketsData?: MarketData,
|
||||
): PositionsData[] => {
|
||||
const balances: PositionsData[] = []
|
||||
|
||||
positionData.forEach((coin) => {
|
||||
const dataEntry: PositionsData = {
|
||||
asset: {
|
||||
amount: getTokenSymbol(coin.denom, marketAssets),
|
||||
type: debt ? 'debt' : undefined,
|
||||
},
|
||||
value: {
|
||||
amount: getTokenTotalUSDValue(coin.amount, coin.denom, marketAssets, tokenPrices),
|
||||
format: 'number',
|
||||
prefix: '$',
|
||||
},
|
||||
size: {
|
||||
amount: convertFromGwei(coin.amount, coin.denom, marketAssets),
|
||||
format: 'number',
|
||||
maxDecimals: 4,
|
||||
minDecimals: 0,
|
||||
},
|
||||
apy: {
|
||||
amount: debt ? Number(marketsData?.[coin.denom].borrow_rate) * 100 : '-',
|
||||
format: debt ? 'number' : 'string',
|
||||
suffix: '%',
|
||||
minDecimals: 0,
|
||||
},
|
||||
}
|
||||
balances.push(dataEntry)
|
||||
})
|
||||
|
||||
return balances
|
||||
}
|
@ -10,28 +10,6 @@ export function truncate(text = '', [h, t]: [number, number] = [6, 6]): string {
|
||||
return text.length > h + t ? [head, tail].join('...') : text
|
||||
}
|
||||
|
||||
export const formatCurrency = (value: string | number) => {
|
||||
return Number(value).toLocaleString('en-US', {
|
||||
style: 'currency',
|
||||
currency: 'USD',
|
||||
})
|
||||
}
|
||||
|
||||
export const getTokenTotalUSDValue = (
|
||||
amount: string,
|
||||
denom: string,
|
||||
marketAssets: Asset[],
|
||||
tokenPrices?: KeyValuePair,
|
||||
) => {
|
||||
if (!tokenPrices) return 0
|
||||
|
||||
return (
|
||||
BigNumber(amount)
|
||||
.div(10 ** getTokenDecimals(denom, marketAssets))
|
||||
.toNumber() * tokenPrices[denom]
|
||||
)
|
||||
}
|
||||
|
||||
export const convertFromGwei = (amount: string | number, denom: string, marketAssets: Asset[]) => {
|
||||
return BigNumber(amount)
|
||||
.div(10 ** getTokenDecimals(denom, marketAssets))
|
||||
@ -44,26 +22,34 @@ export const convertToGwei = (amount: string | number, denom: string, marketAsse
|
||||
.toNumber()
|
||||
}
|
||||
|
||||
export const formatValue = (
|
||||
amount: number | string,
|
||||
minDecimals = 2,
|
||||
maxDecimals = 2,
|
||||
thousandSeparator = true,
|
||||
prefix: boolean | string = false,
|
||||
suffix: boolean | string = false,
|
||||
rounded = false,
|
||||
abbreviated = false,
|
||||
): string => {
|
||||
export interface FormatOptions {
|
||||
decimals?: number
|
||||
minDecimals?: number
|
||||
maxDecimals?: number
|
||||
thousandSeparator?: boolean
|
||||
prefix?: string
|
||||
suffix?: string
|
||||
rounded?: boolean
|
||||
abbreviated?: boolean
|
||||
}
|
||||
|
||||
export const formatValue = (amount: number | string, options?: FormatOptions): string => {
|
||||
let numberOfZeroDecimals: number | null = null
|
||||
const minDecimals = options?.minDecimals ?? 2
|
||||
const maxDecimals = options?.maxDecimals ?? 2
|
||||
const thousandSeparator = options?.thousandSeparator ?? true
|
||||
|
||||
if (typeof amount === 'string') {
|
||||
const decimals = amount.split('.')[1] ?? null
|
||||
if (decimals && Number(decimals) === 0) {
|
||||
numberOfZeroDecimals = decimals.length
|
||||
}
|
||||
}
|
||||
let convertedAmount: number | string = +amount || 0
|
||||
let convertedAmount: number | string = new BigNumber(amount)
|
||||
.dividedBy(10 ** (options?.decimals ?? 0))
|
||||
.toNumber()
|
||||
|
||||
const amountSuffix = abbreviated
|
||||
const amountSuffix = options?.abbreviated
|
||||
? convertedAmount >= 1_000_000_000
|
||||
? 'B'
|
||||
: convertedAmount >= 1_000_000
|
||||
@ -73,19 +59,17 @@ export const formatValue = (
|
||||
: false
|
||||
: ''
|
||||
|
||||
const amountPrefix = prefix
|
||||
|
||||
if (amountSuffix === 'B') {
|
||||
convertedAmount = Number(amount) / 1_000_000_000
|
||||
convertedAmount = Number(convertedAmount) / 1_000_000_000
|
||||
}
|
||||
if (amountSuffix === 'M') {
|
||||
convertedAmount = Number(amount) / 1_000_000
|
||||
convertedAmount = Number(convertedAmount) / 1_000_000
|
||||
}
|
||||
if (amountSuffix === 'K') {
|
||||
convertedAmount = Number(amount) / 1_000
|
||||
convertedAmount = Number(convertedAmount) / 1_000
|
||||
}
|
||||
|
||||
if (rounded) {
|
||||
if (options?.rounded) {
|
||||
convertedAmount = convertedAmount.toFixed(maxDecimals)
|
||||
} else {
|
||||
const amountFractions = String(convertedAmount).split('.')
|
||||
@ -112,8 +96,8 @@ export const formatValue = (
|
||||
}
|
||||
|
||||
let returnValue = ''
|
||||
if (amountPrefix) {
|
||||
returnValue += amountPrefix
|
||||
if (options?.prefix) {
|
||||
returnValue += options.prefix
|
||||
}
|
||||
|
||||
returnValue += convertedAmount
|
||||
@ -131,9 +115,23 @@ export const formatValue = (
|
||||
returnValue += amountSuffix
|
||||
}
|
||||
|
||||
if (suffix) {
|
||||
returnValue += suffix
|
||||
if (options?.suffix) {
|
||||
returnValue += options.suffix
|
||||
}
|
||||
|
||||
return returnValue
|
||||
}
|
||||
|
||||
export function formatLeverage(leverage: number) {
|
||||
return formatValue(leverage, {
|
||||
minDecimals: 0,
|
||||
suffix: 'x',
|
||||
})
|
||||
}
|
||||
|
||||
export function formatPercent(percent: number | string) {
|
||||
return formatValue(+percent * 100, {
|
||||
minDecimals: 0,
|
||||
suffix: '%',
|
||||
})
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user