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:
parent
245b94b7b9
commit
173f764980
@ -1,46 +1,30 @@
|
|||||||
import classNames from 'classnames'
|
|
||||||
|
|
||||||
import Card from 'components/Card'
|
import Card from 'components/Card'
|
||||||
import { Plus, Subtract } from 'components/Icons'
|
|
||||||
import Text from 'components/Text'
|
import AccordionContent, { Item } from './AccordionContent'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
items: Item[]
|
items: Item[]
|
||||||
}
|
allowMultipleOpen?: boolean
|
||||||
|
|
||||||
interface Item {
|
|
||||||
title: string
|
|
||||||
content: React.ReactNode
|
|
||||||
open?: boolean
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function Accordion(props: Props) {
|
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 (
|
return (
|
||||||
<Card className='w-full'>
|
<div className='w-full'>
|
||||||
{props.items.map((item) => (
|
{props.items.map((item, index) => (
|
||||||
<details
|
<Card key={item.title} className='mb-4'>
|
||||||
key={item.title}
|
<AccordionContent item={item} index={index} />
|
||||||
open={item.open}
|
</Card>
|
||||||
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>
|
|
||||||
))}
|
))}
|
||||||
</Card>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
40
src/components/AccordionContent.tsx
Normal file
40
src/components/AccordionContent.tsx
Normal 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>
|
||||||
|
)
|
||||||
|
}
|
@ -21,7 +21,7 @@ interface Props {
|
|||||||
data: Account
|
data: Account
|
||||||
}
|
}
|
||||||
|
|
||||||
export const AcccountBalancesTable = (props: Props) => {
|
export const AccountBalancesTable = (props: Props) => {
|
||||||
const displayCurrency = useStore((s) => s.displayCurrency)
|
const displayCurrency = useStore((s) => s.displayCurrency)
|
||||||
const prices = useStore((s) => s.prices)
|
const prices = useStore((s) => s.prices)
|
||||||
const [sorting, setSorting] = React.useState<SortingState>([])
|
const [sorting, setSorting] = React.useState<SortingState>([])
|
||||||
|
@ -1,11 +1,12 @@
|
|||||||
import Accordion from 'components/Accordion'
|
import Accordion from 'components/Accordion'
|
||||||
import { AcccountBalancesTable } from 'components/Account/AccountBalancesTable'
|
import { AccountBalancesTable } from 'components/Account/AccountBalancesTable'
|
||||||
import AccountComposition from 'components/Account/AccountComposition'
|
import AccountComposition from 'components/Account/AccountComposition'
|
||||||
import AccountHealth from 'components/Account/AccountHealth'
|
import AccountHealth from 'components/Account/AccountHealth'
|
||||||
import Card from 'components/Card'
|
import Card from 'components/Card'
|
||||||
import DisplayCurrency from 'components/DisplayCurrency'
|
import DisplayCurrency from 'components/DisplayCurrency'
|
||||||
import { ArrowChartLineUp } from 'components/Icons'
|
import { ArrowChartLineUp } from 'components/Icons'
|
||||||
import Text from 'components/Text'
|
import Text from 'components/Text'
|
||||||
|
import useIsOpenArray from 'hooks/useIsOpenArray'
|
||||||
import useStore from 'store'
|
import useStore from 'store'
|
||||||
import { calculateAccountDeposits } from 'utils/accounts'
|
import { calculateAccountDeposits } from 'utils/accounts'
|
||||||
import { BN } from 'utils/helpers'
|
import { BN } from 'utils/helpers'
|
||||||
@ -16,13 +17,15 @@ interface Props {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default function AccountSummary(props: Props) {
|
export default function AccountSummary(props: Props) {
|
||||||
|
const [isOpen, toggleOpen] = useIsOpenArray(2, true)
|
||||||
|
|
||||||
const prices = useStore((s) => s.prices)
|
const prices = useStore((s) => s.prices)
|
||||||
const baseCurrency = useStore((s) => s.baseCurrency)
|
const baseCurrency = useStore((s) => s.baseCurrency)
|
||||||
const accountBalance = props.account ? calculateAccountDeposits(props.account, prices) : BN(0)
|
const accountBalance = props.account ? calculateAccountDeposits(props.account, prices) : BN(0)
|
||||||
if (!props.account) return null
|
if (!props.account) return null
|
||||||
|
|
||||||
return (
|
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'>
|
<Card className='mb-4 min-w-fit bg-white/10' contentClassName='flex'>
|
||||||
<Item>
|
<Item>
|
||||||
<DisplayCurrency
|
<DisplayCurrency
|
||||||
@ -44,11 +47,22 @@ export default function AccountSummary(props: Props) {
|
|||||||
items={[
|
items={[
|
||||||
{
|
{
|
||||||
title: `Subaccount ${props.account.id} Composition`,
|
title: `Subaccount ${props.account.id} Composition`,
|
||||||
content: <AccountComposition account={props.account} change={props.change} />,
|
renderContent: () =>
|
||||||
open: true,
|
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>
|
</div>
|
||||||
)
|
)
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" x="0px" y="0px" viewBox="0 0 16 10">
|
<svg width="12" height="6" viewBox="0 0 12 6" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
<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
|
<path d="M1 0.5L6 5.5L11 0.5" stroke="white" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
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>
|
</svg>
|
||||||
|
Before Width: | Height: | Size: 324 B After Width: | Height: | Size: 195 B |
@ -1,4 +1,3 @@
|
|||||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" x="0px" y="0px" viewBox="0 0 10 16">
|
<svg width="6" height="12" viewBox="0 0 6 12" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
<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
|
<path d="M0.5 11L5.5 6L0.5 1" stroke="white" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
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>
|
</svg>
|
||||||
|
Before Width: | Height: | Size: 328 B After Width: | Height: | Size: 195 B |
@ -164,6 +164,7 @@ export default function BorrowModal() {
|
|||||||
amount={amount}
|
amount={amount}
|
||||||
max={max}
|
max={max}
|
||||||
className='w-full'
|
className='w-full'
|
||||||
|
maxText='Max'
|
||||||
/>
|
/>
|
||||||
<Divider className='my-6' />
|
<Divider className='my-6' />
|
||||||
<Text size='lg' className='pb-2'>
|
<Text size='lg' className='pb-2'>
|
||||||
|
@ -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>
|
|
||||||
)
|
|
||||||
}
|
|
@ -1,6 +1,6 @@
|
|||||||
import BorrowModal from 'components/Modals/BorrowModal'
|
import BorrowModal from 'components/Modals/BorrowModal'
|
||||||
import FundAndWithdrawModal from 'components/Modals/FundAndWithdrawModal'
|
import FundAndWithdrawModal from 'components/Modals/fundwithdraw/FundAndWithdrawModal'
|
||||||
import VaultModal from 'components/Modals/VaultModal'
|
import VaultModal from 'components/Modals/vault/VaultModal'
|
||||||
|
|
||||||
export default function ModalsContainer() {
|
export default function ModalsContainer() {
|
||||||
return (
|
return (
|
||||||
|
@ -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>
|
|
||||||
)
|
|
||||||
}
|
|
41
src/components/Modals/fundwithdraw/FundAndWithdrawModal.tsx
Normal file
41
src/components/Modals/fundwithdraw/FundAndWithdrawModal.tsx
Normal 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>
|
||||||
|
)
|
||||||
|
}
|
113
src/components/Modals/fundwithdraw/FundWithdrawModalContent.tsx
Normal file
113
src/components/Modals/fundwithdraw/FundWithdrawModalContent.tsx
Normal 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>
|
||||||
|
)
|
||||||
|
}
|
3
src/components/Modals/vault/VaultBorrowings.tsx
Normal file
3
src/components/Modals/vault/VaultBorrowings.tsx
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
export default function VaultBorrowings() {
|
||||||
|
return null
|
||||||
|
}
|
134
src/components/Modals/vault/VaultDeposit.tsx
Normal file
134
src/components/Modals/vault/VaultDeposit.tsx
Normal 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>
|
||||||
|
)
|
||||||
|
}
|
53
src/components/Modals/vault/VaultModal.tsx
Normal file
53
src/components/Modals/vault/VaultModal.tsx
Normal 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>
|
||||||
|
)
|
||||||
|
}
|
52
src/components/Modals/vault/VaultModalContent.tsx
Normal file
52
src/components/Modals/vault/VaultModalContent.tsx
Normal 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>
|
||||||
|
)
|
||||||
|
}
|
@ -2,7 +2,7 @@ import classNames from 'classnames'
|
|||||||
import { Suspense } from 'react'
|
import { Suspense } from 'react'
|
||||||
import { useParams } from 'react-router-dom'
|
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 AccountComposition from 'components/Account/AccountComposition'
|
||||||
import Card from 'components/Card'
|
import Card from 'components/Card'
|
||||||
import Loading from 'components/Loading'
|
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}>
|
<Card className='h-fit w-full bg-white/5' title={`Account ${account.id}`} key={index}>
|
||||||
<AccountComposition account={account} />
|
<AccountComposition account={account} />
|
||||||
<Text className='mt-3 w-full bg-white/10 px-4 py-2 text-white/40'>Balances</Text>
|
<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>
|
</Card>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
@ -9,8 +9,9 @@ import Select from 'components/Select/Select'
|
|||||||
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 useStore from 'store'
|
||||||
import { magnify } from 'utils/formatters'
|
|
||||||
import { BN } from 'utils/helpers'
|
import { BN } from 'utils/helpers'
|
||||||
|
import { FormattedNumber } from 'components/FormattedNumber'
|
||||||
|
import { Button } from 'components/Button'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
amount: BigNumber
|
amount: BigNumber
|
||||||
@ -24,6 +25,7 @@ interface Props {
|
|||||||
interface SingleProps extends Props {
|
interface SingleProps extends Props {
|
||||||
asset: Asset
|
asset: Asset
|
||||||
max: BigNumber
|
max: BigNumber
|
||||||
|
maxText: string
|
||||||
hasSelect?: boolean
|
hasSelect?: boolean
|
||||||
onChangeAsset?: (asset: Asset, max: BigNumber) => void
|
onChangeAsset?: (asset: Asset, max: BigNumber) => void
|
||||||
}
|
}
|
||||||
@ -31,6 +33,7 @@ interface SingleProps extends Props {
|
|||||||
interface SelectProps extends Props {
|
interface SelectProps extends Props {
|
||||||
asset?: Asset
|
asset?: Asset
|
||||||
max?: BigNumber
|
max?: BigNumber
|
||||||
|
maxText?: string
|
||||||
hasSelect: boolean
|
hasSelect: boolean
|
||||||
onChangeAsset: (asset: Asset, max: BigNumber) => void
|
onChangeAsset: (asset: Asset, max: BigNumber) => void
|
||||||
}
|
}
|
||||||
@ -43,7 +46,10 @@ export default function TokenInput(props: SingleProps | SelectProps) {
|
|||||||
amount: '0',
|
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(
|
const updateAsset = useCallback(
|
||||||
(coinDenom: string) => {
|
(coinDenom: string) => {
|
||||||
@ -55,29 +61,11 @@ export default function TokenInput(props: SingleProps | SelectProps) {
|
|||||||
[props.balances, baseCurrency],
|
[props.balances, baseCurrency],
|
||||||
)
|
)
|
||||||
|
|
||||||
function setDefaultAsset() {
|
function onMaxBtnClick() {
|
||||||
if (!props.balances || props.balances?.length === 0) return setAsset(baseCurrency)
|
if (!props.max) return
|
||||||
if (props.balances.length === 1) {
|
props.onChange(BN(props.max))
|
||||||
const balances = props.balances ?? []
|
|
||||||
return setAsset(ASSETS.find((asset) => asset.denom === balances[0].denom) ?? baseCurrency)
|
|
||||||
}
|
|
||||||
return setAsset(ASSETS.find((asset) => asset.denom === selectedAssetDenom) ?? baseCurrency)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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 (
|
return (
|
||||||
<div
|
<div
|
||||||
className={classNames(
|
className={classNames(
|
||||||
@ -113,14 +101,27 @@ export default function TokenInput(props: SingleProps | SelectProps) {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className='flex'>
|
<div className='flex'>
|
||||||
<div className='flex flex-1'>
|
<div className='flex flex-1 items-center'>
|
||||||
<Text size='xs' className='text-white/50' monospace>
|
{props.max && props.maxText && (
|
||||||
{`1 ${asset.symbol} =`}
|
<>
|
||||||
</Text>
|
<Text size='xs' className='mr-1 text-white' monospace>
|
||||||
<DisplayCurrency
|
{`${props.maxText}:`}
|
||||||
className='inline pl-0.5 text-xs text-white/50'
|
</Text>
|
||||||
coin={{ denom: asset.denom, amount: magnify(1, asset).toString() }}
|
<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>
|
||||||
<div className='flex'>
|
<div className='flex'>
|
||||||
<DisplayCurrency
|
<DisplayCurrency
|
||||||
|
@ -17,6 +17,7 @@ interface Props {
|
|||||||
|
|
||||||
interface SingleProps extends Props {
|
interface SingleProps extends Props {
|
||||||
max: BigNumber
|
max: BigNumber
|
||||||
|
maxText: string
|
||||||
asset: Asset
|
asset: Asset
|
||||||
hasSelect?: boolean
|
hasSelect?: boolean
|
||||||
onChangeAsset?: (asset: Asset) => void
|
onChangeAsset?: (asset: Asset) => void
|
||||||
@ -24,6 +25,7 @@ interface SingleProps extends Props {
|
|||||||
|
|
||||||
interface SelectProps extends Props {
|
interface SelectProps extends Props {
|
||||||
max?: BigNumber
|
max?: BigNumber
|
||||||
|
maxText?: string
|
||||||
asset?: Asset
|
asset?: Asset
|
||||||
onChangeAsset: (asset: Asset) => void
|
onChangeAsset: (asset: Asset) => void
|
||||||
hasSelect: boolean
|
hasSelect: boolean
|
||||||
@ -82,6 +84,7 @@ export default function TokenInputWithSlider(props: SingleProps | SelectProps) {
|
|||||||
onChangeAsset={(asset: Asset, max: BigNumber) => onAssetChange(asset, max)}
|
onChangeAsset={(asset: Asset, max: BigNumber) => onAssetChange(asset, max)}
|
||||||
amount={amount}
|
amount={amount}
|
||||||
max={max}
|
max={max}
|
||||||
|
maxText={props.maxText || ''}
|
||||||
className='mb-4'
|
className='mb-4'
|
||||||
disabled={props.disabled}
|
disabled={props.disabled}
|
||||||
hasSelect={props.hasSelect}
|
hasSelect={props.hasSelect}
|
||||||
|
16
src/hooks/useIsOpenArray.tsx
Normal file
16
src/hooks/useIsOpenArray.tsx
Normal 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
7
src/hooks/usePrice.tsx
Normal 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
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user