Mp 1691 credit account details (#67)

This commit is contained in:
Linkie Link 2022-12-13 14:21:30 +01:00 committed by GitHub
parent 4527423a50
commit 83fa513bf0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
219 changed files with 1323 additions and 1114 deletions

View File

@ -1,220 +0,0 @@
import BigNumber from 'bignumber.js'
import classNames from 'classnames'
import { useState } from 'react'
import Button from 'components/Button'
import FormattedNumber from 'components/FormattedNumber'
import ArrowRightLine from 'components/Icons/arrow-right-line.svg'
import ChevronDown from 'components/Icons/chevron-down.svg'
import ChevronLeft from 'components/Icons/chevron-left.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 { useAccountDetailsStore } from 'stores'
import { chain } from 'utils/chains'
import { getTokenDecimals, getTokenSymbol } from 'utils/tokens'
import AccountManageOverlay from './AccountManageOverlay'
const AccountDetails = () => {
const selectedAccount = useAccountDetailsStore((s) => s.selectedAccount)
const isOpen = useAccountDetailsStore((s) => s.isOpen)
const { data: positionsData, isLoading: isLoadingPositions } = useCreditAccountPositions(
selectedAccount ?? '',
)
const { data: tokenPrices } = useTokenPrices()
const { data: marketsData } = useMarkets()
const accountStats = useAccountStats()
const [showManageMenu, setShowManageMenu] = useState(false)
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={classNames(
'relative flex w-[400px] basis-[400px] flex-wrap content-start border-white/20 bg-header placeholder:border-l',
'transition-[margin] duration-1000 ease-in-out',
isOpen ? 'mr-0' : '-mr-[400px]',
)}
>
<Button
onClick={() => {
useAccountDetailsStore.setState({ isOpen: true })
}}
variant='text'
className={classNames(
'absolute top-1/2 -left-[20px] w-[21px] -translate-y-1/2 bg-header p-0',
'rounded-none rounded-tl-sm rounded-bl-sm',
'border border-white/20',
'transition-[opacity] delay-1000 duration-500 ease-in-out',
isOpen ? 'pointer-events-none opacity-0' : 'opacity-100',
)}
>
<span className='flex h-20 px-1 py-6 text-white/40 transition-[color] hover:text-white'>
<ChevronLeft />
</span>
</Button>
<div className='relative flex w-full flex-wrap items-center border-b border-white/20'>
<Button
variant='text'
className='flex flex-grow flex-nowrap items-center justify-center p-4 text-center text-white text-xl-caps'
onClick={() => setShowManageMenu(!showManageMenu)}
>
Account {selectedAccount}
<span className='ml-2 flex w-4'>
<ChevronDown />
</span>
</Button>
<div className='flex border-l border-white/20' onClick={() => {}}>
<Button
variant='text'
className='w-14 p-4 text-white/40 transition-[color] hover:cursor-pointer hover:text-white'
onClick={() => {
useAccountDetailsStore.setState({ isOpen: false })
}}
>
<ArrowRightLine />
</Button>
</div>
<AccountManageOverlay
className='top-[60px] left-[36px]'
show={showManageMenu}
setShow={setShowManageMenu}
/>
</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 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 className='flex-1 text-white'>
Asset
</Text>
<Text size='xs' uppercase className='flex-1 text-white'>
Value
</Text>
<Text size='xs' uppercase className='flex-1 text-white'>
Size
</Text>
<Text size='xs' uppercase 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 AccountDetails

View File

@ -1,24 +0,0 @@
import CircularProgress from 'components/CircularProgress'
import Modal from 'components/Modal'
import Text from 'components/Text'
import { useModalStore } from 'stores'
const ConfirmModal = () => {
const createOpen = useModalStore((s) => s.createAccountModal)
const deleteOpen = useModalStore((s) => s.deleteAccountModal)
return (
<Modal open={createOpen || deleteOpen}>
<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>
)
}
export default ConfirmModal

View File

@ -1,7 +0,0 @@
export { default as AccountDetails } from './AccountDetails'
export { default as AccountManageOverlay } from './AccountManageOverlay'
export { default as AccountNavigation } from './AccountNavigation'
export { default as AccountStatus } from './AccountStatus'
export { default as ConfirmModal } from './ConfirmModal'
export { default as FundAccountModal } from './FundAccountModal'
export { default as WithdrawModal } from './WithdrawModal'

View File

@ -1,2 +0,0 @@
export { default as AssetRow } from './AssetRow'
export { default as BorrowTable } from './BorrowTable'

View File

@ -1,75 +0,0 @@
import classNames from 'classnames'
import React, { useEffect, useRef } from 'react'
import { animated, useSpring } from 'react-spring'
import { formatValue } from 'utils/formatters'
interface Props {
amount: number
animate?: boolean
className?: string
minDecimals?: number
maxDecimals?: number
thousandSeparator?: boolean
prefix?: boolean | string
suffix?: boolean | string
rounded?: boolean
abbreviated?: boolean
}
const FormattedNumber = ({
amount,
animate = false,
className,
minDecimals = 2,
maxDecimals = 2,
thousandSeparator = true,
prefix = false,
suffix = false,
rounded = false,
abbreviated = false,
}: Props) => {
const prevAmountRef = useRef<number>(0)
useEffect(() => {
if (prevAmountRef.current !== amount) prevAmountRef.current = amount
}, [amount])
const springAmount = useSpring({
number: amount,
from: { number: prevAmountRef.current },
config: { duration: 1000 },
})
return (prevAmountRef.current === amount && amount === 0) || !animate ? (
<span className={classNames('number', className)}>
{formatValue(
amount,
minDecimals,
maxDecimals,
thousandSeparator,
prefix,
suffix,
rounded,
abbreviated,
)}
</span>
) : (
<animated.span className={classNames('number', className)}>
{springAmount.number.to((num) =>
formatValue(
num,
minDecimals,
maxDecimals,
thousandSeparator,
prefix,
suffix,
rounded,
abbreviated,
),
)}
</animated.span>
)
}
export default React.memo(FormattedNumber)

View File

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

View File

@ -1,2 +0,0 @@
export { default as Overlay } from './Overlay'
export { default as OverlayLink } from './OverlayLink'

View File

@ -1,3 +0,0 @@
export { default as ConnectButton } from './ConnectButton'
export { default as ConnectedButton } from './ConnectedButton'
export { default as Wallet } from './Wallet'

View File

@ -1,16 +1,9 @@
const { withSentryConfig } = require('@sentry/nextjs') const { withSentryConfig } = require('@sentry/nextjs')
// This file sets a custom webpack configuration to use your Next.js app
// with Sentry.
// https://nextjs.org/docs/api-reference/next.config.js/introduction
// https://docs.sentry.io/platforms/javascript/guides/nextjs/
/** @type {import('next').NextConfig} */ /** @type {import('next').NextConfig} */
const nextConfig = { const nextConfig = {
reactStrictMode: true, reactStrictMode: true,
// swcMinify: true,
sentry: { sentry: {
// https://docs.sentry.io/platforms/javascript/guides/nextjs/manual-setup/#use-hidden-source-map
hideSourceMaps: true, hideSourceMaps: true,
}, },
async redirects() { async redirects() {

View File

@ -5,9 +5,11 @@
"scripts": { "scripts": {
"build": "next build", "build": "next build",
"dev": "next dev", "dev": "next dev",
"format": "eslint . --ext=ts,tsx --fix && prettier --write ./**/*.ts ./**/*.tsx", "lint": "eslint ./src/ && yarn prettier-check",
"lint": "next lint", "format": "yarn index && eslint ./src/ --fix && prettier --write ./src/",
"start": "next start" "prettier-check": "prettier --ignore-path .gitignore --check ./src/",
"start": "next start",
"index": "vscode-generate-index-standalone src/"
}, },
"dependencies": { "dependencies": {
"@cosmjs/cosmwasm-stargate": "^0.29.4", "@cosmjs/cosmwasm-stargate": "^0.29.4",
@ -27,6 +29,7 @@
"ethereumjs-util": "^7.1.5", "ethereumjs-util": "^7.1.5",
"graphql": "^16.6.0", "graphql": "^16.6.0",
"graphql-request": "^5.0.0", "graphql-request": "^5.0.0",
"moment": "^2.29.4",
"next": "12.3.1", "next": "12.3.1",
"react": "18.2.0", "react": "18.2.0",
"react-dom": "18.2.0", "react-dom": "18.2.0",
@ -34,13 +37,14 @@
"react-spring": "^9.5.5", "react-spring": "^9.5.5",
"react-toastify": "^9.0.8", "react-toastify": "^9.0.8",
"react-use-clipboard": "^1.0.9", "react-use-clipboard": "^1.0.9",
"recharts": "^2.2.0",
"tailwindcss-border-gradient-radius": "^3.0.1", "tailwindcss-border-gradient-radius": "^3.0.1",
"use-local-storage-state": "^18.1.1", "use-local-storage-state": "^18.1.1",
"zustand": "^4.1.4" "zustand": "^4.1.4"
}, },
"devDependencies": { "devDependencies": {
"@svgr/webpack": "^6.4.0", "@svgr/webpack": "^6.4.0",
"@types/node": "18.11.11", "@types/node": "^18.11.13",
"@types/react": "18.0.26", "@types/react": "18.0.26",
"@types/react-dom": "18.0.9", "@types/react-dom": "18.0.9",
"autoprefixer": "^10.4.13", "autoprefixer": "^10.4.13",
@ -51,6 +55,7 @@
"prettier": "^2.7.1", "prettier": "^2.7.1",
"prettier-plugin-tailwindcss": "^0.1.13", "prettier-plugin-tailwindcss": "^0.1.13",
"tailwindcss": "^3.2.1", "tailwindcss": "^3.2.1",
"typescript": "4.8.2" "typescript": "4.8.2",
"vscode-generate-index-standalone": "^1.6.0"
} }
} }

Binary file not shown.

After

Width:  |  Height:  |  Size: 150 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 164 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 KiB

BIN
public/images/fund-bg.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 KiB

BIN
public/images/fund-bg.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 210 KiB

View File

@ -0,0 +1,111 @@
import classNames from 'classnames'
import { useEffect, useState } from 'react'
import { Button, LabelValuePair, PositionsList } from 'components'
import { AccountManageOverlay, RiskChart } from 'components/Account'
import { ArrowRightLine, ChevronDown, ChevronLeft } from 'components/Icons'
import { useAccountStats, useBalances } from 'hooks/data'
import { useAccountDetailsStore, useSettings } from 'stores'
import { chain } from 'utils/chains'
import { lookup } from 'utils/formatters'
import { createRiskData } from 'utils/risk'
export const AccountDetails = () => {
const enableAnimations = useSettings((s) => s.enableAnimations)
const selectedAccount = useAccountDetailsStore((s) => s.selectedAccount)
const isOpen = useAccountDetailsStore((s) => s.isOpen)
const balances = useBalances()
const accountStats = useAccountStats()
const [showManageMenu, setShowManageMenu] = useState(false)
const [riskData, setRiskData] = useState<RiskTimePair[]>()
useEffect(() => {
setRiskData(createRiskData(accountStats?.risk ?? 0))
}, [accountStats?.risk, selectedAccount])
return (
<div
className={classNames(
'relative flex w-[400px] basis-[400px] flex-wrap content-start border-white/20 bg-header',
enableAnimations && 'transition-[margin] duration-1000 ease-in-out',
isOpen ? 'mr-0' : '-mr-[400px]',
)}
>
<Button
onClick={() => {
useAccountDetailsStore.setState({ isOpen: true })
}}
variant='text'
className={classNames(
'absolute top-1/2 -left-[20px] w-[21px] -translate-y-1/2 bg-header p-0',
'rounded-none rounded-tl-sm rounded-bl-sm',
'border border-white/20',
enableAnimations && 'transition-[opacity] delay-1000 duration-500 ease-in-out',
isOpen ? 'pointer-events-none opacity-0' : 'opacity-100',
)}
>
<span
className={classNames(
'flex h-20 px-1 py-6 text-white/40 hover:text-white',
enableAnimations && 'transition-[color]',
)}
>
<ChevronLeft />
</span>
</Button>
<div className='relative flex w-full flex-wrap items-center border-b border-white/20'>
<Button
variant='text'
className='flex flex-grow flex-nowrap items-center justify-center p-4 text-center text-white text-xl-caps'
onClick={() => setShowManageMenu(!showManageMenu)}
>
Account {selectedAccount}
<span className='ml-2 flex w-4'>
<ChevronDown />
</span>
</Button>
<div className='flex border-l border-white/20' onClick={() => {}}>
<Button
variant='text'
className={classNames(
'w-14 p-4 text-white/40 hover:cursor-pointer hover:text-white',
enableAnimations && 'transition-[color]',
)}
onClick={() => {
useAccountDetailsStore.setState({ isOpen: false })
}}
>
<ArrowRightLine />
</Button>
</div>
<AccountManageOverlay
className='top-[60px] left-[36px]'
show={showManageMenu}
setShow={setShowManageMenu}
/>
</div>
<div className='flex w-full flex-wrap p-3'>
<LabelValuePair
className='mb-2'
label='Total Position:'
value={{
format: 'number',
amount: lookup(accountStats?.totalPosition ?? 0, chain.stakeCurrency.coinDenom),
prefix: '$',
}}
/>
<LabelValuePair
label='Total Liabilities:'
value={{
format: 'number',
amount: lookup(accountStats?.totalDebt ?? 0, chain.stakeCurrency.coinDenom),
prefix: '$',
}}
/>
</div>
{riskData && <RiskChart data={riskData} />}
<PositionsList title='Balances' data={balances} />
</div>
)
}

View File

@ -1,16 +1,9 @@
import { useEffect } from 'react' import { useEffect } from 'react'
import Button from 'components/Button' import { Button, Text } from 'components'
import PlusIcon from 'components/Icons/add.svg' import { Add, ArrowDown, ArrowsLeftRight, ArrowUp, Rubbish } from 'components/Icons'
import ArrowDown from 'components/Icons/arrow-down.svg' import { Overlay, OverlayAction } from 'components/Overlay'
import ArrowUp from 'components/Icons/arrow-up.svg' import { useCreateCreditAccount, useDeleteCreditAccount } from 'hooks/mutations'
import ArrowsLeftRight from 'components/Icons/arrows-left-right.svg'
import DeleteIcon from 'components/Icons/rubbish.svg'
import Overlay from 'components/Overlay/Overlay'
import OverlayAction from 'components/Overlay/OverlayLink'
import Text from 'components/Text'
import useCreateCreditAccount from 'hooks/mutations/useCreateCreditAccount'
import useDeleteCreditAccount from 'hooks/mutations/useDeleteCreditAccount'
import { useAccountDetailsStore, useModalStore } from 'stores' import { useAccountDetailsStore, useModalStore } from 'stores'
interface Props { interface Props {
@ -19,7 +12,7 @@ interface Props {
show: boolean show: boolean
} }
const AccountManageOverlay = ({ className, setShow, show }: Props) => { export const AccountManageOverlay = ({ className, setShow, show }: Props) => {
const selectedAccount = useAccountDetailsStore((s) => s.selectedAccount) const selectedAccount = useAccountDetailsStore((s) => s.selectedAccount)
const { mutate: createCreditAccount, isLoading: isLoadingCreate } = useCreateCreditAccount() const { mutate: createCreditAccount, isLoading: isLoadingCreate } = useCreateCreditAccount()
@ -73,13 +66,13 @@ const AccountManageOverlay = ({ className, setShow, show }: Props) => {
setShow={setShow} setShow={setShow}
text='Create New Account' text='Create New Account'
onClick={createCreditAccount} onClick={createCreditAccount}
icon={<PlusIcon />} icon={<Add />}
/> />
<OverlayAction <OverlayAction
setShow={setShow} setShow={setShow}
text='Close Account' text='Close Account'
onClick={deleteCreditAccount} onClick={deleteCreditAccount}
icon={<DeleteIcon />} icon={<Rubbish />}
/> />
<OverlayAction <OverlayAction
setShow={setShow} setShow={setShow}
@ -92,5 +85,3 @@ const AccountManageOverlay = ({ className, setShow, show }: Props) => {
</Overlay> </Overlay>
) )
} }
export default AccountManageOverlay

View File

@ -1,12 +1,11 @@
import classNames from 'classnames' import classNames from 'classnames'
import { useMemo, useState } from 'react' import { useMemo, useState } from 'react'
import Button from 'components/Button' import { Button } from 'components'
import ChevronDownIcon from 'components/Icons/chevron-down.svg' import { ChevronDown } from 'components/Icons'
import Overlay from 'components/Overlay/Overlay' import { Overlay } from 'components/Overlay'
import { useAccountDetailsStore } from 'stores' import { useAccountDetailsStore } from 'stores'
import { AccountManageOverlay } from 'components/Account'
import AccountManageOverlay from './AccountManageOverlay'
interface Props { interface Props {
creditAccountsList: string[] creditAccountsList: string[]
@ -15,7 +14,7 @@ interface Props {
const MAX_VISIBLE_CREDIT_ACCOUNTS = 5 const MAX_VISIBLE_CREDIT_ACCOUNTS = 5
const AccountNavigation = ({ creditAccountsList, selectedAccount }: Props) => { export const AccountNavigation = ({ creditAccountsList, selectedAccount }: Props) => {
const { firstCreditAccounts, restCreditAccounts } = useMemo(() => { const { firstCreditAccounts, restCreditAccounts } = useMemo(() => {
return { return {
firstCreditAccounts: creditAccountsList?.slice(0, MAX_VISIBLE_CREDIT_ACCOUNTS) ?? [], firstCreditAccounts: creditAccountsList?.slice(0, MAX_VISIBLE_CREDIT_ACCOUNTS) ?? [],
@ -53,7 +52,7 @@ const AccountNavigation = ({ creditAccountsList, selectedAccount }: Props) => {
> >
More More
<span className='ml-1 inline-block w-3'> <span className='ml-1 inline-block w-3'>
<ChevronDownIcon /> <ChevronDown />
</span> </span>
</Button> </Button>
<Overlay show={showMoreMenu} setShow={setShowMoreMenu}> <Overlay show={showMoreMenu} setShow={setShowMoreMenu}>
@ -92,7 +91,7 @@ const AccountNavigation = ({ creditAccountsList, selectedAccount }: Props) => {
> >
Manage Manage
<span className='ml-1 inline-block w-3'> <span className='ml-1 inline-block w-3'>
<ChevronDownIcon /> <ChevronDown />
</span> </span>
</Button> </Button>
@ -105,5 +104,3 @@ const AccountNavigation = ({ creditAccountsList, selectedAccount }: Props) => {
</> </>
) )
} }
export default AccountNavigation

View File

@ -1,19 +1,16 @@
import BigNumber from 'bignumber.js' import BigNumber from 'bignumber.js'
import { useEffect } from 'react' import { useEffect } from 'react'
import { Button, FormattedNumber, Gauge, Text } from 'components'
import { BorrowCapacity } from 'components/BorrowCapacity' import { BorrowCapacity } from 'components/BorrowCapacity'
import Button from 'components/Button' import { useAccountStats } from 'hooks/data'
import FormattedNumber from 'components/FormattedNumber' import { useCreateCreditAccount } from 'hooks/mutations'
import Gauge from 'components/Gauge' import { useCreditAccounts } from 'hooks/queries'
import Text from 'components/Text'
import useCreateCreditAccount from 'hooks/mutations/useCreateCreditAccount'
import useAccountStats from 'hooks/useAccountStats'
import useCreditAccounts from 'hooks/useCreditAccounts'
import { useModalStore } from 'stores' import { useModalStore } from 'stores'
import { chain } from 'utils/chains' import { chain } from 'utils/chains'
import { formatValue } from 'utils/formatters' import { formatValue } from 'utils/formatters'
const AccountStatus = () => { export const AccountStatus = () => {
const accountStats = useAccountStats() const accountStats = useAccountStats()
const { data: creditAccountsList } = useCreditAccounts() const { data: creditAccountsList } = useCreditAccounts()
const { mutate: createCreditAccount, isLoading: isLoadingCreate } = useCreateCreditAccount() const { mutate: createCreditAccount, isLoading: isLoadingCreate } = useCreateCreditAccount()
@ -81,4 +78,3 @@ const AccountStatus = () => {
</div> </div>
) )
} }
export default AccountStatus

View File

@ -0,0 +1,36 @@
import classNames from 'classnames'
import { CircularProgress, Modal, Text } from 'components'
import { MarsProtocol } from 'components/Icons'
import { useModalStore } from 'stores'
export const ConfirmModal = () => {
const createOpen = useModalStore((s) => s.createAccountModal)
const deleteOpen = useModalStore((s) => s.deleteAccountModal)
return (
<Modal open={createOpen || deleteOpen}>
<div
className={classNames(
'relative flex h-[630px] w-full flex-wrap items-center justify-center p-6',
createOpen && 'bg-create-modal',
deleteOpen && 'bg-delete-modal',
)}
>
<div className='w-full flex-wrap'>
<div className='flex w-full justify-center pb-6'>
<CircularProgress size={40} />
</div>
<Text size='2xl' uppercase={true} className='w-full text-center'>
{createOpen &&
'A small step for a Smart Contracts but a big leap for your financial freedom.'}
{deleteOpen && 'Some rovers have to be recycled once in a while...'}
</Text>
</div>
<div className='absolute bottom-8 left-8 flex w-[150px]'>
<MarsProtocol />
</div>
</div>
</Modal>
)
}

View File

@ -4,19 +4,14 @@ import { useEffect, useMemo, useState } from 'react'
import { toast } from 'react-toastify' import { toast } from 'react-toastify'
import useLocalStorageState from 'use-local-storage-state' import useLocalStorageState from 'use-local-storage-state'
import Button from 'components/Button' import { Button, CircularProgress, Modal, Slider, Text } from 'components'
import CircularProgress from 'components/CircularProgress' import { MarsProtocol } from 'components/Icons'
import MarsProtocolLogo from 'components/Icons/mars-protocol.svg' import { useDepositCreditAccount } from 'hooks/mutations'
import Modal from 'components/Modal' import { useAllBalances, useAllowedCoins } from 'hooks/queries'
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 { useAccountDetailsStore, useModalStore } from 'stores' import { useAccountDetailsStore, useModalStore } from 'stores'
import { getTokenDecimals, getTokenSymbol } from 'utils/tokens' import { getTokenDecimals, getTokenSymbol } from 'utils/tokens'
const FundAccountModal = () => { export const FundAccountModal = () => {
// --------------- // ---------------
// STORE // STORE
// --------------- // ---------------
@ -105,7 +100,7 @@ const FundAccountModal = () => {
</Text> </Text>
</div> </div>
<div className='w-[153px] text-white'> <div className='w-[153px] text-white'>
<MarsProtocolLogo /> <MarsProtocol />
</div> </div>
</div> </div>
@ -202,7 +197,7 @@ const FundAccountModal = () => {
<span <span
className={`${ className={`${
lendAssets ? 'translate-x-6' : 'translate-x-1' lendAssets ? 'translate-x-6' : 'translate-x-1'
} inline-block h-4 w-4 transform rounded-full bg-white transition`} } inline-block h-4 w-4 transform rounded-full bg-white`}
/> />
</Switch> </Switch>
</div> </div>
@ -218,5 +213,3 @@ const FundAccountModal = () => {
</Modal> </Modal>
) )
} }
export default FundAccountModal

View File

@ -0,0 +1,97 @@
import moment from 'moment'
import {
Area,
AreaChart,
CartesianGrid,
ResponsiveContainer,
Tooltip,
XAxis,
YAxis,
} from 'recharts'
import { FormattedNumber, Text } from 'components'
import { useAccountStats } from 'hooks/data'
import { useSettings } from 'stores'
import { formatValue } from 'utils/formatters'
export const RiskChart = ({ data }: RiskChartProps) => {
const enableAnimations = useSettings((s) => s.enableAnimations)
const accountStats = useAccountStats()
const currentRisk = accountStats?.risk ?? 0
return (
<div className='flex w-full flex-wrap overflow-hidden py-2'>
<FormattedNumber
className='px-3 pb-2 text-lg'
amount={currentRisk * 100}
maxDecimals={0}
minDecimals={0}
animate
prefix='Risk Score: '
suffix='/100'
/>
<div className='-ml-6 h-[100px] w-[412px]'>
<ResponsiveContainer width='100%' height='100%'>
<AreaChart
data={data}
margin={{
top: 0,
right: 0,
left: 0,
bottom: 0,
}}
>
<defs>
<linearGradient id='chartGradient' x1='0' y1='0' x2='0' y2='1'>
<stop offset='0%' stopColor={'#FFFFFF'} stopOpacity={0.2} />
<stop offset='100%' stopColor={'#FFFFFF'} stopOpacity={0.02} />
</linearGradient>
</defs>
<CartesianGrid
strokeDasharray='0'
horizontalPoints={[0, 20, 40, 60, 80, 100]}
vertical={false}
stroke='rgba(255,255,255,0.1)'
/>
<XAxis
stroke='rgba(255, 255, 255, 0.6)'
tickLine={false}
tickFormatter={(value) => {
return moment(value).format('DD')
}}
fontSize={10.53}
dataKey='date'
/>
<YAxis
ticks={[0, 20, 40, 60, 80, 100]}
tickLine={false}
fontSize={10.53}
stroke='rgba(255, 255, 255, 0.6)'
/>
<Tooltip
wrapperStyle={{ outline: 'none' }}
content={({ payload, label }) => {
if (payload && payload.length) {
const risk = Number(payload[0].value) ?? 0
return (
<div className='max-w-[320px] rounded-lg px-4 py-2 shadow-tooltip gradient-tooltip '>
<Text size='sm'>{moment(label).format('MM-DD-YYYY')}</Text>
<Text size='sm'>Risk: {formatValue(risk, 0, 0, true, false, '%')}</Text>
</div>
)
}
}}
/>
<Area
type='monotone'
dataKey='risk'
stroke='#FFFFFF'
fill='url(#chartGradient)'
isAnimationActive={enableAnimations}
/>
</AreaChart>
</ResponsiveContainer>
</div>
</div>
)
}

View File

@ -4,27 +4,27 @@ import classNames from 'classnames'
import React, { useEffect, useMemo, useState } from 'react' import React, { useEffect, useMemo, useState } from 'react'
import { toast } from 'react-toastify' import { toast } from 'react-toastify'
import {
Button,
CircularProgress,
FormattedNumber,
Gauge,
LabelValuePair,
Modal,
PositionsList,
Slider,
Text,
} from 'components'
import { BorrowCapacity } from 'components/BorrowCapacity' import { BorrowCapacity } from 'components/BorrowCapacity'
import Button from 'components/Button' import { useAccountStats, useBalances, useCalculateMaxWithdrawAmount } from 'hooks/data'
import CircularProgress from 'components/CircularProgress' import { useWithdrawFunds } from 'hooks/mutations'
import FormattedNumber from 'components/FormattedNumber' import { useCreditAccountPositions, useTokenPrices } from 'hooks/queries'
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 { useAccountDetailsStore, useModalStore } from 'stores' import { useAccountDetailsStore, useModalStore } from 'stores'
import { chain } from 'utils/chains' import { chain } from 'utils/chains'
import { formatValue } from 'utils/formatters' import { formatValue, lookup } from 'utils/formatters'
import { getTokenDecimals, getTokenSymbol } from 'utils/tokens' import { getTokenDecimals, getTokenSymbol } from 'utils/tokens'
const WithdrawModal = () => { export const WithdrawModal = () => {
// --------------- // ---------------
// STORE // STORE
// --------------- // ---------------
@ -45,9 +45,8 @@ const WithdrawModal = () => {
// --------------- // ---------------
// EXTERNAL HOOKS // EXTERNAL HOOKS
// --------------- // ---------------
const { data: balancesData } = useAllBalances()
const { data: tokenPrices } = useTokenPrices() const { data: tokenPrices } = useTokenPrices()
const { data: marketsData } = useMarkets() const balances = useBalances()
const selectedTokenSymbol = getTokenSymbol(selectedToken) const selectedTokenSymbol = getTokenSymbol(selectedToken)
const selectedTokenDecimals = getTokenDecimals(selectedToken) const selectedTokenDecimals = getTokenDecimals(selectedToken)
@ -100,14 +99,6 @@ const WithdrawModal = () => {
const maxWithdrawAmount = useCalculateMaxWithdrawAmount(selectedToken, isBorrowEnabled) 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(() => { useEffect(() => {
if (positionsData && positionsData.coins.length > 0) { if (positionsData && positionsData.coins.length > 0) {
// initialize selected token when allowedCoins fetch data is available // initialize selected token when allowedCoins fetch data is available
@ -248,7 +239,7 @@ const WithdrawModal = () => {
<span <span
className={`${ className={`${
isBorrowEnabled ? 'translate-x-6' : 'translate-x-1' isBorrowEnabled ? 'translate-x-6' : 'translate-x-1'
} inline-block h-4 w-4 transform rounded-full bg-white transition`} } inline-block h-4 w-4 transform rounded-full bg-white`}
/> />
</Switch> </Switch>
</div> </div>
@ -310,130 +301,28 @@ const WithdrawModal = () => {
)} )}
</div> </div>
<div className='flex w-full flex-wrap border-b border-white/20 p-6'> <div className='flex w-full flex-wrap border-b border-white/20 p-6'>
<div className='mb-2 flex w-full'> <LabelValuePair
<Text size='xs' className='flex-grow text-white/60'> className='mb-2'
Total Position: label='Total Position:'
</Text> value={{
format: 'number',
<Text size='xs' className='text-white/60'> amount: lookup(accountStats?.totalPosition ?? 0, chain.stakeCurrency.coinDenom),
<FormattedNumber prefix: '$',
amount={BigNumber(accountStats?.totalPosition ?? 0) }}
.dividedBy(10 ** chain.stakeCurrency.coinDecimals) />
.toNumber()} <LabelValuePair
prefix='$' label='Total Liabilities:'
animate value={{
/> format: 'number',
</Text> amount: lookup(accountStats?.totalDebt ?? 0, chain.stakeCurrency.coinDenom),
</div> prefix: '$',
<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 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 className='flex-1 text-white'>
Asset
</Text>
<Text size='xs' uppercase className='flex-1 text-white'>
Value
</Text>
<Text size='xs' uppercase className='flex-1 text-white'>
Size
</Text>
<Text size='xs' uppercase 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>
<PositionsList title='Balances' data={balances} />
</div> </div>
</div> </div>
</div> </div>
</Modal> </Modal>
) )
} }
export default WithdrawModal

View File

@ -0,0 +1,10 @@
// @index(['./*.tsx'], f => `export { ${f.name} } from '${f.path}'`)
export { AccountDetails } from './AccountDetails'
export { AccountManageOverlay } from './AccountManageOverlay'
export { AccountNavigation } from './AccountNavigation'
export { AccountStatus } from './AccountStatus'
export { ConfirmModal } from './ConfirmModal'
export { FundAccountModal } from './FundAccountModal'
export { RiskChart } from './RiskChart'
export { WithdrawModal } from './WithdrawModal'
// @endindex

View File

@ -1,9 +1,8 @@
import Image from 'next/image' import Image from 'next/image'
import React, { useState } from 'react' import React, { useState } from 'react'
import ChevronUpIcon from 'components/Icons/chevron-up.svg' import { Button } from 'components'
import ChevronDownIcon from 'components/Icons/chevron-down.svg' import { ChevronDown, ChevronUp } from 'components/Icons'
import Button from 'components/Button'
import { formatCurrency } from 'utils/formatters' import { formatCurrency } from 'utils/formatters'
type AssetRowProps = { type AssetRowProps = {
@ -23,7 +22,7 @@ type AssetRowProps = {
onRepayClick: () => void onRepayClick: () => void
} }
const AssetRow = ({ data, onBorrowClick, onRepayClick }: AssetRowProps) => { export const AssetRow = ({ data, onBorrowClick, onRepayClick }: AssetRowProps) => {
const [isExpanded, setIsExpanded] = useState(false) const [isExpanded, setIsExpanded] = useState(false)
return ( return (
@ -54,7 +53,7 @@ const AssetRow = ({ data, onBorrowClick, onRepayClick }: AssetRowProps) => {
</div> </div>
<div className='flex flex-1 items-center text-xs'>{data.marketLiquidity}</div> <div className='flex flex-1 items-center text-xs'>{data.marketLiquidity}</div>
<div className='flex w-[50px] items-center justify-end'> <div className='flex w-[50px] items-center justify-end'>
<div className='w-5'>{isExpanded ? <ChevronUpIcon /> : <ChevronDownIcon />}</div> <div className='w-5'>{isExpanded ? <ChevronUp /> : <ChevronDown />}</div>
</div> </div>
</div> </div>
{isExpanded && ( {isExpanded && (
@ -84,5 +83,3 @@ const AssetRow = ({ data, onBorrowClick, onRepayClick }: AssetRowProps) => {
</div> </div>
) )
} }
export default AssetRow

View File

@ -9,11 +9,9 @@ import {
import Image from 'next/image' import Image from 'next/image'
import React from 'react' import React from 'react'
import ChevronUpIcon from 'components/Icons/chevron-up.svg' import { ChevronDown, ChevronUp } from 'components/Icons'
import ChevronDownIcon from 'components/Icons/chevron-down.svg'
import { formatCurrency } from 'utils/formatters' import { formatCurrency } from 'utils/formatters'
import { AssetRow } from 'components/Borrow'
import AssetRow from './AssetRow'
interface Market { interface Market {
denom: string denom: string
@ -34,7 +32,7 @@ type Props = {
onRepayClick: (denom: string) => void onRepayClick: (denom: string) => void
} }
const BorrowTable = ({ data, onBorrowClick, onRepayClick }: Props) => { export const BorrowTable = ({ data, onBorrowClick, onRepayClick }: Props) => {
const [sorting, setSorting] = React.useState<SortingState>([]) const [sorting, setSorting] = React.useState<SortingState>([])
const columns = React.useMemo<ColumnDef<Market>[]>( const columns = React.useMemo<ColumnDef<Market>[]>(
@ -91,9 +89,7 @@ const BorrowTable = ({ data, onBorrowClick, onRepayClick }: Props) => {
width: 150, width: 150,
cell: ({ row }) => ( cell: ({ row }) => (
<div className='flex items-center justify-end'> <div className='flex items-center justify-end'>
<div className='w-5'> <div className='w-5'>{row.getIsExpanded() ? <ChevronUp /> : <ChevronDown />}</div>
{row.getIsExpanded() ? <ChevronUpIcon /> : <ChevronDownIcon />}
</div>
</div> </div>
), ),
}, },
@ -158,5 +154,3 @@ const BorrowTable = ({ data, onBorrowClick, onRepayClick }: Props) => {
</div> </div>
) )
} }
export default BorrowTable

View File

@ -0,0 +1,4 @@
// @index(['./**/*.tsx'], f => `export { ${f.name} } from '${f.path}'`)
export { AssetRow } from './AssetRow'
export { BorrowTable } from './BorrowTable'
// @endindex

View File

@ -1,9 +1,8 @@
import classNames from 'classnames' import classNames from 'classnames'
import { useEffect, useState } from 'react' import { useEffect, useState } from 'react'
import FormattedNumber from 'components/FormattedNumber' import { FormattedNumber, Text, Tooltip } from 'components'
import Text from 'components/Text' import { useSettings } from 'stores'
import Tooltip from 'components/Tooltip'
interface Props { interface Props {
balance: number balance: number
@ -28,26 +27,25 @@ export const BorrowCapacity = ({
hideValues, hideValues,
decimals = 2, decimals = 2,
}: Props) => { }: Props) => {
const enableAnimations = useSettings((s) => s.enableAnimations)
const [percentOfMaxRound, setPercentOfMaxRound] = useState(0) const [percentOfMaxRound, setPercentOfMaxRound] = useState(0)
const [percentOfMaxRange, setPercentOfMaxRange] = useState(0) const [percentOfMaxRange, setPercentOfMaxRange] = useState(0)
const [limitPercentOfMax, setLimitPercentOfMax] = useState(0) const [limitPercentOfMax, setLimitPercentOfMax] = useState(0)
useEffect( useEffect(() => {
() => { if (max === 0) {
if (max === 0) { setPercentOfMaxRound(0)
setPercentOfMaxRound(0) setPercentOfMaxRange(0)
setPercentOfMaxRange(0) setLimitPercentOfMax(0)
setLimitPercentOfMax(0) return
return }
}
const pOfMax = +((balance / max) * 100) const pOfMax = +((balance / max) * 100)
setPercentOfMaxRound(+(Math.round(pOfMax * 100) / 100)) setPercentOfMaxRound(+(Math.round(pOfMax * 100) / 100))
setPercentOfMaxRange(Math.min(Math.max(pOfMax, 0), 100)) setPercentOfMaxRange(Math.min(Math.max(pOfMax, 0), 100))
setLimitPercentOfMax((limit / max) * 100) setLimitPercentOfMax((limit / max) * 100)
}, // eslint-disable-next-line react-hooks/exhaustive-deps }, [balance, max, limit])
[balance, max],
)
return ( return (
<div className={classNames('flex items-center justify-center', className)}> <div className={classNames('flex items-center justify-center', className)}>
@ -63,7 +61,8 @@ export const BorrowCapacity = ({
{!hideValues && ( {!hideValues && (
<div <div
className={classNames( className={classNames(
'duration-800 transition-[opcity] delay-[1600ms] text-3xs-caps', enableAnimations && 'duration-800 transition-[opcity] delay-[1600ms]',
'text-3xs-caps',
limitPercentOfMax ? 'opacity-60' : 'opacity-0', limitPercentOfMax ? 'opacity-60' : 'opacity-0',
)} )}
> >
@ -79,7 +78,10 @@ export const BorrowCapacity = ({
> >
<div className='absolute h-full w-full rounded-lg shadow-inset gradient-hatched '> <div className='absolute h-full w-full rounded-lg shadow-inset gradient-hatched '>
<div <div
className='ease-loss absolute left-0 h-full max-w-full rounded-l-3xl bg-body-dark transition-[right] duration-1000' className={classNames(
'absolute left-0 h-full max-w-full rounded-l-3xl bg-body-dark',
enableAnimations && 'transition-[right] duration-1000 ease-linear',
)}
style={{ style={{
right: `${limitPercentOfMax ? 100 - limitPercentOfMax : 100}%`, right: `${limitPercentOfMax ? 100 - limitPercentOfMax : 100}%`,
}} }}
@ -87,7 +89,10 @@ export const BorrowCapacity = ({
<div className='absolute top-0 h-full w-full'> <div className='absolute top-0 h-full w-full'>
<div <div
className='h-full rounded-lg transition-[width] duration-1000 ease-linear' className={classNames(
'h-full rounded-lg',
enableAnimations && 'transition-[width] duration-1000 ease-linear',
)}
style={{ style={{
width: `${percentOfMaxRange || 0.02}%`, width: `${percentOfMaxRange || 0.02}%`,
WebkitMask: 'linear-gradient(#fff 0 0)', WebkitMask: 'linear-gradient(#fff 0 0)',
@ -97,11 +102,19 @@ export const BorrowCapacity = ({
</div> </div>
<div <div
className='absolute bottom-0 h-[120%] w-[1px] bg-white transition-[left] duration-1000 ease-linear' className={classNames(
'absolute bottom-0 h-[120%] w-[1px] bg-white',
enableAnimations && 'transition-[left] duration-1000 ease-linear',
)}
style={{ left: `${limitPercentOfMax || 0}%` }} style={{ left: `${limitPercentOfMax || 0}%` }}
/> />
{showPercentageText ? ( {showPercentageText ? (
<span className='absolute top-1/2 mt-[1px] w-full -translate-y-1/2 animate-fadein text-center opacity-0 text-2xs-caps'> <span
className={classNames(
'absolute top-1/2 mt-[1px] w-full -translate-y-1/2 text-center text-2xs-caps',
enableAnimations && 'animate-fadein opacity-0',
)}
>
{max !== 0 && ( {max !== 0 && (
<FormattedNumber <FormattedNumber
className='text-white' className='text-white'

View File

@ -4,21 +4,20 @@ import React, { useMemo, useState } from 'react'
import { NumericFormat } from 'react-number-format' import { NumericFormat } from 'react-number-format'
import { toast } from 'react-toastify' import { toast } from 'react-toastify'
import Button from 'components/Button' import {
import CircularProgress from 'components/CircularProgress' Button,
import ContainerSecondary from 'components/ContainerSecondary' CircularProgress,
import Gauge from 'components/Gauge' ContainerSecondary,
import ProgressBar from 'components/ProgressBar' Gauge,
import Slider from 'components/Slider' PositionsList,
import Text from 'components/Text' ProgressBar,
import Tooltip from 'components/Tooltip' Slider,
import useBorrowFunds from 'hooks/mutations/useBorrowFunds' Text,
import useAccountStats, { AccountStatsAction } from 'hooks/useAccountStats' Tooltip,
import useAllBalances from 'hooks/useAllBalances' } from 'components'
import useCalculateMaxBorrowAmount from 'hooks/useCalculateMaxBorrowAmount' import { useAccountStats, useBalances, useCalculateMaxBorrowAmount } from 'hooks/data'
import useCreditAccountPositions from 'hooks/useCreditAccountPositions' import { useBorrowFunds } from 'hooks/mutations'
import useMarkets from 'hooks/useMarkets' import { useAllBalances, useMarkets, useTokenPrices } from 'hooks/queries'
import useTokenPrices from 'hooks/useTokenPrices'
import { useAccountDetailsStore } from 'stores' import { useAccountDetailsStore } from 'stores'
import { chain } from 'utils/chains' import { chain } from 'utils/chains'
import { formatCurrency, formatValue } from 'utils/formatters' import { formatCurrency, formatValue } from 'utils/formatters'
@ -30,14 +29,13 @@ type Props = {
tokenDenom: string tokenDenom: string
} }
const BorrowModal = ({ show, onClose, tokenDenom }: Props) => { export const BorrowModal = ({ show, onClose, tokenDenom }: Props) => {
const [amount, setAmount] = useState(0) const [amount, setAmount] = useState(0)
const [isBorrowToCreditAccount, setIsBorrowToCreditAccount] = useState(false) const [isBorrowToCreditAccount, setIsBorrowToCreditAccount] = useState(false)
const selectedAccount = useAccountDetailsStore((s) => s.selectedAccount) const selectedAccount = useAccountDetailsStore((s) => s.selectedAccount)
const { data: positionsData, isLoading: isLoadingPositions } = useCreditAccountPositions(
selectedAccount ?? '', const balances = useBalances()
)
const { actions, borrowAmount } = useMemo(() => { const { actions, borrowAmount } = useMemo(() => {
const borrowAmount = BigNumber(amount) const borrowAmount = BigNumber(amount)
@ -124,17 +122,6 @@ const BorrowModal = ({ show, onClose, tokenDenom }: Props) => {
setAmount(0) 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]
)
}
return ( return (
<Transition appear show={show} as={React.Fragment}> <Transition appear show={show} as={React.Fragment}>
<Dialog as='div' className='relative z-10' onClose={onClose}> <Dialog as='div' className='relative z-10' onClose={onClose}>
@ -310,62 +297,7 @@ const BorrowModal = ({ show, onClose, tokenDenom }: Props) => {
</div> </div>
</div> </div>
</div> </div>
<div className='rounded-md border border-white/20 p-3'> <PositionsList title='Balances' data={balances} />
<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> </div>
</Dialog.Panel> </Dialog.Panel>
</Transition.Child> </Transition.Child>
@ -375,5 +307,3 @@ const BorrowModal = ({ show, onClose, tokenDenom }: Props) => {
</Transition> </Transition>
) )
} }
export default BorrowModal

View File

@ -1,7 +1,8 @@
import classNames from 'classnames' import classNames from 'classnames'
import React, { LegacyRef, ReactNode } from 'react' import React, { LegacyRef, ReactNode } from 'react'
import CircularProgress from 'components/CircularProgress' import { CircularProgress } from 'components'
import { useSettings } from 'stores'
interface Props { interface Props {
children?: string | ReactNode children?: string | ReactNode
@ -57,7 +58,7 @@ export const buttonVariantClasses = {
text: 'border-none bg-transparent', text: 'border-none bg-transparent',
} }
const Button = React.forwardRef(function Button( export const Button = React.forwardRef(function Button(
{ {
children, children,
className = '', className = '',
@ -73,6 +74,7 @@ const Button = React.forwardRef(function Button(
ref, ref,
) { ) {
const buttonClasses = [] const buttonClasses = []
const enableAnimations = useSettings((s) => s.enableAnimations)
switch (variant) { switch (variant) {
case 'round': case 'round':
@ -96,7 +98,8 @@ const Button = React.forwardRef(function Button(
return ( return (
<button <button
className={classNames( className={classNames(
'cursor-pointer appearance-none break-normal rounded-3xl outline-none transition-colors', 'outline-nones cursor-pointer appearance-none break-normal rounded-3xl',
enableAnimations && 'transition-color',
buttonClasses, buttonClasses,
buttonVariantClasses[variant], buttonVariantClasses[variant],
disabled && 'pointer-events-none opacity-50', disabled && 'pointer-events-none opacity-50',
@ -114,5 +117,3 @@ const Button = React.forwardRef(function Button(
</button> </button>
) )
}) })
export default Button

View File

@ -6,7 +6,7 @@ interface Props {
className?: string className?: string
} }
const Card = ({ children, className }: Props) => { export const Card = ({ children, className }: Props) => {
return ( return (
<div <div
className={classNames( className={classNames(
@ -18,5 +18,3 @@ const Card = ({ children, className }: Props) => {
</div> </div>
) )
} }
export default Card

View File

@ -1,18 +1,32 @@
import classNames from 'classnames' import classNames from 'classnames'
import { Text } from 'components'
import { useSettings } from 'stores'
interface Props { interface Props {
color?: string color?: string
size?: number size?: number
className?: string className?: string
} }
const CircularProgress = ({ color = '#FFFFFF', size = 20, className }: Props) => { export const CircularProgress = ({ color = '#FFFFFF', size = 20, className }: Props) => {
const enableAnimations = useSettings((s) => s.enableAnimations)
const borderWidth = `${size / 10}px` const borderWidth = `${size / 10}px`
const borderColor = `${color} transparent transparent transparent` const borderColor = `${color} transparent transparent transparent`
const loaderClasses = classNames('inline-block relative', className) const loaderClasses = classNames('inline-block relative', className)
const elementClasses = const elementClasses =
'block absolute w-4/5 h-4/5 m-[10%] rounded-full animate-progress border-solid' 'block absolute w-4/5 h-4/5 m-[10%] rounded-full animate-progress border-solid'
if (!enableAnimations)
return (
<div className={loaderClasses} style={{ width: `${size}px`, height: `${size}px` }}>
<Text className='text-center' uppercase size='lg'>
...
</Text>
</div>
)
return ( return (
<div className={loaderClasses} style={{ width: `${size}px`, height: `${size}px` }}> <div className={loaderClasses} style={{ width: `${size}px`, height: `${size}px` }}>
<div <div
@ -49,5 +63,3 @@ const CircularProgress = ({ color = '#FFFFFF', size = 20, className }: Props) =>
</div> </div>
) )
} }
export default CircularProgress

View File

@ -1,6 +1,6 @@
import React from 'react' import React from 'react'
const ContainerSecondary = ({ export const ContainerSecondary = ({
children, children,
className, className,
}: { }: {
@ -13,5 +13,3 @@ const ContainerSecondary = ({
</div> </div>
) )
} }
export default ContainerSecondary

View File

@ -0,0 +1,66 @@
import classNames from 'classnames'
import React, { useEffect, useRef } from 'react'
import { animated, useSpring } from 'react-spring'
import { useSettings } from 'stores'
import { formatValue } from 'utils/formatters'
export const FormattedNumber = React.memo(
({
amount,
animate = false,
className,
minDecimals = 2,
maxDecimals = 2,
thousandSeparator = true,
prefix = false,
suffix = false,
rounded = false,
abbreviated = false,
}: FormattedNumberProps) => {
const enableAnimations = useSettings((s) => s.enableAnimations)
const prevAmountRef = useRef<number>(0)
useEffect(() => {
if (prevAmountRef.current !== Number(amount)) prevAmountRef.current = Number(amount)
}, [amount])
const springAmount = useSpring({
number: Number(amount),
from: { number: prevAmountRef.current },
config: { duration: 1000 },
})
return (prevAmountRef.current === amount && amount === 0) || !animate || !enableAnimations ? (
<span className={classNames('number', className)}>
{formatValue(
amount,
minDecimals,
maxDecimals,
thousandSeparator,
prefix,
suffix,
rounded,
abbreviated,
)}
</span>
) : (
<animated.span className={classNames('number', className)}>
{springAmount.number.to((num) =>
formatValue(
num,
minDecimals,
maxDecimals,
thousandSeparator,
prefix,
suffix,
rounded,
abbreviated,
),
)}
</animated.span>
)
},
)
FormattedNumber.displayName = 'FormattedNumber'

View File

@ -1,9 +1,10 @@
import classNames from 'classnames' import classNames from 'classnames'
import { ReactNode } from 'react' import { ReactNode } from 'react'
import Tooltip from './Tooltip' import { Tooltip } from 'components'
import { useSettings } from 'stores'
type Props = { interface Props {
tooltip: string | ReactNode tooltip: string | ReactNode
strokeWidth?: number strokeWidth?: number
background?: string background?: string
@ -12,7 +13,15 @@ type Props = {
label?: string label?: string
} }
const Gauge = ({ background = '#15161A', diameter = 40, value = 0, label, tooltip }: Props) => { export const Gauge = ({
background = '#15161A',
diameter = 40,
value = 0,
label,
tooltip,
}: Props) => {
const enableAnimations = useSettings((s) => s.enableAnimations)
const percentage = value * 100 const percentage = value * 100
const percentageValue = percentage > 100 ? 100 : percentage < 0 ? 0 : percentage const percentageValue = percentage > 100 ? 100 : percentage < 0 ? 0 : percentage
const semiCirclePercentage = Math.abs(percentageValue / 2 - 50) const semiCirclePercentage = Math.abs(percentageValue / 2 - 50)
@ -59,7 +68,7 @@ const Gauge = ({ background = '#15161A', diameter = 40, value = 0, label, toolti
strokeWidth={5} strokeWidth={5}
style={{ style={{
strokeDashoffset: semiCirclePercentage, strokeDashoffset: semiCirclePercentage,
transition: 'stroke-dashoffset 1s ease', transition: enableAnimations ? 'stroke-dashoffset 1s ease' : 'none',
}} }}
shapeRendering='geometricPrecision' shapeRendering='geometricPrecision'
/> />
@ -82,5 +91,3 @@ const Gauge = ({ background = '#15161A', diameter = 40, value = 0, label, toolti
</Tooltip> </Tooltip>
) )
} }
export default Gauge

View File

Before

Width:  |  Height:  |  Size: 964 B

After

Width:  |  Height:  |  Size: 964 B

View File

Before

Width:  |  Height:  |  Size: 672 B

After

Width:  |  Height:  |  Size: 672 B

View File

Before

Width:  |  Height:  |  Size: 382 B

After

Width:  |  Height:  |  Size: 382 B

View File

Before

Width:  |  Height:  |  Size: 537 B

After

Width:  |  Height:  |  Size: 537 B

View File

Before

Width:  |  Height:  |  Size: 933 B

After

Width:  |  Height:  |  Size: 933 B

View File

Before

Width:  |  Height:  |  Size: 397 B

After

Width:  |  Height:  |  Size: 397 B

View File

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 1.9 KiB

View File

Before

Width:  |  Height:  |  Size: 451 B

After

Width:  |  Height:  |  Size: 451 B

View File

Before

Width:  |  Height:  |  Size: 272 B

After

Width:  |  Height:  |  Size: 272 B

View File

Before

Width:  |  Height:  |  Size: 166 B

After

Width:  |  Height:  |  Size: 166 B

View File

Before

Width:  |  Height:  |  Size: 324 B

After

Width:  |  Height:  |  Size: 324 B

View File

Before

Width:  |  Height:  |  Size: 325 B

After

Width:  |  Height:  |  Size: 325 B

View File

Before

Width:  |  Height:  |  Size: 328 B

After

Width:  |  Height:  |  Size: 328 B

View File

Before

Width:  |  Height:  |  Size: 329 B

After

Width:  |  Height:  |  Size: 329 B

View File

Before

Width:  |  Height:  |  Size: 96 KiB

After

Width:  |  Height:  |  Size: 96 KiB

View File

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@ -1,3 +1,3 @@
;<svg fill='currentColor' viewBox='0 0 12 14' xmlns='http://www.w3.org/2000/svg'> <svg fill='currentColor' viewBox='0 0 12 14' xmlns='http://www.w3.org/2000/svg'>
<path d='M6.47317 3.19325C6.40977 3.13256 6.335 3.08498 6.25317 3.05325C6.09086 2.98657 5.90881 2.98657 5.7465 3.05325C5.66467 3.08498 5.58991 3.13256 5.5265 3.19325L2.85984 5.85992C2.79768 5.92208 2.74837 5.99587 2.71473 6.07709C2.68109 6.1583 2.66378 6.24535 2.66378 6.33325C2.66378 6.42116 2.68109 6.5082 2.71473 6.58942C2.74837 6.67063 2.79768 6.74443 2.85984 6.80659C2.922 6.86874 2.99579 6.91805 3.077 6.95169C3.15822 6.98533 3.24526 7.00265 3.33317 7.00265C3.42108 7.00265 3.50812 6.98533 3.58934 6.95169C3.67055 6.91805 3.74434 6.86874 3.8065 6.80659L5.33317 5.27325V12.9999C5.33317 13.1767 5.40341 13.3463 5.52843 13.4713C5.65346 13.5963 5.82303 13.6666 5.99984 13.6666C6.17665 13.6666 6.34622 13.5963 6.47124 13.4713C6.59627 13.3463 6.6665 13.1767 6.6665 12.9999V5.27325L8.19317 6.80659C8.25515 6.86907 8.32888 6.91867 8.41012 6.95251C8.49136 6.98636 8.5785 7.00378 8.6665 7.00378C8.75451 7.00378 8.84165 6.98636 8.92289 6.95251C9.00413 6.91867 9.07786 6.86907 9.13984 6.80659C9.20232 6.74461 9.25192 6.67088 9.28576 6.58964C9.31961 6.5084 9.33704 6.42126 9.33704 6.33325C9.33704 6.24524 9.31961 6.15811 9.28576 6.07687C9.25192 5.99563 9.20232 5.92189 9.13984 5.85992L6.47317 3.19325ZM10.6665 0.333252H1.33317C1.15636 0.333252 0.98679 0.40349 0.861766 0.528514C0.736742 0.653538 0.666504 0.823108 0.666504 0.999919C0.666504 1.17673 0.736742 1.3463 0.861766 1.47132C0.98679 1.59635 1.15636 1.66659 1.33317 1.66659H10.6665C10.8433 1.66659 11.0129 1.59635 11.1379 1.47132C11.2629 1.3463 11.3332 1.17673 11.3332 0.999919C11.3332 0.823108 11.2629 0.653538 11.1379 0.528514C11.0129 0.40349 10.8433 0.333252 10.6665 0.333252Z' /> <path d='M6.47317 3.19325C6.40977 3.13256 6.335 3.08498 6.25317 3.05325C6.09086 2.98657 5.90881 2.98657 5.7465 3.05325C5.66467 3.08498 5.58991 3.13256 5.5265 3.19325L2.85984 5.85992C2.79768 5.92208 2.74837 5.99587 2.71473 6.07709C2.68109 6.1583 2.66378 6.24535 2.66378 6.33325C2.66378 6.42116 2.68109 6.5082 2.71473 6.58942C2.74837 6.67063 2.79768 6.74443 2.85984 6.80659C2.922 6.86874 2.99579 6.91805 3.077 6.95169C3.15822 6.98533 3.24526 7.00265 3.33317 7.00265C3.42108 7.00265 3.50812 6.98533 3.58934 6.95169C3.67055 6.91805 3.74434 6.86874 3.8065 6.80659L5.33317 5.27325V12.9999C5.33317 13.1767 5.40341 13.3463 5.52843 13.4713C5.65346 13.5963 5.82303 13.6666 5.99984 13.6666C6.17665 13.6666 6.34622 13.5963 6.47124 13.4713C6.59627 13.3463 6.6665 13.1767 6.6665 12.9999V5.27325L8.19317 6.80659C8.25515 6.86907 8.32888 6.91867 8.41012 6.95251C8.49136 6.98636 8.5785 7.00378 8.6665 7.00378C8.75451 7.00378 8.84165 6.98636 8.92289 6.95251C9.00413 6.91867 9.07786 6.86907 9.13984 6.80659C9.20232 6.74461 9.25192 6.67088 9.28576 6.58964C9.31961 6.5084 9.33704 6.42126 9.33704 6.33325C9.33704 6.24524 9.31961 6.15811 9.28576 6.07687C9.25192 5.99563 9.20232 5.92189 9.13984 5.85992L6.47317 3.19325ZM10.6665 0.333252H1.33317C1.15636 0.333252 0.98679 0.40349 0.861766 0.528514C0.736742 0.653538 0.666504 0.823108 0.666504 0.999919C0.666504 1.17673 0.736742 1.3463 0.861766 1.47132C0.98679 1.59635 1.15636 1.66659 1.33317 1.66659H10.6665C10.8433 1.66659 11.0129 1.59635 11.1379 1.47132C11.2629 1.3463 11.3332 1.17673 11.3332 0.999919C11.3332 0.823108 11.2629 0.653538 11.1379 0.528514C11.0129 0.40349 10.8433 0.333252 10.6665 0.333252Z' />
</svg> </svg>

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

Before

Width:  |  Height:  |  Size: 225 B

After

Width:  |  Height:  |  Size: 225 B

View File

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 2.1 KiB

View File

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 2.2 KiB

View File

Before

Width:  |  Height:  |  Size: 944 B

After

Width:  |  Height:  |  Size: 944 B

View File

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 1.9 KiB

View File

Before

Width:  |  Height:  |  Size: 3.8 KiB

After

Width:  |  Height:  |  Size: 3.8 KiB

View File

Before

Width:  |  Height:  |  Size: 9.7 KiB

After

Width:  |  Height:  |  Size: 9.7 KiB

View File

Before

Width:  |  Height:  |  Size: 664 B

After

Width:  |  Height:  |  Size: 664 B

View File

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 18 KiB

View File

Before

Width:  |  Height:  |  Size: 388 B

After

Width:  |  Height:  |  Size: 388 B

View File

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 2.2 KiB

View File

Before

Width:  |  Height:  |  Size: 4.2 KiB

After

Width:  |  Height:  |  Size: 4.2 KiB

View File

Before

Width:  |  Height:  |  Size: 191 B

After

Width:  |  Height:  |  Size: 191 B

View File

Before

Width:  |  Height:  |  Size: 746 B

After

Width:  |  Height:  |  Size: 746 B

View File

Before

Width:  |  Height:  |  Size: 442 B

After

Width:  |  Height:  |  Size: 442 B

View File

Before

Width:  |  Height:  |  Size: 443 B

After

Width:  |  Height:  |  Size: 443 B

View File

Before

Width:  |  Height:  |  Size: 547 B

After

Width:  |  Height:  |  Size: 547 B

View File

Before

Width:  |  Height:  |  Size: 485 B

After

Width:  |  Height:  |  Size: 485 B

View File

Before

Width:  |  Height:  |  Size: 371 B

After

Width:  |  Height:  |  Size: 371 B

View File

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

Before

Width:  |  Height:  |  Size: 165 B

After

Width:  |  Height:  |  Size: 165 B

View File

Before

Width:  |  Height:  |  Size: 615 B

After

Width:  |  Height:  |  Size: 615 B

View File

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 1.9 KiB

View File

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 2.1 KiB

View File

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

Before

Width:  |  Height:  |  Size: 498 B

After

Width:  |  Height:  |  Size: 498 B

View File

@ -0,0 +1,48 @@
// @index(['./*.svg'], f => `export { default as ${f.name} } from '${f.path}.svg'`)
export { default as Add } from './Add.svg'
export { default as ArrowBack } from './ArrowBack.svg'
export { default as ArrowDown } from './ArrowDown.svg'
export { default as ArrowLeftLine } from './ArrowLeftLine.svg'
export { default as ArrowRightLine } from './ArrowRightLine.svg'
export { default as ArrowsLeftRight } from './ArrowsLeftRight.svg'
export { default as ArrowsUpDown } from './ArrowsUpDown.svg'
export { default as ArrowUp } from './ArrowUp.svg'
export { default as BurgerMenu } from './BurgerMenu.svg'
export { default as Check } from './Check.svg'
export { default as ChevronDown } from './ChevronDown.svg'
export { default as ChevronLeft } from './ChevronLeft.svg'
export { default as ChevronRight } from './ChevronRight.svg'
export { default as ChevronUp } from './ChevronUp.svg'
export { default as Close } from './Close.svg'
export { default as Copy } from './Copy.svg'
export { default as Deposit } from './Deposit.svg'
export { default as Discord } from './Discord.svg'
export { default as Edit } from './Edit.svg'
export { default as Ellipsis } from './Ellipsis.svg'
export { default as ExternalLink } from './ExternalLink.svg'
export { default as Failed } from './Failed.svg'
export { default as Github } from './Github.svg'
export { default as Info } from './Info.svg'
export { default as Logo } from './Logo.svg'
export { default as MarsProtocol } from './MarsProtocol.svg'
export { default as Medium } from './Medium.svg'
export { default as Osmo } from './Osmo.svg'
export { default as Questionmark } from './Questionmark.svg'
export { default as Reddit } from './Reddit.svg'
export { default as Rubbish } from './Rubbish.svg'
export { default as Search } from './Search.svg'
export { default as SmallClose } from './SmallClose.svg'
export { default as SortAsc } from './SortAsc.svg'
export { default as SortDesc } from './SortDesc.svg'
export { default as SortNone } from './SortNone.svg'
export { default as Subtract } from './Subtract.svg'
export { default as Success } from './Success.svg'
export { default as Telegram } from './Telegram.svg'
export { default as TriangleDown } from './TriangleDown.svg'
export { default as Twitter } from './Twitter.svg'
export { default as Wallet } from './Wallet.svg'
export { default as WalletConnect } from './WalletConnect.svg'
export { default as Warning } from './Warning.svg'
export { default as Withdraw } from './Withdraw.svg'
export { default as YouTube } from './YouTube.svg'
// @endindex

View File

@ -0,0 +1,24 @@
import classNames from 'classnames'
import { FormattedNumber, Text } from 'components'
interface ValueData extends FormattedNumberProps {
format?: 'number' | 'string'
}
interface Props {
label: string
value: ValueData
className?: string
}
export const LabelValuePair = ({ label, value, className }: Props) => (
<div className={classNames('flex w-full', className)}>
<Text size='xs' className='flex-grow text-white/60'>
{label}
</Text>
<Text size='xs' className='text-white/60'>
{value.format === 'number' ? <FormattedNumber animate {...value} /> : value.amount || ''}
</Text>
</div>
)

View File

@ -1,19 +1,21 @@
import { useWallet, WalletConnectionStatus } from '@marsprotocol/wallet-connector'
import classNames from 'classnames' import classNames from 'classnames'
import React, { useEffect } from 'react' import React, { useEffect } from 'react'
import { useWallet, WalletConnectionStatus } from '@marsprotocol/wallet-connector'
import AccountManager from 'components/Account/AccountDetails' import { AccountDetails } from 'components/Account'
import DesktopNavigation from 'components/Navigation/DesktopNavigation' import { DesktopNavigation } from 'components/Navigation'
import useCreditAccounts from 'hooks/useCreditAccounts' import { useCreditAccounts } from 'hooks/queries'
import { useWalletStore } from 'stores' import { useSettings, useWalletStore } from 'stores'
const filter = { const filter = {
day: 'brightness-100 hue-rotate-0', day: 'brightness-100 hue-rotate-0',
night: '-hue-rotate-82 brightness-30', night: '-hue-rotate-82 brightness-30',
} }
const Layout = ({ children }: { children: React.ReactNode }) => { export const Layout = ({ children }: { children: React.ReactNode }) => {
const { data: creditAccountsList, isLoading: isLoadingCreditAccounts } = useCreditAccounts() const enableAnimations = useSettings((s) => s.enableAnimations)
const { data: creditAccountsList } = useCreditAccounts()
const hasCreditAccounts = creditAccountsList && creditAccountsList.length > 0 const hasCreditAccounts = creditAccountsList && creditAccountsList.length > 0
const { status, signingCosmWasmClient, chainInfo, address, name } = useWallet() const { status, signingCosmWasmClient, chainInfo, address, name } = useWallet()
@ -27,19 +29,18 @@ const Layout = ({ children }: { children: React.ReactNode }) => {
const backgroundClasses = classNames( const backgroundClasses = classNames(
isConnected ? filter.day : filter.night, isConnected ? filter.day : filter.night,
'top-0 left-0 absolute block h-full w-full flex-col bg-body bg-mars bg-desktop bg-top bg-no-repeat filter transition-background duration-3000 ease-linear', 'top-0 left-0 absolute block h-full w-full flex-col bg-body bg-mars bg-desktop bg-top bg-no-repeat filter',
enableAnimations && 'transition-background duration-3000 ease-linear',
) )
return ( return (
<div className='relative min-h-screen w-full'> <div className='relative min-h-screen w-full'>
<div className={backgroundClasses} /> <div className={backgroundClasses} />
<DesktopNavigation /> <DesktopNavigation />
<main className='relative flex lg:h-[calc(100vh-120px)]'> <main className='relative flex lg:min-h-[calc(100vh-120px)]'>
<div className='flex flex-grow flex-wrap p-6'>{children}</div> <div className='flex flex-grow flex-wrap p-6'>{children}</div>
{hasCreditAccounts && <AccountManager />} {hasCreditAccounts && <AccountDetails />}
</main> </main>
</div> </div>
) )
} }
export default Layout

View File

@ -1,8 +1,8 @@
import classNames from 'classnames' import classNames from 'classnames'
import { ReactNode } from 'react' import { ReactNode } from 'react'
import Card from 'components/Card' import { Card } from 'components'
import CloseIcon from 'components/Icons/close.svg' import { Close } from 'components/Icons'
interface Props { interface Props {
children?: ReactNode | string children?: ReactNode | string
@ -12,7 +12,7 @@ interface Props {
setOpen?: (open: boolean) => void setOpen?: (open: boolean) => void
} }
const Modal = ({ children, content, className, open, setOpen }: Props) => { export const Modal = ({ children, content, className, open, setOpen }: Props) => {
const onClickAway = () => { const onClickAway = () => {
if (setOpen) setOpen(false) if (setOpen) setOpen(false)
} }
@ -27,7 +27,7 @@ const Modal = ({ children, content, className, open, setOpen }: Props) => {
onClick={onClickAway} onClick={onClickAway}
role='button' role='button'
> >
<CloseIcon /> <Close />
</span> </span>
)} )}
{children ? children : content} {children ? children : content}
@ -41,5 +41,3 @@ const Modal = ({ children, content, className, open, setOpen }: Props) => {
</div> </div>
) : null ) : null
} }
export default Modal

View File

@ -1,11 +1,9 @@
import { ConfirmModal, FundAccountModal, WithdrawModal } from './Account' import { ConfirmModal, FundAccountModal, WithdrawModal } from './Account'
const Modals = () => ( export const Modals = () => (
<> <>
<FundAccountModal /> <FundAccountModal />
<WithdrawModal /> <WithdrawModal />
<ConfirmModal /> <ConfirmModal />
</> </>
) )
export default Modals

View File

@ -1,14 +1,13 @@
import Link from 'next/link' import Link from 'next/link'
import { AccountNavigation, AccountStatus } from 'components/Account' import { AccountNavigation, AccountStatus } from 'components/Account'
import Logo from 'components/Icons/logo.svg' import { Logo } from 'components/Icons'
import { menuTree, NavLink } from 'components/Navigation' import { menuTree, NavLink, SearchInput } from 'components/Navigation'
import SearchInput from 'components/Navigation/SearchInput' import { Wallet } from 'components/Wallet'
import Wallet from 'components/Wallet/Wallet' import { useCreditAccounts } from 'hooks/queries'
import useCreditAccounts from 'hooks/useCreditAccounts'
import { useAccountDetailsStore, useWalletStore } from 'stores' import { useAccountDetailsStore, useWalletStore } from 'stores'
const Navigation = () => { export const DesktopNavigation = () => {
const address = useWalletStore((s) => s.address) const address = useWalletStore((s) => s.address)
const selectedAccount = useAccountDetailsStore((s) => s.selectedAccount) const selectedAccount = useAccountDetailsStore((s) => s.selectedAccount)
@ -52,5 +51,3 @@ const Navigation = () => {
</div> </div>
) )
} }
export default Navigation

View File

@ -8,7 +8,7 @@ interface Props {
children: string | ReactNode children: string | ReactNode
} }
const NavLink = ({ href, children }: Props) => { export const NavLink = ({ href, children }: Props) => {
const router = useRouter() const router = useRouter()
const isActive = router.pathname === href const isActive = router.pathname === href
@ -25,5 +25,3 @@ const NavLink = ({ href, children }: Props) => {
</Link> </Link>
) )
} }
export default NavLink

View File

@ -1,8 +1,8 @@
import SearchIcon from 'components/Icons/search.svg' import { Search } from 'components/Icons'
const SearchInput = () => ( export const SearchInput = () => (
<div className='relative mt-[1px] py-2 text-white'> <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'> <span className='absolute top-1/2 left-0 flex h-6 w-8 -translate-y-1/2 items-center pl-2'>
<SearchIcon /> <Search />
</span> </span>
<input <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:border-white/60 focus:outline-none' 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:border-white/60 focus:outline-none'
@ -10,5 +10,3 @@ const SearchInput = () => (
/> />
</div> </div>
) )
export default SearchInput

View File

@ -0,0 +1,6 @@
// @index(['./*.ts*'], f => `export { ${f.name} } from '${f.path}'`)
export { DesktopNavigation } from './DesktopNavigation'
export { menuTree } from './menuTree'
export { NavLink } from './NavLink'
export { SearchInput } from './SearchInput'
// @endindex

View File

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

View File

@ -9,7 +9,7 @@ interface Props {
setShow: (show: boolean) => void setShow: (show: boolean) => void
} }
const Overlay = ({ children, content, className, show, setShow }: Props) => { export const Overlay = ({ children, content, className, show, setShow }: Props) => {
const onClickAway = () => { const onClickAway = () => {
setShow(false) setShow(false)
} }
@ -32,5 +32,3 @@ const Overlay = ({ children, content, className, show, setShow }: Props) => {
</> </>
) : null ) : null
} }
export default Overlay

View File

@ -1,7 +1,7 @@
import classNames from 'classnames' import classNames from 'classnames'
import { ReactNode } from 'react' import { ReactNode } from 'react'
import Button from 'components/Button' import { Button } from 'components'
interface Props { interface Props {
className?: string className?: string
@ -11,7 +11,7 @@ interface Props {
text: string | ReactNode text: string | ReactNode
} }
const OverlayAction = ({ className, icon, onClick, setShow, text }: Props) => { export const OverlayAction = ({ className, icon, onClick, setShow, text }: Props) => {
return ( return (
<Button <Button
className={classNames( className={classNames(
@ -29,5 +29,3 @@ const OverlayAction = ({ className, icon, onClick, setShow, text }: Props) => {
</Button> </Button>
) )
} }
export default OverlayAction

View File

@ -0,0 +1,4 @@
// @index(['./*.tsx'], f => `export { ${f.name} } from '${f.path}'`)
export { Overlay } from './Overlay'
export { OverlayAction } from './OverlayAction'
// @endindex

View File

@ -0,0 +1,61 @@
import classNames from 'classnames'
import { FormattedNumber, Text } from 'components'
interface Props {
title: string
data?: PositionsData[]
}
export const PositionsList = (props: Props) => {
if (!props?.data || props.data?.length === 0) return null
const arrayKeys = Object.keys(props.data[0])
return (
<div className='flex w-full flex-wrap'>
<Text uppercase className='w-full bg-black/20 px-4 py-2 text-white/40'>
{props.title}
</Text>
<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'>
{arrayKeys.map((label, index) => (
<Text key={index} size='xs' uppercase className='flex-1 text-white'>
{label}
</Text>
))}
</div>
{props.data &&
props.data.map((positionsData: PositionsData, index) => (
<div key={index} className='align-center flex w-full px-4 py-2'>
{arrayKeys.map((key, index) => {
if (index === 0)
return (
<Text
size='xs'
key={index}
className={classNames(
'flex-1 border-l-4 pl-2 text-white/60',
positionsData[key].type === 'debt' ? 'border-loss' : 'border-profit',
)}
>
{positionsData[key].amount}
</Text>
)
return (
<Text size='xs' key={index} className='flex-1 text-white/60'>
{positionsData[key].format && positionsData[key].format === 'number' ? (
<FormattedNumber animate {...positionsData[key]} />
) : (
positionsData[key]?.amount || ''
)}
</Text>
)
})}
</div>
))}
</>
</div>
</div>
)
}

View File

@ -4,7 +4,7 @@ type Props = {
value: number value: number
} }
const ProgressBar = ({ value }: Props) => { export const ProgressBar = ({ value }: Props) => {
const percentageValue = `${(value * 100).toFixed(0)}%` const percentageValue = `${(value * 100).toFixed(0)}%`
let bgColorClass = 'bg-green-500' let bgColorClass = 'bg-green-500'
@ -26,5 +26,3 @@ const ProgressBar = ({ value }: Props) => {
</div> </div>
) )
} }
export default ProgressBar

View File

@ -5,17 +5,12 @@ import React, { useMemo, useState } from 'react'
import { NumericFormat } from 'react-number-format' import { NumericFormat } from 'react-number-format'
import { toast } from 'react-toastify' import { toast } from 'react-toastify'
import Button from 'components/Button' import { Button, CircularProgress, ContainerSecondary, Slider } from 'components'
import CircularProgress from 'components/CircularProgress' import { useRepayFunds } from 'hooks/mutations'
import ContainerSecondary from 'components/ContainerSecondary'
import Slider from 'components/Slider'
import useRepayFunds from 'hooks/mutations/useRepayFunds'
import useAllBalances from 'hooks/useAllBalances'
import useCreditAccountPositions from 'hooks/useCreditAccountPositions'
import useTokenPrices from 'hooks/useTokenPrices'
import { useAccountDetailsStore } from 'stores' import { useAccountDetailsStore } from 'stores'
import { formatCurrency } from 'utils/formatters' import { formatCurrency } from 'utils/formatters'
import { getTokenDecimals, getTokenSymbol } from 'utils/tokens' import { getTokenDecimals, getTokenSymbol } from 'utils/tokens'
import { useAllBalances, useCreditAccountPositions, useTokenPrices } from 'hooks/queries'
// 0.001% buffer / slippage to avoid repay action from not fully repaying the debt amount // 0.001% buffer / slippage to avoid repay action from not fully repaying the debt amount
const REPAY_BUFFER = 1.00001 const REPAY_BUFFER = 1.00001
@ -26,7 +21,7 @@ type Props = {
tokenDenom: string tokenDenom: string
} }
const RepayModal = ({ show, onClose, tokenDenom }: Props) => { export const RepayModal = ({ show, onClose, tokenDenom }: Props) => {
const [amount, setAmount] = useState(0) const [amount, setAmount] = useState(0)
const selectedAccount = useAccountDetailsStore((s) => s.selectedAccount) const selectedAccount = useAccountDetailsStore((s) => s.selectedAccount)
@ -190,5 +185,3 @@ const RepayModal = ({ show, onClose, tokenDenom }: Props) => {
</Transition> </Transition>
) )
} }
export default RepayModal

View File

@ -8,7 +8,7 @@ type Props = {
onMaxClick: () => void onMaxClick: () => void
} }
const Slider = ({ className, value, onChange, onMaxClick }: Props) => { export const Slider = ({ className, value, onChange, onMaxClick }: Props) => {
return ( return (
<div className={`relative flex flex-1 items-center ${className || ''}`}> <div className={`relative flex flex-1 items-center ${className || ''}`}>
<RadixSlider.Root <RadixSlider.Root
@ -35,5 +35,3 @@ const Slider = ({ className, value, onChange, onMaxClick }: Props) => {
</div> </div>
) )
} }
export default Slider

Some files were not shown because too many files have changed in this diff Show More