Mp 1671 header (#59)

* MP-1674: replaced the logo and added dekstop only nav

* MP-1677: borrowCapacity implemented into the SubAccount Nav

* MP-1677: adjusted the SubAccount navigation

* M1677: fixed the button and SearchInput component

* MP-1674: fixed the NavLink component

* MP-1674: fixed the SubAccount navigation

* tidy: cleaning up the trading view

* MP-1674: added withdraw and funding functions

* MP-1674: worked on the AccountStats

* MP-1671: modal work

* MP-1647: improvised CreditAccount expander

* tidy: fixed the page structure

* MP-1758: finished the SearchInput layout

* MP-1759: updated the semicircle graphs

* MP-1759: SemiCircle to Gauge

* fix: implemented animated numbers

* tidy: refactor

* MP-1759: added Tooltip to the Gauge

* fix: replace animate={true} with animate

* fix: fixed the Gauge timing

* fix: updated the BorrowCapacity styles

* fix: renamed SubAccount to Account

* fix: Text should not be a button, Button should be

* tidy: format

* fix: Text no clicky

* fix: replaced all the Text appearances with onClick
This commit is contained in:
Linkie Link 2022-12-06 10:20:22 +01:00 committed by GitHub
parent 8aaeb36efa
commit 2f7b266e6b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
49 changed files with 1723 additions and 1782 deletions

View File

@ -0,0 +1,80 @@
import BigNumber from 'bignumber.js'
import { BorrowCapacity } from 'components/BorrowCapacity'
import Button from 'components/Button'
import FormattedNumber from 'components/FormattedNumber'
import Gauge from 'components/Gauge'
import Text from 'components/Text'
import useAccountStats from 'hooks/useAccountStats'
import useCreditAccounts from 'hooks/useCreditAccounts'
import { chain } from 'utils/chains'
import { formatValue } from 'utils/formatters'
interface Props {
createCreditAccount: () => void
}
const AccountStatus = ({ createCreditAccount }: Props) => {
const accountStats = useAccountStats()
const { data: creditAccountsList, isLoading: isLoadingCreditAccounts } = useCreditAccounts()
const hasCreditAccounts = creditAccountsList && creditAccountsList.length > 0
if (!hasCreditAccounts) {
return (
<Button className='my-3 mr-6' onClick={() => createCreditAccount()}>
Create Credit Account
</Button>
)
}
return (
<div className='flex w-[400px] items-center justify-between gap-3 border-l border-l-white/20 px-3 py-3'>
{accountStats && (
<>
<Text size='sm' className='flex flex-grow text-white'>
<FormattedNumber
amount={BigNumber(accountStats.netWorth)
.dividedBy(10 ** chain.stakeCurrency.coinDecimals)
.toNumber()}
animate
prefix='$: '
/>
</Text>
<Gauge
value={accountStats.currentLeverage / accountStats.maxLeverage}
label='Lvg'
tooltip={
<Text size='sm'>
Current Leverage:{' '}
{formatValue(accountStats.currentLeverage, 0, 2, true, false, 'x')}
<br />
Max Leverage: {formatValue(accountStats.maxLeverage, 0, 2, true, false, 'x')}
</Text>
}
/>
<Gauge
value={accountStats.risk}
label='Risk'
tooltip={
<Text size='sm'>
Current Risk: {formatValue(accountStats.risk * 100, 0, 2, true, false, '%')}
</Text>
}
/>
<BorrowCapacity
limit={80}
max={100}
balance={100 - accountStats.health * 100}
barHeight='16px'
hideValues={true}
showTitle={false}
className='w-[140px]'
/>
</>
)}
</div>
)
}
export default AccountStatus

View File

@ -0,0 +1,213 @@
import { Switch } from '@headlessui/react'
import BigNumber from 'bignumber.js'
import { useEffect, useMemo, useState } from 'react'
import { toast } from 'react-toastify'
import useLocalStorageState from 'use-local-storage-state'
import Button from 'components/Button'
import CircularProgress from 'components/CircularProgress'
import MarsProtocolLogo from 'components/Icons/mars-protocol.svg'
import Modal from 'components/Modal'
import Slider from 'components/Slider'
import Text from 'components/Text'
import useDepositCreditAccount from 'hooks/mutations/useDepositCreditAccount'
import useAllBalances from 'hooks/useAllBalances'
import useAllowedCoins from 'hooks/useAllowedCoins'
import useCreditManagerStore from 'stores/useCreditManagerStore'
import { getTokenDecimals, getTokenSymbol } from 'utils/tokens'
interface Props {
open: boolean
setOpen: (open: boolean) => void
}
const FundAccountModal = ({ open, setOpen }: Props) => {
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`)
setOpen(false)
},
},
)
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 (
<Modal open={open} setOpen={setOpen}>
<div className='flex min-h-[520px] w-full'>
{isLoading && (
<div className='absolute inset-0 z-40 grid place-items-center bg-black/50'>
<CircularProgress />
</div>
)}
<div className='flex flex-1 flex-col items-start justify-between bg-fund-modal bg-cover p-6'>
<div>
<Text size='2xs' uppercase={true} className='mb-3 text-white'>
About
</Text>
<Text size='xl' className='mb-4 text-white'>
Bringing the next generation of video creation to the Metaverse.
<br />
Powered by deep-learning.
</Text>
</div>
<div className='w-[153px] text-white'>
<MarsProtocolLogo />
</div>
</div>
<div className='flex flex-1 flex-col p-6'>
<Text size='xl' uppercase={true} className='mb-4 text-white'>
Account {selectedAccount}
</Text>
<div className='p-3" mb-2'>
<Text size='sm' uppercase={true} className='mb-1 text-white'>
Fund Account
</Text>
<Text size='sm' className='mb-6 text-white/60'>
Transfer assets from your injective wallet to your Mars credit account. If you dont
have any assets in your injective wallet use the injective bridge to transfer funds to
your injective wallet.
</Text>
{isLoadingAllowedCoins ? (
<p>Loading...</p>
) : (
<>
<div className='mb-4 rounded-md border border-white/20'>
<div className='mb-1 flex justify-between border-b border-white/20 p-2'>
<Text size='sm' className='text-white'>
Asset:
</Text>
<select
className='bg-transparent text-white 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'>
<Text size='sm' className='text-white'>
Amount:
</Text>
<input
type='number'
className='appearance-none bg-transparent text-right text-white'
value={amount}
min='0'
onChange={(e) => handleValueChange(e.target.valueAsNumber)}
onBlur={(e) => {
if (e.target.value === '') setAmount(0)
}}
/>
</div>
</div>
<Text size='xs' uppercase={true} className='mb-2 text-white/60'>
{`In wallet: ${walletAmount.toLocaleString()} ${getTokenSymbol(selectedToken)}`}
</Text>
<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)}
/>
</>
)}
</div>
<div className='mb-2 flex items-center justify-between'>
<div>
<Text size='sm' uppercase={true} className='mb-1 text-white'>
Lending Assets
</Text>
<Text size='sm' className='text-white/60'>
Lend assets from account to earn yield.
</Text>
</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>
</div>
<Button
className='mt-auto w-full'
onClick={() => mutate()}
disabled={amount === 0 || !amount}
>
Fund Account
</Button>
</div>
</div>
</Modal>
)
}
export default FundAccountModal

View File

@ -0,0 +1,191 @@
import { ExecuteResult } from '@cosmjs/cosmwasm-stargate'
import { UseMutateFunction } from '@tanstack/react-query'
import classNames from 'classnames'
import { useMemo, useState } from 'react'
import { FundAccountModal, WithdrawModal } from 'components/Account'
import Button from 'components/Button'
import ArrowDown from 'components/Icons/arrow-down.svg'
import ArrowUp from 'components/Icons/arrow-up.svg'
import ChevronDownIcon from 'components/Icons/expand.svg'
import Overlay from 'components/Overlay'
import Text from 'components/Text'
interface Props {
creditAccountsList: string[]
selectedAccount: string | null
deleteCreditAccount: UseMutateFunction<ExecuteResult | undefined, Error, void, unknown>
setSelectedAccount: (id: string) => void
createCreditAccount: () => void
}
const MAX_VISIBLE_CREDIT_ACCOUNTS = 5
const SubAccountNavigation = ({
creditAccountsList,
createCreditAccount,
deleteCreditAccount,
selectedAccount,
setSelectedAccount,
}: Props) => {
const { firstCreditAccounts, restCreditAccounts } = useMemo(() => {
return {
firstCreditAccounts: creditAccountsList?.slice(0, MAX_VISIBLE_CREDIT_ACCOUNTS) ?? [],
restCreditAccounts: creditAccountsList?.slice(MAX_VISIBLE_CREDIT_ACCOUNTS) ?? [],
}
}, [creditAccountsList])
const [showManageMenu, setShowManageMenu] = useState(false)
const [showMoreMenu, setShowMoreMenu] = useState(false)
const [showFundWalletModal, setShowFundWalletModal] = useState(false)
const [showWithdrawModal, setShowWithdrawModal] = useState(false)
return (
<>
{firstCreditAccounts.map((account) => (
<Button
key={account}
className={classNames(
'cursor-pointer whitespace-nowrap px-4 text-base hover:text-white',
selectedAccount === account ? 'text-white' : ' text-white/40',
)}
variant='text'
onClick={() => setSelectedAccount(account)}
>
Account {account}
</Button>
))}
<div className='relative'>
{restCreditAccounts.length > 0 && (
<>
<Button
className='flex items-center px-3 py-3 text-base hover:text-white'
variant='text'
onClick={() => setShowMoreMenu(!showMoreMenu)}
>
More
<span className='ml-1 inline-block w-3'>
<ChevronDownIcon />
</span>
</Button>
<Overlay show={showMoreMenu} setShow={setShowMoreMenu}>
<div className='flex w-[120px] flex-wrap p-4'>
{restCreditAccounts.map((account) => (
<Button
key={account}
variant='text'
className={classNames(
'w-full whitespace-nowrap py-2 text-sm',
selectedAccount === account
? 'text-secondary'
: 'cursor-pointer text-accent-dark hover:text-secondary',
)}
onClick={() => {
setShowMoreMenu(!showMoreMenu)
setSelectedAccount(account)
}}
>
Account {account}
</Button>
))}
</div>
</Overlay>
</>
)}
</div>
<div className='relative'>
<Button
className={classNames(
'flex items-center px-3 py-3 text-base hover:text-white',
showManageMenu ? 'text-white' : 'text-white/40',
)}
onClick={() => setShowManageMenu(!showManageMenu)}
variant='text'
>
Manage
<span className='ml-1 inline-block w-3'>
<ChevronDownIcon />
</span>
</Button>
<Overlay className='-left-[86px]' show={showManageMenu} setShow={setShowManageMenu}>
<div className='flex w-[274px] flex-wrap'>
<Text
size='sm'
uppercase={true}
className='w-full px-4 pt-4 text-center text-accent-dark'
>
Manage
</Text>
<div className='flex w-full justify-between border-b border-b-black/10 p-4'>
<Button
className='flex w-[115px] items-center justify-center pl-0 pr-2'
onClick={() => {
setShowFundWalletModal(true)
setShowManageMenu(!showManageMenu)
}}
>
<span className='mr-1 w-3'>
<ArrowUp />
</span>
Fund
</Button>
<Button
className='flex w-[115px] items-center justify-center pl-0 pr-2'
color='secondary'
onClick={() => {
setShowWithdrawModal(true)
setShowManageMenu(!showManageMenu)
}}
>
<span className='mr-1 w-3'>
<ArrowDown />
</span>
Withdraw
</Button>
</div>
<div className='flex w-full flex-wrap p-4'>
<Button
className='w-full cursor-pointer whitespace-nowrap py-2 text-left text-sm text-accent-dark hover:text-secondary'
variant='text'
onClick={() => {
setShowManageMenu(!showManageMenu)
createCreditAccount()
}}
>
Create Account
</Button>
<Button
className='w-full cursor-pointer whitespace-nowrap py-2 text-left text-sm text-accent-dark hover:text-secondary'
variant='text'
onClick={() => {
setShowManageMenu(!showManageMenu)
deleteCreditAccount()
}}
>
Close Account
</Button>
<Button
className='w-full cursor-pointer whitespace-nowrap py-2 text-left text-sm text-accent-dark hover:text-secondary'
variant='text'
onClick={() => alert('TODO')}
>
Transfer Balance
</Button>
<Button
className='w-full cursor-pointer whitespace-nowrap py-2 text-left text-sm text-accent-dark hover:text-secondary'
variant='text'
onClick={() => alert('TODO')}
>
Rearrange
</Button>
</div>
</div>
</Overlay>
</div>
<FundAccountModal open={showFundWalletModal} setOpen={setShowFundWalletModal} />
<WithdrawModal open={showWithdrawModal} setOpen={setShowWithdrawModal} />
</>
)
}
export default SubAccountNavigation

View File

@ -0,0 +1,428 @@
import { Switch } from '@headlessui/react'
import BigNumber from 'bignumber.js'
import classNames from 'classnames'
import React, { useEffect, useMemo, useState } from 'react'
import { toast } from 'react-toastify'
import { BorrowCapacity } from 'components/BorrowCapacity'
import Button from 'components/Button'
import CircularProgress from 'components/CircularProgress'
import FormattedNumber from 'components/FormattedNumber'
import Gauge from 'components/Gauge'
import Modal from 'components/Modal'
import Slider from 'components/Slider'
import Text from 'components/Text'
import useWithdrawFunds from 'hooks/mutations/useWithdrawFunds'
import useAccountStats, { AccountStatsAction } from 'hooks/useAccountStats'
import useAllBalances from 'hooks/useAllBalances'
import useCalculateMaxWithdrawAmount from 'hooks/useCalculateMaxWithdrawAmount'
import useCreditAccountPositions from 'hooks/useCreditAccountPositions'
import useMarkets from 'hooks/useMarkets'
import useTokenPrices from 'hooks/useTokenPrices'
import useCreditManagerStore from 'stores/useCreditManagerStore'
import { chain } from 'utils/chains'
import { formatValue } from 'utils/formatters'
import { getTokenDecimals, getTokenSymbol } from 'utils/tokens'
interface Props {
open: boolean
setOpen: (open: boolean) => void
}
const WithdrawModal = ({ open, setOpen }: Props) => {
const [amount, setAmount] = useState(0)
const [selectedToken, setSelectedToken] = useState('')
const [isBorrowEnabled, setIsBorrowEnabled] = useState(false)
const selectedAccount = useCreditManagerStore((s) => s.selectedAccount)
const { data: positionsData, isLoading: isLoadingPositions } = useCreditAccountPositions(
selectedAccount ?? '',
)
const { data: balancesData } = useAllBalances()
const { data: tokenPrices } = useTokenPrices()
const { data: marketsData } = useMarkets()
const selectedTokenSymbol = getTokenSymbol(selectedToken)
const selectedTokenDecimals = getTokenDecimals(selectedToken)
const tokenAmountInCreditAccount = useMemo(() => {
return BigNumber(positionsData?.coins.find((coin) => coin.denom === selectedToken)?.amount ?? 0)
.div(10 ** selectedTokenDecimals)
.toNumber()
}, [positionsData, selectedTokenDecimals, selectedToken])
const { actions, borrowAmount, withdrawAmount } = useMemo(() => {
const borrowAmount =
amount > tokenAmountInCreditAccount
? BigNumber(amount)
.minus(tokenAmountInCreditAccount)
.times(10 ** selectedTokenDecimals)
.toNumber()
: 0
const withdrawAmount = BigNumber(amount)
.times(10 ** selectedTokenDecimals)
.toNumber()
return {
borrowAmount,
withdrawAmount,
actions: [
{
type: 'borrow',
amount: borrowAmount,
denom: selectedToken,
},
{
type: 'withdraw',
amount: withdrawAmount,
denom: selectedToken,
},
] as AccountStatsAction[],
}
}, [amount, selectedToken, selectedTokenDecimals, tokenAmountInCreditAccount])
const accountStats = useAccountStats(actions)
const { mutate, isLoading } = useWithdrawFunds(withdrawAmount, borrowAmount, selectedToken, {
onSuccess: () => {
setOpen(false)
toast.success(`${amount} ${selectedTokenSymbol} successfully withdrawn`)
},
})
const maxWithdrawAmount = useCalculateMaxWithdrawAmount(selectedToken, isBorrowEnabled)
const walletAmount = useMemo(() => {
if (!selectedToken) return 0
return BigNumber(balancesData?.find((balance) => balance.denom === selectedToken)?.amount ?? 0)
.div(10 ** selectedTokenDecimals)
.toNumber()
}, [balancesData, selectedToken, selectedTokenDecimals])
useEffect(() => {
if (positionsData && positionsData.coins.length > 0) {
// initialize selected token when allowedCoins fetch data is available
setSelectedToken(positionsData.coins[0].denom)
}
}, [positionsData])
const handleTokenChange = (e: React.ChangeEvent<HTMLSelectElement>) => {
setSelectedToken(e.target.value)
if (e.target.value !== selectedToken) setAmount(0)
}
const handleValueChange = (value: number) => {
if (value > maxWithdrawAmount) {
setAmount(maxWithdrawAmount)
return
}
setAmount(value)
}
const handleBorrowChange = () => {
setIsBorrowEnabled((c) => !c)
// reset amount due to max value calculations changing depending on wheter the user is borrowing or not
setAmount(0)
}
const getTokenTotalUSDValue = (amount: string, denom: string) => {
// early return if prices are not fetched yet
if (!tokenPrices) return 0
return (
BigNumber(amount)
.div(10 ** getTokenDecimals(denom))
.toNumber() * tokenPrices[denom]
)
}
const percentageValue = useMemo(() => {
if (isNaN(amount) || maxWithdrawAmount === 0) return 0
return (amount * 100) / maxWithdrawAmount
}, [amount, maxWithdrawAmount])
return (
<Modal open={open} setOpen={setOpen}>
<div className='flex min-h-[470px] w-full flex-wrap'>
{isLoading && (
<div className='absolute inset-0 z-40 grid place-items-center bg-black/50'>
<CircularProgress />
</div>
)}
<Text
className='flex w-full border-b border-white/20 px-8 pt-4 pb-2 text-white'
size='2xl'
uppercase={true}
>
Withdraw from Account {selectedAccount}
</Text>
<div className='flex w-full'>
<div className='flex flex-1 flex-col border-r border-white/20'>
<div className='border-b border-white/20 p-6'>
<div className='mb-4 rounded-md border border-white/20'>
<div className='mb-1 flex justify-between border-b border-white/20 p-2'>
<Text size='sm' className='text-white'>
Asset:
</Text>
<select
className='bg-transparent text-white outline-0'
onChange={handleTokenChange}
value={selectedToken}
>
{positionsData?.coins?.map((coin) => (
<option key={coin.denom} value={coin.denom}>
{getTokenSymbol(coin.denom)}
</option>
))}
</select>
</div>
<div className='flex justify-between p-2'>
<Text size='sm' className='text-white'>
Amount:
</Text>
<input
type='number'
className='appearance-none bg-transparent text-right text-white'
value={amount}
min='0'
onChange={(e) => handleValueChange(e.target.valueAsNumber)}
onBlur={(e) => {
if (e.target.value === '') setAmount(0)
}}
/>
</div>
</div>
<Text size='xs' uppercase={true} className='mb-2 text-white/60'>
Available: {formatValue(maxWithdrawAmount, 0, 4, true, false, false, false, false)}
</Text>
<Slider
className='mb-6'
value={percentageValue}
onChange={(value) => {
const decimal = value[0] / 100
// limit decimal precision based on token contract decimals
const newAmount = Number(
(decimal * maxWithdrawAmount).toFixed(selectedTokenDecimals),
)
setAmount(newAmount)
}}
onMaxClick={() => setAmount(maxWithdrawAmount)}
/>
</div>
<div className='flex items-center justify-between p-6'>
<div className='flex flex-1 flex-wrap'>
<Text size='sm' className='text-white' uppercase={true}>
Withdraw with borrowing
</Text>
<Text size='sm' className='text-white/60'>
Borrow assets from account to withdraw to your wallet
</Text>
</div>
<Switch
checked={isBorrowEnabled}
onChange={handleBorrowChange}
className={classNames(
'relative inline-flex h-6 w-11 items-center rounded-full',
isBorrowEnabled ? 'bg-blue-600' : 'bg-gray-400',
)}
>
<span
className={`${
isBorrowEnabled ? 'translate-x-6' : 'translate-x-1'
} inline-block h-4 w-4 transform rounded-full bg-white transition`}
/>
</Switch>
</div>
<div className='flex p-6'>
<Button className='mt-auto w-full' onClick={() => mutate()}>
Withdraw
</Button>
</div>
</div>
<div className='flex w-[430px] flex-wrap content-start bg-black/60'>
<div className='flex w-full flex-wrap border-b border-white/20 p-6'>
<Text size='xl' className='mb-4 flex w-full text-white'>
Account {selectedAccount}
</Text>
{accountStats && (
<div className='flex w-full items-center gap-x-3'>
<Text size='sm' className='flex flex-grow text-white'>
<FormattedNumber
amount={BigNumber(accountStats.netWorth)
.dividedBy(10 ** chain.stakeCurrency.coinDecimals)
.toNumber()}
prefix='$: '
animate
/>
</Text>
<Gauge
value={accountStats.currentLeverage / accountStats.maxLeverage}
label='Lvg'
tooltip={
<Text size='sm'>
Current Leverage:{' '}
{formatValue(accountStats.currentLeverage, 0, 2, true, false, 'x')}
<br />
Max Leverage:{' '}
{formatValue(accountStats.maxLeverage, 0, 2, true, false, 'x')}
</Text>
}
/>
<Gauge
value={accountStats.risk}
label='Risk'
tooltip={
<Text size='sm'>
Current Risk: {formatValue(accountStats.risk * 100, 0, 2, true, false, '%')}
</Text>
}
/>
<BorrowCapacity
limit={80}
max={100}
balance={100 - accountStats.health * 100}
barHeight='16px'
hideValues={true}
showTitle={false}
className='w-[140px]'
/>
</div>
)}
</div>
<div className='flex w-full flex-wrap border-b border-white/20 p-6'>
<div className='mb-2 flex w-full'>
<Text size='xs' className='flex-grow text-white/60'>
Total Position:
</Text>
<Text size='xs' className='text-white/60'>
<FormattedNumber
amount={BigNumber(accountStats?.totalPosition ?? 0)
.dividedBy(10 ** chain.stakeCurrency.coinDecimals)
.toNumber()}
prefix='$'
animate
/>
</Text>
</div>
<div className='flex w-full justify-between'>
<Text size='xs' className='flex-grow text-white/60'>
Total Liabilities:
</Text>
<Text size='xs' className=' text-white/60'>
<FormattedNumber
amount={BigNumber(accountStats?.totalDebt ?? 0)
.dividedBy(10 ** chain.stakeCurrency.coinDecimals)
.toNumber()}
prefix='$'
animate
/>
</Text>
</div>
</div>
<div className='flex w-full flex-wrap'>
<Text uppercase={true} className='w-full bg-black/20 px-6 py-2 text-white/40'>
Balances
</Text>
{isLoadingPositions ? (
<div>Loading...</div>
) : (
<div className='flex w-full flex-wrap'>
<div className='mb-2 flex w-full border-b border-white/20 bg-black/20 px-6 py-2 '>
<Text size='xs' uppercase={true} className='flex-1 text-white'>
Asset
</Text>
<Text size='xs' uppercase={true} className='flex-1 text-white'>
Value
</Text>
<Text size='xs' uppercase={true} className='flex-1 text-white'>
Size
</Text>
<Text size='xs' uppercase={true} className='flex-1 text-white'>
APY
</Text>
</div>
{positionsData?.coins.map((coin) => (
<div key={coin.denom} className='flex w-full px-4 py-2'>
<Text
size='xs'
className='flex-1 border-l-4 border-profit pl-2 text-white/60'
>
{getTokenSymbol(coin.denom)}
</Text>
<Text size='xs' className='flex-1 text-white/60'>
<FormattedNumber
amount={getTokenTotalUSDValue(coin.amount, coin.denom)}
prefix='$'
animate
/>
</Text>
<Text size='xs' className='flex-1 text-white/60'>
<FormattedNumber
amount={BigNumber(coin.amount)
.div(10 ** getTokenDecimals(coin.denom))
.toNumber()}
minDecimals={0}
maxDecimals={4}
animate
/>
</Text>
<Text size='xs' className='flex-1 text-white/60'>
-
</Text>
</div>
))}
{positionsData?.debts.map((coin) => (
<div key={coin.denom} className='flex w-full px-4 py-2'>
<Text size='xs' className='flex-1 border-l-4 border-loss pl-2 text-white/60'>
{getTokenSymbol(coin.denom)}
</Text>
<Text size='xs' className='flex-1 text-white/60'>
<FormattedNumber
amount={getTokenTotalUSDValue(coin.amount, coin.denom)}
prefix='-$'
animate
/>
</Text>
<Text size='xs' className='flex-1 text-white/60'>
<FormattedNumber
amount={BigNumber(coin.amount)
.div(10 ** getTokenDecimals(coin.denom))
.toNumber()}
minDecimals={0}
maxDecimals={4}
animate
/>
</Text>
<Text size='xs' className='flex-1 text-white/60'>
<FormattedNumber
amount={Number(marketsData?.[coin.denom].borrow_rate) * 100}
minDecimals={0}
maxDecimals={2}
prefix='-'
suffix='%'
animate
/>
</Text>
</div>
))}
</div>
)}
</div>
</div>
</div>
</div>
</Modal>
)
}
export default WithdrawModal

View File

@ -0,0 +1,4 @@
export { default as AccountStatus } from './AccountStatus'
export { default as FundAccountModal } from './FundAccountModal'
export { default as SubAccountNavigation } from './SubAccountNavigation'
export { default as WithdrawModal } from './WithdrawModal'

View File

@ -1,7 +1,8 @@
import classNames from 'classnames'
import { useEffect, useState } from 'react'
import Number from 'components/Number'
import FormattedNumber from 'components/FormattedNumber'
import Text from 'components/Text'
import Tooltip from 'components/Tooltip'
interface Props {
@ -13,6 +14,7 @@ interface Props {
showPercentageText?: boolean
className?: string
hideValues?: boolean
decimals?: number
}
export const BorrowCapacity = ({
@ -24,42 +26,25 @@ export const BorrowCapacity = ({
showPercentageText = true,
className,
hideValues,
decimals = 2,
}: Props) => {
const [percentOfMaxRound, setPercentOfMaxRound] = useState(0)
const [percentOfMaxRange, setPercentOfMaxRange] = useState(0)
const [percentOfMaxMargin, setPercentOfMaxMargin] = useState('10px')
const [limitPercentOfMax, setLimitPercentOfMax] = useState(0)
const [timer, setTimer] = useState<ReturnType<typeof setTimeout>>()
useEffect(
() => {
clearTimeout(timer)
const percent = max === 0 ? 0 : (balance / max) * 100
const delta = percent - percentOfMaxRound
const startingPoint = percentOfMaxRound
if (max === 0) {
setPercentOfMaxMargin('10px')
setPercentOfMaxRound(0)
setPercentOfMaxRange(0)
setLimitPercentOfMax(0)
return
}
const pOfMax = +((balance / max) * 100).toFixed(2)
setPercentOfMaxRound(+(Math.round(pOfMax * 100) / 100).toFixed(1))
const pOfMax = +((balance / max) * 100)
setPercentOfMaxRound(+(Math.round(pOfMax * 100) / 100))
setPercentOfMaxRange(Math.min(Math.max(pOfMax, 0), 100))
setLimitPercentOfMax((limit / max) * 100)
for (let i = 0; i < 20; i++) {
setTimer(
setTimeout(() => {
const currentPercentOfMax = startingPoint + (delta / 20) * i
setPercentOfMaxMargin(currentPercentOfMax > 15 ? '-60px' : '10px')
}, 50 * (i + 1)),
)
}
return () => clearTimeout(timer)
}, // eslint-disable-next-line react-hooks/exhaustive-deps
[balance, max],
)
@ -82,46 +67,48 @@ export const BorrowCapacity = ({
limitPercentOfMax ? 'opacity-60' : 'opacity-0',
)}
>
<Number animate={true} amount={limit} />
<FormattedNumber animate amount={limit} />
</div>
)}
</div>
<Tooltip content='Tooltip'>
<div className='relative' style={{ height: barHeight }}>
<div className='absolute h-full w-full rounded-3xl shadow-inset gradient-hatched'>
<Tooltip content={<Text size='sm'>Borrow Capacity Tooltip</Text>}>
<div className='relative'>
<div
className='absolute bottom-0 h-[120%] w-[1px] bg-white transition-[left] duration-1000 ease-linear'
style={{ left: `${limitPercentOfMax || 0}%` }}
/>
className='overflow-hidden rounded-3xl border-r-2 border-r-loss '
style={{ height: barHeight }}
>
<div className='absolute h-full w-full rounded-lg shadow-inset gradient-hatched '>
<div
className='absolute left-0 h-full max-w-full rounded-l-3xl bg-body-dark transition-[right] duration-1000 ease-linear'
className='ease-loss absolute left-0 h-full max-w-full rounded-l-3xl bg-body-dark transition-[right] duration-1000'
style={{
right: `${limitPercentOfMax ? 100 - limitPercentOfMax : 100}%`,
}}
></div>
/>
<div className='absolute top-0 h-full w-full overflow-hidden'>
<div className='absolute top-0 h-full w-full'>
<div
className='h-full rounded-l transition-[width] duration-1000 ease-linear'
className='h-full rounded-lg transition-[width] duration-1000 ease-linear'
style={{
width: `${percentOfMaxRange || 0.01}%`,
width: `${percentOfMaxRange || 0.02}%`,
WebkitMask: 'linear-gradient(#fff 0 0)',
}}
>
<div className='absolute top-0 bottom-0 left-0 right-0 gradient-limit' />
</div>
<div
className='absolute bottom-0 h-[120%] w-[1px] bg-white transition-[left] duration-1000 ease-linear'
style={{ left: `${limitPercentOfMax || 0}%` }}
/>
{showPercentageText ? (
<span
className='absolute top-1/2 w-[53px] -translate-y-1/2 transition-[left] duration-1000 ease-linear text-2xs-caps'
style={{
left: `${percentOfMaxRange}%`,
marginLeft: percentOfMaxMargin,
}}
>
<span className='absolute top-1/2 mt-[1px] w-full -translate-y-1/2 animate-fadein text-center opacity-0 text-2xs-caps'>
{max !== 0 && (
<Number
animate={true}
<FormattedNumber
className='text-white'
animate
amount={percentOfMaxRound}
minDecimals={decimals}
maxDecimals={decimals}
suffix='%'
abbreviated={false}
/>
@ -131,12 +118,13 @@ export const BorrowCapacity = ({
</div>
</div>
</div>
</div>
</Tooltip>
{!hideValues && (
<div className='mt-2 flex opacity-60 text-3xs-caps'>
<Number animate={true} amount={balance} className='mr-1' />
<FormattedNumber animate amount={balance} className='mr-1' />
<span className='mr-1'>of</span>
<Number animate={true} amount={max} />
<FormattedNumber animate amount={max} />
</div>
)}
</div>

View File

@ -7,8 +7,8 @@ import { toast } from 'react-toastify'
import Button from 'components/Button'
import CircularProgress from 'components/CircularProgress'
import ContainerSecondary from 'components/ContainerSecondary'
import Gauge from 'components/Gauge'
import ProgressBar from 'components/ProgressBar'
import SemiCircleProgress from 'components/SemiCircleProgress'
import Slider from 'components/Slider'
import Text from 'components/Text'
import Tooltip from 'components/Tooltip'
@ -21,7 +21,7 @@ import useMarkets from 'hooks/useMarkets'
import useTokenPrices from 'hooks/useTokenPrices'
import useCreditManagerStore from 'stores/useCreditManagerStore'
import { chain } from 'utils/chains'
import { formatCurrency } from 'utils/formatters'
import { formatCurrency, formatValue } from 'utils/formatters'
import { getTokenDecimals, getTokenSymbol } from 'utils/tokens'
type Props = {
@ -261,14 +261,29 @@ const BorrowModal = ({ show, onClose, tokenDenom }: Props) => {
.toNumber(),
)}
</p>
{/* TOOLTIP */}
<div title={`${String(accountStats.currentLeverage.toFixed(1))}x`}>
<SemiCircleProgress
<Gauge
value={accountStats.currentLeverage / accountStats.maxLeverage}
label='Lvg'
tooltip={
<Text size='sm'>
Current Leverage:{' '}
{formatValue(accountStats.currentLeverage, 0, 2, true, false, 'x')}
<br />
Max Leverage:{' '}
{formatValue(accountStats.maxLeverage, 0, 2, true, false, 'x')}
</Text>
}
/>
<Gauge
value={accountStats.risk}
label='Risk'
tooltip={
<Text size='sm'>
Current Risk:{' '}
{formatValue(accountStats.risk * 100, 0, 2, true, false, '%')}
</Text>
}
/>
</div>
<SemiCircleProgress value={accountStats.risk} label='Risk' />
<ProgressBar value={accountStats.health} />
</div>
)}

View File

@ -1,5 +1,5 @@
import classNames from 'classnames'
import React, { ReactNode } from 'react'
import React, { LegacyRef, ReactNode } from 'react'
import CircularProgress from 'components/CircularProgress'
@ -12,17 +12,17 @@ interface Props {
showProgressIndicator?: boolean
size?: 'small' | 'medium' | 'large'
text?: string | ReactNode
variant?: 'solid' | 'transparent' | 'round'
variant?: 'solid' | 'transparent' | 'round' | 'text'
onClick?: (e: React.MouseEvent<HTMLButtonElement>) => void
}
const colorClasses = {
primary:
'border-none bg-primary hover:bg-primary-highlight active:bg-primary-highlight-10 focus:bg-primary-highlight',
'border-none text-white bg-primary hover:bg-primary-highlight active:bg-primary-highlight-10 focus:bg-primary-highlight',
secondary:
'border-none bg-secondary hover:bg-secondary-highlight active:bg-secondary-highlight-10 focus:bg-secondary-highlight',
'border-none text-white bg-secondary hover:bg-secondary-highlight active:bg-secondary-highlight-10 focus:bg-secondary-highlight',
tertiary:
'border bg-secondary-dark/60 border-white/60 hover:bg-secondary-dark hover:border-white active:bg-secondary-dark-10 active:border-white focus:bg-secondary-dark focus:border-white',
'border text-white bg-secondary-dark/60 border-white/60 hover:bg-secondary-dark hover:border-white active:bg-secondary-dark-10 active:border-white focus:bg-secondary-dark focus:border-white',
quaternary:
'border bg-transparent text-white/60 border-transparent hover:text-white hover:border-white active:text-white active:border-white',
}
@ -54,9 +54,11 @@ const variantClasses = {
solid: 'text-white',
transparent: 'bg-transparent p-0',
round: 'rounded-full p-0',
text: 'border-none bg-transparent',
}
const Button = ({
const Button = React.forwardRef(function Button(
{
children,
className = '',
color = 'primary',
@ -67,18 +69,36 @@ const Button = ({
text,
variant = 'solid',
onClick,
}: Props) => {
}: Props,
ref,
) {
const buttonClasses = []
switch (variant) {
case 'round':
buttonClasses.push(sizeClasses[size], roundSizeClasses[size], colorClasses[color])
break
case 'transparent':
buttonClasses.push(sizeClasses[size], transparentColorClasses[color])
break
case 'solid':
buttonClasses.push(sizeClasses[size], colorClasses[color])
break
default:
}
return (
<button
className={classNames(
'cursor-pointer appearance-none break-normal rounded-3xl outline-none transition-colors',
variant === 'round' ? `${sizeClasses[size]} ${roundSizeClasses[size]}` : sizeClasses[size],
variant === 'transparent' ? transparentColorClasses[color] : colorClasses[color],
variantClasses[variant],
buttonClasses,
disabled && 'pointer-events-none opacity-50',
className,
)}
id={id}
ref={ref as LegacyRef<HTMLButtonElement>}
onClick={disabled ? () => {} : onClick}
>
{text && !children && !showProgressIndicator && <span>{text}</span>}
@ -88,6 +108,6 @@ const Button = ({
)}
</button>
)
}
})
export default Button

View File

@ -11,7 +11,7 @@ const Card = ({ children, className }: Props) => {
<div
className={classNames(
className,
'h-fit w-full max-w-full rounded-xl border-[7px] border-accent-highlight p-4 gradient-card',
'h-fit w-full max-w-full overflow-hidden rounded-xl border-[7px] border-accent-highlight p-4 gradient-card',
)}
>
{children}

View File

@ -1,12 +1,10 @@
import { SigningCosmWasmClient } from '@cosmjs/cosmwasm-stargate'
import { Coin } from '@cosmjs/stargate'
import { Dialog, Transition } from '@headlessui/react'
import Image from 'next/image'
import React, { Fragment, useState } from 'react'
import { Fragment, useState } from 'react'
import { toast } from 'react-toastify'
import useWalletStore from 'stores/useWalletStore'
import { ChainId, Wallet } from 'types'
import { Wallet } from 'types'
import { getInjectiveAddress } from 'utils/address'
import { chain } from 'utils/chains'
import { getExperimentalChainConfigBasedOnChainId } from 'utils/experimental-chains'

View File

@ -0,0 +1,172 @@
import BigNumber from 'bignumber.js'
import FormattedNumber from 'components/FormattedNumber'
import ArrowRightLine from 'components/Icons/arrow-right-line.svg'
import Text from 'components/Text'
import useAccountStats from 'hooks/useAccountStats'
import useCreditAccountPositions from 'hooks/useCreditAccountPositions'
import useMarkets from 'hooks/useMarkets'
import useTokenPrices from 'hooks/useTokenPrices'
import useCreditManagerStore from 'stores/useCreditManagerStore'
import useWalletStore from 'stores/useWalletStore'
import { chain } from 'utils/chains'
import { getTokenDecimals, getTokenSymbol } from 'utils/tokens'
const CreditManager = () => {
const address = useWalletStore((s) => s.address)
const selectedAccount = useCreditManagerStore((s) => s.selectedAccount)
const toggleCreditManager = useCreditManagerStore((s) => s.actions.toggleCreditManager)
const { data: positionsData, isLoading: isLoadingPositions } = useCreditAccountPositions(
selectedAccount ?? '',
)
const { data: tokenPrices } = useTokenPrices()
const { data: marketsData } = useMarkets()
const accountStats = useAccountStats()
const getTokenTotalUSDValue = (amount: string, denom: string) => {
// early return if prices are not fetched yet
if (!tokenPrices) return 0
return (
BigNumber(amount)
.div(10 ** getTokenDecimals(denom))
.toNumber() * tokenPrices[denom]
)
}
return (
<div className='flex w-[400px] basis-[400px] flex-wrap content-start border-white/20 bg-header placeholder:border-l'>
<div className='flex w-full flex-wrap items-center border-b border-white/20'>
<Text size='xl' uppercase={true} className='flex-grow text-center text-white'>
Account {selectedAccount}
</Text>
<div className='flex border-l border-white/20 p-4' onClick={() => {}}>
<span className='w-5 hover:cursor-pointer' onClick={toggleCreditManager}>
<ArrowRightLine />
</span>
</div>
</div>
<div className='flex w-full flex-wrap p-2'>
<div className='mb-2 flex w-full'>
<Text size='xs' className='flex-grow text-white/60'>
Total Position:
</Text>
<Text size='xs' className='text-white/60'>
<FormattedNumber
amount={BigNumber(accountStats?.totalPosition ?? 0)
.dividedBy(10 ** chain.stakeCurrency.coinDecimals)
.toNumber()}
animate
prefix='$'
/>
</Text>
</div>
<div className='flex w-full justify-between'>
<Text size='xs' className='flex-grow text-white/60'>
Total Liabilities:
</Text>
<Text size='xs' className=' text-white/60'>
<FormattedNumber
amount={BigNumber(accountStats?.totalDebt ?? 0)
.dividedBy(10 ** chain.stakeCurrency.coinDecimals)
.toNumber()}
animate
prefix='$'
/>
</Text>
</div>
</div>
<div className='flex w-full flex-wrap'>
<Text uppercase={true} className='w-full bg-black/20 px-4 py-2 text-white/40'>
Balances
</Text>
{isLoadingPositions ? (
<div>Loading...</div>
) : (
<div className='flex w-full flex-wrap'>
<div className='mb-2 flex w-full border-b border-white/20 bg-black/20 px-4 py-2'>
<Text size='xs' uppercase={true} className='flex-1 text-white'>
Asset
</Text>
<Text size='xs' uppercase={true} className='flex-1 text-white'>
Value
</Text>
<Text size='xs' uppercase={true} className='flex-1 text-white'>
Size
</Text>
<Text size='xs' uppercase={true} className='flex-1 text-white'>
APY
</Text>
</div>
{positionsData?.coins.map((coin) => (
<div key={coin.denom} className='flex w-full px-4 py-2'>
<Text size='xs' className='flex-1 border-l-4 border-profit pl-2 text-white/60'>
{getTokenSymbol(coin.denom)}
</Text>
<Text size='xs' className='flex-1 text-white/60'>
<FormattedNumber
amount={getTokenTotalUSDValue(coin.amount, coin.denom)}
animate
prefix='$'
/>
</Text>
<Text size='xs' className='flex-1 text-white/60'>
<FormattedNumber
amount={BigNumber(coin.amount)
.div(10 ** getTokenDecimals(coin.denom))
.toNumber()}
animate
minDecimals={0}
maxDecimals={4}
/>
</Text>
<Text size='xs' className='flex-1 text-white/60'>
-
</Text>
</div>
))}
{positionsData?.debts.map((coin) => (
<div key={coin.denom} className='flex w-full px-4 py-2'>
<Text size='xs' className='flex-1 border-l-4 border-loss pl-2 text-white/60'>
{getTokenSymbol(coin.denom)}
</Text>
<Text size='xs' className='flex-1 text-white/60'>
<FormattedNumber
amount={getTokenTotalUSDValue(coin.amount, coin.denom)}
prefix='-$'
animate
/>
</Text>
<Text size='xs' className='flex-1 text-white/60'>
<FormattedNumber
amount={BigNumber(coin.amount)
.div(10 ** getTokenDecimals(coin.denom))
.toNumber()}
minDecimals={0}
maxDecimals={4}
animate
/>
</Text>
<Text size='xs' className='flex-1 text-white/60'>
<FormattedNumber
amount={Number(marketsData?.[coin.denom].borrow_rate) * 100}
minDecimals={0}
maxDecimals={2}
prefix='-'
suffix='%'
animate
/>
</Text>
</div>
))}
</div>
)}
</div>
</div>
)
}
export default CreditManager

View File

@ -1,167 +0,0 @@
import BigNumber from 'bignumber.js'
import { useRef, useState } from 'react'
import Button from 'components/Button'
import ContainerSecondary from 'components/ContainerSecondary'
import FundAccountModal from 'components/FundAccountModal'
import WithdrawModal from 'components/WithdrawModal'
import useAccountStats from 'hooks/useAccountStats'
import useCreditAccountPositions from 'hooks/useCreditAccountPositions'
import useMarkets from 'hooks/useMarkets'
import useTokenPrices from 'hooks/useTokenPrices'
import useCreditManagerStore from 'stores/useCreditManagerStore'
import useWalletStore from 'stores/useWalletStore'
import { chain } from 'utils/chains'
import { formatCurrency } from 'utils/formatters'
import { getTokenDecimals, getTokenSymbol } from 'utils/tokens'
const CreditManager = () => {
const [showFundWalletModal, setShowFundWalletModal] = useState(false)
const [showWithdrawModal, setShowWithdrawModal] = useState(false)
// recreate modals and reset state whenever ref changes
const modalId = useRef(0)
const address = useWalletStore((s) => s.address)
const selectedAccount = useCreditManagerStore((s) => s.selectedAccount)
const { data: positionsData, isLoading: isLoadingPositions } = useCreditAccountPositions(
selectedAccount ?? '',
)
const { data: tokenPrices } = useTokenPrices()
const { data: marketsData } = useMarkets()
const accountStats = useAccountStats()
const getTokenTotalUSDValue = (amount: string, denom: string) => {
// early return if prices are not fetched yet
if (!tokenPrices) return 0
return (
BigNumber(amount)
.div(10 ** getTokenDecimals(denom))
.toNumber() * tokenPrices[denom]
)
}
if (!address) {
return (
<div className='absolute inset-0 left-auto w-[400px] border-l border-white/20 bg-grey-medium p-2'>
<ContainerSecondary>You must have a connected wallet</ContainerSecondary>
</div>
)
}
return (
<div className='absolute inset-0 left-auto w-[400px] border-l border-white/20 bg-grey-medium p-2'>
<ContainerSecondary className='mb-2 flex gap-3'>
<Button
className='flex-1 rounded-md'
onClick={() => {
setShowFundWalletModal(true)
modalId.current += 1
}}
>
Fund
</Button>
<Button
className='flex-1 rounded-md'
onClick={() => {
setShowWithdrawModal(true)
modalId.current += 1
}}
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(
BigNumber(accountStats?.totalPosition ?? 0)
.dividedBy(10 ** chain.stakeCurrency.coinDecimals)
.toNumber(),
)}
</div>
</div>
<div className='flex justify-between'>
<div>Total Liabilities:</div>
<div className='font-semibold'>
{formatCurrency(
BigNumber(accountStats?.totalDebt ?? 0)
.dividedBy(10 ** chain.stakeCurrency.coinDecimals)
.toNumber(),
)}
</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_${modalId.current}`}
show={showFundWalletModal}
onClose={() => setShowFundWalletModal(false)}
/>
<WithdrawModal
key={`withdrawModal_${modalId.current}`}
show={showWithdrawModal}
onClose={() => setShowWithdrawModal(false)}
/>
</div>
)
}
export default CreditManager

View File

@ -17,7 +17,7 @@ interface Props {
abbreviated?: boolean
}
const Number = ({
const FormattedNumber = ({
amount,
animate = false,
className,
@ -72,4 +72,4 @@ const Number = ({
)
}
export default React.memo(Number)
export default React.memo(FormattedNumber)

View File

@ -1,220 +0,0 @@
import { Dialog, Switch, Transition } from '@headlessui/react'
import BigNumber from 'bignumber.js'
import Image from 'next/image'
import React, { useEffect, useMemo, useState } from 'react'
import { toast } from 'react-toastify'
import useLocalStorageState from 'use-local-storage-state'
import Slider from 'components/Slider'
import useDepositCreditAccount from 'hooks/mutations/useDepositCreditAccount'
import useAllBalances from 'hooks/useAllBalances'
import useAllowedCoins from 'hooks/useAllowedCoins'
import useCreditManagerStore from 'stores/useCreditManagerStore'
import { getTokenDecimals, getTokenSymbol } from 'utils/tokens'
import Button from 'components/Button'
import ContainerSecondary from 'components/ContainerSecondary'
import CircularProgress from 'components/CircularProgress'
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'>
<CircularProgress />
</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={50} 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
dont 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

86
components/Gauge.tsx Normal file
View File

@ -0,0 +1,86 @@
import classNames from 'classnames'
import { ReactNode } from 'react'
import Tooltip from './Tooltip'
type Props = {
tooltip: string | ReactNode
strokeWidth?: number
background?: string
diameter?: number
value: number
label?: string
}
const Gauge = ({ background = '#15161A', diameter = 40, value = 0, label, tooltip }: Props) => {
const percentage = value * 100
const percentageValue = percentage > 100 ? 100 : percentage < 0 ? 0 : percentage
const semiCirclePercentage = Math.abs(percentageValue / 2 - 50)
return (
<Tooltip content={tooltip}>
<div
className={classNames(
'relative overflow-hidden',
`w-${diameter / 4} h-${diameter / 8 + 1}`,
)}
>
<svg
viewBox='2 -2 28 36'
width={diameter}
height={diameter}
style={{ transform: 'rotate(180deg)' }}
className='absolute top-0 left-0'
>
<linearGradient id='gradient'>
<stop stopColor='#C13338' offset='0%'></stop>
<stop stopColor='#4F3D9F' offset='50%'></stop>
<stop stopColor='#15BFA9' offset='100%'></stop>
</linearGradient>
<circle
fill='none'
stroke={background}
strokeWidth={4}
strokeDasharray='50 100'
strokeLinecap='round'
r='16'
cx='16'
cy='16'
shapeRendering='geometricPrecision'
/>
<circle
r='16'
cx='16'
cy='16'
fill='none'
strokeLinecap='round'
stroke='url(#gradient)'
strokeDasharray='50 100'
strokeWidth={5}
style={{
strokeDashoffset: semiCirclePercentage,
transition: 'stroke-dashoffset 1s ease',
}}
shapeRendering='geometricPrecision'
/>
</svg>
{label && (
<span
className='text-xs'
style={{
width: '100%',
left: '0',
textAlign: 'center',
bottom: '-2px',
position: 'absolute',
}}
>
{label}
</span>
)}
</div>
</Tooltip>
)
}
export default Gauge

View File

@ -1,4 +1,4 @@
;<svg version='1.1' viewBox='0 0 48 48' xmlns='http://www.w3.org/2000/svg'>
<svg version='1.1' viewBox='0 0 48 48' xmlns='http://www.w3.org/2000/svg'>
<circle
cx='24'
cy='24'

Before

Width:  |  Height:  |  Size: 673 B

After

Width:  |  Height:  |  Size: 672 B

View File

@ -1,3 +1,5 @@
<svg version='1.1' viewBox='0 0 8 6' xmlns='http://www.w3.org/2000/svg' fill="currentColor">
<path d='M4 6L0.535899 -1.75695e-07L7.4641 4.29987e-07L4 6Z' />
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" x="0px" y="0px" viewBox="0 0 24 24">
<path fill='currentColor' d="M10.67,1.33v18.11l-6.21-6.21c-0.52-0.52-1.36-0.52-1.89,0c-0.52,0.52-0.52,1.36,0,1.89l8.49,8.49
c0.52,0.52,1.36,0.52,1.89,0l8.49-8.49c0.52-0.52,0.52-1.36,0-1.89c-0.52-0.52-1.36-0.52-1.89,0l-6.21,6.21V1.33
C13.33,0.6,12.74,0,12,0S10.67,0.6,10.67,1.33z"/>
</svg>

Before

Width:  |  Height:  |  Size: 165 B

After

Width:  |  Height:  |  Size: 382 B

View File

@ -0,0 +1,6 @@
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" x="0px" y="0px" viewBox="0 0 14 13">
<path fill="currentColor" d="M13.8,6.6c0-0.5-0.3-0.8-0.9-0.8H6L4.4,5.8l2.2-2l1.5-1.5c0.2-0.2,0.3-0.4,0.3-0.6c0-0.5-0.3-0.8-0.8-0.8
C7.3,0.9,7.1,1,6.9,1.2L2.1,6C2,6.1,1.9,6.2,1.9,6.4V1.6c0-0.5-0.4-0.8-0.8-0.8c-0.5,0-0.8,0.3-0.8,0.8v9.9c0,0.5,0.4,0.8,0.8,0.8
c0.5,0,0.8-0.3,0.8-0.8V6.8c0,0.2,0.1,0.3,0.3,0.4L6.9,12c0.2,0.2,0.4,0.3,0.6,0.3c0.5,0,0.8-0.3,0.8-0.8c0-0.2-0.1-0.5-0.3-0.6
L6.5,9.3l-2.2-2L6,7.4h7C13.4,7.4,13.8,7.1,13.8,6.6z"/>
</svg>

After

Width:  |  Height:  |  Size: 537 B

View File

@ -0,0 +1,6 @@
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" x="0px" y="0px" viewBox="0 0 24 24" >
<path fill='currentColor' d="M13.33,22.67V4.55l6.21,6.21c0.52,0.52,1.36,0.52,1.89,0c0.52-0.52,0.52-1.36,0-1.89l-8.49-8.49
c-0.52-0.52-1.36-0.52-1.89,0L2.57,8.88c-0.52,0.52-0.52,1.36,0,1.89c0.52,0.52,1.36,0.52,1.89,0l6.21-6.21v18.11
c0,0.74,0.6,1.33,1.33,1.33C12.74,24,13.33,23.4,13.33,22.67z"/>
</svg>

After

Width:  |  Height:  |  Size: 397 B

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 9.7 KiB

After

Width:  |  Height:  |  Size: 3.8 KiB

View File

@ -0,0 +1,90 @@
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" x="0px" y="0px" viewBox="0 0 242 80">
<path fill-rule='evenodd' clip-rule='evenodd' fill='currentColor' d="M40.3,0l12.4,2l11.2,5.7l8.8,8.8l5.7,11.2l2,12.4l-2,12.4
l-5.7,11.2l-8.8,8.8L52.7,78l-12.4,2L28,78l-11.2-5.7L8,63.5L2.3,52.4L0.3,40l2-12.4L8,16.5l8.8-8.8L28,2L40.3,0z M34.6,31.5
l5.7,7.9l5.7-7.9H34.6z M34,31.9l5.7,7.9l-9.3,3L34,31.9z M29.3,43.2l-8-8.7l-2.8,12.2L29.3,43.2z M17.8,46.4l2.7-12.1l-9.3,2.9
L17.8,46.4z M4.6,39.3l5.6-1.8L8.8,48.8L4.6,39.3z M1.8,40.1l1.8-0.6l-0.7,8L1.8,40.1z M3.6,51.5l-0.2-1l0.8-10.1l4.2,9.4L3.6,51.5z
M8.8,50.6l2.1,10.1l-6.6-7.7l-0.4-0.9L8.8,50.6z M9.7,50.3l8,8.6l-0.1-11.2L9.7,50.3z M18.7,47.4l9.4,8.3l1.4-11.8L18.7,47.4z
M27.7,56.3l-9.3-8.2l0.1,11.3L27.7,56.3z M17.4,59.7l-7.7-8.3l2.2,10.2L17.4,59.7z M10.9,61.9l-5.2-6.1l3.4,6.7L10.9,61.9z
M30.2,44l-1.4,11.8l10.8-4.9L30.2,44z M40,51.5l-10.8,4.9L40,62.9V51.5z M28.7,66.8l0.1-9.8l10.7,6.3L28.7,66.8z M28.6,73.8
l0.1-5.9L39,72.7L28.6,73.8z M28.6,76.7l0-1.9l7.4,3.1L28.6,76.7z M40,78.5L39,78.3l-9.3-3.9L40,73.3V78.5z M40.7,72.3l10.7-4.9
L40.7,64V72.3z M40.7,73.3v5.2l1-0.2l9.3-3.9L40.7,73.3z M44.7,77.8l7.4-1.2l0-1.9L44.7,77.8z M52.1,73.8L52,67.9l-10.3,4.8
L52.1,73.8z M52,66.8l-0.1-9.8l-10.7,6.3L52,66.8z M51.5,56.5l-10.8,6.4V51.5L51.5,56.5z M41.1,50.9l9.4-6.8l1.4,11.8L41.1,50.9z
M51.2,43.9l1.4,11.8l9.4-8.3L51.2,43.9z M62.2,59.4L53,56.3l9.3-8.2L62.2,59.4z M68.8,61.6l-5.6-1.9l7.7-8.3L68.8,61.6z M71.6,62.5
l-1.8-0.6l5.2-6.1L71.6,62.5z M76.8,52.2l-0.4,0.9l-6.6,7.7l2.1-10.1L76.8,52.2z M71.2,49.6l-7.9-2.6l6.5-9.1L71.2,49.6z M62.2,46.7
l-10.8-3.5l8-8.7L62.2,46.7z M60.1,34.3l9.3,2.9l-6.6,9.2L60.1,34.3z M70.5,37.5l5.6,1.8l-4.2,9.5L70.5,37.5z M77,39.6l0.7,8
l1.2-7.4L77,39.6z M77.2,50.6L77,51.5l-4.9-1.6l4.2-9.4L77.2,50.6z M50.9,42.7l-3.6-11.1L58.9,34L50.9,42.7z M47.4,30.9l6.7-9.2
l5,11.5L47.4,30.9z M59.6,14.1l-4.9,6.7l10.6,3.5L59.6,14.1z M69.2,18.4l-9-5.2l3-4.2l0.7,0.7L69.2,18.4z M54.8,21.6L59.7,33
l5.8-7.9L54.8,21.6z M66.1,11.9l5.3,5.3l-1.1,1.6L66.1,11.9z M60.6,14.4l9,5.2l-3.5,4.7L60.6,14.4z M60.3,33.5l5.7-7.7l3.5,10.7
L60.3,33.5L60.3,33.5L60.3,33.5z M78.8,39.4L77,38.8l0.6-6.6L78.8,39.4z M77,28.5l0.1,0.9l-0.8,7.9l-2.5-7.8L77,28.5z M70.5,36.8
l2.8-6.5l2.7,8.2L70.5,36.8z M70.7,19.4l4.4,5l-3.3-6.5L70.7,19.4z M76.4,27l0.4,0.8l-3.2,1L71.1,21L76.4,27z M70.1,20.2l2.7,8.2
l-6.1-3.6L70.1,20.2z M71,50.3L63,47.8L63,59L71,50.3z M52.6,57L52.6,57L52.6,57l9.2,3l-9.1,6.6L52.6,57z M52.8,76.3l0-1.9l6.5-1.4
L52.8,76.3z M62.6,71.3l-0.8,0.4l-7.8,1.7l6.6-4.8L62.6,71.3z M52.7,67.7l7,0.7l-7,5.1L52.7,67.7z M68.3,62.2l-5.4-1.8l-1.5,6.9
L68.3,62.2z M63.8,70.3l-0.6,0.6l-2-2.7l6.6-4.8L63.8,70.3z M65.9,68.2l3.4-5.7l1.8,0.6L65.9,68.2z M40,72.3V64l-10.7,3.4L40,72.3z
M28,56.9L28,56.9L28,56.9l0,9.7L18.8,60L28,56.9z M9.6,63.1l1.8-0.6l3.4,5.7L9.6,63.1z M17.4,70.9l-0.6-0.6l-4-6.9l6.6,4.8
L17.4,70.9z M17.8,60.4l1.5,6.9l-7-5.1L17.8,60.4z M27.9,73.5l0.1-5.7l-7,0.7L27.9,73.5z M18.8,71.7L18,71.3l2-2.7l6.6,4.8
L18.8,71.7z M21.4,73l6.5,1.4l0,1.9L21.4,73z M9.5,49.6l7.9-2.6L10.9,38L9.5,49.6z M29.8,42.7l3.6-11.1L21.7,34L29.8,42.7z
M25.9,20.8l-10.6,3.5L21,14.1L25.9,20.8z M26.6,21.7l-5,11.5l11.6-2.3L26.6,21.7z M20.9,33l4.9-11.4l-10.7,3.6L20.9,33z M14.5,24.3
L11,19.6l9-5.2L14.5,24.3z M10.4,18.8l-1.1-1.5l5.3-5.3L10.4,18.8z M16.7,9.8l-5.2,8.7l9-5.2l-3-4.2L16.7,9.8z M20.4,33.5l-5.7-7.7
l-3.5,10.7L20.4,33.5L20.4,33.5L20.4,33.5z M8.9,17.9l1.1,1.5l-4.4,4.9L8.9,17.9z M3.9,27.8L4.3,27l5.3-6L7,28.8L3.9,27.8z
M10.6,20.2l-2.7,8.2l6.1-3.6L10.6,20.2z M3.6,28.5l3.2,1l-2.5,7.8l-0.8-7.9L3.6,28.5z M3,32.2l0.6,6.6l-1.8,0.6L3,32.2z M4.7,38.5
l2.7-8.2l2.8,6.5L4.7,38.5z M26.5,20.4l-4.9-6.7l11.5-2.3L26.5,20.4z M33.9,30.5l-6.7-9.2l12.5-1.2L33.9,30.5z M34,11.4l5.7,8
l-12.4,1.1L34,11.4z M30,5.8l3.4,4.8l-11.1,2.2L30,5.8z M28.3,3.4L29.4,5l-7.8,1.8L28.3,3.4z M28.7,5.9l-9.9,2.3L18,8.7l3,4.2
L28.7,5.9z M41,20.1l5.8,10.3l6.7-9.2L41,20.1z M59.6,12.8l3-4.2l-0.9-0.4l-9.8-2.3L59.6,12.8z M59,6.8l-6.7-3.4L51.2,5L59,6.8z
M50.7,5.8l-3.4,4.8l11.2,2.2L50.7,5.8z M46.7,11.4l-5.7,8l12.4,1.1L46.7,11.4z M59,13.6l-4.9,6.7l-6.6-9L59,13.6z M46.1,30.8
l-5.8-10.3l-5.8,10.3H46.1z M40.4,19L40.4,19L40.4,19l-5.7-7.8H46L40.4,19z M51.6,3.3l-1.1,1.5l-6.1-2.6L51.6,3.3z M40.7,1.5
l0.9,0.1l7.3,3.2h-8.2V1.5z M46.6,10.3l-5.3-4.7h8.6L46.6,10.3z M30.7,5.6l3.3,4.7l5.3-4.7H30.7z M30.2,4.8l-1.1-1.5l7.2-1.1
L30.2,4.8z M39.1,1.7l-7.3,3.2H40V1.5L39.1,1.7z M46.6,31.9l-5.7,7.9l9.3,3L46.6,31.9z M50,43.5l-9.3-3v9.7L50,43.5z M40,50.2v-9.7
l-9.3,3L40,50.2z M35,10.4l5.4-4.7l5.4,4.7H35z M13.9,25.8l-6.1,3.6l2.8,6.6L13.9,25.8z M18.6,60.8l1.6,7l7.1-0.7L18.6,60.8z
M53.4,67.1l7.1,0.7l1.6-7L53.4,67.1z M66.8,25.8l6.1,3.6L70.1,36L66.8,25.8z"/>
<path fill='currentColor' d="M100.8,29l2.7,4.6l2.7-4.6l0.6,5.5h1.6l-1.2-9.4l-3.7,6.1l-3.7-6.1l-1.2,9.4h1.6L100.8,29z"/>
<path fill='currentColor' d="M123.6,32.2h4.7l-0.3-1.3h-4.2L123.6,32.2z M126,28l1.4,3.3l0,0.4l1.2,2.7h1.7L126,25l-4.4,9.5h1.7
l1.2-2.8l0-0.3L126,28z"/>
<path fill='currentColor' d="M145.9,30.2l2.9,4.3h1.8l-3-4.3H145.9z M144.1,25.5v9h1.5v-9H144.1z M145,26.8h1.8
c0.3,0,0.6,0.1,0.9,0.2c0.3,0.1,0.5,0.3,0.6,0.5c0.1,0.2,0.2,0.5,0.2,0.8c0,0.3-0.1,0.6-0.2,0.8c-0.1,0.2-0.3,0.4-0.6,0.5
c-0.3,0.1-0.6,0.2-0.9,0.2H145v1.2h1.9c0.6,0,1.2-0.1,1.7-0.3c0.5-0.2,0.9-0.6,1.1-1s0.4-0.9,0.4-1.5c0-0.6-0.1-1-0.4-1.5
c-0.3-0.4-0.6-0.7-1.1-1c-0.5-0.2-1-0.3-1.7-0.3H145V26.8z"/>
<path fill='currentColor' d="M164.9,31.6l-1.1,0.8c0.2,0.4,0.5,0.8,0.8,1.1s0.7,0.6,1.2,0.8c0.5,0.2,0.9,0.3,1.5,0.3
c0.4,0,0.8-0.1,1.1-0.2c0.4-0.1,0.7-0.3,1-0.5c0.3-0.2,0.5-0.5,0.7-0.8c0.2-0.3,0.2-0.7,0.2-1.1c0-0.4-0.1-0.7-0.2-1
c-0.1-0.3-0.3-0.5-0.5-0.8c-0.2-0.2-0.5-0.4-0.7-0.6c-0.3-0.1-0.5-0.3-0.8-0.4c-0.5-0.2-0.9-0.3-1.2-0.5c-0.3-0.2-0.5-0.3-0.7-0.5
c-0.1-0.2-0.2-0.4-0.2-0.6c0-0.3,0.1-0.5,0.3-0.7c0.2-0.2,0.5-0.3,0.9-0.3c0.3,0,0.6,0.1,0.8,0.2c0.2,0.1,0.4,0.3,0.6,0.5
c0.2,0.2,0.3,0.4,0.4,0.6l1.2-0.7c-0.2-0.3-0.4-0.6-0.6-0.9c-0.3-0.3-0.6-0.5-1-0.7c-0.4-0.2-0.9-0.3-1.4-0.3c-0.5,0-1,0.1-1.4,0.3
c-0.4,0.2-0.8,0.5-1,0.8c-0.2,0.4-0.4,0.8-0.4,1.3c0,0.4,0.1,0.8,0.2,1.1c0.2,0.3,0.4,0.5,0.6,0.7c0.2,0.2,0.5,0.4,0.8,0.5
c0.3,0.1,0.5,0.2,0.7,0.3c0.4,0.1,0.7,0.3,1,0.4c0.3,0.1,0.5,0.3,0.7,0.5c0.2,0.2,0.2,0.5,0.2,0.8c0,0.3-0.1,0.6-0.4,0.9
c-0.3,0.2-0.6,0.3-1,0.3c-0.3,0-0.6-0.1-0.9-0.2c-0.3-0.1-0.5-0.3-0.8-0.6C165.3,32.2,165.1,31.9,164.9,31.6z"/>
<path fill='currentColor' d="M99.1,44.8v9h1.5v-9H99.1z M100,46.2h1.8c0.5,0,0.9,0.1,1.3,0.4c0.3,0.2,0.5,0.6,0.5,1.1
s-0.2,0.8-0.5,1.1c-0.3,0.2-0.7,0.4-1.3,0.4H100v1.3h1.8c0.6,0,1.2-0.1,1.7-0.3c0.5-0.2,0.9-0.6,1.1-1c0.3-0.4,0.4-0.9,0.4-1.5
c0-0.6-0.1-1.1-0.4-1.5c-0.3-0.4-0.6-0.7-1.1-1c-0.5-0.2-1-0.3-1.7-0.3H100V46.2z"/>
<path fill='currentColor' d="M119.2,49.5l2.9,4.3h1.8l-3-4.3H119.2z M117.4,44.8v9h1.5v-9H117.4z M118.3,46.1h1.8
c0.3,0,0.6,0.1,0.9,0.2c0.3,0.1,0.5,0.3,0.6,0.5c0.1,0.2,0.2,0.5,0.2,0.8c0,0.3-0.1,0.6-0.2,0.8c-0.1,0.2-0.3,0.4-0.6,0.5
c-0.3,0.1-0.6,0.2-0.9,0.2h-1.8v1.2h1.9c0.6,0,1.2-0.1,1.7-0.3c0.5-0.2,0.9-0.6,1.1-1c0.3-0.4,0.4-0.9,0.4-1.5c0-0.6-0.1-1-0.4-1.5
c-0.3-0.4-0.6-0.7-1.1-1c-0.5-0.2-1-0.3-1.7-0.3h-1.9V46.1z"/>
<path fill='currentColor' d="M137,49.3c0-0.6,0.1-1.2,0.4-1.6c0.3-0.5,0.6-0.9,1.1-1.1c0.5-0.3,1-0.4,1.6-0.4
c0.6,0,1.1,0.1,1.6,0.4c0.5,0.3,0.8,0.7,1.1,1.1c0.3,0.5,0.4,1,0.4,1.6s-0.1,1.2-0.4,1.7c-0.3,0.5-0.6,0.9-1.1,1.1
c-0.5,0.3-1,0.4-1.6,0.4c-0.6,0-1.1-0.1-1.6-0.4c-0.5-0.3-0.8-0.6-1.1-1.1C137.1,50.5,137,49.9,137,49.3z M135.4,49.3
c0,0.7,0.1,1.3,0.3,1.9c0.2,0.6,0.6,1.1,1,1.5c0.4,0.4,0.9,0.7,1.5,1c0.6,0.2,1.2,0.3,1.9,0.3c0.7,0,1.3-0.1,1.9-0.3
c0.6-0.2,1.1-0.6,1.5-1c0.4-0.4,0.7-0.9,1-1.5s0.3-1.2,0.3-1.9c0-0.7-0.1-1.3-0.3-1.9c-0.2-0.6-0.6-1-1-1.5c-0.4-0.4-0.9-0.7-1.5-1
c-0.6-0.2-1.2-0.3-1.9-0.3c-0.7,0-1.3,0.1-1.8,0.3c-0.6,0.2-1.1,0.6-1.5,1c-0.4,0.4-0.7,0.9-1,1.5C135.5,48,135.4,48.6,135.4,49.3z"
/>
<path fill='currentColor' d="M155.7,46.2h2.4v7.6h1.5v-7.6h2.4v-1.4h-6.2V46.2z"/>
<path fill='currentColor' d="M174.4,49.3c0-0.6,0.1-1.2,0.4-1.6c0.3-0.5,0.6-0.9,1.1-1.1c0.5-0.3,1-0.4,1.6-0.4
c0.6,0,1.1,0.1,1.6,0.4c0.5,0.3,0.8,0.7,1.1,1.1c0.3,0.5,0.4,1,0.4,1.6s-0.1,1.2-0.4,1.7c-0.3,0.5-0.6,0.9-1.1,1.1
c-0.5,0.3-1,0.4-1.6,0.4c-0.6,0-1.1-0.1-1.6-0.4c-0.5-0.3-0.8-0.6-1.1-1.1C174.5,50.5,174.4,49.9,174.4,49.3z M172.8,49.3
c0,0.7,0.1,1.3,0.3,1.9c0.2,0.6,0.6,1.1,1,1.5c0.4,0.4,0.9,0.7,1.5,1c0.6,0.2,1.2,0.3,1.9,0.3c0.7,0,1.3-0.1,1.9-0.3
c0.6-0.2,1.1-0.6,1.5-1c0.4-0.4,0.7-0.9,1-1.5c0.2-0.6,0.3-1.2,0.3-1.9c0-0.7-0.1-1.3-0.3-1.9c-0.2-0.6-0.6-1-1-1.5
c-0.4-0.4-0.9-0.7-1.5-1c-0.6-0.2-1.2-0.3-1.9-0.3c-0.7,0-1.3,0.1-1.8,0.3c-0.6,0.2-1.1,0.6-1.5,1c-0.4,0.4-0.7,0.9-1,1.5
C172.9,48,172.8,48.6,172.8,49.3z"/>
<path fill='currentColor' d="M195.6,49.3c0-0.6,0.1-1.2,0.4-1.7c0.3-0.5,0.7-0.8,1.1-1.1c0.5-0.3,1-0.4,1.5-0.4
c0.4,0,0.8,0.1,1.2,0.2c0.3,0.1,0.6,0.3,0.9,0.5c0.3,0.2,0.5,0.4,0.7,0.6v-1.8c-0.4-0.3-0.8-0.5-1.2-0.7c-0.4-0.2-1-0.2-1.6-0.2
c-0.7,0-1.3,0.1-1.8,0.3c-0.6,0.2-1.1,0.5-1.5,1c-0.4,0.4-0.7,0.9-1,1.5c-0.2,0.6-0.3,1.2-0.3,1.9s0.1,1.3,0.3,1.9
c0.2,0.6,0.6,1.1,1,1.5c0.4,0.4,0.9,0.7,1.5,1c0.6,0.2,1.2,0.3,1.8,0.3c0.7,0,1.2-0.1,1.6-0.2c0.4-0.2,0.8-0.4,1.2-0.7v-1.8
c-0.2,0.2-0.4,0.5-0.7,0.7c-0.3,0.2-0.6,0.3-0.9,0.4c-0.3,0.1-0.7,0.2-1.2,0.2c-0.5,0-1.1-0.1-1.5-0.4c-0.5-0.3-0.8-0.6-1.1-1.1
C195.7,50.5,195.6,50,195.6,49.3z"/>
<path fill='currentColor' d="M215.3,49.3c0-0.6,0.1-1.2,0.4-1.6c0.3-0.5,0.6-0.9,1.1-1.1c0.5-0.3,1-0.4,1.6-0.4
c0.6,0,1.1,0.1,1.6,0.4c0.5,0.3,0.8,0.7,1.1,1.1c0.3,0.5,0.4,1,0.4,1.6s-0.1,1.2-0.4,1.7c-0.3,0.5-0.6,0.9-1.1,1.1
c-0.5,0.3-1,0.4-1.6,0.4c-0.6,0-1.1-0.1-1.6-0.4c-0.5-0.3-0.8-0.6-1.1-1.1C215.4,50.5,215.3,49.9,215.3,49.3z M213.7,49.3
c0,0.7,0.1,1.3,0.3,1.9c0.2,0.6,0.6,1.1,1,1.5c0.4,0.4,0.9,0.7,1.5,1c0.6,0.2,1.2,0.3,1.9,0.3c0.7,0,1.3-0.1,1.9-0.3
c0.6-0.2,1.1-0.6,1.5-1c0.4-0.4,0.7-0.9,1-1.5c0.2-0.6,0.3-1.2,0.3-1.9c0-0.7-0.1-1.3-0.3-1.9c-0.2-0.6-0.6-1-1-1.5
c-0.4-0.4-0.9-0.7-1.5-1c-0.6-0.2-1.2-0.3-1.9-0.3c-0.7,0-1.3,0.1-1.8,0.3c-0.6,0.2-1.1,0.6-1.5,1c-0.4,0.4-0.7,0.9-1,1.5
C213.8,48,213.7,48.6,213.7,49.3z"/>
<path fill='currentColor' d="M235.4,44.8v9h5.1v-1.4h-3.6v-7.6H235.4z"/>
</svg>

After

Width:  |  Height:  |  Size: 9.7 KiB

View File

@ -0,0 +1,3 @@
<svg fill='none' stroke='currentColor' strokeLinecap='round' strokeLinejoin='round' strokeWidth='2' viewBox='0 0 24 24'>
<path d='M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z'></path>
</svg>

After

Width:  |  Height:  |  Size: 191 B

View File

@ -0,0 +1,3 @@
<svg version='1.1' viewBox='0 0 8 6' xmlns='http://www.w3.org/2000/svg' fill="currentColor">
<path d='M4 6L0.535899 -1.75695e-07L7.4641 4.29987e-07L4 6Z' />
</svg>

After

Width:  |  Height:  |  Size: 165 B

View File

@ -2,13 +2,18 @@ import classNames from 'classnames'
import React from 'react'
import CreditManager from 'components/CreditManager'
import Navigation from 'components/Navigation'
import ArrowLeftLine from 'components/Icons/arrow-left-line.svg'
import DesktopNavigation from 'components/Navigation/DesktopNavigation'
import useCreditAccounts from 'hooks/useCreditAccounts'
import useCreditManagerStore from 'stores/useCreditManagerStore'
import useWalletStore from 'stores/useWalletStore'
const Layout = ({ children }: { children: React.ReactNode }) => {
const toggleCreditManager = useCreditManagerStore((s) => s.actions.toggleCreditManager)
const isOpen = useCreditManagerStore((s) => s.isOpen)
const address = useWalletStore((s) => s.address)
const { data: creditAccountsList, isLoading: isLoadingCreditAccounts } = useCreditAccounts()
const hasCreditAccounts = creditAccountsList && creditAccountsList.length > 0
const filter = {
day: 'brightness-100 hue-rotate-0',
@ -25,10 +30,17 @@ const Layout = ({ children }: { children: React.ReactNode }) => {
return (
<div className='relative min-h-screen w-full'>
<div className={backgroundClasses} />
<Navigation />
<main className='relative flex-1 p-6'>
{children}
{isOpen && <CreditManager />}
<DesktopNavigation />
<main className='relative flex lg:h-[calc(100vh-120px)]'>
<div className='flex flex-grow flex-wrap p-6'>{children}</div>
{isOpen && hasCreditAccounts && <CreditManager />}
{!isOpen && hasCreditAccounts && (
<div className='absolute top-0 right-0 border-l border-b border-white/20 bg-header p-4'>
<div className='w-5 hover:cursor-pointer' onClick={toggleCreditManager}>
<ArrowLeftLine />
</div>
</div>
)}
</main>
</div>
)

View File

@ -9,29 +9,27 @@ interface Props {
content?: ReactNode | string
className?: string
open: boolean
setOpen: (open: boolean) => void
setOpen?: (open: boolean) => void
}
const Modal = ({ children, content, className, open, setOpen }: Props) => {
const onClickAway = () => {
setOpen(false)
if (setOpen) setOpen(false)
}
return open ? (
<>
<Card
className={classNames(
'absolute top-0 left-0 right-0 bottom-0 z-40 mx-auto my-0 w-[790px] max-w-full',
className,
)}
>
<div className='fixed top-0 left-0 z-20 h-screen w-screen'>
<div className='relative flex h-full w-full items-center justify-center'>
<Card className={classNames('relative z-40 w-[790px] max-w-full p-0', className)}>
{setOpen && (
<span
className='absolute top-4 right-4 w-[32px] opacity-40 hover:cursor-pointer hover:opacity-80'
className='absolute top-4 right-4 z-50 w-[32px] text-white opacity-60 hover:cursor-pointer hover:opacity-100'
onClick={onClickAway}
role='button'
>
<CloseIcon />
</span>
)}
{children ? children : content}
</Card>
<div
@ -39,7 +37,8 @@ const Modal = ({ children, content, className, open, setOpen }: Props) => {
onClick={onClickAway}
role='button'
/>
</>
</div>
</div>
) : null
}

View File

@ -1,233 +0,0 @@
import { Popover } from '@headlessui/react'
import BigNumber from 'bignumber.js'
import Image from 'next/image'
import Link from 'next/link'
import { useRouter } from 'next/router'
import { useMemo } from 'react'
import ChevronDownIcon from 'components/Icons/expand.svg'
import Button from 'components/Button'
import CircularProgress from 'components/CircularProgress'
import ArrowRightLineIcon from 'components/Icons/arrow-right-line.svg'
import ProgressBar from 'components/ProgressBar'
import SearchInput from 'components/SearchInput'
import SemiCircleProgress from 'components/SemiCircleProgress'
import Wallet from 'components/Wallet'
import useCreateCreditAccount from 'hooks/mutations/useCreateCreditAccount'
import useDeleteCreditAccount from 'hooks/mutations/useDeleteCreditAccount'
import useAccountStats from 'hooks/useAccountStats'
import useCreditAccounts from 'hooks/useCreditAccounts'
import useCreditManagerStore from 'stores/useCreditManagerStore'
import useWalletStore from 'stores/useWalletStore'
import { chain } from 'utils/chains'
import { formatCurrency } from 'utils/formatters'
// TODO: will require some tweaks depending on how lower viewport mocks pans out
const MAX_VISIBLE_CREDIT_ACCOUNTS = 5
const navItems = [
{ href: '/trade', label: 'Trade' },
{ href: '/earn', label: 'Earn' },
{ href: '/borrow', label: 'Borrow' },
{ href: '/portfolio', label: 'Portfolio' },
{ href: '/council', label: 'Council' },
]
const NavLink = ({ href, children }: { href: string; children: string }) => {
const router = useRouter()
return (
<Link href={href} passHref>
<a className={`${router.pathname === href ? 'text-white' : ''} hover:text-white`}>
{children}
</a>
</Link>
)
}
const Navigation = () => {
const address = useWalletStore((s) => s.address)
const selectedAccount = useCreditManagerStore((s) => s.selectedAccount)
const setSelectedAccount = useCreditManagerStore((s) => s.actions.setSelectedAccount)
const toggleCreditManager = useCreditManagerStore((s) => s.actions.toggleCreditManager)
const { data: creditAccountsList, isLoading: isLoadingCreditAccounts } = useCreditAccounts()
const { mutate: createCreditAccount, isLoading: isLoadingCreate } = useCreateCreditAccount()
const { mutate: deleteCreditAccount, isLoading: isLoadingDelete } = useDeleteCreditAccount(
selectedAccount || '',
)
const accountStats = useAccountStats()
const { firstCreditAccounts, restCreditAccounts } = useMemo(() => {
return {
firstCreditAccounts: creditAccountsList?.slice(0, MAX_VISIBLE_CREDIT_ACCOUNTS) ?? [],
restCreditAccounts: creditAccountsList?.slice(MAX_VISIBLE_CREDIT_ACCOUNTS) ?? [],
}
}, [creditAccountsList])
const isConnected = !!address
const hasCreditAccounts = creditAccountsList && creditAccountsList.length > 0
const rightSideContent = () => {
if ((!isConnected && !hasCreditAccounts) || isLoadingCreditAccounts) {
return null
}
if (!hasCreditAccounts) {
return <Button onClick={() => createCreditAccount()}>Create Credit Account</Button>
}
return (
<div className='flex items-center gap-4'>
{accountStats && (
<>
<p>
{formatCurrency(
BigNumber(accountStats.netWorth)
.dividedBy(10 ** chain.stakeCurrency.coinDecimals)
.toNumber(),
)}
</p>
{/* TOOLTIP */}
<div title={`${String(accountStats.currentLeverage.toFixed(1))}x`}>
<SemiCircleProgress
value={accountStats.currentLeverage / accountStats.maxLeverage}
label='Lvg'
/>
</div>
<SemiCircleProgress value={accountStats.risk} label='Risk' />
<ProgressBar value={accountStats.health} />
</>
)}
<div
className='flex w-16 cursor-pointer justify-center hover:text-white'
onClick={toggleCreditManager}
>
<ArrowRightLineIcon />
</div>
</div>
)
}
return (
<div className='relative'>
{/* Main navigation bar */}
<div className='flex items-center justify-between border-b border-white/20 px-6 py-3'>
<Link href='/' passHref>
<a>
<Image src='/logo.svg' alt='mars' width={40} height={40} />
</a>
</Link>
<div className='flex gap-5 px-12 text-white/40'>
{navItems.map((item, index) => (
<NavLink key={index} href={item.href}>
{item.label}
</NavLink>
))}
</div>
<Wallet />
</div>
{/* Sub navigation bar */}
<div className='flex items-center justify-between border-b border-white/20 px-6 py-3 text-sm text-white/40'>
<div className='flex items-center'>
<SearchInput />
{isConnected && hasCreditAccounts && (
<>
{firstCreditAccounts.map((account) => (
<div
key={account}
className={`cursor-pointer px-4 hover:text-white ${
selectedAccount === account ? 'text-white' : ''
}`}
onClick={() => setSelectedAccount(account)}
>
Account {account}
</div>
))}
{restCreditAccounts.length > 0 && (
<Popover className='relative'>
<Popover.Button>
<div className='flex cursor-pointer items-center px-3 hover:text-white'>
More
<span className='ml-1 h-4 w-4'>
<ChevronDownIcon />
</span>
</div>
</Popover.Button>
<Popover.Panel className='absolute z-10 w-[200px] pt-2'>
<div className='rounded-2xl bg-white p-4 text-gray-900'>
{restCreditAccounts.map((account) => (
<div
key={account}
className={`cursor-pointer hover:text-orange-500 ${
selectedAccount === account ? 'text-orange-500' : ''
}`}
onClick={() => setSelectedAccount(account)}
>
Account {account}
</div>
))}
</div>
</Popover.Panel>
</Popover>
)}
<Popover className='relative'>
<Popover.Button>
<div className='flex cursor-pointer items-center px-3 hover:text-white'>
Manage
<ChevronDownIcon className='ml-1 h-4 w-4' />
</div>
</Popover.Button>
<Popover.Panel className='absolute z-10 w-[200px] pt-2'>
{({ close }) => (
<div className='rounded-2xl bg-white p-4 text-gray-900'>
<div
className='mb-2 cursor-pointer hover:text-orange-500'
onClick={() => {
close()
createCreditAccount()
}}
>
Create Account
</div>
<div
className='mb-2 cursor-pointer hover:text-orange-500'
onClick={() => {
close()
deleteCreditAccount()
}}
>
Close Account
</div>
<div
className='mb-2 cursor-pointer hover:text-orange-500'
onClick={() => alert('TODO')}
>
Transfer Balance
</div>
<div
className='cursor-pointer hover:text-orange-500'
onClick={() => alert('TODO')}
>
Rearrange
</div>
</div>
)}
</Popover.Panel>
</Popover>
</>
)}
</div>
{rightSideContent()}
</div>
{(isLoadingCreate || isLoadingDelete) && (
<div className='absolute inset-0 z-40 grid place-items-center bg-black/50'>
<CircularProgress />
</div>
)}
</div>
)
}
export default Navigation

View File

@ -0,0 +1,80 @@
import Link from 'next/link'
import { AccountStatus, SubAccountNavigation } from 'components/Account'
import CircularProgress from 'components/CircularProgress'
import Logo from 'components/Icons/logo.svg'
import Modal from 'components/Modal'
import { menuTree, NavLink } from 'components/Navigation'
import SearchInput from 'components/Navigation/SearchInput'
import Text from 'components/Text'
import Wallet from 'components/Wallet'
import useCreateCreditAccount from 'hooks/mutations/useCreateCreditAccount'
import useDeleteCreditAccount from 'hooks/mutations/useDeleteCreditAccount'
import useCreditAccounts from 'hooks/useCreditAccounts'
import useCreditManagerStore from 'stores/useCreditManagerStore'
import useWalletStore from 'stores/useWalletStore'
const Navigation = () => {
const address = useWalletStore((s) => s.address)
const selectedAccount = useCreditManagerStore((s) => s.selectedAccount)
const setSelectedAccount = useCreditManagerStore((s) => s.actions.setSelectedAccount)
const { mutate: createCreditAccount, isLoading: isLoadingCreate } = useCreateCreditAccount()
const { mutate: deleteCreditAccount, isLoading: isLoadingDelete } = useDeleteCreditAccount(
selectedAccount || '',
)
const { data: creditAccountsList, isLoading: isLoadingCreditAccounts } = useCreditAccounts()
const isConnected = !!address
const hasCreditAccounts = creditAccountsList && creditAccountsList.length > 0
return (
<div className='relative hidden bg-header lg:block'>
<div className='flex items-center justify-between border-b border-white/20 px-6 py-3'>
<div className='flex flex-grow items-center'>
<Link href='/trade' passHref>
<span className='h-10 w-10'>
<Logo />
</span>
</Link>
<div className='flex gap-8 px-6'>
{menuTree.map((item, index) => (
<NavLink key={index} href={item.href}>
{item.label}
</NavLink>
))}
</div>
</div>
<Wallet />
</div>
{/* Sub navigation bar */}
<div className='flex items-center justify-between border-b border-white/20 pl-6 text-sm text-white/40'>
<div className='flex items-center'>
<SearchInput />
{isConnected && hasCreditAccounts && (
<SubAccountNavigation
selectedAccount={selectedAccount}
createCreditAccount={createCreditAccount}
deleteCreditAccount={deleteCreditAccount}
setSelectedAccount={setSelectedAccount}
creditAccountsList={creditAccountsList}
/>
)}
</div>
{isConnected && <AccountStatus createCreditAccount={createCreditAccount} />}
</div>
<Modal open={isLoadingCreate || isLoadingDelete}>
<div className='w-full p-6'>
<Text size='2xl' uppercase={true} className='mb-6 w-full text-center'>
Confirm Transaction
</Text>
<div className='flex w-full justify-center pb-6'>
<CircularProgress size={40} />
</div>
</div>
</Modal>
</div>
)
}
export default Navigation

View File

@ -0,0 +1,29 @@
import Link from 'next/link'
import { useRouter } from 'next/router'
import { ReactNode } from 'react'
import classNames from 'classnames'
interface Props {
href: string
children: string | ReactNode
}
const NavLink = ({ href, children }: Props) => {
const router = useRouter()
const isActive = router.pathname === href
return (
<Link href={href} passHref>
<a
className={classNames(
'text-lg-caps hover:text-white active:text-white',
isActive ? 'pointer-events-none text-white' : 'text-white/60',
)}
>
{children}
</a>
</Link>
)
}
export default NavLink

View File

@ -0,0 +1,14 @@
import SearchIcon from 'components/Icons/search.svg'
const SearchInput = () => (
<div className='relative mt-[1px] py-2 text-white'>
<span className='absolute top-1/2 left-0 flex h-6 w-8 -translate-y-1/2 items-center pl-2'>
<SearchIcon />
</span>
<input
className='w-[280px] rounded-md border border-white/20 bg-black/30 py-2 pl-10 text-sm text-white placeholder:text-white/40 focus:outline-none'
placeholder='Search'
/>
</div>
)
export default SearchInput

View File

@ -0,0 +1,3 @@
export { default as DesktopNavigation } from './DesktopNavigation'
export { default as menuTree } from './menuTree'
export { default as NavLink } from './NavLink'

View File

@ -0,0 +1,9 @@
const navItems = [
{ href: '/trade', label: 'Trade' },
{ href: '/earn', label: 'Earn' },
{ href: '/borrow', label: 'Borrow' },
{ href: '/portfolio', label: 'Portfolio' },
{ href: '/council', label: 'Council' },
]
export default navItems

View File

@ -18,7 +18,7 @@ const Overlay = ({ children, content, className, show, setShow }: Props) => {
<>
<div
className={classNames(
'absolute z-50 flex max-w-full rounded-lg p-6 text-accent-dark shadow-overlay gradient-popover',
'max-w-screen absolute z-50 rounded-lg shadow-overlay gradient-popover',
className,
)}
>

View File

@ -1,25 +0,0 @@
import React from 'react'
const SearchInput = () => (
<div className='relative text-white'>
<span className='absolute inset-y-0 left-0 flex items-center pl-2'>
<svg
fill='none'
stroke='currentColor'
strokeLinecap='round'
strokeLinejoin='round'
strokeWidth='2'
viewBox='0 0 24 24'
className='h-6 w-6'
>
<path d='M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z'></path>
</svg>
</span>
<input
className='rounded-md bg-black/70 py-2 pl-10 text-sm text-white focus:outline-none'
placeholder='Search'
/>
</div>
)
export default SearchInput

View File

@ -1,112 +0,0 @@
import React from 'react'
type Props = {
stroke?: string
strokeWidth?: number
background?: string
diameter?: 60
orientation?: any
direction?: any
value: number
label?: string
}
const SemiCircleProgress = ({
stroke = '#02B732',
strokeWidth = 6,
background = '#D0D0CE',
diameter = 60,
orientation = 'up',
direction = 'right',
value = 0,
label,
}: Props) => {
const coordinateForCircle = diameter / 2
const radius = (diameter - 2 * strokeWidth) / 2
const circumference = Math.PI * radius
const percentage = value * 100
let percentageValue
if (percentage > 100) {
percentageValue = 100
} else if (percentage < 0) {
percentageValue = 0
} else {
percentageValue = percentage
}
const semiCirclePercentage = percentageValue * (circumference / 100)
let rotation
if (orientation === 'down') {
if (direction === 'left') {
rotation = 'rotate(180deg) rotateY(180deg)'
} else {
rotation = 'rotate(180deg)'
}
} else {
if (direction === 'right') {
rotation = 'rotateY(180deg)'
}
}
let strokeColorClass = 'stroke-green-500'
if (value > 2 / 3) {
strokeColorClass = 'stroke-red-500'
} else if (value > 1 / 3) {
strokeColorClass = 'stroke-yellow-500'
}
return (
<div className='semicircle-container' style={{ position: 'relative' }}>
<svg
width={diameter}
height={diameter / 2}
style={{ transform: rotation, overflow: 'hidden' }}
>
<circle
cx={coordinateForCircle}
cy={coordinateForCircle}
r={radius}
fill='none'
stroke={background}
strokeWidth={strokeWidth}
strokeDasharray={circumference}
style={{
strokeDashoffset: circumference,
}}
/>
<circle
className={strokeColorClass}
cx={coordinateForCircle}
cy={coordinateForCircle}
r={radius}
fill='none'
// stroke={stroke}
strokeWidth={strokeWidth}
strokeDasharray={circumference}
style={{
strokeDashoffset: semiCirclePercentage,
transition: 'stroke-dashoffset .3s ease 0s, stroke-dasharray .3s ease 0s, stroke .3s',
}}
/>
</svg>
{label && (
<span
className='text-xs'
style={{
width: '100%',
left: '0',
textAlign: 'center',
bottom: orientation === 'down' ? 'auto' : '-4px',
position: 'absolute',
}}
>
{label}
</span>
)}
</div>
)
}
export default SemiCircleProgress

View File

@ -1,5 +1,5 @@
import classNames from 'classnames'
import React, { ReactNode } from 'react'
import React, { LegacyRef, ReactNode } from 'react'
interface Props extends React.HTMLProps<HTMLAnchorElement> {
children?: string | ReactNode
@ -8,6 +8,7 @@ interface Props extends React.HTMLProps<HTMLAnchorElement> {
externalLink?: boolean
textSize?: 'small' | 'medium' | 'large'
text?: string | ReactNode
uppercase?: boolean
}
const colorClasses = {
@ -17,7 +18,7 @@ const colorClasses = {
'text-secondary hover:text-secondary-highlight active:text-secondary-highlight-10 focus:text-secondary-highlight',
tertiary:
'text-secondary-dark/60 hover:text-secondary-dark active:text-secondary-dark-10 focus:text-secondary-dark',
quaternary: 'text-transparent text-white/60 hover:text-white active:text-white',
quaternary: 'hover:text-white active:text-white',
}
const textSizeClasses = {
small: 'text-sm',
@ -25,7 +26,8 @@ const textSizeClasses = {
large: 'text-lg',
}
const TextLink = ({
const TextLink = React.forwardRef(function TextLink(
{
children,
className = '',
color = 'primary',
@ -34,23 +36,25 @@ const TextLink = ({
href,
textSize = 'small',
text,
uppercase,
onClick,
...restProps
}: Props) => {
const linkClasses = classNames(
textSizeClasses[textSize],
}: Props,
ref,
) {
return (
<a
className={classNames(
uppercase ? `${textSizeClasses[textSize]}-caps` : textSizeClasses[textSize],
colorClasses[color],
disabled && 'pointer-events-none opacity-50',
className,
)
return (
<a
className={linkClasses}
)}
ref={ref as LegacyRef<HTMLAnchorElement>}
target={externalLink ? '_blank' : '_self'}
rel='noreferrer'
onClick={
onClick && href
onClick && !href
? (e) => {
e.preventDefault()
if (disabled) return
@ -65,6 +69,6 @@ const TextLink = ({
{children && children}
</a>
)
}
})
export default TextLink

View File

@ -1,356 +0,0 @@
import { Dialog, Switch, Transition } from '@headlessui/react'
import BigNumber from 'bignumber.js'
import React, { useEffect, useMemo, useState } from 'react'
import { toast } from 'react-toastify'
import Slider from 'components/Slider'
import useWithdrawFunds from 'hooks/mutations/useWithdrawFunds'
import useAllBalances from 'hooks/useAllBalances'
import useCalculateMaxWithdrawAmount from 'hooks/useCalculateMaxWithdrawAmount'
import useCreditAccountPositions from 'hooks/useCreditAccountPositions'
import useMarkets from 'hooks/useMarkets'
import useTokenPrices from 'hooks/useTokenPrices'
import useCreditManagerStore from 'stores/useCreditManagerStore'
import { formatCurrency } from 'utils/formatters'
import { getTokenDecimals, getTokenSymbol } from 'utils/tokens'
import useAccountStats, { AccountStatsAction } from 'hooks/useAccountStats'
import { chain } from 'utils/chains'
import Button from 'components/Button'
import ContainerSecondary from 'components/ContainerSecondary'
import ProgressBar from 'components/ProgressBar'
import SemiCircleProgress from 'components/SemiCircleProgress'
import CircularProgress from 'components/CircularProgress'
const WithdrawModal = ({ show, onClose }: any) => {
const [amount, setAmount] = useState(0)
const [selectedToken, setSelectedToken] = useState('')
const [isBorrowEnabled, setIsBorrowEnabled] = useState(false)
const selectedAccount = useCreditManagerStore((s) => s.selectedAccount)
const { data: positionsData, isLoading: isLoadingPositions } = useCreditAccountPositions(
selectedAccount ?? '',
)
const { data: balancesData } = useAllBalances()
const { data: tokenPrices } = useTokenPrices()
const { data: marketsData } = useMarkets()
const selectedTokenSymbol = getTokenSymbol(selectedToken)
const selectedTokenDecimals = getTokenDecimals(selectedToken)
const tokenAmountInCreditAccount = useMemo(() => {
return BigNumber(positionsData?.coins.find((coin) => coin.denom === selectedToken)?.amount ?? 0)
.div(10 ** selectedTokenDecimals)
.toNumber()
}, [positionsData, selectedTokenDecimals, selectedToken])
const { actions, borrowAmount, withdrawAmount } = useMemo(() => {
const borrowAmount =
amount > tokenAmountInCreditAccount
? BigNumber(amount)
.minus(tokenAmountInCreditAccount)
.times(10 ** selectedTokenDecimals)
.toNumber()
: 0
const withdrawAmount = BigNumber(amount)
.times(10 ** selectedTokenDecimals)
.toNumber()
return {
borrowAmount,
withdrawAmount,
actions: [
{
type: 'borrow',
amount: borrowAmount,
denom: selectedToken,
},
{
type: 'withdraw',
amount: withdrawAmount,
denom: selectedToken,
},
] as AccountStatsAction[],
}
}, [amount, selectedToken, selectedTokenDecimals, tokenAmountInCreditAccount])
const accountStats = useAccountStats(actions)
const { mutate, isLoading } = useWithdrawFunds(withdrawAmount, borrowAmount, selectedToken, {
onSuccess: () => {
onClose()
toast.success(`${amount} ${selectedTokenSymbol} successfully withdrawn`)
},
})
const maxWithdrawAmount = useCalculateMaxWithdrawAmount(selectedToken, isBorrowEnabled)
const walletAmount = useMemo(() => {
if (!selectedToken) return 0
return BigNumber(balancesData?.find((balance) => balance.denom === selectedToken)?.amount ?? 0)
.div(10 ** selectedTokenDecimals)
.toNumber()
}, [balancesData, selectedToken, selectedTokenDecimals])
useEffect(() => {
if (positionsData && positionsData.coins.length > 0) {
// initialize selected token when allowedCoins fetch data is available
setSelectedToken(positionsData.coins[0].denom)
}
}, [positionsData])
const handleTokenChange = (e: React.ChangeEvent<HTMLSelectElement>) => {
setSelectedToken(e.target.value)
if (e.target.value !== selectedToken) setAmount(0)
}
const handleValueChange = (value: number) => {
if (value > maxWithdrawAmount) {
setAmount(maxWithdrawAmount)
return
}
setAmount(value)
}
const handleBorrowChange = () => {
setIsBorrowEnabled((c) => !c)
// reset amount due to max value calculations changing depending on wheter the user is borrowing or not
setAmount(0)
}
const getTokenTotalUSDValue = (amount: string, denom: string) => {
// early return if prices are not fetched yet
if (!tokenPrices) return 0
return (
BigNumber(amount)
.div(10 ** getTokenDecimals(denom))
.toNumber() * tokenPrices[denom]
)
}
const percentageValue = useMemo(() => {
if (isNaN(amount) || maxWithdrawAmount === 0) return 0
return (amount * 100) / maxWithdrawAmount
}, [amount, maxWithdrawAmount])
return (
<Transition appear show={show} as={React.Fragment}>
<Dialog as='div' className='relative z-10' onClose={onClose}>
<Transition.Child
as={React.Fragment}
enter='ease-out duration-300'
enterFrom='opacity-0'
enterTo='opacity-100'
leave='ease-in duration-200'
leaveFrom='opacity-100'
leaveTo='opacity-0'
>
<div className='fixed inset-0 bg-black bg-opacity-80' />
</Transition.Child>
<div className='fixed inset-0 overflow-y-auto'>
<div className='flex min-h-full items-center justify-center p-4'>
<Transition.Child
as={React.Fragment}
enter='ease-out duration-300'
enterFrom='opacity-0 scale-95'
enterTo='opacity-100 scale-100'
leave='ease-in duration-200'
leaveFrom='opacity-100 scale-100'
leaveTo='opacity-0 scale-95'
>
<Dialog.Panel className='flex w-full max-w-3xl transform overflow-hidden rounded-2xl bg-[#585A74] align-middle shadow-xl transition-all'>
{isLoading && (
<div className='absolute inset-0 z-40 grid place-items-center bg-black/50'>
<CircularProgress />
</div>
)}
<div className='flex w-1/2 flex-col p-4'>
<Dialog.Title as='h3' className='mb-4 text-center text-lg font-medium'>
Withdraw from Account {selectedAccount}
</Dialog.Title>
<div>
<ContainerSecondary className='mb-3 p-3'>
<div className='mb-4 text-sm'>
<div className='mb-1 flex justify-between'>
<div className='font-bold'>Asset:</div>
<select className='bg-transparent' onChange={handleTokenChange}>
{positionsData?.coins?.map((coin) => (
<option key={coin.denom} value={coin.denom}>
{getTokenSymbol(coin.denom)}
</option>
))}
</select>
</div>
<div className='flex justify-between'>
<div className='font-bold'>Amount:</div>
<input
type='number'
className='border border-black/50 bg-transparent px-2'
value={amount}
min='0'
onChange={(e) => handleValueChange(e.target.valueAsNumber)}
/>
</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
// limit decimal precision based on token contract decimals
const newAmount = Number(
(decimal * maxWithdrawAmount).toFixed(selectedTokenDecimals),
)
setAmount(newAmount)
}}
onMaxClick={() => setAmount(maxWithdrawAmount)}
/>
</ContainerSecondary>
<ContainerSecondary className='mb-10 flex items-center justify-between'>
<div className='text-left'>
<h3 className='font-bold'>Withdraw with borrowing</h3>
<div className='text-sm text-[#585A74]/50'>Explanation....</div>
</div>
<Switch
checked={isBorrowEnabled}
onChange={handleBorrowChange}
className={`${
isBorrowEnabled ? 'bg-blue-600' : 'bg-gray-400'
} relative inline-flex h-6 w-11 items-center rounded-full`}
>
<span
className={`${
isBorrowEnabled ? 'translate-x-6' : 'translate-x-1'
} inline-block h-4 w-4 transform rounded-full bg-white transition`}
/>
</Switch>
</ContainerSecondary>
</div>
<Button className='mt-auto w-full' onClick={() => mutate()}>
Withdraw
</Button>
</div>
<div className='flex w-1/2 flex-col justify-center bg-[#4A4C60] p-4'>
<p className='text-bold mb-3 text-xs uppercase text-white/50'>About</p>
<h4 className='mb-4 text-xl'>Subaccount {selectedAccount}</h4>
<div className='mb-2 rounded-md border border-white/20 p-3'>
{accountStats && (
<div className='flex items-center gap-x-3'>
<p className='flex-1 text-xs'>
{formatCurrency(
BigNumber(accountStats.netWorth)
.dividedBy(10 ** chain.stakeCurrency.coinDecimals)
.toNumber(),
)}
</p>
{/* TOOLTIP */}
<div title={`${String(accountStats.currentLeverage.toFixed(1))}x`}>
<SemiCircleProgress
value={accountStats.currentLeverage / accountStats.maxLeverage}
label='Lvg'
/>
</div>
<SemiCircleProgress value={accountStats.risk} label='Risk' />
<ProgressBar value={accountStats.health} />
</div>
)}
</div>
<div className='mb-2 rounded-md border border-white/20 p-3 text-sm'>
<div className='mb-1 flex justify-between'>
<div>Total Position:</div>
<div className='font-semibold'>
{formatCurrency(
BigNumber(accountStats?.totalPosition ?? 0)
.dividedBy(10 ** chain.stakeCurrency.coinDecimals)
.toNumber(),
)}
</div>
</div>
<div className='flex justify-between'>
<div>Total Liabilities:</div>
<div className='font-semibold'>
{formatCurrency(
BigNumber(accountStats?.totalDebt ?? 0)
.dividedBy(10 ** chain.stakeCurrency.coinDecimals)
.toNumber(),
)}
</div>
</div>
</div>
<div className='rounded-md border border-white/20 p-3'>
<h4 className='mb-2 font-bold'>Balances</h4>
{isLoadingPositions ? (
<div>Loading...</div>
) : (
<table className='w-full border-separate border-spacing-1'>
<thead className='text-left text-xs font-semibold'>
<tr>
<th>Asset</th>
<th>Value</th>
<th>Size</th>
<th className='text-right'>APY</th>
</tr>
</thead>
<tbody>
{accountStats?.assets.map((coin) => (
<tr key={coin.denom} className='text-xs text-white/50'>
<td>{getTokenSymbol(coin.denom)}</td>
<td>
{formatCurrency(getTokenTotalUSDValue(coin.amount, coin.denom))}
</td>
<td>
{BigNumber(coin.amount)
.div(10 ** getTokenDecimals(coin.denom))
.toNumber()
.toLocaleString(undefined, {
maximumFractionDigits: getTokenDecimals(coin.denom),
})}
</td>
<td className='text-right'>-</td>
</tr>
))}
{accountStats?.debts.map((coin) => (
<tr key={coin.denom} className='text-xs text-red-500'>
<td className='text-white/50'>{getTokenSymbol(coin.denom)}</td>
<td>
-{formatCurrency(getTokenTotalUSDValue(coin.amount, coin.denom))}
</td>
<td>
-
{BigNumber(coin.amount)
.div(10 ** getTokenDecimals(coin.denom))
.toNumber()
.toLocaleString(undefined, {
maximumFractionDigits: 6,
})}
</td>
<td className='text-right'>
-{(Number(marketsData?.[coin.denom].borrow_rate) * 100).toFixed(1)}%
</td>
</tr>
))}
</tbody>
</table>
)}
</div>
</div>
</Dialog.Panel>
</Transition.Child>
</div>
</div>
</Dialog>
</Transition>
)
}
export default WithdrawModal

View File

@ -53,15 +53,19 @@ const calculateStatsFromAccountPositions = (assets: Asset[], debts: Debt[]) => {
const netWorth = BigNumber(totalPosition).minus(totalDebt).toNumber()
const liquidationLTVsWeightedAverage = BigNumber(totalWeightedPositions)
.div(totalPosition)
.toNumber()
const liquidationLTVsWeightedAverage =
totalWeightedPositions === 0
? 0
: BigNumber(totalWeightedPositions).div(totalPosition).toNumber()
const maxLeverage = BigNumber(1)
.div(BigNumber(1).minus(liquidationLTVsWeightedAverage))
.toNumber()
const currentLeverage = BigNumber(totalPosition).div(netWorth).toNumber()
const health = BigNumber(1).minus(BigNumber(currentLeverage).div(maxLeverage)).toNumber() || 1
const currentLeverage = netWorth === 0 ? 0 : BigNumber(totalPosition).div(netWorth).toNumber()
const health =
maxLeverage === 0
? 1
: BigNumber(1).minus(BigNumber(currentLeverage).div(maxLeverage)).toNumber() || 1
const risk = liquidationLTVsWeightedAverage
? getRiskFromAverageLiquidationLTVs(liquidationLTVsWeightedAverage)

View File

@ -13,6 +13,15 @@ const nextConfig = {
// https://docs.sentry.io/platforms/javascript/guides/nextjs/manual-setup/#use-hidden-source-map
hideSourceMaps: true,
},
async redirects() {
return [
{
source: '/',
destination: '/trade',
permanent: true,
},
]
},
webpack(config) {
config.module.rules.push({
test: /\.svg$/i,

View File

@ -1,5 +1,5 @@
import BigNumber from 'bignumber.js'
import React, { useMemo, useRef, useState } from 'react'
import { useMemo, useRef, useState } from 'react'
import BorrowTable from 'components/Borrow/BorrowTable'
import BorrowModal from 'components/BorrowModal'
@ -116,7 +116,7 @@ const Borrow = () => {
}
return (
<div className='flex items-start gap-4'>
<div className='flex w-full items-start gap-4'>
<div className='flex-1'>
<Card className='mb-4'>
<div>

View File

@ -1,11 +1,14 @@
import React from 'react'
import Card from 'components/Card'
import Text from 'components/Text'
const Council = () => {
return (
<div>
<Card>Council Placeholder</Card>
<div className='flex w-full'>
<Card>
<Text size='lg' uppercase={true}>
Council Placeholder
</Text>
</Card>
</div>
)
}

View File

@ -1,12 +1,21 @@
import React from 'react'
import Card from 'components/Card'
import Text from 'components/Text'
const Earn = () => {
return (
<div className='flex gap-4'>
<Card className='flex-1'>Yield Module</Card>
<Card className='w-[450px]'>Placeholder</Card>
<div className='flex w-full gap-4'>
<Card>
<Text size='lg' uppercase={true}>
Yield Module
</Text>
</Card>
<div className='w-[450px]'>
<Card>
<Text size='lg' uppercase={true}>
Placeholder
</Text>
</Card>
</div>
</div>
)
}

View File

@ -1,369 +0,0 @@
import { SigningCosmWasmClient } from '@cosmjs/cosmwasm-stargate'
import { useQueryClient } from '@tanstack/react-query'
import BigNumber from 'bignumber.js'
import type { NextPage } from 'next'
import { useEffect, useState } from 'react'
import { toast } from 'react-toastify'
import Button from 'components/Button'
import Card from 'components/Card'
import CircularProgress from 'components/CircularProgress'
import Text from 'components/Text'
import { contractAddresses } from 'config/contracts'
import useMarkets from 'hooks/useMarkets'
import useTokenPrices from 'hooks/useTokenPrices'
import useCreditManagerStore from 'stores/useCreditManagerStore'
import useWalletStore from 'stores/useWalletStore'
import { queryKeys } from 'types/query-keys-factory'
import { chain } from 'utils/chains'
import { hardcodedFee } from 'utils/contants'
const Home: NextPage = () => {
const [sendAmount, setSendAmount] = useState('')
const [recipientAddress, setRecipientAddress] = useState('')
const [allTokens, setAllTokens] = useState<string[] | null>(null)
const [walletTokens, setWalletTokens] = useState<string[] | null>(null)
const [borrowAmount, setBorrowAmount] = useState(0)
const [error, setError] = useState(null)
const [isLoading, setIsLoading] = useState(false)
const address = useWalletStore((s) => s.address)
const selectedAccount = useCreditManagerStore((s) => s.selectedAccount)
const queryClient = useQueryClient()
const [signingClient, setSigningClient] = useState<SigningCosmWasmClient>()
const { data: marketsData } = useMarkets()
const { data: tokenPrices } = useTokenPrices()
useEffect(() => {
;(async () => {
if (!window.keplr) return
const offlineSigner = window.keplr.getOfflineSigner(chain.chainId)
const clientInstance = await SigningCosmWasmClient.connectWithSigner(chain.rpc, offlineSigner)
setSigningClient(clientInstance)
})()
}, [address])
const handleSendClick = async () => {
setError(null)
setIsLoading(true)
try {
// console.log(await signingClient.getHeight());
// console.log(
// "contract info",
// signingClient.getContract(
// "osmo1zf26ahe5gqjtvnedh7ems7naf2wtw3z4ll6atf3t0hptal8ss4vq2mlx6w"
// )
// );
const res = await signingClient?.sendTokens(
address,
recipientAddress,
[
{
denom: chain.stakeCurrency.coinMinimalDenom,
amount: BigNumber(sendAmount)
.times(10 ** chain.stakeCurrency.coinDecimals)
.toString(),
},
],
hardcodedFee,
)
console.log('txResponse', res)
toast.success(
<div>
<a
href={`https://testnet.mintscan.io/osmosis-testnet/txs/${res?.transactionHash}`}
target='_blank'
rel='noreferrer'
>
Check transaction
</a>
</div>,
{ autoClose: false },
)
} catch (e: any) {
console.log(e)
setError(e.message)
} finally {
setIsLoading(false)
}
}
const handleCreateCreditAccount = async () => {
setError(null)
setIsLoading(true)
try {
// 200000 gas used
const executeMsg = {
create_credit_account: {},
}
const createResult = await signingClient?.execute(
address,
contractAddresses.creditManager,
executeMsg,
hardcodedFee,
)
console.log('mint result', createResult)
toast.success(
<div>
<a
href={`https://testnet.mintscan.io/osmosis-testnet/txs/${createResult?.transactionHash}`}
target='_blank'
rel='noreferrer'
>
Check transaction
</a>
</div>,
{ autoClose: false },
)
queryClient.invalidateQueries(queryKeys.creditAccounts(address))
} catch (e: any) {
console.log(e)
setError(e.message)
} finally {
setIsLoading(false)
}
}
// https://github.com/mars-protocol/rover/blob/master/scripts/types/generated/account-nft/AccountNft.types.ts
const handleGetCreditAccounts = async () => {
setError(null)
setIsLoading(true)
try {
const allTokensQueryMsg = {
all_tokens: {},
}
const allTokensResponse = await signingClient?.queryContractSmart(
contractAddresses.accountNft,
allTokensQueryMsg,
)
setAllTokens(allTokensResponse.tokens)
console.log('all tokens', allTokensResponse)
// Returns de owner of a specific "credit account"
// const ownerOfQueryMsg = {
// owner_of: {
// include_expired: false,
// token_id: "1",
// },
// };
// const ownerResponse = await signingClient.queryContractSmart(
// contractAddresses.accountNft,
// ownerOfQueryMsg
// );
// console.log("res owner", ownerResponse);
const tokensQueryMsg = {
tokens: {
owner: address,
},
}
const tokensResponse = await signingClient?.queryContractSmart(
contractAddresses.accountNft,
tokensQueryMsg,
)
console.log('res tokens', tokensResponse)
setWalletTokens(tokensResponse.tokens)
} catch (e: any) {
console.log(e)
setError(e.message)
} finally {
setIsLoading(false)
}
}
const handleBorrowClick = async () => {
setError(null)
setIsLoading(true)
try {
if (!selectedAccount) return
const executeMsg = {
update_credit_account: {
account_id: selectedAccount,
actions: [
{
borrow: {
denom: 'uosmo',
amount: BigNumber(borrowAmount)
.times(10 ** 6)
.toString(),
},
},
],
},
}
const borrowResult = await signingClient?.execute(
address,
contractAddresses.creditManager,
executeMsg,
hardcodedFee,
)
console.log('borrow result', borrowResult)
toast.success(
<div>
<a
href={`https://testnet.mintscan.io/osmosis-testnet/txs/${borrowResult?.transactionHash}`}
target='_blank'
rel='noreferrer'
>
Check transaction
</a>
</div>,
{ autoClose: false },
)
queryClient.invalidateQueries(queryKeys.creditAccounts(address))
queryClient.invalidateQueries(queryKeys.creditAccountsPositions(selectedAccount))
queryClient.invalidateQueries(queryKeys.tokenBalance(address, 'uosmo'))
} catch (e: any) {
console.log(e)
setError(e.message)
} finally {
setIsLoading(false)
}
}
return (
<div className='mx-auto flex max-w-6xl flex-col gap-y-6'>
<Card>
<Text tag='h4' className='mb-5'>
Send Tokens
</Text>
<div className='mb-5 flex flex-wrap gap-2'>
<div>
<Text>Address:</Text>
<input
className='rounded-lg bg-black/40 px-3 py-1'
value={recipientAddress}
placeholder='address'
onChange={(e) => setRecipientAddress(e.target.value)}
/>
</div>
<div>
<Text>Amount:</Text>
<input
type='number'
className='rounded-lg bg-black/40 px-3 py-1'
value={sendAmount}
placeholder='amount'
onChange={(e) => setSendAmount(e.target.value)}
/>
</div>
</div>
<Button color='secondary' onClick={handleSendClick}>
Send
</Button>
</Card>
<Card>
<Text tag='h4' className='mb-5'>
Create Credit Account (Mint NFT)
</Text>
<Button color='secondary' onClick={handleCreateCreditAccount}>
Create
</Button>
</Card>
<Card>
<Text tag='h4' className='mb-5'>
Get all Credit Accounts
</Text>
<Button color='secondary' onClick={handleGetCreditAccounts}>
Fetch
</Button>
</Card>
<Card>
<Text tag='h4' className='mb-5'>
Borrow OSMO
</Text>
<input
className='rounded-lg bg-black/40 px-3 py-1'
type='number'
onChange={(e) => setBorrowAmount(e.target.valueAsNumber)}
/>
<Button className='ml-4' color='secondary' onClick={handleBorrowClick}>
Borrow
</Button>
</Card>
<div>
{allTokens && (
<div className='mb-4'>
<div className='flex items-end'>
<Text size='2xl'>All Tokens</Text>
<Text size='sm' className='ml-2'>
- {allTokens.length} total
</Text>
</div>
{allTokens.map((token) => (
<p key={token}>{token}</p>
))}
</div>
)}
{walletTokens && (
<>
<div className='flex items-end'>
<Text size='2xl'>Your Tokens</Text>
<Text size='sm' className='ml-2'>
- {walletTokens.length} total
</Text>
</div>
{walletTokens.map((token) => (
<p key={token}>{token}</p>
))}
</>
)}
</div>
<div>
{tokenPrices && (
<div className='mb-6'>
<h3 className='text-xl font-semibold'>Token Prices:</h3>
<pre>{JSON.stringify(tokenPrices, null, 2)}</pre>
</div>
)}
{marketsData && (
<div>
<h3 className='text-xl font-semibold'>Markets Data:</h3>
<pre>{JSON.stringify(marketsData, null, 2)}</pre>
</div>
)}
</div>
{error && (
<div className='mt-8 rounded-base bg-white p-4'>
<Text className='text-red-500'>{error}</Text>
</div>
)}
{isLoading && (
<div>
<CircularProgress />
</div>
)}
</div>
)
}
export default Home

View File

@ -1,10 +1,5 @@
import { useState } from 'react'
import Button from 'components/Button'
import Card from 'components/Card'
import Modal from 'components/Modal'
import Number from 'components/Number'
import Overlay from 'components/Overlay'
import FormattedNumber from 'components/FormattedNumber'
import Text from 'components/Text'
const mockedAccounts = [
@ -51,17 +46,12 @@ const mockedAccounts = [
]
const Portfolio = () => {
const [show, setShow] = useState<boolean>(false)
const [open, setOpen] = useState<boolean>(true)
return (
<div className='flex flex-col gap-4'>
<div className='flex w-full items-start gap-4'>
<Card className='flex-1'>
<span onClick={() => setShow(!show)} role='button'>
<Text size='lg' uppercase={true}>
Portfolio Module
</span>
<Overlay show={show} setShow={setShow}>
A test overlay
</Overlay>
</Text>
</Card>
<div className='grid grid-cols-2 gap-4'>
{mockedAccounts.map((account) => (
@ -72,7 +62,7 @@ const Portfolio = () => {
<div className='grid grid-cols-3 gap-4'>
<div>
<Text>
<Number amount={account.networth} animate={true} prefix='$' />
<FormattedNumber amount={account.networth} animate prefix='$' />
</Text>
<Text size='sm' className='text-white/40'>
Net worth
@ -80,7 +70,7 @@ const Portfolio = () => {
</div>
<div>
<Text>
<Number amount={account.totalPositionValue} animate={true} prefix='$' />
<FormattedNumber amount={account.totalPositionValue} animate prefix='$' />
</Text>
<Text size='sm' className='text-white/40'>
Total Position Value
@ -88,7 +78,7 @@ const Portfolio = () => {
</div>
<div>
<Text>
<Number amount={account.debt} animate={true} prefix='$' />
<FormattedNumber amount={account.debt} animate prefix='$' />
</Text>
<Text size='sm' className='text-white/40'>
Debt
@ -96,9 +86,9 @@ const Portfolio = () => {
</div>
<div>
<Text className={account.profit > 0 ? 'text-green-400' : 'text-red-500'}>
<Number
<FormattedNumber
amount={account.debt}
animate={true}
animate
prefix={account.profit > 0 ? '+$' : '$'}
/>
</Text>
@ -108,7 +98,7 @@ const Portfolio = () => {
</div>
<div>
<Text>
<Number amount={account.leverage} minDecimals={0} suffix='x' />
<FormattedNumber amount={account.leverage} minDecimals={0} suffix='x' />
</Text>
<Text size='sm' className='text-white/40'>
Current Leverage
@ -116,7 +106,7 @@ const Portfolio = () => {
</div>
<div>
<Text>
<Number amount={account.maxLeverage} minDecimals={0} suffix='x' />
<FormattedNumber amount={account.maxLeverage} minDecimals={0} suffix='x' />
</Text>
<Text size='sm' className='text-white/40'>
Max Leverage
@ -126,77 +116,6 @@ const Portfolio = () => {
</Card>
))}
</div>
<div className='w-full'>
<Button
onClick={() => {
setOpen(!open)
}}
text='Toggle Modal'
/>
</div>
<Modal open={open} setOpen={setOpen}>
{mockedAccounts.map((account) => (
<div key={account.id} className='mb-8'>
<Text size='lg' uppercase={true} className='mb-4 px-10 text-center'>
{account.label}
</Text>
<div className='grid grid-cols-3 gap-4'>
<div>
<Text>
<Number amount={account.networth} animate={true} prefix='$' />
</Text>
<Text size='sm' className='text-white/40'>
Net worth
</Text>
</div>
<div>
<Text>
<Number amount={account.totalPositionValue} animate={true} prefix='$' />
</Text>
<Text size='sm' className='text-white/40'>
Total Position Value
</Text>
</div>
<div>
<Text>
<Number amount={account.debt} animate={true} prefix='$' />
</Text>
<Text size='sm' className='text-white/40'>
Debt
</Text>
</div>
<div>
<Text className={account.profit > 0 ? 'text-green-400' : 'text-red-500'}>
<Number
amount={account.debt}
animate={true}
prefix={account.profit > 0 ? '+$' : '$'}
/>
</Text>
<Text size='sm' className='text-white/40'>
P&L
</Text>
</div>
<div>
<Text>
<Number amount={account.leverage} minDecimals={0} suffix='x' />
</Text>
<Text size='sm' className='text-white/40'>
Current Leverage
</Text>
</div>
<div>
<Text>
<Number amount={account.maxLeverage} minDecimals={0} suffix='x' />
</Text>
<Text size='sm' className='text-white/40'>
Max Leverage
</Text>
</div>
</div>
</div>
))}
</Modal>
</div>
)
}

View File

@ -1,22 +1,32 @@
import React from 'react'
import Card from 'components/Card'
import Text from 'components/Text'
import TradeActionModule from 'components/Trade/TradeActionModule'
const Trade = () => {
return (
<div>
<div className='mb-4 flex gap-4'>
<Card className='flex-1'>Graph/Tradingview Module</Card>
<div className='flex w-full flex-wrap'>
<div className='mb-4 flex flex-grow gap-4'>
<Card className='flex-1'>
<Text size='lg' uppercase={true}>
Tradingview Graph
</Text>
</Card>
<div className='flex flex-col gap-4'>
<Card>
<TradeActionModule />
</Card>
<Card>Orderbook module (optional)</Card>
<Card>
<Text size='lg' uppercase={true}>
Orderbook module (optional)
</Text>
</Card>
</div>
<Card>Credit Account essential module</Card>
</div>
<Card>Trader order overview</Card>
<Card>
<Text size='lg' uppercase={true}>
Order history
</Text>
</Card>
</div>
)
}

View File

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 210 KiB

View File

@ -13,7 +13,7 @@ interface CreditManagerStore {
const useCreditManagerStore = create<CreditManagerStore>()(
persist(
(set, get) => ({
isOpen: false,
isOpen: true,
selectedAccount: null,
actions: {
toggleCreditManager: () => set(() => ({ isOpen: !get().isOpen })),

View File

@ -12,6 +12,8 @@ module.exports = {
'text-xs',
'text-sm-caps',
'text-sm',
'text-base-caps',
'text-base',
'text-lg-caps',
'text-lg',
'text-xl-caps',
@ -29,9 +31,11 @@ module.exports = {
extend: {
animation: {
progress: 'spin 1.2s cubic-bezier(0.5, 0, 0.5, 1) infinite',
fadein: 'fadein 1s ease-in-out forwards',
},
backgroundImage: {
mars: 'url(/bg.svg)',
mars: 'url(/images/bg.svg)',
'fund-modal': 'url(/images/fund-modal-bg.png)',
},
backgroundSize: {
desktop: '100% auto',
@ -71,6 +75,7 @@ module.exports = {
'grey-highlight': '#4c4c4c',
'grey-light': '#bfbfbf',
'grey-medium': '#5f697a',
header: 'rgba(59, 25, 40, 0.4);',
input: '#282a33',
loss: '#f96363',
mars: '#a03b45',
@ -111,6 +116,12 @@ module.exports = {
hueRotate: {
'-82': '-82deg',
},
keyframes: {
fadein: {
'0%': { opacity: 0 },
'100%': { opacity: 1 },
},
},
letterSpacing: {
normal: 0,
wide: '2px',
@ -204,6 +215,13 @@ module.exports = {
fontWeight: theme('fontWeight.semibold'),
letterSpacing: theme('letterSpacing.wider'),
},
'.text-base-caps': {
fontSize: '15px',
lineHeight: '20px',
textTransform: 'uppercase',
fontWeight: theme('fontWeight.semibold'),
letterSpacing: theme('letterSpacing.wider'),
},
'.text-lg-caps': {
fontSize: '16.88px',
lineHeight: '24px',

View File

@ -1432,11 +1432,6 @@
resolved "https://registry.npmjs.org/@headlessui/react/-/react-1.7.0.tgz"
integrity sha512-/nDsijOXRwXVLpUBEiYuWguIBSIN3ZbKyah+KPUiD8bdIKtX1U/k+qLYUEr7NCQnSF2e4w1dr8me42ECuG3cvw==
"@heroicons/react@^2.0.11":
version "2.0.11"
resolved "https://registry.yarnpkg.com/@heroicons/react/-/react-2.0.11.tgz#2c6cf4c66d81142ec87c102502407d8c353558bb"
integrity sha512-bASjOgSSaYj8HqXWsOqaBiB6ZLalE/g90WYGgZ5lPm4KCCG7wPXntY4kzHf5NrLh6UBAcnPwvbiw1Ne9GYfJtw==
"@humanwhocodes/config-array@^0.10.4":
version "0.10.4"
resolved "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.10.4.tgz"
@ -2296,18 +2291,6 @@
resolved "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz"
integrity sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==
"@types/lodash.isequal@^4.5.6":
version "4.5.6"
resolved "https://registry.yarnpkg.com/@types/lodash.isequal/-/lodash.isequal-4.5.6.tgz#ff42a1b8e20caa59a97e446a77dc57db923bc02b"
integrity sha512-Ww4UGSe3DmtvLLJm2F16hDwEQSv7U0Rr8SujLUA2wHI2D2dm8kPu6Et+/y303LfjTIwSBKXB/YTUcAKpem/XEg==
dependencies:
"@types/lodash" "*"
"@types/lodash@*":
version "4.14.188"
resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.188.tgz#e4990c4c81f7c9b00c5ff8eae389c10f27980da5"
integrity sha512-zmEmF5OIM3rb7SbLCFYoQhO4dGt2FRM9AMkxvA3LaADOF1n8in/zGJlWji9fmafLoNyz+FoL6FE0SLtGIArD7w==
"@types/long@^4.0.1":
version "4.0.2"
resolved "https://registry.yarnpkg.com/@types/long/-/long-4.0.2.tgz#b74129719fc8d11c01868010082d483b7545591a"
@ -4827,11 +4810,6 @@ lodash.debounce@^4.0.8:
resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af"
integrity sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==
lodash.isequal@^4.5.0:
version "4.5.0"
resolved "https://registry.yarnpkg.com/lodash.isequal/-/lodash.isequal-4.5.0.tgz#415c4478f2bcc30120c22ce10ed3226f7d3e18e0"
integrity sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==
lodash.merge@^4.6.2:
version "4.6.2"
resolved "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz"