MP-1566: Deposit funds modal (#33)
* feat: deposit account modal * style: button default colors to match wireframes * react-query-devtools package added * slider moved to separate component
This commit is contained in:
parent
d3a1e9f3f1
commit
55d0910d10
@ -1,7 +1,6 @@
|
||||
import React, { useMemo, useState } from 'react'
|
||||
import { XMarkIcon } from '@heroicons/react/24/solid'
|
||||
import { toast } from 'react-toastify'
|
||||
import * as Slider from '@radix-ui/react-slider'
|
||||
|
||||
import Button from 'components/Button'
|
||||
import Container from 'components/Container'
|
||||
@ -17,6 +16,7 @@ import Tooltip from 'components/Tooltip'
|
||||
import ContainerSecondary from 'components/ContainerSecondary'
|
||||
import Spinner from 'components/Spinner'
|
||||
import useCalculateMaxBorrowAmount from 'hooks/useCalculateMaxBorrowAmount'
|
||||
import Slider from 'components/Slider'
|
||||
|
||||
const BorrowFunds = ({ tokenDenom, onClose }: any) => {
|
||||
const [amount, setAmount] = useState(0)
|
||||
@ -113,36 +113,19 @@ const BorrowFunds = ({ tokenDenom, onClose }: any) => {
|
||||
</div>
|
||||
</ContainerSecondary>
|
||||
<ContainerSecondary>
|
||||
<div className="relative mb-4 flex flex-1 items-center">
|
||||
<Slider.Root
|
||||
className="relative flex h-[20px] w-full cursor-pointer touch-none select-none items-center"
|
||||
value={[percentageValue]}
|
||||
min={0}
|
||||
max={100}
|
||||
step={1}
|
||||
onValueChange={(value) => {
|
||||
const decimal = value[0] / 100
|
||||
const tokenDecimals = getTokenDecimals(tokenDenom)
|
||||
// limit decimal precision based on token contract decimals
|
||||
const newAmount = Number((decimal * maxValue).toFixed(tokenDecimals))
|
||||
<Slider
|
||||
className="mb-6"
|
||||
value={percentageValue}
|
||||
onChange={(value) => {
|
||||
const decimal = value[0] / 100
|
||||
const tokenDecimals = getTokenDecimals(tokenDenom)
|
||||
// limit decimal precision based on token contract decimals
|
||||
const newAmount = Number((decimal * maxValue).toFixed(tokenDecimals))
|
||||
|
||||
setAmount(newAmount)
|
||||
}}
|
||||
>
|
||||
<Slider.Track className="relative h-[6px] grow rounded-full bg-gray-400">
|
||||
<Slider.Range className="absolute h-[100%] rounded-full bg-blue-600" />
|
||||
</Slider.Track>
|
||||
<Slider.Thumb className="flex h-[20px] w-[20px] items-center justify-center rounded-full bg-white !outline-none">
|
||||
<div className="relative top-5 text-xs">{percentageValue.toFixed(0)}%</div>
|
||||
</Slider.Thumb>
|
||||
</Slider.Root>
|
||||
<button
|
||||
className="ml-4 rounded-md bg-blue-600 py-1 px-2 text-xs font-semibold text-white"
|
||||
onClick={() => setAmount(maxValue)}
|
||||
>
|
||||
MAX
|
||||
</button>
|
||||
</div>
|
||||
setAmount(newAmount)
|
||||
}}
|
||||
onMaxClick={() => setAmount(maxValue)}
|
||||
/>
|
||||
</ContainerSecondary>
|
||||
<ContainerSecondary className="flex items-center justify-between">
|
||||
<div className="flex">
|
||||
|
@ -1,7 +1,6 @@
|
||||
import React, { useMemo, useState } from 'react'
|
||||
import { XMarkIcon } from '@heroicons/react/24/solid'
|
||||
import { toast } from 'react-toastify'
|
||||
import * as Slider from '@radix-ui/react-slider'
|
||||
|
||||
import Button from 'components/Button'
|
||||
import Container from 'components/Container'
|
||||
@ -13,6 +12,7 @@ import BigNumber from 'bignumber.js'
|
||||
import useAllBalances from 'hooks/useAllBalances'
|
||||
import ContainerSecondary from 'components/ContainerSecondary'
|
||||
import Spinner from 'components/Spinner'
|
||||
import Slider from 'components/Slider'
|
||||
|
||||
const RepayFunds = ({ tokenDenom, amount: repayAmount, onClose }: any) => {
|
||||
const [amount, setAmount] = useState(0)
|
||||
@ -91,36 +91,19 @@ const RepayFunds = ({ tokenDenom, amount: repayAmount, onClose }: any) => {
|
||||
</div>
|
||||
</ContainerSecondary>
|
||||
<ContainerSecondary>
|
||||
<div className="relative mb-4 flex flex-1 items-center">
|
||||
<Slider.Root
|
||||
className="relative flex h-[20px] w-full cursor-pointer touch-none select-none items-center"
|
||||
value={[percentageValue]}
|
||||
min={0}
|
||||
max={100}
|
||||
step={1}
|
||||
onValueChange={(value) => {
|
||||
const decimal = value[0] / 100
|
||||
const tokenDecimals = getTokenDecimals(tokenDenom)
|
||||
// limit decimal precision based on token contract decimals
|
||||
const newAmount = Number((decimal * maxValue).toFixed(tokenDecimals))
|
||||
<Slider
|
||||
className="mb-6"
|
||||
value={percentageValue}
|
||||
onChange={(value) => {
|
||||
const decimal = value[0] / 100
|
||||
const tokenDecimals = getTokenDecimals(tokenDenom)
|
||||
// limit decimal precision based on token contract decimals
|
||||
const newAmount = Number((decimal * maxValue).toFixed(tokenDecimals))
|
||||
|
||||
setAmount(newAmount)
|
||||
}}
|
||||
>
|
||||
<Slider.Track className="relative h-[6px] grow rounded-full bg-gray-400">
|
||||
<Slider.Range className="absolute h-[100%] rounded-full bg-blue-600" />
|
||||
</Slider.Track>
|
||||
<Slider.Thumb className="flex h-[20px] w-[20px] items-center justify-center rounded-full bg-white !outline-none">
|
||||
<div className="relative top-5 text-xs">{percentageValue.toFixed(0)}%</div>
|
||||
</Slider.Thumb>
|
||||
</Slider.Root>
|
||||
<button
|
||||
className="ml-4 rounded-md bg-blue-600 py-1 px-2 text-xs font-semibold text-white"
|
||||
onClick={() => setAmount(maxValue)}
|
||||
>
|
||||
MAX
|
||||
</button>
|
||||
</div>
|
||||
setAmount(newAmount)
|
||||
}}
|
||||
onMaxClick={() => setAmount(maxValue)}
|
||||
/>
|
||||
</ContainerSecondary>
|
||||
</div>
|
||||
<Button className="w-full" onClick={handleSubmit} disabled={isSubmitDisabled}>
|
||||
|
@ -12,7 +12,7 @@ const Button = React.forwardRef<any, Props>(
|
||||
<button
|
||||
ref={ref}
|
||||
onClick={onClick}
|
||||
className={`overflow-hidden text-ellipsis rounded-3xl bg-green-500 py-2 px-5 text-sm font-semibold text-white ${className} ${
|
||||
className={`overflow-hidden text-ellipsis rounded-md bg-blue-500 py-2 px-5 text-sm font-semibold text-white ${className} ${
|
||||
disabled ? 'opacity-40' : ''
|
||||
}`}
|
||||
disabled={disabled}
|
||||
|
@ -1,169 +0,0 @@
|
||||
import React, { useEffect, useMemo, useState } from 'react'
|
||||
import * as Slider from '@radix-ui/react-slider'
|
||||
import BigNumber from 'bignumber.js'
|
||||
import { Switch } from '@headlessui/react'
|
||||
import useLocalStorageState from 'use-local-storage-state'
|
||||
|
||||
import Button from '../Button'
|
||||
import useAllowedCoins from 'hooks/useAllowedCoins'
|
||||
import useDepositCreditAccount from 'hooks/useDepositCreditAccount'
|
||||
import useCreditManagerStore from 'stores/useCreditManagerStore'
|
||||
import useAllBalances from 'hooks/useAllBalances'
|
||||
import { getTokenDecimals, getTokenSymbol } from 'utils/tokens'
|
||||
import ContainerSecondary from 'components/ContainerSecondary'
|
||||
|
||||
const FundAccount = () => {
|
||||
const [amount, setAmount] = useState(0)
|
||||
const [selectedToken, setSelectedToken] = useState('')
|
||||
|
||||
const selectedAccount = useCreditManagerStore((s) => s.selectedAccount)
|
||||
|
||||
const [lendAssets, setLendAssets] = useLocalStorageState(`lendAssets_${selectedAccount}`, {
|
||||
defaultValue: false,
|
||||
})
|
||||
|
||||
const { data: balancesData } = useAllBalances()
|
||||
const { data: allowedCoinsData, isLoading: isLoadingAllowedCoins } = useAllowedCoins()
|
||||
const { mutate } = useDepositCreditAccount(
|
||||
selectedAccount || '',
|
||||
selectedToken,
|
||||
BigNumber(amount)
|
||||
.times(10 ** getTokenDecimals(selectedToken))
|
||||
.toNumber()
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
if (allowedCoinsData && allowedCoinsData.length > 0) {
|
||||
// initialize selected token when allowedCoins fetch data is available
|
||||
setSelectedToken(allowedCoinsData[0])
|
||||
}
|
||||
}, [allowedCoinsData])
|
||||
|
||||
const walletAmount = useMemo(() => {
|
||||
if (!selectedToken) return 0
|
||||
|
||||
return BigNumber(balancesData?.find((balance) => balance.denom === selectedToken)?.amount ?? 0)
|
||||
.div(10 ** getTokenDecimals(selectedToken))
|
||||
.toNumber()
|
||||
}, [balancesData, selectedToken])
|
||||
|
||||
const handleValueChange = (value: number) => {
|
||||
if (value > walletAmount) {
|
||||
setAmount(walletAmount)
|
||||
return
|
||||
}
|
||||
|
||||
setAmount(value)
|
||||
}
|
||||
|
||||
const maxValue = walletAmount
|
||||
const percentageValue = isNaN(amount) ? 0 : (amount * 100) / maxValue
|
||||
|
||||
return (
|
||||
<>
|
||||
<ContainerSecondary className="mb-2 p-3">
|
||||
<p className="mb-6 text-sm">
|
||||
Transfer assets from your injective wallet to your Mars credit account. If you don’t have
|
||||
any assets in your injective wallet use the injective bridge to transfer funds to your
|
||||
injective wallet.
|
||||
</p>
|
||||
{isLoadingAllowedCoins ? (
|
||||
<p>Loading...</p>
|
||||
) : (
|
||||
<>
|
||||
<div className="mb-4 text-sm">
|
||||
<div className="mb-1 flex justify-between">
|
||||
<div>Asset:</div>
|
||||
<select
|
||||
className="bg-transparent"
|
||||
onChange={(e) => {
|
||||
setSelectedToken(e.target.value)
|
||||
|
||||
if (e.target.value !== selectedToken) setAmount(0)
|
||||
}}
|
||||
>
|
||||
{allowedCoinsData?.map((entry) => (
|
||||
<option key={entry} value={entry}>
|
||||
{getTokenSymbol(entry)}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<div>Amount:</div>
|
||||
<input
|
||||
type="number"
|
||||
className="border border-black/50 bg-transparent px-2"
|
||||
value={amount}
|
||||
onChange={(e) => handleValueChange(e.target.valueAsNumber)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<p className="text-sm">In wallet: {walletAmount.toLocaleString()}</p>
|
||||
{/* SLIDER - initial implementation to test functionality */}
|
||||
{/* TODO: will need to be revamped later on */}
|
||||
<div className="relative mb-6 flex flex-1 items-center">
|
||||
<Slider.Root
|
||||
className="relative flex h-[20px] w-full cursor-pointer touch-none select-none items-center"
|
||||
value={[percentageValue]}
|
||||
min={0}
|
||||
max={100}
|
||||
step={1}
|
||||
onValueChange={(value) => {
|
||||
const decimal = value[0] / 100
|
||||
const tokenDecimals = getTokenDecimals(selectedToken)
|
||||
// limit decimal precision based on token contract decimals
|
||||
const newAmount = Number((decimal * maxValue).toFixed(tokenDecimals))
|
||||
|
||||
setAmount(newAmount)
|
||||
}}
|
||||
>
|
||||
<Slider.Track className="relative h-[6px] grow rounded-full bg-gray-400">
|
||||
<Slider.Range className="absolute h-[100%] rounded-full bg-blue-600" />
|
||||
</Slider.Track>
|
||||
<Slider.Thumb className="flex h-[20px] w-[20px] items-center justify-center rounded-full bg-white !outline-none">
|
||||
<div className="relative top-5 text-xs">{percentageValue.toFixed(0)}%</div>
|
||||
</Slider.Thumb>
|
||||
</Slider.Root>
|
||||
<button
|
||||
className="ml-4 rounded-md bg-blue-600 py-1 px-2 text-xs font-semibold text-white"
|
||||
onClick={() => setAmount(maxValue)}
|
||||
>
|
||||
MAX
|
||||
</button>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</ContainerSecondary>
|
||||
<ContainerSecondary className="mb-2 flex items-center justify-between">
|
||||
<div>
|
||||
<h3 className="font-bold">Lending Assets</h3>
|
||||
<div className="text-sm text-[#585A74]/50">Lend assets from account to earn yield.</div>
|
||||
</div>
|
||||
|
||||
<Switch
|
||||
checked={lendAssets}
|
||||
onChange={setLendAssets}
|
||||
className={`${
|
||||
lendAssets ? 'bg-blue-600' : 'bg-gray-400'
|
||||
} relative inline-flex h-6 w-11 items-center rounded-full`}
|
||||
>
|
||||
<span
|
||||
className={`${
|
||||
lendAssets ? 'translate-x-6' : 'translate-x-1'
|
||||
} inline-block h-4 w-4 transform rounded-full bg-white transition`}
|
||||
/>
|
||||
</Switch>
|
||||
</ContainerSecondary>
|
||||
<Button
|
||||
className="w-full !rounded-lg"
|
||||
onClick={() => mutate()}
|
||||
disabled={amount === 0 || !amount}
|
||||
>
|
||||
Fund
|
||||
</Button>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default FundAccount
|
@ -7,14 +7,14 @@ import useCreditManagerStore from 'stores/useCreditManagerStore'
|
||||
import useWalletStore from 'stores/useWalletStore'
|
||||
import useCreditAccountPositions from 'hooks/useCreditAccountPositions'
|
||||
import { getTokenDecimals, getTokenSymbol } from 'utils/tokens'
|
||||
import FundAccount from './FundAccount'
|
||||
import useTokenPrices from 'hooks/useTokenPrices'
|
||||
import useAccountStats from 'hooks/useAccountStats'
|
||||
import useMarkets from 'hooks/useMarkets'
|
||||
import ContainerSecondary from 'components/ContainerSecondary'
|
||||
import FundAccountModal from 'components/FundAccountModal'
|
||||
|
||||
const CreditManager = () => {
|
||||
const [isFund, setIsFund] = useState(false)
|
||||
const [showFundWalletModal, setShowFundWalletModal] = useState(false)
|
||||
|
||||
const address = useWalletStore((s) => s.address)
|
||||
const selectedAccount = useCreditManagerStore((s) => s.selectedAccount)
|
||||
@ -48,95 +48,85 @@ const CreditManager = () => {
|
||||
|
||||
return (
|
||||
<div className="absolute inset-0 left-auto w-[400px] border-l border-white/20 bg-background-2 p-2">
|
||||
<ContainerSecondary className="mb-2">
|
||||
{isFund ? (
|
||||
<div className="flex items-center justify-between">
|
||||
<h3 className="font-bold">Fund Account</h3>
|
||||
<Button className="rounded-md" onClick={() => setIsFund(false)}>
|
||||
Cancel
|
||||
</Button>
|
||||
</div>
|
||||
<ContainerSecondary className="mb-2 flex gap-3">
|
||||
<Button className="flex-1 rounded-md" onClick={() => setShowFundWalletModal(true)}>
|
||||
Fund
|
||||
</Button>
|
||||
<Button
|
||||
className="flex-1 rounded-md"
|
||||
onClick={() => alert('TODO')}
|
||||
disabled={!positionsData || positionsData.coins.length === 0}
|
||||
>
|
||||
Withdraw
|
||||
</Button>
|
||||
</ContainerSecondary>
|
||||
<ContainerSecondary className="mb-2 text-sm">
|
||||
<div className="mb-1 flex justify-between">
|
||||
<div>Total Position:</div>
|
||||
<div className="font-semibold">{formatCurrency(accountStats?.totalPosition ?? 0)}</div>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<div>Total Liabilities:</div>
|
||||
<div className="font-semibold">{formatCurrency(accountStats?.totalDebt ?? 0)}</div>
|
||||
</div>
|
||||
</ContainerSecondary>
|
||||
<ContainerSecondary>
|
||||
<h4 className="font-bold">Balances</h4>
|
||||
{isLoadingPositions ? (
|
||||
<div>Loading...</div>
|
||||
) : (
|
||||
<div className="flex gap-3">
|
||||
<Button className="flex-1 rounded-md" onClick={() => setIsFund(true)}>
|
||||
Fund
|
||||
</Button>
|
||||
<Button className="flex-1 rounded-md" onClick={() => alert('TODO')}>
|
||||
Withdraw
|
||||
</Button>
|
||||
</div>
|
||||
<>
|
||||
<div className="flex text-xs font-semibold">
|
||||
<div className="flex-1">Asset</div>
|
||||
<div className="flex-1">Value</div>
|
||||
<div className="flex-1">Size</div>
|
||||
<div className="flex-1">APY</div>
|
||||
</div>
|
||||
{positionsData?.coins.map((coin) => (
|
||||
<div key={coin.denom} className="flex text-xs text-black/40">
|
||||
<div className="flex-1">{getTokenSymbol(coin.denom)}</div>
|
||||
<div className="flex-1">
|
||||
{formatCurrency(getTokenTotalUSDValue(coin.amount, coin.denom))}
|
||||
</div>
|
||||
<div className="flex-1">
|
||||
{BigNumber(coin.amount)
|
||||
.div(10 ** getTokenDecimals(coin.denom))
|
||||
.toNumber()
|
||||
.toLocaleString(undefined, {
|
||||
maximumFractionDigits: getTokenDecimals(coin.denom),
|
||||
})}
|
||||
</div>
|
||||
<div className="flex-1">-</div>
|
||||
</div>
|
||||
))}
|
||||
{positionsData?.debts.map((coin) => (
|
||||
<div key={coin.denom} className="flex text-xs text-red-500">
|
||||
<div className="flex-1 text-black/40">{getTokenSymbol(coin.denom)}</div>
|
||||
<div className="flex-1">
|
||||
-{formatCurrency(getTokenTotalUSDValue(coin.amount, coin.denom))}
|
||||
</div>
|
||||
<div className="flex-1">
|
||||
-
|
||||
{BigNumber(coin.amount)
|
||||
.div(10 ** getTokenDecimals(coin.denom))
|
||||
.toNumber()
|
||||
.toLocaleString(undefined, {
|
||||
maximumFractionDigits: 6,
|
||||
})}
|
||||
</div>
|
||||
<div className="flex-1">
|
||||
-{(Number(marketsData?.[coin.denom].borrow_rate) * 100).toFixed(1)}%
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</>
|
||||
)}
|
||||
</ContainerSecondary>
|
||||
{isFund ? (
|
||||
<FundAccount />
|
||||
) : (
|
||||
<>
|
||||
<ContainerSecondary className="mb-2 text-sm">
|
||||
<div className="mb-1 flex justify-between">
|
||||
<div>Total Position:</div>
|
||||
<div className="font-semibold">
|
||||
{formatCurrency(accountStats?.totalPosition ?? 0)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<div>Total Liabilities:</div>
|
||||
<div className="font-semibold">{formatCurrency(accountStats?.totalDebt ?? 0)}</div>
|
||||
</div>
|
||||
</ContainerSecondary>
|
||||
<ContainerSecondary>
|
||||
<h4 className="font-bold">Balances</h4>
|
||||
{isLoadingPositions ? (
|
||||
<div>Loading...</div>
|
||||
) : (
|
||||
<>
|
||||
<div className="flex text-xs font-semibold">
|
||||
<div className="flex-1">Asset</div>
|
||||
<div className="flex-1">Value</div>
|
||||
<div className="flex-1">Size</div>
|
||||
<div className="flex-1">APY</div>
|
||||
</div>
|
||||
{positionsData?.coins.map((coin) => (
|
||||
<div key={coin.denom} className="flex text-xs text-black/40">
|
||||
<div className="flex-1">{getTokenSymbol(coin.denom)}</div>
|
||||
<div className="flex-1">
|
||||
{formatCurrency(getTokenTotalUSDValue(coin.amount, coin.denom))}
|
||||
</div>
|
||||
<div className="flex-1">
|
||||
{BigNumber(coin.amount)
|
||||
.div(10 ** getTokenDecimals(coin.denom))
|
||||
.toNumber()
|
||||
.toLocaleString(undefined, {
|
||||
maximumFractionDigits: getTokenDecimals(coin.denom),
|
||||
})}
|
||||
</div>
|
||||
<div className="flex-1">-</div>
|
||||
</div>
|
||||
))}
|
||||
{positionsData?.debts.map((coin) => (
|
||||
<div key={coin.denom} className="flex text-xs text-red-500">
|
||||
<div className="flex-1 text-black/40">{getTokenSymbol(coin.denom)}</div>
|
||||
<div className="flex-1">
|
||||
-{formatCurrency(getTokenTotalUSDValue(coin.amount, coin.denom))}
|
||||
</div>
|
||||
<div className="flex-1">
|
||||
-
|
||||
{BigNumber(coin.amount)
|
||||
.div(10 ** getTokenDecimals(coin.denom))
|
||||
.toNumber()
|
||||
.toLocaleString(undefined, {
|
||||
maximumFractionDigits: 6,
|
||||
})}
|
||||
</div>
|
||||
<div className="flex-1">
|
||||
-{(Number(marketsData?.[coin.denom].borrow_rate) * 100).toFixed(1)}%
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</>
|
||||
)}
|
||||
</ContainerSecondary>
|
||||
</>
|
||||
)}
|
||||
<FundAccountModal
|
||||
key={`fundModal_${selectedAccount}`}
|
||||
show={showFundWalletModal}
|
||||
onClose={() => setShowFundWalletModal(false)}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
220
components/FundAccountModal.tsx
Normal file
220
components/FundAccountModal.tsx
Normal file
@ -0,0 +1,220 @@
|
||||
import React, { useEffect, useMemo, useState } from 'react'
|
||||
import Image from 'next/image'
|
||||
import { Transition, Dialog, Switch } from '@headlessui/react'
|
||||
import BigNumber from 'bignumber.js'
|
||||
import { toast } from 'react-toastify'
|
||||
import useLocalStorageState from 'use-local-storage-state'
|
||||
|
||||
import { getTokenDecimals, getTokenSymbol } from 'utils/tokens'
|
||||
import ContainerSecondary from './ContainerSecondary'
|
||||
import useCreditManagerStore from 'stores/useCreditManagerStore'
|
||||
import Button from './Button'
|
||||
import Spinner from './Spinner'
|
||||
import useAllBalances from 'hooks/useAllBalances'
|
||||
import useAllowedCoins from 'hooks/useAllowedCoins'
|
||||
import useDepositCreditAccount from 'hooks/useDepositCreditAccount'
|
||||
import Slider from 'components/Slider'
|
||||
|
||||
const FundAccountModal = ({ show, onClose }: any) => {
|
||||
const [amount, setAmount] = useState(0)
|
||||
const [selectedToken, setSelectedToken] = useState('')
|
||||
|
||||
const selectedAccount = useCreditManagerStore((s) => s.selectedAccount)
|
||||
|
||||
const [lendAssets, setLendAssets] = useLocalStorageState(`lendAssets_${selectedAccount}`, {
|
||||
defaultValue: false,
|
||||
})
|
||||
|
||||
const { data: balancesData } = useAllBalances()
|
||||
const { data: allowedCoinsData, isLoading: isLoadingAllowedCoins } = useAllowedCoins()
|
||||
const { mutate, isLoading } = useDepositCreditAccount(
|
||||
selectedAccount || '',
|
||||
selectedToken,
|
||||
BigNumber(amount)
|
||||
.times(10 ** getTokenDecimals(selectedToken))
|
||||
.toNumber(),
|
||||
{
|
||||
onSuccess: () => {
|
||||
setAmount(0)
|
||||
toast.success(`${amount} ${getTokenSymbol(selectedToken)} successfully Deposited`)
|
||||
onClose()
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
if (allowedCoinsData && allowedCoinsData.length > 0) {
|
||||
// initialize selected token when allowedCoins fetch data is available
|
||||
setSelectedToken(allowedCoinsData[0])
|
||||
}
|
||||
}, [allowedCoinsData])
|
||||
|
||||
const walletAmount = useMemo(() => {
|
||||
if (!selectedToken) return 0
|
||||
|
||||
return BigNumber(balancesData?.find((balance) => balance.denom === selectedToken)?.amount ?? 0)
|
||||
.div(10 ** getTokenDecimals(selectedToken))
|
||||
.toNumber()
|
||||
}, [balancesData, selectedToken])
|
||||
|
||||
const handleValueChange = (value: number) => {
|
||||
if (value > walletAmount) {
|
||||
setAmount(walletAmount)
|
||||
return
|
||||
}
|
||||
|
||||
setAmount(value)
|
||||
}
|
||||
|
||||
const maxValue = walletAmount
|
||||
const percentageValue = isNaN(amount) ? 0 : (amount * 100) / maxValue
|
||||
|
||||
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 min-h-[520px] 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">
|
||||
<Spinner />
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="flex flex-1 flex-col items-start justify-between bg-[#4A4C60] p-6">
|
||||
<div>
|
||||
<p className="text-bold mb-3 text-xs uppercase text-white/50">About</p>
|
||||
<h4 className="mb-4 text-xl leading-8">
|
||||
Bringing the next generation of video creation to the Metaverse.
|
||||
<br />
|
||||
Powered by deep-learning.
|
||||
</h4>
|
||||
</div>
|
||||
<Image src="/logo.svg" alt="mars" width={150} height={50} />
|
||||
</div>
|
||||
|
||||
<div className="flex flex-1 flex-col p-4">
|
||||
<Dialog.Title as="h3" className="mb-4 text-center font-medium">
|
||||
Fund Account {selectedAccount}
|
||||
</Dialog.Title>
|
||||
<ContainerSecondary className="mb-2 p-3">
|
||||
<p className="mb-6 text-sm text-[#585A74]/50">
|
||||
Transfer assets from your injective wallet to your Mars credit account. If you
|
||||
don’t have any assets in your injective wallet use the injective bridge to
|
||||
transfer funds to your injective wallet.
|
||||
</p>
|
||||
{isLoadingAllowedCoins ? (
|
||||
<p>Loading...</p>
|
||||
) : (
|
||||
<>
|
||||
<div className="mb-2 rounded-md border border-[#585A74] text-sm">
|
||||
<div className="mb-1 flex justify-between border-b border-[#585A74] p-2">
|
||||
<div className="font-bold">Asset:</div>
|
||||
<select
|
||||
className="bg-transparent outline-0"
|
||||
onChange={(e) => {
|
||||
setSelectedToken(e.target.value)
|
||||
|
||||
if (e.target.value !== selectedToken) setAmount(0)
|
||||
}}
|
||||
value={selectedToken}
|
||||
>
|
||||
{allowedCoinsData?.map((entry) => (
|
||||
<option key={entry} value={entry}>
|
||||
{getTokenSymbol(entry)}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
<div className="flex justify-between p-2">
|
||||
<div className="font-bold">Amount:</div>
|
||||
<input
|
||||
type="number"
|
||||
className="bg-transparent text-right outline-0"
|
||||
value={amount}
|
||||
min="0"
|
||||
onChange={(e) => handleValueChange(e.target.valueAsNumber)}
|
||||
onBlur={(e) => {
|
||||
if (e.target.value === '') setAmount(0)
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<p className="mb-2 text-sm">In wallet: {walletAmount.toLocaleString()}</p>
|
||||
<Slider
|
||||
className="mb-6"
|
||||
value={percentageValue}
|
||||
onChange={(value) => {
|
||||
const decimal = value[0] / 100
|
||||
const tokenDecimals = getTokenDecimals(selectedToken)
|
||||
// limit decimal precision based on token contract decimals
|
||||
const newAmount = Number((decimal * maxValue).toFixed(tokenDecimals))
|
||||
|
||||
setAmount(newAmount)
|
||||
}}
|
||||
onMaxClick={() => setAmount(maxValue)}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</ContainerSecondary>
|
||||
<ContainerSecondary className="mb-2 flex items-center justify-between">
|
||||
<div>
|
||||
<h3 className="font-bold">Lending Assets</h3>
|
||||
<div className="text-sm text-[#585A74]/50">
|
||||
Lend assets from account to earn yield.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Switch
|
||||
checked={lendAssets}
|
||||
onChange={setLendAssets}
|
||||
className={`${
|
||||
lendAssets ? 'bg-blue-600' : 'bg-gray-400'
|
||||
} relative inline-flex h-6 w-11 items-center rounded-full`}
|
||||
>
|
||||
<span
|
||||
className={`${
|
||||
lendAssets ? 'translate-x-6' : 'translate-x-1'
|
||||
} inline-block h-4 w-4 transform rounded-full bg-white transition`}
|
||||
/>
|
||||
</Switch>
|
||||
</ContainerSecondary>
|
||||
<Button
|
||||
className="mt-auto w-full"
|
||||
onClick={() => mutate()}
|
||||
disabled={amount === 0 || !amount}
|
||||
>
|
||||
Fund Account
|
||||
</Button>
|
||||
</div>
|
||||
</Dialog.Panel>
|
||||
</Transition.Child>
|
||||
</div>
|
||||
</div>
|
||||
</Dialog>
|
||||
</Transition>
|
||||
)
|
||||
}
|
||||
|
||||
export default FundAccountModal
|
39
components/Slider.tsx
Normal file
39
components/Slider.tsx
Normal file
@ -0,0 +1,39 @@
|
||||
import React from 'react'
|
||||
import * as RadixSlider from '@radix-ui/react-slider'
|
||||
|
||||
type Props = {
|
||||
className?: string
|
||||
value: number
|
||||
onChange: (value: number[]) => void
|
||||
onMaxClick: () => void
|
||||
}
|
||||
|
||||
const Slider = ({ className, value, onChange, onMaxClick }: Props) => {
|
||||
return (
|
||||
<div className={`relative flex flex-1 items-center ${className || ''}`}>
|
||||
<RadixSlider.Root
|
||||
className="relative flex h-[20px] w-full cursor-pointer touch-none select-none items-center"
|
||||
value={[value]}
|
||||
min={0}
|
||||
max={100}
|
||||
step={1}
|
||||
onValueChange={(value) => onChange(value)}
|
||||
>
|
||||
<RadixSlider.Track className="relative h-[6px] grow rounded-full bg-gray-400">
|
||||
<RadixSlider.Range className="absolute h-[100%] rounded-full bg-blue-600" />
|
||||
</RadixSlider.Track>
|
||||
<RadixSlider.Thumb className="flex h-[20px] w-[20px] items-center justify-center rounded-full bg-white !outline-none">
|
||||
<div className="relative top-5 text-xs">{value.toFixed(0)}%</div>
|
||||
</RadixSlider.Thumb>
|
||||
</RadixSlider.Root>
|
||||
<button
|
||||
className="ml-4 rounded-md bg-blue-600 py-1 px-2 text-xs font-semibold text-white"
|
||||
onClick={onMaxClick}
|
||||
>
|
||||
MAX
|
||||
</button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Slider
|
@ -18,7 +18,7 @@ const WalletPopover = ({ children }: { children: React.ReactNode }) => {
|
||||
|
||||
return (
|
||||
<Popover className="relative">
|
||||
<Popover.Button as={Button} className="w-[200px]">
|
||||
<Popover.Button as={Button} className="w-[200px] !rounded-3xl !bg-green-500">
|
||||
{children}
|
||||
</Popover.Button>
|
||||
|
||||
@ -79,7 +79,10 @@ const Wallet = () => {
|
||||
{address ? (
|
||||
<WalletPopover>{formatWalletAddress(address)}</WalletPopover>
|
||||
) : (
|
||||
<Button className="w-[200px]" onClick={() => setShowConnectModal(true)}>
|
||||
<Button
|
||||
className="w-[200px] !rounded-3xl !bg-green-500"
|
||||
onClick={() => setShowConnectModal(true)}
|
||||
>
|
||||
Connect Wallet
|
||||
</Button>
|
||||
)}
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { SigningCosmWasmClient } from '@cosmjs/cosmwasm-stargate'
|
||||
import { useMutation, useQueryClient } from '@tanstack/react-query'
|
||||
import { useMutation, UseMutationOptions, useQueryClient } from '@tanstack/react-query'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { toast } from 'react-toastify'
|
||||
|
||||
@ -9,7 +9,14 @@ import { contractAddresses } from 'config/contracts'
|
||||
import { hardcodedFee } from 'utils/contants'
|
||||
import { queryKeys } from 'types/query-keys-factory'
|
||||
|
||||
const useDepositCreditAccount = (accountId: string, denom: string, amount: number) => {
|
||||
const useDepositCreditAccount = (
|
||||
accountId: string,
|
||||
denom: string,
|
||||
amount: number,
|
||||
options?: {
|
||||
onSuccess?: () => void
|
||||
}
|
||||
) => {
|
||||
const [signingClient, setSigningClient] = useState<SigningCosmWasmClient>()
|
||||
const address = useWalletStore((s) => s.address)
|
||||
|
||||
@ -62,7 +69,7 @@ const useDepositCreditAccount = (accountId: string, denom: string, amount: numbe
|
||||
queryClient.invalidateQueries(queryKeys.tokenBalance(address, denom))
|
||||
queryClient.invalidateQueries(queryKeys.creditAccountsPositions(accountId))
|
||||
|
||||
toast.success('Deposited Successfully')
|
||||
options?.onSuccess && options.onSuccess()
|
||||
},
|
||||
}
|
||||
)
|
||||
|
@ -18,6 +18,7 @@
|
||||
"@radix-ui/react-slider": "^1.0.0",
|
||||
"@sentry/nextjs": "^7.12.1",
|
||||
"@tanstack/react-query": "^4.3.4",
|
||||
"@tanstack/react-query-devtools": "^4.12.0",
|
||||
"@tanstack/react-table": "^8.5.15",
|
||||
"@tippyjs/react": "^4.2.6",
|
||||
"bech32": "^2.0.0",
|
||||
|
@ -5,6 +5,7 @@ import { ToastContainer, Zoom } from 'react-toastify'
|
||||
import 'react-toastify/dist/ReactToastify.min.css'
|
||||
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
|
||||
import detectEthereumProvider from '@metamask/detect-provider'
|
||||
import { ReactQueryDevtools } from '@tanstack/react-query-devtools'
|
||||
|
||||
import '../styles/globals.css'
|
||||
import Layout from 'components/Layout'
|
||||
@ -41,7 +42,10 @@ function MyApp({ Component, pageProps }: AppProps) {
|
||||
<link rel="icon" href="/favicon.svg" />
|
||||
</Head>
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<Layout>{address ? <Component {...pageProps} /> : <div>No wallet connected</div>}</Layout>
|
||||
<ReactQueryDevtools initialIsOpen={false} />
|
||||
<Layout>
|
||||
<Component {...pageProps} />
|
||||
</Layout>
|
||||
<ToastContainer
|
||||
autoClose={1500}
|
||||
closeButton={false}
|
||||
|
40
yarn.lock
40
yarn.lock
@ -2140,11 +2140,27 @@
|
||||
dependencies:
|
||||
tslib "^2.4.0"
|
||||
|
||||
"@tanstack/match-sorter-utils@^8.1.1":
|
||||
version "8.5.14"
|
||||
resolved "https://registry.yarnpkg.com/@tanstack/match-sorter-utils/-/match-sorter-utils-8.5.14.tgz#12efcd536abe491d09521e0242bc4d51442f8a8a"
|
||||
integrity sha512-lVNhzTcOJ2bZ4IU+PeCPQ36vowBHvviJb2ZfdRFX5uhy7G0jM8N34zAMbmS5ZmVH8D2B7oU82OWo0e/5ZFzQrw==
|
||||
dependencies:
|
||||
remove-accents "0.4.2"
|
||||
|
||||
"@tanstack/query-core@4.3.4":
|
||||
version "4.3.4"
|
||||
resolved "https://registry.yarnpkg.com/@tanstack/query-core/-/query-core-4.3.4.tgz#b93da17232fda91ec08ef14d2ca1d8326fc599f2"
|
||||
integrity sha512-NLAe3j5Vk1yYEtoPP5fPGPjRzkZPx67KUM3f14L3InziJZJ0wVecCh7uKfgYkbRKJSeq6PlbND7iuCGdTplN6Q==
|
||||
|
||||
"@tanstack/react-query-devtools@^4.12.0":
|
||||
version "4.12.0"
|
||||
resolved "https://registry.yarnpkg.com/@tanstack/react-query-devtools/-/react-query-devtools-4.12.0.tgz#18f341185f0a5580fc8b2daf8197389108be7131"
|
||||
integrity sha512-gUV+aKpwgP7Cfp2+vwgu6krXCytncGWjTqFcnzC1l2INOf8dRi8/GEupYF7npxD9ky72FTuxc5+TI/lC8eB0Eg==
|
||||
dependencies:
|
||||
"@tanstack/match-sorter-utils" "^8.1.1"
|
||||
superjson "^1.10.0"
|
||||
use-sync-external-store "^1.2.0"
|
||||
|
||||
"@tanstack/react-query@^4.3.4":
|
||||
version "4.3.4"
|
||||
resolved "https://registry.yarnpkg.com/@tanstack/react-query/-/react-query-4.3.4.tgz#91a4232a967de04a0662d63d53b18ecc3e11c7e2"
|
||||
@ -3006,6 +3022,13 @@ cookie@^0.4.1:
|
||||
resolved "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz"
|
||||
integrity sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==
|
||||
|
||||
copy-anything@^3.0.2:
|
||||
version "3.0.2"
|
||||
resolved "https://registry.yarnpkg.com/copy-anything/-/copy-anything-3.0.2.tgz#7189171ff5e1893b2287e8bf574b8cd448ed50b1"
|
||||
integrity sha512-CzATjGXzUQ0EvuvgOCI6A4BGOo2bcVx8B+eC2nF862iv9fopnPQwlrbACakNCHRIJbCSBj+J/9JeDf60k64MkA==
|
||||
dependencies:
|
||||
is-what "^4.1.6"
|
||||
|
||||
copy-descriptor@^0.1.0:
|
||||
version "0.1.1"
|
||||
resolved "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz"
|
||||
@ -4375,6 +4398,11 @@ is-weakref@^1.0.2:
|
||||
dependencies:
|
||||
call-bind "^1.0.2"
|
||||
|
||||
is-what@^4.1.6:
|
||||
version "4.1.7"
|
||||
resolved "https://registry.yarnpkg.com/is-what/-/is-what-4.1.7.tgz#c41dc1d2d2d6a9285c624c2505f61849c8b1f9cc"
|
||||
integrity sha512-DBVOQNiPKnGMxRMLIYSwERAS5MVY1B7xYiGnpgctsOFvVDz9f9PFXXxMcTOHuoqYp4NK9qFYQaIC1NRRxLMpBQ==
|
||||
|
||||
is-windows@^1.0.2:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz"
|
||||
@ -5514,6 +5542,11 @@ regjsparser@^0.9.1:
|
||||
dependencies:
|
||||
jsesc "~0.5.0"
|
||||
|
||||
remove-accents@0.4.2:
|
||||
version "0.4.2"
|
||||
resolved "https://registry.yarnpkg.com/remove-accents/-/remove-accents-0.4.2.tgz#0a43d3aaae1e80db919e07ae254b285d9e1c7bb5"
|
||||
integrity sha512-7pXIJqJOq5tFgG1A2Zxti3Ht8jJF337m4sowbuHsW30ZnkQFnDzy9qBNhgzX8ZLW4+UBcXiiR7SwR6pokHsxiA==
|
||||
|
||||
repeat-element@^1.1.2:
|
||||
version "1.1.4"
|
||||
resolved "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.4.tgz"
|
||||
@ -5949,6 +5982,13 @@ sucrase@^3.20.0:
|
||||
pirates "^4.0.1"
|
||||
ts-interface-checker "^0.1.9"
|
||||
|
||||
superjson@^1.10.0:
|
||||
version "1.11.0"
|
||||
resolved "https://registry.yarnpkg.com/superjson/-/superjson-1.11.0.tgz#f6e2ae0d8fbac61c3fca09ab6739ac9678414d1b"
|
||||
integrity sha512-6PfAg1FKhqkwWvPb2uXhH4MkMttdc17eJ91+Aoz4s1XUEDZFmLfFx/xVA3wgkPxAGy5dpozgGdK6V/n20Wj9yg==
|
||||
dependencies:
|
||||
copy-anything "^3.0.2"
|
||||
|
||||
supports-color@^5.3.0:
|
||||
version "5.5.0"
|
||||
resolved "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz"
|
||||
|
Loading…
Reference in New Issue
Block a user