WIP: Mp 2542 vault deposit (#216)

* Change accordion interactions

* finish interaction

* set min width accountsummary

* finish user interaction of accordion

* finish deposit interaction

* Fix Accordion

* Refactor TokenInput

* Refactor VaultModalContent

* fix minor build errors
This commit is contained in:
Bob van der Helm 2023-05-23 15:10:26 +02:00 committed by GitHub
parent 245b94b7b9
commit 173f764980
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 542 additions and 339 deletions

View File

@ -1,46 +1,30 @@
import classNames from 'classnames'
import Card from 'components/Card'
import { Plus, Subtract } from 'components/Icons'
import Text from 'components/Text'
import AccordionContent, { Item } from './AccordionContent'
interface Props {
items: Item[]
}
interface Item {
title: string
content: React.ReactNode
open?: boolean
allowMultipleOpen?: boolean
}
export default function Accordion(props: Props) {
if (props.allowMultipleOpen) {
return (
<Card className='w-full'>
{props.items.map((item, index) => (
<AccordionContent key={item.title} item={item} index={index} />
))}
</Card>
)
}
return (
<Card className='w-full'>
{props.items.map((item) => (
<details
key={item.title}
open={item.open}
className='group border-b-white/10 [&:not(:last-child)]:border-b'
>
<summary
className={classNames(
'mb-0 flex cursor-pointer items-center justify-between border-t border-white/10 bg-white/10 p-4 text-white',
'group-[&:first-child]:border-t-0 group-[[open]]:border-b',
'[&::marker]:hidden [&::marker]:content-[""]',
)}
>
<Text>{item.title}</Text>
<div className='block h-[14px] w-[14px] group-[[open]]:hidden'>
<Plus />
</div>
<div className='hidden h-[1px] w-[14px] group-[[open]]:block'>
<Subtract />
</div>
</summary>
<div className='bg-white/5 transition-[padding]'>{item.content}</div>
</details>
<div className='w-full'>
{props.items.map((item, index) => (
<Card key={item.title} className='mb-4'>
<AccordionContent item={item} index={index} />
</Card>
))}
</Card>
</div>
)
}

View File

@ -0,0 +1,40 @@
import classNames from 'classnames'
import { ChevronDown, ChevronRight } from 'components/Icons'
import Text from 'components/Text'
interface Props {
item: Item
index: number
}
export interface Item {
title: string
renderContent: () => React.ReactNode
isOpen?: boolean
toggleOpen: (index: number) => void
}
export default function AccordionContent(props: Props) {
return (
<div key={props.item.title} className='group border-b-white/10 [&:not(:last-child)]:border-b'>
<div
onClick={() => props.item.toggleOpen(props.index)}
className={classNames(
'mb-0 flex cursor-pointer items-center justify-between border-t border-white/10 bg-white/10 p-4 text-white',
'group-[&:first-child]:border-t-0 group-[[open]]:border-b',
'[&::marker]:hidden [&::marker]:content-[""]',
props.item.isOpen && 'border-b [&:first-child]:border-t-0',
)}
>
<Text>{props.item.title}</Text>
<div className='block pr-1 group-[[open]]:hidden'>
{props.item.isOpen ? <ChevronRight /> : <ChevronDown />}
</div>
</div>
{props.item.isOpen && (
<div className='bg-white/5 transition-[padding]'>{props.item.renderContent()}</div>
)}
</div>
)
}

View File

@ -21,7 +21,7 @@ interface Props {
data: Account
}
export const AcccountBalancesTable = (props: Props) => {
export const AccountBalancesTable = (props: Props) => {
const displayCurrency = useStore((s) => s.displayCurrency)
const prices = useStore((s) => s.prices)
const [sorting, setSorting] = React.useState<SortingState>([])

View File

@ -1,11 +1,12 @@
import Accordion from 'components/Accordion'
import { AcccountBalancesTable } from 'components/Account/AccountBalancesTable'
import { AccountBalancesTable } from 'components/Account/AccountBalancesTable'
import AccountComposition from 'components/Account/AccountComposition'
import AccountHealth from 'components/Account/AccountHealth'
import Card from 'components/Card'
import DisplayCurrency from 'components/DisplayCurrency'
import { ArrowChartLineUp } from 'components/Icons'
import Text from 'components/Text'
import useIsOpenArray from 'hooks/useIsOpenArray'
import useStore from 'store'
import { calculateAccountDeposits } from 'utils/accounts'
import { BN } from 'utils/helpers'
@ -16,13 +17,15 @@ interface Props {
}
export default function AccountSummary(props: Props) {
const [isOpen, toggleOpen] = useIsOpenArray(2, true)
const prices = useStore((s) => s.prices)
const baseCurrency = useStore((s) => s.baseCurrency)
const accountBalance = props.account ? calculateAccountDeposits(props.account, prices) : BN(0)
if (!props.account) return null
return (
<div className='flex max-w-[345px] basis-[345px] flex-wrap'>
<div className='flex min-w-[345px] basis-[345px] flex-wrap'>
<Card className='mb-4 min-w-fit bg-white/10' contentClassName='flex'>
<Item>
<DisplayCurrency
@ -44,11 +47,22 @@ export default function AccountSummary(props: Props) {
items={[
{
title: `Subaccount ${props.account.id} Composition`,
content: <AccountComposition account={props.account} change={props.change} />,
open: true,
renderContent: () =>
props.account ? (
<AccountComposition account={props.account} change={props.change} />
) : null,
isOpen: isOpen[0],
toggleOpen: (index: number) => toggleOpen(index),
},
{
title: 'Balances',
renderContent: () =>
props.account ? <AccountBalancesTable data={props.account} /> : null,
isOpen: isOpen[1],
toggleOpen: (index: number) => toggleOpen(index),
},
{ title: 'Balances', content: <AcccountBalancesTable data={props.account} /> },
]}
allowMultipleOpen
/>
</div>
)

View File

@ -1,4 +1,3 @@
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" x="0px" y="0px" viewBox="0 0 16 10">
<path fill="currentColor" d="M0.2,0.7C0.5,0.4,1,0.4,1.3,0.7l0.1,0.1L8,7.5l6.6-6.7c0.3-0.3,0.7-0.3,1.1-0.1l0.1,0.1
c0.3,0.3,0.3,0.8,0.1,1.1l-0.1,0.1L8.6,9.3C8.3,9.6,7.8,9.6,7.5,9.3L7.4,9.3L0.2,1.9C-0.1,1.6-0.1,1.1,0.2,0.7z"/>
<svg width="12" height="6" viewBox="0 0 12 6" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M1 0.5L6 5.5L11 0.5" stroke="white" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

Before

Width:  |  Height:  |  Size: 324 B

After

Width:  |  Height:  |  Size: 195 B

View File

@ -1,4 +1,3 @@
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" x="0px" y="0px" viewBox="0 0 10 16">
<path fill='currentColor' d="M0.7,15.8c-0.3-0.3-0.3-0.7-0.1-1.1l0.1-0.1L7.5,8L0.7,1.4C0.4,1.1,0.4,0.7,0.7,0.3l0.1-0.1C1-0.1,1.5-0.1,1.8,0.2l0.1,0.1
l7.3,7.2c0.3,0.3,0.3,0.7,0.1,1.1L9.3,8.6l-7.3,7.2C1.6,16.1,1.1,16.1,0.7,15.8z"/>
<svg width="6" height="12" viewBox="0 0 6 12" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M0.5 11L5.5 6L0.5 1" stroke="white" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

Before

Width:  |  Height:  |  Size: 328 B

After

Width:  |  Height:  |  Size: 195 B

View File

@ -164,6 +164,7 @@ export default function BorrowModal() {
amount={amount}
max={max}
className='w-full'
maxText='Max'
/>
<Divider className='my-6' />
<Text size='lg' className='pb-2'>

View File

@ -1,138 +0,0 @@
import { useEffect, useState } from 'react'
import AccountSummary from 'components/Account/AccountSummary'
import { Button } from 'components/Button'
import Card from 'components/Card'
import Divider from 'components/Divider'
import { ArrowRight } from 'components/Icons'
import Modal from 'components/Modal'
import Text from 'components/Text'
import TokenInputWithSlider from 'components/TokenInputWithSlider'
import useCurrentAccount from 'hooks/useCurrentAccount'
import useToggle from 'hooks/useToggle'
import useStore from 'store'
import { getAmount } from 'utils/accounts'
import { hardcodedFee } from 'utils/contants'
import { BN } from 'utils/helpers'
export default function FundAndWithdrawModal() {
const currentAccount = useCurrentAccount()
const modal = useStore<string | null>((s) => s.fundAndWithdrawModal)
const baseCurrency = useStore((s) => s.baseCurrency)
const withdraw = useStore((s) => s.withdraw)
const deposit = useStore((s) => s.deposit)
const [amount, setAmount] = useState(BN(0))
const [currentAsset, setCurrentAsset] = useState(baseCurrency)
const [change, setChange] = useState<AccountChange | undefined>()
const [isConfirming, setIsConfirming] = useToggle()
const balances = useStore((s) => s.balances)
const isFunding = modal === 'fund'
function resetState() {
setCurrentAsset(baseCurrency)
setAmount(BN(0))
setChange(undefined)
}
function onClose() {
resetState()
useStore.setState({ fundAndWithdrawModal: null })
}
async function onConfirm() {
if (!currentAccount) return
setIsConfirming(true)
let result
if (isFunding) {
result = await deposit({
fee: hardcodedFee,
accountId: currentAccount.id,
coin: {
denom: currentAsset.denom,
amount: amount.toString(),
},
})
} else {
result = await withdraw({
fee: hardcodedFee,
accountId: currentAccount.id,
coin: {
denom: currentAsset.denom,
amount: amount.toString(),
},
})
}
setIsConfirming(false)
if (result) {
resetState()
useStore.setState({ fundAndWithdrawModal: null })
}
}
const max = isFunding
? getAmount(currentAsset.denom, balances ?? [])
: currentAccount
? getAmount(currentAsset.denom, currentAccount.deposits)
: BN(0)
useEffect(() => {
setChange({
deposits: [
{
amount: isFunding ? BN(0).plus(amount).toString() : BN(0).minus(amount).toString(),
denom: currentAsset.denom,
},
],
})
}, [amount, currentAsset, currentAccount, isFunding])
return (
<Modal
open={!!modal}
onClose={onClose}
header={
<span className='flex items-center gap-4 px-4'>
<Text>
{isFunding
? `Fund Account ${currentAccount?.id ?? '0'}`
: `Withdraw from Account ${currentAccount?.id ?? '0'}`}
</Text>
</span>
}
headerClassName='gradient-header pl-2 pr-2.5 py-2.5 border-b-white/5 border-b'
contentClassName='flex flex-col min-h-[400px]'
>
<div className='flex flex-grow items-start gap-6 p-6'>
<Card
className='flex flex-grow bg-white/5 p-4'
contentClassName='gap-6 flex flex-col justify-between h-full'
>
<TokenInputWithSlider
asset={currentAsset}
onChange={(val) => {
setAmount(val)
}}
onChangeAsset={(asset) => {
setCurrentAsset(asset)
}}
amount={amount}
max={max}
hasSelect
balances={isFunding ? balances : currentAccount?.deposits ?? []}
accountId={!isFunding ? currentAccount?.id ?? '0' : undefined}
/>
<Divider />
<Button
onClick={onConfirm}
showProgressIndicator={isConfirming}
className='w-full'
text={isFunding ? 'Fund' : 'Withdraw'}
rightIcon={<ArrowRight />}
/>
</Card>
<AccountSummary account={currentAccount} change={change} />
</div>
</Modal>
)
}

View File

@ -1,6 +1,6 @@
import BorrowModal from 'components/Modals/BorrowModal'
import FundAndWithdrawModal from 'components/Modals/FundAndWithdrawModal'
import VaultModal from 'components/Modals/VaultModal'
import FundAndWithdrawModal from 'components/Modals/fundwithdraw/FundAndWithdrawModal'
import VaultModal from 'components/Modals/vault/VaultModal'
export default function ModalsContainer() {
return (

View File

@ -1,119 +0,0 @@
import { BigNumber } from 'bignumber.js'
import { useState } from 'react'
import AccountSummary from 'components/Account/AccountSummary'
import { Button } from 'components/Button'
import Card from 'components/Card'
import Divider from 'components/Divider'
import VaultLogo from 'components/Earn/vault/VaultLogo'
import { FormattedNumber } from 'components/FormattedNumber'
import { ArrowRight } from 'components/Icons'
import Modal from 'components/Modal'
import Slider from 'components/Slider'
import Switch from 'components/Switch'
import Text from 'components/Text'
import TitleAndSubCell from 'components/TitleAndSubCell'
import TokenInput from 'components/TokenInput'
import { ASSETS } from 'constants/assets'
import useCurrentAccount from 'hooks/useCurrentAccount'
import useStore from 'store'
import { getAmount } from 'utils/accounts'
import { formatValue } from 'utils/formatters'
import { BN } from 'utils/helpers'
export default function VaultModal() {
const currentAccount = useCurrentAccount()
const modal = useStore((s) => s.vaultModal)
const [amount, setAmount] = useState(BN(0))
const [percentage, setPercentage] = useState(0)
const [isCustomAmount, setIsCustomAmount] = useState(false)
function handleSwitch() {
setIsCustomAmount(() => !isCustomAmount)
}
function onClose() {
useStore.setState({ vaultModal: null })
setAmount(BN(0))
setPercentage(0)
}
function onChangeSlider(value: number) {}
function onChangePrimary(value: BigNumber) {}
function onChangeSecondary(value: BigNumber) {}
const primaryAsset =
ASSETS.find((asset) => asset.denom === modal?.vault.denoms.primary) ?? ASSETS[0]
const secondaryAsset =
ASSETS.find((asset) => asset.denom === modal?.vault.denoms.secondary) ?? ASSETS[0]
const hasValidData = primaryAsset && currentAccount && secondaryAsset
const maxPrimaryAmount = hasValidData
? getAmount(primaryAsset.denom, currentAccount.deposits)
: BN(0)
const maxSecondaryAmount = hasValidData
? getAmount(secondaryAsset.denom, currentAccount.deposits)
: BN(0)
return (
<Modal
open={!!(modal && hasValidData)}
onClose={onClose}
header={
modal && (
<span className='flex items-center gap-4 px-4'>
<VaultLogo vault={modal.vault} />
<Text>{`${modal.vault.symbols.primary} - ${modal.vault.symbols.secondary}`}</Text>
</span>
)
}
headerClassName='gradient-header pl-2 pr-2.5 py-2.5 border-b-white/5 border-b'
contentClassName='flex flex-col'
>
<div className='flex gap-3 border-b border-b-white/5 px-6 py-4 gradient-header'>
<TitleAndSubCell
title={formatValue(1000000, { abbreviated: true, decimals: 6 })}
sub={'Borrowed'}
/>
<div className='h-100 w-[1px] bg-white/10'></div>
<TitleAndSubCell title={`${1000} (${10000})`} sub={'Liquidity available'} />
</div>
<div className='flex flex-grow items-start gap-6 p-6'>
<Card
className='flex flex-grow bg-white/5 p-4'
contentClassName='gap-6 flex flex-col justify-between h-full'
>
<TokenInput
onChange={onChangePrimary}
amount={amount}
max={maxPrimaryAmount}
asset={primaryAsset}
/>
<Slider value={percentage} onChange={onChangeSlider} />
<TokenInput
onChange={onChangeSecondary}
amount={amount}
max={maxSecondaryAmount}
asset={secondaryAsset}
/>
<Divider />
<div className='flex justify-between'>
<Text className='text-white/50'>Custom amount</Text>
<Switch checked={isCustomAmount} onChange={handleSwitch} name='customAmount' />
</div>
<div className='flex justify-between'>
<Text className='text-white/50'>{`${primaryAsset.symbol}-${secondaryAsset.symbol} Position Value`}</Text>
<FormattedNumber amount={0} options={{ prefix: '$' }} />
</div>
<Button
onClick={() => {}}
className='w-full'
text='Continue'
rightIcon={<ArrowRight />}
/>
</Card>
<AccountSummary account={currentAccount} />
</div>
</Modal>
)
}

View File

@ -0,0 +1,41 @@
import Modal from 'components/Modal'
import Text from 'components/Text'
import useCurrentAccount from 'hooks/useCurrentAccount'
import useStore from 'store'
import { CircularProgress } from 'components/CircularProgress'
import FundWithdrawModalContent from './FundWithdrawModalContent'
export default function FundAndWithdrawModal() {
const currentAccount = useCurrentAccount()
const modal = useStore<string | null>((s) => s.fundAndWithdrawModal)
const isFunding = modal === 'fund'
function onClose() {
useStore.setState({ fundAndWithdrawModal: null })
}
return (
<Modal
open={!!modal}
onClose={onClose}
header={
<span className='flex items-center gap-4 px-4'>
<Text>
{isFunding
? `Fund Account ${currentAccount?.id ?? '0'}`
: `Withdraw from Account ${currentAccount?.id ?? '0'}`}
</Text>
</span>
}
headerClassName='gradient-header pl-2 pr-2.5 py-2.5 border-b-white/5 border-b'
contentClassName='flex flex-col min-h-[400px]'
>
{modal && currentAccount ? (
<FundWithdrawModalContent account={currentAccount} isFunding={isFunding} />
) : (
<CircularProgress />
)}
</Modal>
)
}

View File

@ -0,0 +1,113 @@
import BigNumber from 'bignumber.js'
import { useState } from 'react'
import AccountSummary from 'components/Account/AccountSummary'
import { Button } from 'components/Button'
import Card from 'components/Card'
import Divider from 'components/Divider'
import { ArrowRight } from 'components/Icons'
import TokenInputWithSlider from 'components/TokenInputWithSlider'
import useToggle from 'hooks/useToggle'
import useStore from 'store'
import { getAmount } from 'utils/accounts'
import { hardcodedFee } from 'utils/contants'
import { BN } from 'utils/helpers'
interface Props {
account: Account
isFunding: boolean
}
export default function FundWithdrawModalContent(props: Props) {
const baseCurrency = useStore((s) => s.baseCurrency)
const withdraw = useStore((s) => s.withdraw)
const deposit = useStore((s) => s.deposit)
const balances = useStore((s) => s.balances)
const [isConfirming, setIsConfirming] = useToggle()
const [currentAsset, setCurrentAsset] = useState(baseCurrency)
const [amount, setAmount] = useState(BN(0))
const [change, setChange] = useState<AccountChange | undefined>()
const max = props.isFunding
? getAmount(currentAsset.denom, balances ?? [])
: props.account
? getAmount(currentAsset.denom, props.account.deposits)
: BN(0)
function onChangeAmount(val: BigNumber) {
setAmount(val)
setChange({
deposits: [
{
amount: props.isFunding ? BN(0).plus(amount).toString() : BN(0).minus(amount).toString(),
denom: currentAsset.denom,
},
],
})
}
function resetState() {
setCurrentAsset(baseCurrency)
setAmount(BN(0))
setChange(undefined)
}
async function onConfirm() {
setIsConfirming(true)
let result
if (props.isFunding) {
result = await deposit({
fee: hardcodedFee,
accountId: props.account.id,
coin: {
denom: currentAsset.denom,
amount: amount.toString(),
},
})
} else {
result = await withdraw({
fee: hardcodedFee,
accountId: props.account.id,
coin: {
denom: currentAsset.denom,
amount: amount.toString(),
},
})
}
setIsConfirming(false)
if (result) {
resetState()
useStore.setState({ fundAndWithdrawModal: null })
}
}
return (
<div className='flex flex-grow items-start gap-6 p-6'>
<Card
className='flex flex-grow bg-white/5 p-4'
contentClassName='gap-6 flex flex-col justify-between h-full'
>
<TokenInputWithSlider
asset={currentAsset}
onChange={onChangeAmount}
onChangeAsset={setCurrentAsset}
amount={amount}
max={max}
balances={props.isFunding ? balances : props.account.deposits ?? []}
accountId={!props.isFunding ? props.account.id : undefined}
hasSelect
/>
<Divider />
<Button
onClick={onConfirm}
showProgressIndicator={isConfirming}
className='w-full'
text={props.isFunding ? 'Fund' : 'Withdraw'}
rightIcon={<ArrowRight />}
/>
</Card>
<AccountSummary account={props.account} change={change} />
</div>
)
}

View File

@ -0,0 +1,3 @@
export default function VaultBorrowings() {
return null
}

View File

@ -0,0 +1,134 @@
import BigNumber from 'bignumber.js'
import { useState } from 'react'
import { Button } from 'components/Button'
import Divider from 'components/Divider'
import { FormattedNumber } from 'components/FormattedNumber'
import { ArrowRight } from 'components/Icons'
import Slider from 'components/Slider'
import Switch from 'components/Switch'
import Text from 'components/Text'
import TokenInput from 'components/TokenInput'
import usePrice from 'hooks/usePrice'
import { getAmount } from 'utils/accounts'
import { BN } from 'utils/helpers'
interface Props {
primaryAsset: Asset
secondaryAsset: Asset
account: Account
onChangeDeposits: (deposits: Map<string, BigNumber>) => void
toggleOpen: (index: number) => void
}
export default function VaultDeposit(props: Props) {
const [isCustomAmount, setIsCustomAmount] = useState(false)
const [percentage, setPercentage] = useState(0)
const [deposits, setDeposits] = useState<Map<string, BigNumber>>(new Map())
const availablePrimaryAmount = getAmount(props.primaryAsset.denom, props.account.deposits)
const availableSecondaryAmount = getAmount(props.secondaryAsset.denom, props.account.deposits)
const primaryPrice = usePrice(props.primaryAsset.denom)
const secondaryPrice = usePrice(props.secondaryAsset.denom)
const maxAssetValueNonCustom = BN(
Math.min(availablePrimaryAmount.toNumber(), availableSecondaryAmount.toNumber()),
)
const primaryMax = isCustomAmount
? availablePrimaryAmount
: maxAssetValueNonCustom.dividedBy(primaryPrice)
const secondaryMax = isCustomAmount
? availableSecondaryAmount
: maxAssetValueNonCustom.dividedBy(secondaryPrice)
function handleSwitch() {
const isCustomAmountNew = !isCustomAmount
if (!isCustomAmountNew) {
setDeposits((deposits) => {
deposits.clear()
return new Map(deposits)
})
setPercentage(0)
}
setIsCustomAmount(isCustomAmountNew)
}
function onChangePrimaryDeposit(amount: BigNumber) {
onChangeDeposit(props.primaryAsset.denom, amount)
if (!isCustomAmount) {
onChangeDeposit(
props.secondaryAsset.denom,
secondaryMax.multipliedBy(amount.dividedBy(primaryMax)),
)
}
}
function onChangeSecondaryDeposit(amount: BigNumber) {
onChangeDeposit(props.secondaryAsset.denom, amount)
if (!isCustomAmount) {
onChangeDeposit(
props.primaryAsset.denom,
primaryMax.multipliedBy(amount.dividedBy(secondaryMax)),
)
}
}
function onChangeDeposit(denom: string, amount: BigNumber) {
if (amount.isZero()) {
return setDeposits((deposits) => {
deposits.delete(denom)
return new Map(deposits)
})
}
setDeposits((deposits) => {
deposits.set(denom, amount)
props.onChangeDeposits(deposits)
return new Map(deposits)
})
}
function onChangeSlider(value: number) {
setPercentage(value)
setDeposits((deposits) => {
deposits.set(props.primaryAsset.denom, primaryMax.multipliedBy(value / 100))
deposits.set(props.secondaryAsset.denom, secondaryMax.multipliedBy(value / 100))
return new Map(deposits)
})
}
return (
<div className='flex h-full flex-col justify-between gap-6 p-4'>
<TokenInput
onChange={onChangePrimaryDeposit}
amount={deposits.get(props.primaryAsset.denom) ?? BN(0)}
max={primaryMax}
maxText='Balance'
asset={props.primaryAsset}
/>
{!isCustomAmount && <Slider value={percentage} onChange={onChangeSlider} />}
<TokenInput
onChange={onChangeSecondaryDeposit}
amount={deposits.get(props.secondaryAsset.denom) ?? BN(0)}
max={secondaryMax}
maxText='Balance'
asset={props.secondaryAsset}
/>
<Divider />
<div className='flex justify-between'>
<Text className='text-white/50'>Custom amount</Text>
<Switch checked={isCustomAmount} onChange={handleSwitch} name='customAmount' />
</div>
<div className='flex justify-between'>
<Text className='text-white/50'>{`${props.primaryAsset.symbol}-${props.secondaryAsset.symbol} Position Value`}</Text>
<FormattedNumber amount={0} options={{ prefix: '$' }} />
</div>
<Button
onClick={() => props.toggleOpen(1)}
className='w-full'
text='Continue'
rightIcon={<ArrowRight />}
/>
</div>
)
}

View File

@ -0,0 +1,53 @@
import VaultLogo from 'components/Earn/vault/VaultLogo'
import Modal from 'components/Modal'
import Text from 'components/Text'
import { ASSETS } from 'constants/assets'
import useCurrentAccount from 'hooks/useCurrentAccount'
import useStore from 'store'
import { CircularProgress } from 'components/CircularProgress'
import VaultModalContent from './VaultModalContent'
export default function VaultModal() {
const currentAccount = useCurrentAccount()
const modal = useStore((s) => s.vaultModal)
const primaryAsset =
ASSETS.find((asset) => asset.denom === modal?.vault.denoms.primary) ?? ASSETS[0]
const secondaryAsset =
ASSETS.find((asset) => asset.denom === modal?.vault.denoms.secondary) ?? ASSETS[0]
const hasValidData = primaryAsset && currentAccount && secondaryAsset
function onClose() {
useStore.setState({ vaultModal: null })
}
return (
<Modal
open={!!(modal && hasValidData)}
onClose={onClose}
header={
modal && (
<span className='flex items-center gap-4 px-4'>
<VaultLogo vault={modal.vault} />
<Text>{`${modal.vault.symbols.primary} - ${modal.vault.symbols.secondary}`}</Text>
</span>
)
}
headerClassName='gradient-header pl-2 pr-2.5 py-2.5 border-b-white/5 border-b'
contentClassName='flex flex-col'
>
{modal?.vault && currentAccount ? (
<VaultModalContent
vault={modal.vault}
primaryAsset={primaryAsset}
secondaryAsset={secondaryAsset}
account={currentAccount}
/>
) : (
<CircularProgress />
)}
</Modal>
)
}

View File

@ -0,0 +1,52 @@
import BigNumber from 'bignumber.js'
import { useState } from 'react'
import Accordion from 'components/Accordion'
import AccountSummary from 'components/Account/AccountSummary'
import useIsOpenArray from 'hooks/useIsOpenArray'
import VaultDeposit from './VaultDeposit'
import VaultBorrowings from './VaultBorrowings'
interface Props {
vault: Vault
primaryAsset: Asset
secondaryAsset: Asset
account: Account
}
export default function VaultModalContent(props: Props) {
const [isOpen, toggleOpen] = useIsOpenArray(2, false)
const [deposits, setDeposits] = useState<Map<string, BigNumber>>(new Map())
return (
<div className='flex flex-grow items-start gap-6 p-6'>
<Accordion
items={[
{
renderContent: () => (
<VaultDeposit
onChangeDeposits={(deposits) => setDeposits(deposits)}
primaryAsset={props.primaryAsset}
secondaryAsset={props.secondaryAsset}
account={props.account}
toggleOpen={toggleOpen}
/>
),
title: 'Deposit',
isOpen: isOpen[0],
toggleOpen: (index: number) => toggleOpen(index),
},
{
renderContent: () => <VaultBorrowings />,
title: 'Borrow',
isOpen: isOpen[1],
toggleOpen: (index: number) => toggleOpen(index),
},
]}
/>
<AccountSummary account={props.account} />
</div>
)
}

View File

@ -2,7 +2,7 @@ import classNames from 'classnames'
import { Suspense } from 'react'
import { useParams } from 'react-router-dom'
import { AcccountBalancesTable } from 'components/Account/AccountBalancesTable'
import { AccountBalancesTable } from 'components/Account/AccountBalancesTable'
import AccountComposition from 'components/Account/AccountComposition'
import Card from 'components/Card'
import Loading from 'components/Loading'
@ -35,7 +35,7 @@ function Content() {
<Card className='h-fit w-full bg-white/5' title={`Account ${account.id}`} key={index}>
<AccountComposition account={account} />
<Text className='mt-3 w-full bg-white/10 px-4 py-2 text-white/40'>Balances</Text>
<AcccountBalancesTable data={account} />
<AccountBalancesTable data={account} />
</Card>
))}
</div>

View File

@ -9,8 +9,9 @@ import Select from 'components/Select/Select'
import Text from 'components/Text'
import { ASSETS } from 'constants/assets'
import useStore from 'store'
import { magnify } from 'utils/formatters'
import { BN } from 'utils/helpers'
import { FormattedNumber } from 'components/FormattedNumber'
import { Button } from 'components/Button'
interface Props {
amount: BigNumber
@ -24,6 +25,7 @@ interface Props {
interface SingleProps extends Props {
asset: Asset
max: BigNumber
maxText: string
hasSelect?: boolean
onChangeAsset?: (asset: Asset, max: BigNumber) => void
}
@ -31,6 +33,7 @@ interface SingleProps extends Props {
interface SelectProps extends Props {
asset?: Asset
max?: BigNumber
maxText?: string
hasSelect: boolean
onChangeAsset: (asset: Asset, max: BigNumber) => void
}
@ -43,7 +46,10 @@ export default function TokenInput(props: SingleProps | SelectProps) {
amount: '0',
})
const selectedAssetDenom = props.asset ? props.asset.denom : baseCurrency.denom
// TODO: Refactor the useEffect
useEffect(() => {
props.onChangeAsset && props.onChangeAsset(asset, coin ? BN(coin.amount) : BN(0))
}, [coin, asset])
const updateAsset = useCallback(
(coinDenom: string) => {
@ -55,29 +61,11 @@ export default function TokenInput(props: SingleProps | SelectProps) {
[props.balances, baseCurrency],
)
function setDefaultAsset() {
if (!props.balances || props.balances?.length === 0) return setAsset(baseCurrency)
if (props.balances.length === 1) {
const balances = props.balances ?? []
return setAsset(ASSETS.find((asset) => asset.denom === balances[0].denom) ?? baseCurrency)
}
return setAsset(ASSETS.find((asset) => asset.denom === selectedAssetDenom) ?? baseCurrency)
function onMaxBtnClick() {
if (!props.max) return
props.onChange(BN(props.max))
}
useEffect(
() => {
setDefaultAsset()
updateAsset(asset.denom)
},
// eslint-disable-next-line react-hooks/exhaustive-deps
[],
)
useEffect(() => {
props.onChangeAsset && props.onChangeAsset(asset, coin ? BN(coin.amount) : BN(0))
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [coin, asset])
return (
<div
className={classNames(
@ -113,14 +101,27 @@ export default function TokenInput(props: SingleProps | SelectProps) {
</div>
<div className='flex'>
<div className='flex flex-1'>
<Text size='xs' className='text-white/50' monospace>
{`1 ${asset.symbol} =`}
</Text>
<DisplayCurrency
className='inline pl-0.5 text-xs text-white/50'
coin={{ denom: asset.denom, amount: magnify(1, asset).toString() }}
/>
<div className='flex flex-1 items-center'>
{props.max && props.maxText && (
<>
<Text size='xs' className='mr-1 text-white' monospace>
{`${props.maxText}:`}
</Text>
<FormattedNumber
className='mr-1 text-xs text-white/50'
amount={props.max?.toNumber() || 0}
options={{ decimals: asset.decimals }}
/>
<Button
color='tertiary'
className='h-4 bg-white/20 px-1.5 py-0.5 text-2xs'
variant='transparent'
onClick={onMaxBtnClick}
>
MAX
</Button>
</>
)}
</div>
<div className='flex'>
<DisplayCurrency

View File

@ -17,6 +17,7 @@ interface Props {
interface SingleProps extends Props {
max: BigNumber
maxText: string
asset: Asset
hasSelect?: boolean
onChangeAsset?: (asset: Asset) => void
@ -24,6 +25,7 @@ interface SingleProps extends Props {
interface SelectProps extends Props {
max?: BigNumber
maxText?: string
asset?: Asset
onChangeAsset: (asset: Asset) => void
hasSelect: boolean
@ -82,6 +84,7 @@ export default function TokenInputWithSlider(props: SingleProps | SelectProps) {
onChangeAsset={(asset: Asset, max: BigNumber) => onAssetChange(asset, max)}
amount={amount}
max={max}
maxText={props.maxText || ''}
className='mb-4'
disabled={props.disabled}
hasSelect={props.hasSelect}

View File

@ -0,0 +1,16 @@
import { useState } from 'react'
export default function useIsOpenArray(length: number, allowMultiple: boolean) {
const [isOpen, setIsOpen] = useState<boolean[]>(Array.from({ length }, (_, i) => i === 0))
function toggleOpen(index: number) {
setIsOpen((prev) => {
return prev.map((_, i) => {
if (i === index) return !prev[i]
return allowMultiple ? prev[i] : false
})
})
}
return [isOpen, toggleOpen] as const
}

7
src/hooks/usePrice.tsx Normal file
View File

@ -0,0 +1,7 @@
import usePrices from './usePrices'
export default function usePrice(denom: string) {
const { data: prices } = usePrices()
return prices.find((coin) => coin.denom === denom)?.amount ?? 0
}